Skip to content
This repository was archived by the owner on Nov 10, 2022. It is now read-only.

Commit d5a31cb

Browse files
committed
[FEAT] 添加打分逻辑,功能未自测;
1 parent 1b6a28b commit d5a31cb

File tree

3 files changed

+191
-35
lines changed

3 files changed

+191
-35
lines changed

lrcview/src/main/java/io/agora/lrcview/PitchView.java

Lines changed: 185 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package io.agora.lrcview;
22

33
import android.animation.ObjectAnimator;
4-
import android.animation.ValueAnimator;
54
import android.content.Context;
65
import android.content.res.TypedArray;
76
import android.graphics.Canvas;
@@ -13,14 +12,13 @@
1312
import android.os.Handler;
1413
import android.os.Looper;
1514
import android.util.AttributeSet;
16-
import android.util.FloatProperty;
17-
import android.util.Property;
1815
import android.util.TypedValue;
1916
import android.view.View;
2017

2118
import androidx.annotation.Nullable;
2219
import androidx.annotation.RequiresApi;
2320

21+
import java.util.ArrayList;
2422
import java.util.List;
2523

2624
import io.agora.lrcview.bean.LrcData;
@@ -46,7 +44,34 @@ public class PitchView extends View {
4644

4745
private int pitchMax = 0;//最大值
4846
private int pitchMin = 100;//最小值
47+
private int totalPitch = 0;
48+
49+
// 完成 PitchView.OnActionListener#onOriginalPitch的需求
50+
// 当前 Pitch 所在的字的开始时间
51+
private long currentPitchStartTime = 0;
52+
// 当前 Pitch 所在的字的结束时间
53+
private long currentPitchEndTime = 0;
54+
// 当前 Pitch 所在的句的结束时间
55+
private long currentEntryEndTime = 0;
56+
// 当前在打分的所在句的结束时间
57+
private long currentScoreEntryEndTime = 0;
58+
// 当前在打分的所在句的结束时间
59+
private long lrcEndTime = 0;
60+
61+
// 音调指示器的半径
4962
private int indicatorRadius;
63+
// 每句最高分
64+
private int scorePerSentence = 100;
65+
// 初始分数
66+
private float mInitialScore;
67+
// 每句歌词分数
68+
public List<Double> sentenceScoreList = new ArrayList<>();
69+
// 累计分数
70+
public float cumulatedScore;
71+
// 歌曲总分数
72+
public float totalScore;
73+
// 分数阈值 大于此值计分 小于不计分
74+
public final float scoreCountLine = 0.4f;
5075

5176
private final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
5277
private int mNormalTextColor;
@@ -56,14 +81,16 @@ public class PitchView extends View {
5681

5782
private float dotPointX = 0F;//亮点坐标
5883

84+
// 音调及分数回调
85+
public OnActionListener onActionListener;
86+
87+
//<editor-fold desc="Init Related">
5988
public PitchView(Context context) {
60-
super(context);
61-
init(null);
89+
this(context, null);
6290
}
6391

6492
public PitchView(Context context, @Nullable AttributeSet attrs) {
65-
super(context, attrs);
66-
init(attrs);
93+
this(context, attrs, 0);
6794
}
6895

6996
public PitchView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
@@ -74,7 +101,6 @@ public PitchView(Context context, @Nullable AttributeSet attrs, int defStyleAttr
74101
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
75102
public PitchView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
76103
super(context, attrs, defStyleAttr, defStyleRes);
77-
78104
init(attrs);
79105
}
80106

@@ -87,12 +113,12 @@ private void init(@Nullable AttributeSet attrs) {
87113
TypedArray ta = getContext().obtainStyledAttributes(attrs, R.styleable.PitchView);
88114
mNormalTextColor = ta.getColor(R.styleable.PitchView_pitchNormalTextColor, getResources().getColor(R.color.lrc_normal_text_color));
89115
mDoneTextColor = ta.getColor(R.styleable.PitchView_pitchDoneTextColor, getResources().getColor(R.color.lrc_current_text_color));
116+
mInitialScore = ta.getFloat(R.styleable.PitchView_pitchInitialScore, 50f);
90117
ta.recycle();
91118

92119
int startColor = getResources().getColor(R.color.pitch_start);
93120
int endColor = getResources().getColor(R.color.pitch_end);
94121
linearGradient = new LinearGradient(dotPointX, 0, 0, 0, startColor, endColor, Shader.TileMode.CLAMP);
95-
96122
}
97123

98124
@Override
@@ -124,24 +150,24 @@ protected void onDraw(Canvas canvas) {
124150
drawLocalPitch(canvas);
125151
drawItems(canvas);
126152
}
153+
//</editor-fold>
127154

128155
private void drawLocalPitch(Canvas canvas) {
129156
mPaint.setShader(null);
130157
mPaint.setColor(mNormalTextColor);
131158
float value = getPitchHeight();
132-
if(value >= 0){
159+
if (value >= 0) {
133160
canvas.drawCircle(dotPointX, value, indicatorRadius, mPaint);
134161
}
135162
}
136163

137164
private float getPitchHeight() {
138165
float res = 0;
139-
if(mLocalPitch!=0 && pitchMax != 0 && pitchMin != 100){
166+
if (mLocalPitch != 0 && pitchMax != 0 && pitchMin != 100) {
140167
float realPitchMax = pitchMax + 5;
141168
float realPitchMin = pitchMin - 5;
142-
res = (float) (1 - ((mLocalPitch - pitchMin) / (realPitchMax - realPitchMin)) ) * getHeight();
143-
}
144-
else if(mLocalPitch == 0){
169+
res = (1 - ((mLocalPitch - pitchMin) / (realPitchMax - realPitchMin))) * getHeight();
170+
} else if (mLocalPitch == 0) {
145171
res = getHeight();
146172
}
147173
return res;
@@ -168,7 +194,7 @@ private void drawItems(Canvas canvas) {
168194
float x = dotPointX * 1.3f - currentPX;
169195
float y = 0;
170196
float widthTone = 0;
171-
float mItemHeight = getHeight() / (float) (realPitchMax - realPitchMin);//高度
197+
float mItemHeight = getHeight() / (realPitchMax - realPitchMin);//高度
172198
long preEndTIme = 0;
173199
for (int i = 0; i < entrys.size(); i++) {
174200
LrcEntryData entry = lrcData.entrys.get(i);
@@ -213,14 +239,30 @@ private void drawItems(Canvas canvas) {
213239
*
214240
* @param data 歌词信息对象
215241
*/
216-
public void setLrcData(LrcData data) {
242+
public void setLrcData(@Nullable LrcData data) {
217243
lrcData = data;
244+
totalPitch = 0;
245+
246+
mCurrentTime = 0;
247+
pitchMax = 0;
248+
pitchMin = 100;
249+
250+
currentPitchStartTime = 0;
251+
currentPitchEndTime = 0;
252+
currentEntryEndTime = 0;
253+
currentScoreEntryEndTime = 0;
254+
sentenceScoreList.clear();
218255

219256
if (lrcData != null && lrcData.entrys != null && !lrcData.entrys.isEmpty()) {
257+
258+
lrcEndTime = lrcData.entrys.get(lrcData.entrys.size() - 1).getEndTime();
259+
totalScore = scorePerSentence * lrcData.entrys.size() + mInitialScore;
260+
220261
for (LrcEntryData entry : lrcData.entrys) {
221262
for (LrcEntryData.Tone tone : entry.tones) {
222263
pitchMin = Math.min(pitchMin, tone.pitch);
223264
pitchMax = Math.max(pitchMax, tone.pitch);
265+
totalPitch++;
224266
}
225267
}
226268
}
@@ -233,49 +275,157 @@ public void setLrcData(LrcData data) {
233275

234276
private void setMLocalPitch(float mLocalPitch) {
235277
this.mLocalPitch = mLocalPitch;
278+
invalidate();
236279
}
280+
237281
/**
238-
* 更新音调
282+
* 根据当前播放时间获取 Pitch
239283
*
240-
* @param pitch 单位hz
284+
* @return 当前时间歌词的 Pitch
241285
*/
242-
public void updateLocalPitch(double pitch) {
243-
mHandler.postDelayed(new Runnable() {
244-
@Override
245-
public void run() {
246-
if(mLocalPitch == pitch){
247-
mLocalPitch = 0;
286+
private float findPitchByTime() {
287+
if (lrcData == null) return 0;
288+
289+
float resPitch = 0;
290+
int entryCount = lrcData.entrys.size();
291+
for (int i = 0; i < entryCount; i++) {
292+
LrcEntryData tempEntry = lrcData.entrys.get(i);
293+
if (mCurrentTime >= tempEntry.getStartTime()) { // 索引
294+
int toneCount = tempEntry.tones.size();
295+
for (int j = 0; j < toneCount; j++) {
296+
LrcEntryData.Tone tempTone = tempEntry.tones.get(j);
297+
if (mCurrentTime <= tempTone.end) {
298+
resPitch = tempTone.pitch;
299+
currentPitchStartTime = tempTone.begin;
300+
currentPitchEndTime = tempTone.end;
301+
302+
currentEntryEndTime = tempEntry.getEndTime();
303+
break;
304+
}
248305
}
306+
break;
307+
}
308+
}
309+
if (resPitch == 0) {
310+
currentPitchStartTime = 0;
311+
currentPitchEndTime = 0;
312+
currentEntryEndTime = 0;
313+
}
314+
return resPitch;
315+
}
316+
317+
/**
318+
* 更新音调,更新分数,执行圆点动画
319+
*
320+
* @param pitch 单位hz
321+
*/
322+
public void updateLocalPitch(float pitch) {
323+
if (lrcData == null) return;
324+
float desiredPitch = findPitchByTime();
325+
if (desiredPitch != 0)
326+
updateScore(pitchToTone(pitch), pitchToTone(desiredPitch));
327+
328+
mHandler.removeCallbacksAndMessages(null);
329+
mHandler.postDelayed(() -> {
330+
if (mLocalPitch == pitch) {
331+
mLocalPitch = 0;
249332
}
250333
}, 2000L);
251-
ObjectAnimator.ofFloat(this, "mLocalPitch", this.mLocalPitch, (float) pitch).setDuration(50).start();
252-
invalidate();
334+
ObjectAnimator.ofFloat(this, "mLocalPitch", this.mLocalPitch, pitch).setDuration(50).start();
335+
}
336+
337+
/**
338+
* 更新当前分数
339+
*
340+
* @param currentTone 演唱值
341+
* @param desiredTone 理想值
342+
*/
343+
private void updateScore(double currentTone, double desiredTone) {
344+
double score = 1 - Math.abs(desiredTone - currentTone) / desiredTone;
345+
score = score >= scoreCountLine ? score : 0f;
346+
score *= scorePerSentence;
347+
348+
// 当前未在打分 <==> 定位打分句结束时间到当前句
349+
if (sentenceScoreList.isEmpty()) currentScoreEntryEndTime = currentEntryEndTime;
350+
351+
// 打分句结束时间已过 或者 最后一句已经结束
352+
if (mCurrentTime > currentScoreEntryEndTime || mCurrentTime > lrcEndTime) { // 已经到下一句了
353+
// 分数列表不为空
354+
if (!sentenceScoreList.isEmpty()) {
355+
356+
// 计算歌词当前句的分数 = 所有打分/分数个数
357+
double tempScore = 0;
358+
for (Double toneScore : sentenceScoreList)
359+
tempScore += toneScore;
360+
361+
// 统计到累计分数
362+
cumulatedScore += tempScore / sentenceScoreList.size();
363+
// 回调到上层
364+
dispatchScore(score);
365+
// 清除打分
366+
sentenceScoreList.clear();
367+
}
368+
}
369+
370+
sentenceScoreList.add(score);
371+
}
372+
373+
/**
374+
* 根据当前歌曲时间决定是否回调{@link OnActionListener#onScore(double, double, double)}
375+
*
376+
* @param score 本次算法返回的分数
377+
*/
378+
private void dispatchScore(double score) {
379+
if (onActionListener != null) onActionListener.onScore(score, cumulatedScore, totalScore);
253380
}
254381

255382
/**
256383
* 更新进度,单位毫秒
384+
* 根据当前时间,决定是否回调{@link OnActionListener#onOriginalPitch(float, int)}
385+
* 与打分逻辑无关
257386
*
258387
* @param time 当前播放时间,毫秒
259388
*/
260389
public void updateTime(long time) {
261390
if (lrcData == null) {
262391
return;
392+
} else if (time < currentPitchStartTime || time > currentPitchEndTime) {
393+
onActionListener.onOriginalPitch(findPitchByTime(), totalPitch);
263394
}
264395

265396
this.mCurrentTime = time;
266397

267398
invalidate();
268399
}
269400

270-
/**
271-
* 重置内部状态,清空已经加载的歌词
272-
*/
273-
public void reset() {
274-
lrcData = null;
275-
mCurrentTime = 0;
276-
pitchMax = 0;
277-
pitchMin = 100;
401+
@Override
402+
protected void onDetachedFromWindow() {
403+
super.onDetachedFromWindow();
404+
if (onActionListener != null) onActionListener = null;
405+
}
278406

279-
invalidate();
407+
public static double pitchToTone(double pitch) {
408+
double eps = 1e-6;
409+
return (Math.max(0, Math.log(pitch / 55 + eps) / Math.log(2))) * 12;
410+
}
411+
412+
public static interface OnActionListener {
413+
414+
/**
415+
* 咪咕歌词原始参考pitch值回调, 用于开发者自行实现打分逻辑. 歌词每个tone回调一次
416+
* pitch: 当前tone的pitch值
417+
* totalCount: 整个xml的tone个数, 用于开发者方便自己在app层计算平均分.
418+
*/
419+
void onOriginalPitch(float pitch, int totalCount);
420+
421+
/**
422+
* paas组件内置的打分回调, 每句歌词结束的时候提供回调(句指xml中的sentence节点),
423+
* 并提供totalScore参考值用于按照百分比方式显示分数
424+
*
425+
* @param score 这次回调的分数 0-10之间
426+
* @param cumulativeScore 累计的分数 初始分累计到当前的分数
427+
* @param totalScore 总分 = 初始分(默认值0分) + xml中sentence的个数 * 10
428+
*/
429+
void onScore(double score, double cumulativeScore, double totalScore);
280430
}
281431
}

lrcview/src/main/java/io/agora/lrcview/bean/LrcEntryData.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,4 +46,9 @@ public long getStartTime() {
4646
Tone first = tones.get(0);
4747
return first.begin;
4848
}
49+
50+
public long getEndTime(){
51+
if (tones == null || tones.isEmpty()) return 0;
52+
else return tones.get(tones.size()-1).end;
53+
}
4954
}

lrcview/src/main/res/values/attrs.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,5 +20,6 @@
2020
<declare-styleable name="PitchView">
2121
<attr name="pitchNormalTextColor" format="reference|color" />
2222
<attr name="pitchDoneTextColor" format="reference|color" />
23+
<attr name="pitchInitialScore" format="float" />
2324
</declare-styleable>
2425
</resources>

0 commit comments

Comments
 (0)