|
@@ -14,7 +14,6 @@ import java.util.stream.Collectors;
|
|
|
|
|
|
import javax.sound.sampled.AudioFormat;
|
|
import javax.sound.sampled.AudioFormat;
|
|
|
|
|
|
-import org.apache.commons.lang3.ArrayUtils;
|
|
|
|
import org.apache.commons.lang3.StringUtils;
|
|
import org.apache.commons.lang3.StringUtils;
|
|
import org.slf4j.Logger;
|
|
import org.slf4j.Logger;
|
|
import org.slf4j.LoggerFactory;
|
|
import org.slf4j.LoggerFactory;
|
|
@@ -75,7 +74,7 @@ public class UserChannelContext {
|
|
|
|
|
|
private float beatDuration;
|
|
private float beatDuration;
|
|
|
|
|
|
- private boolean delayProcessed;
|
|
|
|
|
|
+ private boolean isProcessedDynamicOffset;
|
|
|
|
|
|
// 曲目与musicxml对应关系
|
|
// 曲目与musicxml对应关系
|
|
private ConcurrentHashMap<Integer, MusicXmlBasicInfo> songMusicXmlMap = new ConcurrentHashMap<Integer, MusicXmlBasicInfo>();
|
|
private ConcurrentHashMap<Integer, MusicXmlBasicInfo> songMusicXmlMap = new ConcurrentHashMap<Integer, MusicXmlBasicInfo>();
|
|
@@ -232,7 +231,7 @@ public class UserChannelContext {
|
|
floatSamples = new ArrayList<Float>();
|
|
floatSamples = new ArrayList<Float>();
|
|
recordId = null;
|
|
recordId = null;
|
|
playTime = 0;
|
|
playTime = 0;
|
|
- delayProcessed = false;
|
|
|
|
|
|
+ isProcessedDynamicOffset = false;
|
|
offsetMS = 0;
|
|
offsetMS = 0;
|
|
dynamicOffset = 0;
|
|
dynamicOffset = 0;
|
|
handlerSwitch = false;
|
|
handlerSwitch = false;
|
|
@@ -270,7 +269,7 @@ public class UserChannelContext {
|
|
|
|
|
|
return null;
|
|
return null;
|
|
}
|
|
}
|
|
-
|
|
|
|
|
|
+
|
|
public int getTotalMusicNoteIndex(Integer songId) {
|
|
public int getTotalMusicNoteIndex(Integer songId) {
|
|
if (songMusicXmlMap.size() == 0) {
|
|
if (songMusicXmlMap.size() == 0) {
|
|
return -1;
|
|
return -1;
|
|
@@ -351,6 +350,10 @@ public class UserChannelContext {
|
|
|
|
|
|
public void handle(byte[] datas) {
|
|
public void handle(byte[] datas) {
|
|
|
|
|
|
|
|
+ //applyLowPassFilter(datas, 3000, audioFormat.getSampleRate());
|
|
|
|
+ //applyMovingAverageFilter(datas, 5);
|
|
|
|
+ //datas = applyNoiseGate(datas, 1000);
|
|
|
|
+
|
|
if(converter == null) {
|
|
if(converter == null) {
|
|
converter = AudioFloatConverter.getConverter(audioFormat);
|
|
converter = AudioFloatConverter.getConverter(audioFormat);
|
|
}
|
|
}
|
|
@@ -377,7 +380,8 @@ public class UserChannelContext {
|
|
|
|
|
|
double splDb = 0;
|
|
double splDb = 0;
|
|
float[] energyEnvelop = Signals.energyEnvelope(samples, frameSize);
|
|
float[] energyEnvelop = Signals.energyEnvelope(samples, frameSize);
|
|
- int amplitude = (int) Signals.norm(samples);
|
|
|
|
|
|
+ float amplitude = Signals.norm(samples);
|
|
|
|
+ float power = Signals.power(samples);
|
|
|
|
|
|
int decibels = 0;
|
|
int decibels = 0;
|
|
|
|
|
|
@@ -417,20 +421,17 @@ public class UserChannelContext {
|
|
basicFrequency = totalChunkAnalysisList.get(totalChunkAnalysisList.size() - 1).getFrequency();
|
|
basicFrequency = totalChunkAnalysisList.get(totalChunkAnalysisList.size() - 1).getFrequency();
|
|
}
|
|
}
|
|
chunkAnalysis.setFrequency(handleHarmonic(basicFrequency, playFrequency));
|
|
chunkAnalysis.setFrequency(handleHarmonic(basicFrequency, playFrequency));
|
|
|
|
+ chunkAnalysis.setFrequency(handleHarmonic(musicXmlNote.getFrequency(), playFrequency));
|
|
|
|
|
|
totalChunkAnalysisList.add(chunkAnalysis);
|
|
totalChunkAnalysisList.add(chunkAnalysis);
|
|
|
|
|
|
- LOGGER.debug("user:{} delayProcessed:{} dynamicOffset:{} Frequency:{} splDb:{} amplitude:{} decibels:{} endtime:{}", user,
|
|
|
|
- delayProcessed, dynamicOffset, chunkAnalysis.getFrequency(), splDb, amplitude, decibels, playTime);
|
|
|
|
|
|
+ LOGGER.debug("user:{} isProcessedDynamicOffset:{} dynamicOffset:{} Frequency:{} splDb:{} amplitude:{} power:{} decibels:{} endtime:{}", user,
|
|
|
|
+ isProcessedDynamicOffset, dynamicOffset, chunkAnalysis.getFrequency(), splDb, amplitude, power, decibels, playTime);
|
|
|
|
|
|
if (playTime >= (musicXmlNote.getDuration() + musicXmlNote.getTimeStamp() + getOffsetMS() + beatDuration)) {
|
|
if (playTime >= (musicXmlNote.getDuration() + musicXmlNote.getTimeStamp() + getOffsetMS() + beatDuration)) {
|
|
|
|
|
|
musicXmlNote.setTimeStamp(musicXmlNote.getTimeStamp() + getOffsetMS() + beatDuration);
|
|
musicXmlNote.setTimeStamp(musicXmlNote.getTimeStamp() + getOffsetMS() + beatDuration);
|
|
|
|
|
|
- if(musicXmlNote.getFrequency() <= 0) {
|
|
|
|
- musicXmlNote.setDontEvaluating(true);
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
if (musicXmlNote.getDontEvaluating()) {
|
|
if (musicXmlNote.getDontEvaluating()) {
|
|
noteAnalysis.setIgnore(true);
|
|
noteAnalysis.setIgnore(true);
|
|
}
|
|
}
|
|
@@ -439,16 +440,16 @@ public class UserChannelContext {
|
|
|
|
|
|
// 判断节奏(音符持续时间内有不间断的音高,就节奏正确)
|
|
// 判断节奏(音符持续时间内有不间断的音高,就节奏正确)
|
|
if (!StringUtils.equalsIgnoreCase(evaluationCriteria, EvaluationCriteriaEnum.FREQUENCY.getCode())) {
|
|
if (!StringUtils.equalsIgnoreCase(evaluationCriteria, EvaluationCriteriaEnum.FREQUENCY.getCode())) {
|
|
- noteAnalysis.setPlayFrequency(-1);
|
|
|
|
noteAnalysis.setTempoStatus(computeTempoWithAmplitude(musicXmlNote, noteAnalysis, nodeChunkAnalysisList));
|
|
noteAnalysis.setTempoStatus(computeTempoWithAmplitude(musicXmlNote, noteAnalysis, nodeChunkAnalysisList));
|
|
|
|
+ noteAnalysis.setPlayFrequency(-1);
|
|
} else {
|
|
} else {
|
|
- noteAnalysis.setPlayFrequency(computeFrequency(musicXmlNote, nodeChunkAnalysisList));
|
|
|
|
noteAnalysis.setTempoStatus(computeTempoWithFrequency(musicXmlNote, noteAnalysis, nodeChunkAnalysisList));
|
|
noteAnalysis.setTempoStatus(computeTempoWithFrequency(musicXmlNote, noteAnalysis, nodeChunkAnalysisList));
|
|
|
|
+ noteAnalysis.setPlayFrequency(computeFrequency(musicXmlNote, noteAnalysis, nodeChunkAnalysisList));
|
|
}
|
|
}
|
|
|
|
|
|
evaluateForNote(musicXmlNote, noteAnalysis, nodeChunkAnalysisList);// 对当前音符评分
|
|
evaluateForNote(musicXmlNote, noteAnalysis, nodeChunkAnalysisList);// 对当前音符评分
|
|
|
|
|
|
- LOGGER.debug("当前音符下标[{}] 预计频率:{} 实际频率:{} 节奏:{}", noteAnalysis.getMusicalNotesIndex(), musicXmlNote.getFrequency(),
|
|
|
|
|
|
+ LOGGER.debug("当前小节[{}] 音符下标[{}] 预计频率:{} 实际频率:{} 节奏:{}", musicXmlNote.getMeasureIndex(), noteAnalysis.getMusicalNotesIndex(), musicXmlNote.getFrequency(),
|
|
noteAnalysis.getPlayFrequency(), noteAnalysis.getTempoStatus());
|
|
noteAnalysis.getPlayFrequency(), noteAnalysis.getTempoStatus());
|
|
|
|
|
|
doneNoteAnalysisList.add(noteAnalysis);
|
|
doneNoteAnalysisList.add(noteAnalysis);
|
|
@@ -641,15 +642,33 @@ public class UserChannelContext {
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
- private float computeFrequency(MusicXmlNote musicXmlNote, List<ChunkAnalysis> chunkAnalysisList) {
|
|
|
|
|
|
+ private float computeFrequency(MusicXmlNote musicXmlNote, NoteAnalysis noteAnalysis, List<ChunkAnalysis> chunkAnalysisList) {
|
|
|
|
|
|
if (chunkAnalysisList == null || chunkAnalysisList.size() == 0 || musicXmlNote.getDontEvaluating()) {
|
|
if (chunkAnalysisList == null || chunkAnalysisList.size() == 0 || musicXmlNote.getDontEvaluating()) {
|
|
return -1;
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
|
|
- reduceNoise(chunkAnalysisList, EvaluationCriteriaEnum.FREQUENCY);
|
|
|
|
|
|
+ List<ChunkAnalysis> chunkList = null;
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ if(noteAnalysis.getRhythmStartTime() > 0) {
|
|
|
|
+ chunkList = chunkAnalysisList.stream().filter(t -> t.getStartTime() >= noteAnalysis.getRhythmStartTime()).collect(Collectors.toList());
|
|
|
|
+ } else {
|
|
|
|
+ chunkList = new ArrayList<ChunkAnalysis>(chunkAnalysisList);
|
|
|
|
+ }*/
|
|
|
|
+
|
|
|
|
+ chunkAnalysisList = totalChunkAnalysisList.stream()
|
|
|
|
+ .filter(t -> Double.doubleToLongBits(t.getStartTime()) >= Double.doubleToLongBits(noteAnalysis.getRhythmStartTime())
|
|
|
|
+ && Double.doubleToLongBits(t.getEndTime()) <= Double.doubleToLongBits(noteAnalysis.getRhythmStartTime() + musicXmlNote.getDuration()))
|
|
|
|
+ .collect(Collectors.toList());
|
|
|
|
|
|
- List<ChunkAnalysis> chunkList = new ArrayList<ChunkAnalysis>(chunkAnalysisList);
|
|
|
|
|
|
+ reduceNoise(chunkAnalysisList, EvaluationCriteriaEnum.FREQUENCY);
|
|
|
|
+
|
|
|
|
+ int range = hardLevel.getTempoEffectiveRange(musicXmlNote.getDenominator(), musicXmlNote.getDuration());
|
|
|
|
+
|
|
|
|
+ chunkList = chunkAnalysisList.subList(0, chunkAnalysisList.size() * (100 - range) / 100);
|
|
|
|
+
|
|
|
|
+ LOGGER.debug("根据节奏点[{}],取当前音符有效的信号范围[ {} - {} ]计算评率", noteAnalysis.getRhythmStartTime(), chunkList.get(0).getStartTime(), chunkList.get(chunkList.size() - 1).getEndTime());
|
|
|
|
|
|
List<Float> chunkFrequencyList = chunkList.stream().map(t -> t.getFrequency())
|
|
List<Float> chunkFrequencyList = chunkList.stream().map(t -> t.getFrequency())
|
|
.filter(t -> t.doubleValue() > MIN_FREQUECY && t.doubleValue() < MAX_FREQUECY).collect(Collectors.toList());
|
|
.filter(t -> t.doubleValue() > MIN_FREQUECY && t.doubleValue() < MAX_FREQUECY).collect(Collectors.toList());
|
|
@@ -679,10 +698,23 @@ public class UserChannelContext {
|
|
|
|
|
|
ChunkAnalysis firstChunkAnalysis = chunkAnalysisList.get(0);
|
|
ChunkAnalysis firstChunkAnalysis = chunkAnalysisList.get(0);
|
|
|
|
|
|
|
|
+ noteAnalysis.setRhythmStartTime(musicXmlNote.getTimeStamp() + dynamicOffset);//默认值
|
|
|
|
+
|
|
Map<Integer, Double> frequencyRhythmMap = queryRhythmsByFrequency(musicXmlNote, noteAnalysis, chunkAnalysisList);
|
|
Map<Integer, Double> frequencyRhythmMap = queryRhythmsByFrequency(musicXmlNote, noteAnalysis, chunkAnalysisList);
|
|
|
|
+
|
|
|
|
+ if(musicXmlNote.getFrequency() < MIN_FREQUECY) {
|
|
|
|
+ if(frequencyRhythmMap.size() == 0) {
|
|
|
|
+ noteAnalysis.setMusicalErrorType(NoteErrorType.RIGHT);
|
|
|
|
+ return 99;
|
|
|
|
+ } else {
|
|
|
|
+ noteAnalysis.setMusicalErrorType(NoteErrorType.CADENCE_WRONG);
|
|
|
|
+ LOGGER.debug("根据能量包络检测到[{}]个断点,分别在[{}]", frequencyRhythmMap.size(), frequencyRhythmMap.values().stream().map(value -> value).map(Object::toString).collect(Collectors.joining(",")));
|
|
|
|
+ return 0;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
|
|
if(frequencyRhythmMap.size() > 1) {
|
|
if(frequencyRhythmMap.size() > 1) {
|
|
- LOGGER.debug("根据音高检测到[{}]个断点,分别在[{}]", frequencyRhythmMap.size(), frequencyRhythmMap.values().stream().map(value -> value + firstChunkAnalysis.getStartTime()).map(Object::toString).collect(Collectors.joining(",")));
|
|
|
|
|
|
+ LOGGER.debug("根据音高检测到[{}]个断点,分别在[{}]", frequencyRhythmMap.size(), frequencyRhythmMap.values().stream().map(value -> value).map(Object::toString).collect(Collectors.joining(",")));
|
|
return 0;
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
|
|
@@ -694,6 +726,8 @@ public class UserChannelContext {
|
|
firstBeatTime = entry.getValue();
|
|
firstBeatTime = entry.getValue();
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ noteAnalysis.setRhythmStartTime(firstBeatTime);
|
|
|
|
+
|
|
if ((firstBeatTime - firstChunkAnalysis.getStartTime()) * 100 / musicXmlNote.getDuration() > hardLevel.getTempoEffectiveRange(musicXmlNote.getDenominator(),
|
|
if ((firstBeatTime - firstChunkAnalysis.getStartTime()) * 100 / musicXmlNote.getDuration() > hardLevel.getTempoEffectiveRange(musicXmlNote.getDenominator(),
|
|
musicXmlNote.getDuration()) * 2) {
|
|
musicXmlNote.getDuration()) * 2) {
|
|
LOGGER.debug("节奏错误原因:进入时间点[{}]太晚", firstBeatTime);
|
|
LOGGER.debug("节奏错误原因:进入时间点[{}]太晚", firstBeatTime);
|
|
@@ -735,8 +769,11 @@ public class UserChannelContext {
|
|
}
|
|
}
|
|
*/
|
|
*/
|
|
|
|
|
|
- if(dynamicOffset == 0) {
|
|
|
|
- dynamicOffset = firstBeatTime - firstChunkAnalysis.getStartTime();
|
|
|
|
|
|
+ if(isProcessedDynamicOffset == false) {
|
|
|
|
+
|
|
|
|
+ double startTime = musicXmlNote.getTimeStamp() + dynamicOffset;
|
|
|
|
+ dynamicOffset = firstBeatTime - startTime;
|
|
|
|
+ isProcessedDynamicOffset = true;
|
|
}
|
|
}
|
|
|
|
|
|
return 99;
|
|
return 99;
|
|
@@ -753,8 +790,20 @@ public class UserChannelContext {
|
|
|
|
|
|
Map<Integer, Double> rhythmMap = queryRhythmsByAmplitude(musicXmlNote, noteAnalysis, chunkAnalysisList);
|
|
Map<Integer, Double> rhythmMap = queryRhythmsByAmplitude(musicXmlNote, noteAnalysis, chunkAnalysisList);
|
|
|
|
|
|
|
|
+ ChunkAnalysis firstChunkAnalysis = chunkAnalysisList.get(0);
|
|
|
|
+
|
|
|
|
+ if(musicXmlNote.getFrequency() < MIN_FREQUECY) {
|
|
|
|
+ if(rhythmMap.size() == 0) {
|
|
|
|
+ noteAnalysis.setMusicalErrorType(NoteErrorType.RIGHT);
|
|
|
|
+ return 3;
|
|
|
|
+ } else {
|
|
|
|
+ noteAnalysis.setMusicalErrorType(NoteErrorType.CADENCE_WRONG);
|
|
|
|
+ LOGGER.debug("根据能量包络检测到[{}]个断点,分别在[{}]", rhythmMap.size(), rhythmMap.values().stream().map(value -> value + firstChunkAnalysis.getStartTime()).map(Object::toString).collect(Collectors.joining(",")));
|
|
|
|
+ return 0;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
if(rhythmMap.size() != 1) {
|
|
if(rhythmMap.size() != 1) {
|
|
- ChunkAnalysis firstChunkAnalysis = chunkAnalysisList.get(0);
|
|
|
|
LOGGER.debug("根据能量包络检测到[{}]个断点,分别在[{}]", rhythmMap.size(), rhythmMap.values().stream().map(value -> value + firstChunkAnalysis.getStartTime()).map(Object::toString).collect(Collectors.joining(",")));
|
|
LOGGER.debug("根据能量包络检测到[{}]个断点,分别在[{}]", rhythmMap.size(), rhythmMap.values().stream().map(value -> value + firstChunkAnalysis.getStartTime()).map(Object::toString).collect(Collectors.joining(",")));
|
|
|
|
|
|
if(rhythmMap.size() > 1) {
|
|
if(rhythmMap.size() > 1) {
|
|
@@ -765,8 +814,6 @@ public class UserChannelContext {
|
|
return 0;
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
|
|
- ChunkAnalysis firstChunkAnalysis = chunkAnalysisList.get(0);
|
|
|
|
-
|
|
|
|
// 判断进入时间点
|
|
// 判断进入时间点
|
|
double firstBeatTime = 0;
|
|
double firstBeatTime = 0;
|
|
for(Entry<Integer, Double> entry : rhythmMap.entrySet()) {
|
|
for(Entry<Integer, Double> entry : rhythmMap.entrySet()) {
|
|
@@ -787,7 +834,12 @@ public class UserChannelContext {
|
|
|
|
|
|
noteAnalysis.setMusicalErrorType(NoteErrorType.RIGHT);
|
|
noteAnalysis.setMusicalErrorType(NoteErrorType.RIGHT);
|
|
|
|
|
|
- dynamicOffset = firstBeatTime - firstChunkAnalysis.getStartTime();
|
|
|
|
|
|
+ if(isProcessedDynamicOffset == false) {
|
|
|
|
+
|
|
|
|
+ double startTime = musicXmlNote.getTimeStamp() + dynamicOffset;
|
|
|
|
+ dynamicOffset = firstBeatTime - startTime;
|
|
|
|
+ isProcessedDynamicOffset = true;
|
|
|
|
+ }
|
|
|
|
|
|
return 3;
|
|
return 3;
|
|
}
|
|
}
|
|
@@ -905,6 +957,77 @@ public class UserChannelContext {
|
|
return Math.max(musicXmlNote.getTimeStamp() + dynamicOffset, onsetStartTime);
|
|
return Math.max(musicXmlNote.getTimeStamp() + dynamicOffset, onsetStartTime);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ private void applyLowPassFilter(byte[] buffer, int cutoffFrequency, float sampleRate) {
|
|
|
|
+ int sampleSize = 2; // 16-bit audio
|
|
|
|
+ int length = buffer.length / sampleSize;
|
|
|
|
+ double rc = 1.0 / (cutoffFrequency * 2 * Math.PI);
|
|
|
|
+ double dt = 1.0 / sampleRate;
|
|
|
|
+ double alpha = dt / (rc + dt);
|
|
|
|
+ short[] samples = new short[length];
|
|
|
|
+
|
|
|
|
+ // 将字节数组转换为短整型数组
|
|
|
|
+ for (int i = 0; i < length; i++) {
|
|
|
|
+ samples[i] = (short) ((buffer[2 * i + 1] << 8) | (buffer[2 * i] & 0xff));
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // 应用低通滤波器
|
|
|
|
+ for (int i = 1; i < length; i++) {
|
|
|
|
+ samples[i] = (short) (samples[i - 1] + alpha * (samples[i] - samples[i - 1]));
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // 将处理后的短整型数组转换回字节数组
|
|
|
|
+ for (int i = 0; i < length; i++) {
|
|
|
|
+ buffer[2 * i] = (byte) (samples[i] & 0xff);
|
|
|
|
+ buffer[2 * i + 1] = (byte) ((samples[i] >> 8) & 0xff);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ private static byte[] applyNoiseGate(byte[] buffer, int threshold) {
|
|
|
|
+ byte[] filteredBuffer = new byte[buffer.length];
|
|
|
|
+ for (int i = 0; i < buffer.length; i++) {
|
|
|
|
+ if (Math.abs(buffer[i]) < threshold) {
|
|
|
|
+ filteredBuffer[i] = 0; // 设置为静音
|
|
|
|
+ } else {
|
|
|
|
+ filteredBuffer[i] = buffer[i];
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ return filteredBuffer;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // 移动平均滤波器
|
|
|
|
+ private void applyMovingAverageFilter(byte[] buffer, int windowSize) {
|
|
|
|
+ int sampleSize = 2; // 16-bit audio
|
|
|
|
+ int length = buffer.length / sampleSize;
|
|
|
|
+ short[] samples = new short[length];
|
|
|
|
+
|
|
|
|
+ // 将字节数组转换为短整型数组
|
|
|
|
+ for (int i = 0; i < length; i++) {
|
|
|
|
+ samples[i] = (short) ((buffer[2 * i + 1] << 8) | (buffer[2 * i] & 0xff));
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ short[] filteredSamples = new short[length];
|
|
|
|
+
|
|
|
|
+ // 应用移动平均滤波器
|
|
|
|
+ for (int i = 0; i < length; i++) {
|
|
|
|
+ int sum = 0;
|
|
|
|
+ int count = 0;
|
|
|
|
+ for (int j = i - windowSize / 2; j <= i + windowSize / 2; j++) {
|
|
|
|
+ if (j >= 0 && j < length) {
|
|
|
|
+ sum += samples[j];
|
|
|
|
+ count++;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ filteredSamples[i] = (short) (sum / count);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // 将处理后的短整型数组转换回字节数组
|
|
|
|
+ for (int i = 0; i < length; i++) {
|
|
|
|
+ buffer[2 * i] = (byte) (filteredSamples[i] & 0xff);
|
|
|
|
+ buffer[2 * i + 1] = (byte) ((filteredSamples[i] >> 8) & 0xff);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+
|
|
|
|
|
|
|
|
|
|
private void reduceNoise(List<ChunkAnalysis> chunkAnalysisList, EvaluationCriteriaEnum criteria) {
|
|
private void reduceNoise(List<ChunkAnalysis> chunkAnalysisList, EvaluationCriteriaEnum criteria) {
|
|
@@ -940,8 +1063,6 @@ public class UserChannelContext {
|
|
return true;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
|
|
- frequency = handleHarmonic(basicFrequency, frequency);
|
|
|
|
-
|
|
|
|
return (basicFrequency != -1 && frequency != -1) && Math.abs(YINPitchDetector.getDeviationCent(basicFrequency, frequency)) < 50;
|
|
return (basicFrequency != -1 && frequency != -1) && Math.abs(YINPitchDetector.getDeviationCent(basicFrequency, frequency)) < 50;
|
|
}
|
|
}
|
|
|
|
|
|
@@ -1049,15 +1170,16 @@ public class UserChannelContext {
|
|
|
|
|
|
Map<Integer, Double> rhythMap = new HashMap<Integer, Double>();
|
|
Map<Integer, Double> rhythMap = new HashMap<Integer, Double>();
|
|
|
|
|
|
- reduceNoise(datas, EvaluationCriteriaEnum.AMPLITUDE);
|
|
|
|
|
|
+ //reduceNoise(datas, EvaluationCriteriaEnum.AMPLITUDE);
|
|
|
|
|
|
- int amplitudeThreshold = 2;
|
|
|
|
|
|
+ float amplitudeThreshold = 0.5f;
|
|
int beatContinueNum = 0;
|
|
int beatContinueNum = 0;
|
|
- int intervalTime = 150;
|
|
|
|
|
|
+ int intervalTime = 50;
|
|
ChunkAnalysis chunkAnalysis = null;
|
|
ChunkAnalysis chunkAnalysis = null;
|
|
double rhythmTime = -1;
|
|
double rhythmTime = -1;
|
|
int peakIndex = 0;
|
|
int peakIndex = 0;
|
|
int continueNumThreshold = 0;
|
|
int continueNumThreshold = 0;
|
|
|
|
+ boolean isContinue = false;
|
|
|
|
|
|
for (int i = 0; i < datas.size(); i++) {
|
|
for (int i = 0; i < datas.size(); i++) {
|
|
chunkAnalysis = datas.get(i);
|
|
chunkAnalysis = datas.get(i);
|
|
@@ -1068,15 +1190,17 @@ public class UserChannelContext {
|
|
rhythmTime = i * bufferSize * 1000 / audioFormat.getSampleRate();
|
|
rhythmTime = i * bufferSize * 1000 / audioFormat.getSampleRate();
|
|
}
|
|
}
|
|
|
|
|
|
- if (beatContinueNum > continueNumThreshold) {
|
|
|
|
|
|
+ if (beatContinueNum > continueNumThreshold && isContinue == false) {
|
|
if (rhythMap.size() == 0 || rhythmTime - rhythMap.get(peakIndex) > intervalTime) {
|
|
if (rhythMap.size() == 0 || rhythmTime - rhythMap.get(peakIndex) > intervalTime) {
|
|
peakIndex++;
|
|
peakIndex++;
|
|
rhythMap.put(peakIndex, rhythmTime);
|
|
rhythMap.put(peakIndex, rhythmTime);
|
|
|
|
+ isContinue =true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
} else {
|
|
} else {
|
|
beatContinueNum = 0;
|
|
beatContinueNum = 0;
|
|
|
|
+ isContinue = false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
@@ -1086,17 +1210,18 @@ public class UserChannelContext {
|
|
|
|
|
|
private Map<Integer, Double> queryRhythmsByFrequency(MusicXmlNote musicXmlNote, NoteAnalysis noteAnalysis, List<ChunkAnalysis> datas){
|
|
private Map<Integer, Double> queryRhythmsByFrequency(MusicXmlNote musicXmlNote, NoteAnalysis noteAnalysis, List<ChunkAnalysis> datas){
|
|
LOGGER.debug("------------利用频率寻找节奏点------------");
|
|
LOGGER.debug("------------利用频率寻找节奏点------------");
|
|
|
|
+
|
|
/**
|
|
/**
|
|
//获取上一个音符
|
|
//获取上一个音符
|
|
Optional<NoteAnalysis> preNoteAnalysisOptinal = doneNoteAnalysisList.stream().filter(t -> t.getIndex() == musicXmlNote.getMusicalNotesIndex() - 1).findFirst();
|
|
Optional<NoteAnalysis> preNoteAnalysisOptinal = doneNoteAnalysisList.stream().filter(t -> t.getIndex() == musicXmlNote.getMusicalNotesIndex() - 1).findFirst();
|
|
|
|
|
|
- double preNoteAvgFrequency = -1;
|
|
|
|
|
|
+ float preNoteAvgFrequency = -1;
|
|
if(preNoteAnalysisOptinal.isPresent()) {
|
|
if(preNoteAnalysisOptinal.isPresent()) {
|
|
preNoteAvgFrequency = preNoteAnalysisOptinal.get().getPlayFrequency();
|
|
preNoteAvgFrequency = preNoteAnalysisOptinal.get().getPlayFrequency();
|
|
}
|
|
}
|
|
|
|
|
|
LOGGER.debug("上一个音符的平均音高[{}]", preNoteAvgFrequency);
|
|
LOGGER.debug("上一个音符的平均音高[{}]", preNoteAvgFrequency);
|
|
- */
|
|
|
|
|
|
+ **/
|
|
|
|
|
|
//获取上一个信号
|
|
//获取上一个信号
|
|
ChunkAnalysis firstChunkAnalysis = datas.get(0);
|
|
ChunkAnalysis firstChunkAnalysis = datas.get(0);
|
|
@@ -1116,6 +1241,7 @@ public class UserChannelContext {
|
|
|
|
|
|
float preNoteAvgFrequency = lastChunkAnalysis.getFrequency();
|
|
float preNoteAvgFrequency = lastChunkAnalysis.getFrequency();
|
|
|
|
|
|
|
|
+
|
|
ChunkAnalysis chunkAnalysis = null;
|
|
ChunkAnalysis chunkAnalysis = null;
|
|
Map<Integer, Double> rhythMap = new HashMap<Integer, Double>();
|
|
Map<Integer, Double> rhythMap = new HashMap<Integer, Double>();
|
|
|
|
|
|
@@ -1128,59 +1254,58 @@ public class UserChannelContext {
|
|
int peakIndex = 0;
|
|
int peakIndex = 0;
|
|
int continueNumThreshold = 0;
|
|
int continueNumThreshold = 0;
|
|
|
|
|
|
- for(int i = 0; i < datas.size(); i++) {
|
|
|
|
-
|
|
|
|
|
|
+ for (int i = 0; i < datas.size(); i++) {
|
|
|
|
+
|
|
chunkAnalysis = datas.get(i);
|
|
chunkAnalysis = datas.get(i);
|
|
-
|
|
|
|
- //若不是休止符
|
|
|
|
- if(musicXmlNote.getFrequency() >= MIN_FREQUECY) {
|
|
|
|
- if(chunkAnalysis.getFrequency() < MIN_FREQUECY) {
|
|
|
|
-
|
|
|
|
- silenceContinueNum++;
|
|
|
|
- beatContinueNum = 0;
|
|
|
|
-
|
|
|
|
- if(silenceContinueNum > continueNumThreshold) {
|
|
|
|
- preNoteAvgFrequency = chunkAnalysis.getFrequency();
|
|
|
|
|
|
+
|
|
|
|
+ // 若不是休止符
|
|
|
|
+ if (chunkAnalysis.getFrequency() < MIN_FREQUECY) {
|
|
|
|
+
|
|
|
|
+ silenceContinueNum++;
|
|
|
|
+ beatContinueNum = 0;
|
|
|
|
+
|
|
|
|
+ if (silenceContinueNum > continueNumThreshold) {
|
|
|
|
+ preNoteAvgFrequency = chunkAnalysis.getFrequency();
|
|
|
|
+ }
|
|
|
|
+ } else {
|
|
|
|
+
|
|
|
|
+ silenceContinueNum = 0;
|
|
|
|
+
|
|
|
|
+ if (preNoteAvgFrequency < MIN_FREQUECY || !isSamePitch(preNoteAvgFrequency, chunkAnalysis.getFrequency()) || musicXmlNote.getFrequency() < MIN_FREQUECY) {
|
|
|
|
+
|
|
|
|
+ if (beatContinueNum == 0) {
|
|
|
|
+ rhythmTime = chunkAnalysis.getStartTime();
|
|
}
|
|
}
|
|
- }else {
|
|
|
|
-
|
|
|
|
- silenceContinueNum = 0;
|
|
|
|
-
|
|
|
|
- if(preNoteAvgFrequency < MIN_FREQUECY || !isSamePitch(preNoteAvgFrequency, chunkAnalysis.getFrequency())) {
|
|
|
|
|
|
|
|
- if (beatContinueNum == 0) {
|
|
|
|
- rhythmTime = chunkAnalysis.getStartTime();
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- beatContinueNum++;
|
|
|
|
- if (beatContinueNum > continueNumThreshold) {
|
|
|
|
- if (chunkAnalysis.getStartTime() - lastestRhythmTime > intervalTime) {
|
|
|
|
|
|
+ beatContinueNum++;
|
|
|
|
+ if (beatContinueNum > continueNumThreshold) {
|
|
|
|
+ if (chunkAnalysis.getStartTime() - lastestRhythmTime > intervalTime) {
|
|
|
|
+
|
|
|
|
+ lastestRhythmTime = rhythmTime;
|
|
|
|
|
|
- lastestRhythmTime = rhythmTime;
|
|
|
|
|
|
+ if (peakIndex == 0 || lastestRhythmTime - rhythMap.get(peakIndex) > intervalTime) {
|
|
peakIndex++;
|
|
peakIndex++;
|
|
-
|
|
|
|
- if(peakIndex == 1 || lastestRhythmTime - rhythMap.get(peakIndex - 1) > intervalTime) {
|
|
|
|
- rhythMap.put(peakIndex, lastestRhythmTime);
|
|
|
|
- LOGGER.debug("范围内查询到音高信号,preNoteFrequency:{} peakIndex:{} EndTime:{}", preNoteAvgFrequency, peakIndex, lastestRhythmTime);
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- preNoteAvgFrequency = chunkAnalysis.getFrequency();
|
|
|
|
- beatContinueNum = 0;
|
|
|
|
|
|
+ rhythMap.put(peakIndex, lastestRhythmTime);
|
|
|
|
+ LOGGER.debug("范围内查询到音高信号,preNoteFrequency:{} peakIndex:{} EndTime:{}", preNoteAvgFrequency, peakIndex, lastestRhythmTime);
|
|
}
|
|
}
|
|
|
|
+
|
|
|
|
+ preNoteAvgFrequency = chunkAnalysis.getFrequency();
|
|
|
|
+ beatContinueNum = 0;
|
|
}
|
|
}
|
|
- }else {
|
|
|
|
- beatContinueNum = 0;
|
|
|
|
}
|
|
}
|
|
-
|
|
|
|
|
|
+ } else {
|
|
|
|
+ beatContinueNum = 0;
|
|
}
|
|
}
|
|
|
|
+
|
|
}
|
|
}
|
|
|
|
+ }
|
|
|
|
|
|
|
|
|
|
- }
|
|
|
|
|
|
|
|
return rhythMap;
|
|
return rhythMap;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+
|
|
private List<ChunkAnalysis> extract(MusicXmlNote musicXmlNote, NoteAnalysis noteAnalysis) {
|
|
private List<ChunkAnalysis> extract(MusicXmlNote musicXmlNote, NoteAnalysis noteAnalysis) {
|
|
|
|
|
|
LOGGER.debug("---------------------Extract Data----------------------");
|
|
LOGGER.debug("---------------------Extract Data----------------------");
|
|
@@ -1193,11 +1318,20 @@ public class UserChannelContext {
|
|
|
|
|
|
double endTime = startTime + musicXmlNote.getDuration();
|
|
double endTime = startTime + musicXmlNote.getDuration();
|
|
|
|
|
|
- LOGGER.debug("当前音符有效信号时值[{}]偏移[{}]后的范围[ {} - {} ]", musicXmlNote.getDuration(), floatingRange, startTime, endTime);
|
|
|
|
|
|
+ /**
|
|
|
|
+ //下一个音符信息
|
|
|
|
+ MusicXmlNote nextMusicXmlNote = getCurrentMusicNote(null, musicXmlNote.getMusicalNotesIndex() + 1);
|
|
|
|
+ if(nextMusicXmlNote != null) {
|
|
|
|
+ endTime = endTime - nextMusicXmlNote.getDuration() * hardLevel.getTempoEffectiveRange(nextMusicXmlNote.getDenominator(), nextMusicXmlNote.getDuration()) / 100;
|
|
|
|
+ }**/
|
|
|
|
+
|
|
|
|
+ double finalEndTime = endTime;
|
|
|
|
+
|
|
|
|
+ LOGGER.debug("当前音符有效信号时值[{}]偏移[{}]后的范围[ {} - {} ]", musicXmlNote.getDuration(), floatingRange, startTime, finalEndTime);
|
|
|
|
|
|
List<ChunkAnalysis> chunkAnalysisList = totalChunkAnalysisList.stream()
|
|
List<ChunkAnalysis> chunkAnalysisList = totalChunkAnalysisList.stream()
|
|
.filter(t -> Double.doubleToLongBits(t.getEndTime()) >= Double.doubleToLongBits(startTime)
|
|
.filter(t -> Double.doubleToLongBits(t.getEndTime()) >= Double.doubleToLongBits(startTime)
|
|
- && Double.doubleToLongBits(t.getStartTime()) <= Double.doubleToLongBits(endTime))
|
|
|
|
|
|
+ && Double.doubleToLongBits(t.getStartTime()) <= Double.doubleToLongBits(finalEndTime))
|
|
.collect(Collectors.toList());
|
|
.collect(Collectors.toList());
|
|
|
|
|
|
// 根据完整度取部分有效信号
|
|
// 根据完整度取部分有效信号
|
|
@@ -1213,7 +1347,7 @@ public class UserChannelContext {
|
|
return datas;
|
|
return datas;
|
|
}
|
|
}
|
|
|
|
|
|
- private float handleHarmonic(float basicFrequency, float frequency) {
|
|
|
|
|
|
+ private float handleHarmonic2(float basicFrequency, float frequency) {
|
|
|
|
|
|
if (basicFrequency > frequency) {
|
|
if (basicFrequency > frequency) {
|
|
return frequency;
|
|
return frequency;
|
|
@@ -1230,4 +1364,28 @@ public class UserChannelContext {
|
|
return frequency;
|
|
return frequency;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ private float handleHarmonic(float basicFrequency, float frequency) {
|
|
|
|
+
|
|
|
|
+ if(frequency < MIN_FREQUECY || basicFrequency < MIN_FREQUECY) {
|
|
|
|
+ return frequency;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ int roundedRatio = 0;
|
|
|
|
+ float targetFrequency = frequency;
|
|
|
|
+
|
|
|
|
+ if(basicFrequency > frequency) {
|
|
|
|
+ roundedRatio = Math.round(basicFrequency / frequency);
|
|
|
|
+ targetFrequency = frequency * roundedRatio;
|
|
|
|
+ }else {
|
|
|
|
+ roundedRatio = Math.round(frequency / basicFrequency);
|
|
|
|
+ targetFrequency = frequency / roundedRatio;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if(isSamePitch(basicFrequency, targetFrequency)) {
|
|
|
|
+ return targetFrequency;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return frequency;
|
|
|
|
+ }
|
|
|
|
+
|
|
}
|
|
}
|