|
@@ -2,7 +2,10 @@ package com.yonge.nettty.dto;
|
|
|
|
|
|
import java.util.ArrayList;
|
|
|
import java.util.Comparator;
|
|
|
+import java.util.HashMap;
|
|
|
import java.util.List;
|
|
|
+import java.util.Map;
|
|
|
+import java.util.Map.Entry;
|
|
|
import java.util.concurrent.ConcurrentHashMap;
|
|
|
import java.util.concurrent.atomic.AtomicInteger;
|
|
|
import java.util.stream.Collectors;
|
|
@@ -17,7 +20,8 @@ import be.tarsos.dsp.pitch.PitchDetectionHandler;
|
|
|
import be.tarsos.dsp.pitch.PitchDetectionResult;
|
|
|
|
|
|
import com.yonge.audio.analysis.Signals;
|
|
|
-import com.yonge.audio.analysis.detector.FrequencyDetector;
|
|
|
+import com.yonge.audio.analysis.detector.YINPitchDetector;
|
|
|
+import com.yonge.nettty.dto.NoteAnalysis.NoteErrorType;
|
|
|
import com.yonge.nettty.entity.MusicXmlBasicInfo;
|
|
|
import com.yonge.nettty.entity.MusicXmlNote;
|
|
|
import com.yonge.nettty.entity.MusicXmlSection;
|
|
@@ -29,22 +33,46 @@ import com.yonge.netty.server.processor.WaveformWriter;
|
|
|
public class UserChannelContext implements PitchDetectionHandler {
|
|
|
|
|
|
private final static Logger LOGGER = LoggerFactory.getLogger(UserChannelContext.class);
|
|
|
-
|
|
|
+
|
|
|
+ private final double offsetMS = 300;
|
|
|
+
|
|
|
+ private Long recordId;
|
|
|
+
|
|
|
// 曲目与musicxml对应关系
|
|
|
private ConcurrentHashMap<Integer, MusicXmlBasicInfo> songMusicXmlMap = new ConcurrentHashMap<Integer, MusicXmlBasicInfo>();
|
|
|
|
|
|
private WaveformWriter waveFileProcessor;
|
|
|
|
|
|
- private NoteAnalysis processingNote = new NoteAnalysis(0, 0);
|
|
|
+ private NoteAnalysis processingNote = new NoteAnalysis(0, 0, -1);
|
|
|
|
|
|
- private AtomicInteger currentSectionIndex = new AtomicInteger(0);
|
|
|
+ private AtomicInteger evaluatingSectionIndex = new AtomicInteger(0);
|
|
|
|
|
|
private List<NoteAnalysis> doneNoteAnalysisList = new ArrayList<NoteAnalysis>();
|
|
|
|
|
|
+ private List<SectionAnalysis> doneSectionAnalysisList = new ArrayList<SectionAnalysis>();
|
|
|
+
|
|
|
private byte[] channelBufferBytes = new byte[0];
|
|
|
|
|
|
+ private double playTime;
|
|
|
+
|
|
|
+ private double receivedTime;
|
|
|
+
|
|
|
+ private List<Integer> firstNoteIndexPerSectionList;
|
|
|
+
|
|
|
private List<ChunkAnalysis> chunkAnalysisList = new ArrayList<ChunkAnalysis>();
|
|
|
|
|
|
+ public void init(){
|
|
|
+ firstNoteIndexPerSectionList = getFirstNoteIndexPerSection(null);
|
|
|
+ }
|
|
|
+
|
|
|
+ public Long getRecordId() {
|
|
|
+ return recordId;
|
|
|
+ }
|
|
|
+
|
|
|
+ public void setRecordId(Long recordId) {
|
|
|
+ this.recordId = recordId;
|
|
|
+ }
|
|
|
+
|
|
|
public ConcurrentHashMap<Integer, MusicXmlBasicInfo> getSongMusicXmlMap() {
|
|
|
return songMusicXmlMap;
|
|
|
}
|
|
@@ -65,24 +93,40 @@ public class UserChannelContext implements PitchDetectionHandler {
|
|
|
this.processingNote = processingNote;
|
|
|
}
|
|
|
|
|
|
- public void resetUserInfo() {
|
|
|
+ public List<SectionAnalysis> getDoneSectionAnalysisList() {
|
|
|
+ return doneSectionAnalysisList;
|
|
|
+ }
|
|
|
+
|
|
|
+ public List<NoteAnalysis> getDoneNoteAnalysisList() {
|
|
|
+ return doneNoteAnalysisList;
|
|
|
+ }
|
|
|
|
|
|
+ public void resetUserInfo() {
|
|
|
waveFileProcessor = null;
|
|
|
- processingNote = new NoteAnalysis(0,0);
|
|
|
- currentSectionIndex = new AtomicInteger(0);
|
|
|
+ processingNote = new NoteAnalysis(0,0,-1);
|
|
|
+ evaluatingSectionIndex = new AtomicInteger(0);
|
|
|
channelBufferBytes = new byte[0];
|
|
|
doneNoteAnalysisList = new ArrayList<NoteAnalysis>();
|
|
|
+ doneSectionAnalysisList = new ArrayList<SectionAnalysis>();
|
|
|
chunkAnalysisList = new ArrayList<ChunkAnalysis>();
|
|
|
+ recordId = null;
|
|
|
+ playTime = 0;
|
|
|
+ receivedTime = 0;
|
|
|
}
|
|
|
|
|
|
- public MusicXmlSection getCurrentMusicSection(Integer songId){
|
|
|
+ public MusicXmlBasicInfo getMusicXmlBasicInfo(Integer songId){
|
|
|
MusicXmlBasicInfo musicXmlBasicInfo = null;
|
|
|
if (songId == null) {
|
|
|
musicXmlBasicInfo = songMusicXmlMap.values().stream().findFirst().get();
|
|
|
} else {
|
|
|
musicXmlBasicInfo = songMusicXmlMap.get(songId);
|
|
|
}
|
|
|
- return musicXmlBasicInfo.getMusicXmlSectionMap().get(currentSectionIndex.get());
|
|
|
+ return musicXmlBasicInfo;
|
|
|
+ }
|
|
|
+
|
|
|
+ public MusicXmlSection getCurrentMusicSection(Integer songId, int sectionIndex){
|
|
|
+ MusicXmlBasicInfo musicXmlBasicInfo = getMusicXmlBasicInfo(songId);
|
|
|
+ return musicXmlBasicInfo.getMusicXmlSectionMap().get(sectionIndex);
|
|
|
}
|
|
|
|
|
|
public MusicXmlNote getCurrentMusicNote(Integer songId, Integer noteIndex) {
|
|
@@ -90,15 +134,10 @@ public class UserChannelContext implements PitchDetectionHandler {
|
|
|
return null;
|
|
|
}
|
|
|
if(noteIndex == null){
|
|
|
- noteIndex = processingNote.getIndex();
|
|
|
+ noteIndex = processingNote.getMusicalNotesIndex();
|
|
|
}
|
|
|
final int index = noteIndex;
|
|
|
- MusicXmlBasicInfo musicXmlBasicInfo = null;
|
|
|
- if (songId == null) {
|
|
|
- musicXmlBasicInfo = songMusicXmlMap.values().stream().findFirst().get();
|
|
|
- } else {
|
|
|
- musicXmlBasicInfo = songMusicXmlMap.get(songId);
|
|
|
- }
|
|
|
+ MusicXmlBasicInfo musicXmlBasicInfo = getMusicXmlBasicInfo(songId);
|
|
|
|
|
|
if (musicXmlBasicInfo != null && index <= getTotalMusicNoteIndex(null)) {
|
|
|
return musicXmlBasicInfo.getMusicXmlInfos().stream().filter(t -> t.getMusicalNotesIndex() == index).findFirst().get();
|
|
@@ -111,12 +150,7 @@ public class UserChannelContext implements PitchDetectionHandler {
|
|
|
if (songMusicXmlMap.size() == 0) {
|
|
|
return -1;
|
|
|
}
|
|
|
- MusicXmlBasicInfo musicXmlBasicInfo = null;
|
|
|
- if (songId == null) {
|
|
|
- musicXmlBasicInfo = songMusicXmlMap.values().stream().findFirst().get();
|
|
|
- } else {
|
|
|
- musicXmlBasicInfo = songMusicXmlMap.get(songId);
|
|
|
- }
|
|
|
+ MusicXmlBasicInfo musicXmlBasicInfo = getMusicXmlBasicInfo(songId);
|
|
|
|
|
|
if (musicXmlBasicInfo != null) {
|
|
|
return musicXmlBasicInfo.getMusicXmlInfos().stream().map(t -> t.getMusicalNotesIndex()).distinct().max(Integer::compareTo).get();
|
|
@@ -125,7 +159,7 @@ public class UserChannelContext implements PitchDetectionHandler {
|
|
|
return -1;
|
|
|
}
|
|
|
|
|
|
- public List<MusicXmlNote> getCurrentMusicSection(Integer songId,Integer sectionIndex) {
|
|
|
+ public List<MusicXmlNote> getCurrentMusicSection(Integer songId, Integer sectionIndex) {
|
|
|
if (songMusicXmlMap.size() == 0) {
|
|
|
return null;
|
|
|
}
|
|
@@ -133,12 +167,7 @@ public class UserChannelContext implements PitchDetectionHandler {
|
|
|
sectionIndex = processingNote.getSectionIndex();
|
|
|
}
|
|
|
final int index = sectionIndex;
|
|
|
- MusicXmlBasicInfo musicXmlBasicInfo = null;
|
|
|
- if (songId == null) {
|
|
|
- musicXmlBasicInfo = songMusicXmlMap.values().stream().findFirst().get();
|
|
|
- } else {
|
|
|
- musicXmlBasicInfo = songMusicXmlMap.get(songId);
|
|
|
- }
|
|
|
+ MusicXmlBasicInfo musicXmlBasicInfo = getMusicXmlBasicInfo(songId);
|
|
|
|
|
|
if (musicXmlBasicInfo != null) {
|
|
|
return musicXmlBasicInfo.getMusicXmlInfos().stream().filter(t -> t.getMusicalNotesIndex() == index)
|
|
@@ -148,19 +177,14 @@ public class UserChannelContext implements PitchDetectionHandler {
|
|
|
return null;
|
|
|
}
|
|
|
|
|
|
- public int getTotalMusicSectionIndex(Integer songId) {
|
|
|
+ public int getTotalMusicSectionSize(Integer songId) {
|
|
|
if (songMusicXmlMap.size() == 0) {
|
|
|
return -1;
|
|
|
}
|
|
|
- MusicXmlBasicInfo musicXmlBasicInfo = null;
|
|
|
- if (songId == null) {
|
|
|
- musicXmlBasicInfo = songMusicXmlMap.values().stream().findFirst().get();
|
|
|
- } else {
|
|
|
- musicXmlBasicInfo = songMusicXmlMap.get(songId);
|
|
|
- }
|
|
|
+ MusicXmlBasicInfo musicXmlBasicInfo = getMusicXmlBasicInfo(songId);
|
|
|
|
|
|
if (musicXmlBasicInfo != null) {
|
|
|
- return musicXmlBasicInfo.getMusicXmlInfos().stream().map(t -> t.getMeasureIndex()).distinct().max(Integer::compareTo).get();
|
|
|
+ return (int) musicXmlBasicInfo.getMusicXmlInfos().stream().map(t -> t.getMeasureIndex()).distinct().count();
|
|
|
}
|
|
|
|
|
|
return -1;
|
|
@@ -170,12 +194,12 @@ public class UserChannelContext implements PitchDetectionHandler {
|
|
|
if (songMusicXmlMap.size() == 0) {
|
|
|
return -1;
|
|
|
}
|
|
|
- MusicXmlBasicInfo musicXmlBasicInfo = null;
|
|
|
- if (songId == null) {
|
|
|
- musicXmlBasicInfo = songMusicXmlMap.values().stream().findFirst().get();
|
|
|
- } else {
|
|
|
- musicXmlBasicInfo = songMusicXmlMap.get(songId);
|
|
|
+
|
|
|
+ if(getTotalMusicNoteIndex(null) < musicXmlNoteIndex){
|
|
|
+ return -1;
|
|
|
}
|
|
|
+
|
|
|
+ MusicXmlBasicInfo musicXmlBasicInfo = getMusicXmlBasicInfo(songId);
|
|
|
|
|
|
if (musicXmlBasicInfo != null) {
|
|
|
return musicXmlBasicInfo.getMusicXmlInfos().stream().filter(t -> t.getMusicalNotesIndex() == musicXmlNoteIndex).findFirst().get().getMeasureIndex();
|
|
@@ -183,6 +207,22 @@ public class UserChannelContext implements PitchDetectionHandler {
|
|
|
|
|
|
return -1;
|
|
|
}
|
|
|
+
|
|
|
+ private List<Integer> getFirstNoteIndexPerSection(Integer songId){
|
|
|
+
|
|
|
+ List<Integer> result = new ArrayList<Integer>();
|
|
|
+
|
|
|
+ MusicXmlBasicInfo musicXmlBasicInfo = getMusicXmlBasicInfo(songId);
|
|
|
+
|
|
|
+ if(musicXmlBasicInfo != null){
|
|
|
+
|
|
|
+ Map<Integer,List<MusicXmlNote>> map = musicXmlBasicInfo.getMusicXmlInfos().stream().collect(Collectors.groupingBy(MusicXmlNote :: getMeasureIndex));
|
|
|
+ for(Entry<Integer, List<MusicXmlNote>> entry : map.entrySet()){
|
|
|
+ result.add(entry.getValue().stream().map(t -> t.getMusicalNotesIndex()).reduce(Integer :: min).get());
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return result;
|
|
|
+ }
|
|
|
|
|
|
public byte[] getChannelBufferBytes() {
|
|
|
return channelBufferBytes;
|
|
@@ -192,16 +232,29 @@ public class UserChannelContext implements PitchDetectionHandler {
|
|
|
this.channelBufferBytes = channelBufferBytes;
|
|
|
}
|
|
|
|
|
|
+ public AtomicInteger getEvaluatingSectionIndex() {
|
|
|
+ return evaluatingSectionIndex;
|
|
|
+ }
|
|
|
+
|
|
|
public void handle1(float[] samples, AudioFormat audioFormat){
|
|
|
|
|
|
- FrequencyDetector frequencyDetector = new FrequencyDetector(samples, audioFormat.getSampleRate(), false);
|
|
|
+ //FrequencyDetector frequencyDetector = new FrequencyDetector(samples, audioFormat.getSampleRate(), true);
|
|
|
+ YINPitchDetector frequencyDetector = new YINPitchDetector(samples.length , audioFormat.getSampleRate());
|
|
|
+
|
|
|
+ double playFrequency = frequencyDetector.getFrequency(samples);
|
|
|
+ double splDb = Signals.soundPressureLevel(samples);
|
|
|
+ float power = Signals.power(samples);
|
|
|
+ float energy = Signals.energy(samples);
|
|
|
|
|
|
- int decibel = Signals.decibels(samples);
|
|
|
- double pitch = frequencyDetector.getFrequency();
|
|
|
+ double durationTime = 1000 * (samples.length * 2) / audioFormat.getSampleRate() / (audioFormat.getSampleSizeInBits() / 8);
|
|
|
|
|
|
- LOGGER.info("Frequency:{} Db:{}", pitch, decibel);
|
|
|
+ receivedTime += durationTime;
|
|
|
|
|
|
- double durationTime = 1000 * (samples.length * 2) / audioFormat.getSampleRate() / (audioFormat.getSampleSizeInBits() / 8);
|
|
|
+ if(receivedTime < offsetMS){
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ playTime += durationTime;
|
|
|
|
|
|
// 获取当前音符信息
|
|
|
MusicXmlNote musicXmlNote = getCurrentMusicNote(null,null);
|
|
@@ -212,40 +265,108 @@ public class UserChannelContext implements PitchDetectionHandler {
|
|
|
|
|
|
//取出当前处理中的音符信息
|
|
|
NoteAnalysis noteAnalysis = getProcessingNote();
|
|
|
- if(noteAnalysis == null){
|
|
|
- noteAnalysis = new NoteAnalysis(musicXmlNote.getMusicalNotesIndex(), musicXmlNote.getMeasureIndex());
|
|
|
+ if(noteAnalysis == null || noteAnalysis.getDurationTime() == 0) {
|
|
|
+ noteAnalysis = new NoteAnalysis(musicXmlNote.getMusicalNotesIndex(), musicXmlNote.getMeasureIndex(), musicXmlNote.getFrequency(), noteAnalysis.getDurationTime());
|
|
|
}
|
|
|
|
|
|
- if(noteAnalysis.getIndex() <= getTotalMusicNoteIndex(null)){
|
|
|
-
|
|
|
- double noteDurationTime = noteAnalysis.getDurationTime() + durationTime;
|
|
|
- noteAnalysis.setDurationTime(noteDurationTime);
|
|
|
-
|
|
|
- if(noteDurationTime >= musicXmlNote.getDuration()){
|
|
|
-
|
|
|
+ evaluatingSectionIndex.set(noteAnalysis.getSectionIndex());
|
|
|
+
|
|
|
+ if (noteAnalysis.getMusicalNotesIndex() >= 0 && noteAnalysis.getMusicalNotesIndex() <= getTotalMusicNoteIndex(null)) {
|
|
|
+
|
|
|
+ if (playTime >= (musicXmlNote.getDuration() + musicXmlNote.getTimeStamp())) {
|
|
|
+
|
|
|
if (musicXmlNote.getDontEvaluating()) {
|
|
|
noteAnalysis.setIgnore(true);
|
|
|
- } else {
|
|
|
- noteAnalysis.setFrequency(noteAnalysis.getTotalPitch() / noteAnalysis.getChunks());
|
|
|
+ }
|
|
|
|
|
|
- LOGGER.info("当前音符下标[{}] 预计频率:{} 实际频率:{} 持续时间:{}", noteAnalysis.getIndex(), musicXmlNote.getFrequency(), noteAnalysis.getFrequency(),
|
|
|
- noteAnalysis.getDurationTime());
|
|
|
+ if(noteAnalysis.getChunkFrequencyList().size() > 0){
|
|
|
+ //noteAnalysis.setPlayFrequency(noteAnalysis.getChunkFrequencyList().stream().mapToDouble(t -> t).sum()/noteAnalysis.getChunkFrequencyList().size());
|
|
|
+ noteAnalysis.setPlayFrequency(computeFrequency(noteAnalysis.getChunkFrequencyList(), 10));
|
|
|
}
|
|
|
+
|
|
|
+ final double avgFrequency = noteAnalysis.getPlayFrequency();
|
|
|
+
|
|
|
+ //判断节奏(音符持续时间内有不间断的音高,就节奏正确)
|
|
|
+ ChunkAnalysis chunkAnalysis = null;
|
|
|
+ boolean tempo = false;
|
|
|
+ boolean isContinue = true;
|
|
|
+ for (int i = 0; i < chunkAnalysisList.size(); i++) {
|
|
|
+ chunkAnalysis = chunkAnalysisList.get(i);
|
|
|
+ if (chunkAnalysis != null) {
|
|
|
+ if (chunkAnalysis.getFrequency() > 100) {
|
|
|
+ tempo = true;
|
|
|
+ if (isContinue == false) {
|
|
|
+ tempo = false;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ if (tempo == true) {
|
|
|
+ isContinue = false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ noteAnalysis.setTempo(tempo);
|
|
|
+
|
|
|
+ long wrongChunkSize = chunkAnalysisList.stream().filter(t -> Math.abs(t.getFrequency() - avgFrequency) > 10).count();
|
|
|
+ if(wrongChunkSize * 100 /chunkAnalysisList.size() > 40){
|
|
|
+ noteAnalysis.setTempo(false);
|
|
|
+ }
|
|
|
+
|
|
|
+ evaluateForNote(noteAnalysis);
|
|
|
+
|
|
|
+ LOGGER.info("当前音符下标[{}] 预计频率:{} 实际频率:{} 持续时间:{}", noteAnalysis.getMusicalNotesIndex(), musicXmlNote.getFrequency(), noteAnalysis.getPlayFrequency(),
|
|
|
+ noteAnalysis.getDurationTime() - durationTime);
|
|
|
+
|
|
|
doneNoteAnalysisList.add(noteAnalysis);
|
|
|
|
|
|
+ chunkAnalysisList.clear();
|
|
|
+
|
|
|
// 准备处理下一个音符
|
|
|
- noteAnalysis = new NoteAnalysis(musicXmlNote.getMusicalNotesIndex() + 1, getMusicSectionIndex(null, musicXmlNote.getMusicalNotesIndex() + 1));
|
|
|
+ int nextNoteIndex = musicXmlNote.getMusicalNotesIndex() + 1;
|
|
|
+ float nextNoteFrequence = -1;
|
|
|
+ double standDuration = 0;
|
|
|
+ MusicXmlNote nextMusicXmlNote = getCurrentMusicNote(null, nextNoteIndex);
|
|
|
+ if(nextMusicXmlNote != null){
|
|
|
+ nextNoteFrequence = nextMusicXmlNote.getFrequency();
|
|
|
+ standDuration = nextMusicXmlNote.getDuration();
|
|
|
+ }
|
|
|
+
|
|
|
+ NoteAnalysis nextNoteAnalysis = new NoteAnalysis(nextNoteIndex, getMusicSectionIndex(null, nextNoteIndex), nextNoteFrequence, standDuration);
|
|
|
+ if (noteAnalysis.isTempo() == true && wrongChunkSize == 0) {
|
|
|
+ nextNoteAnalysis.setTempo(false);
|
|
|
+ LOGGER.info("节奏错误:频率没变");
|
|
|
+ }
|
|
|
+
|
|
|
+ noteAnalysis = nextNoteAnalysis;
|
|
|
+
|
|
|
+ } else {
|
|
|
+ noteAnalysis.setDurationTime(noteAnalysis.getDurationTime() + durationTime);
|
|
|
|
|
|
- //评分
|
|
|
+ /*double skip = 0;
|
|
|
+ if (firstNoteIndexPerSectionList.contains(noteAnalysis.getMusicalNotesIndex())) {
|
|
|
+ skip = offsetMSOfSection;
|
|
|
+ }*/
|
|
|
+ //skip = noteAnalysis.getStandardDurationTime() * 0.2;
|
|
|
|
|
|
- }else{
|
|
|
+ LOGGER.info("Frequency:{} splDb:{} Power:{} energy:{}", playFrequency, splDb, power, energy);
|
|
|
|
|
|
- if(pitch < 2000 && pitch > 100){
|
|
|
- noteAnalysis.setChunks(noteAnalysis.getChunks() + 1);
|
|
|
- noteAnalysis.setTotalPitch(noteAnalysis.getTotalPitch() + pitch);
|
|
|
+ /*int chunkSize = noteAnalysis.getChunkFrequencyList().size();
|
|
|
+ if (chunkSize > 0 && Math.abs(noteAnalysis.getChunkFrequencyList().stream().mapToDouble(t -> t).sum() / chunkSize - playFrequency) > 10) {
|
|
|
+ noteAnalysis.setTempo(false);
|
|
|
+ LOGGER.info("节奏错误:频率发生变化");
|
|
|
+ }*/
|
|
|
+
|
|
|
+ if (playFrequency < 2000 && playFrequency > 100) {
|
|
|
+ noteAnalysis.getChunkFrequencyList().add(playFrequency);
|
|
|
+ noteAnalysis.setPlayDurationTime(noteAnalysis.getPlayDurationTime() + durationTime);
|
|
|
}
|
|
|
+
|
|
|
+ chunkAnalysisList.add(new ChunkAnalysis(playFrequency, splDb, power));
|
|
|
+
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
setProcessingNote(noteAnalysis);
|
|
|
}
|
|
|
|
|
@@ -253,8 +374,9 @@ public class UserChannelContext implements PitchDetectionHandler {
|
|
|
|
|
|
public void handle2(float[] samples, AudioFormat audioFormat){
|
|
|
|
|
|
- FrequencyDetector frequencyDetector = new FrequencyDetector(samples, audioFormat.getSampleRate(), false);
|
|
|
- double frequency = frequencyDetector.getFrequency();
|
|
|
+ //FrequencyDetector frequencyDetector = new FrequencyDetector(samples, audioFormat.getSampleRate(), false);
|
|
|
+ YINPitchDetector frequencyDetector = new YINPitchDetector(samples.length/2 , audioFormat.getSampleRate());
|
|
|
+ double frequency = frequencyDetector.getFrequency(samples);
|
|
|
|
|
|
double splDb = Signals.soundPressureLevel(samples);
|
|
|
|
|
@@ -280,7 +402,7 @@ public class UserChannelContext implements PitchDetectionHandler {
|
|
|
chunkAnalysisList.clear();
|
|
|
|
|
|
//判断是否需要评分
|
|
|
- MusicXmlSection musicXmlSection = getCurrentMusicSection(null);
|
|
|
+ MusicXmlSection musicXmlSection = getCurrentMusicSection(null, 0);
|
|
|
if(musicXmlSection != null){
|
|
|
if(musicXmlSection.getDuration() < startTime){
|
|
|
|
|
@@ -300,7 +422,7 @@ public class UserChannelContext implements PitchDetectionHandler {
|
|
|
|
|
|
float pitch = pitchDetectionResult.getPitch();
|
|
|
|
|
|
- //LOGGER.info("pitch:{} timeStamp:{} endTimeStamp:{} durationTime:{}", pitch, audioEvent.getTimeStamp(), audioEvent.getEndTimeStamp(), durationTime);
|
|
|
+ LOGGER.info("pitch:{} timeStamp:{} endTimeStamp:{} durationTime:{}", pitch, audioEvent.getTimeStamp(), audioEvent.getEndTimeStamp(), durationTime);
|
|
|
|
|
|
// 获取当前音符信息
|
|
|
MusicXmlNote musicXmlNote = getCurrentMusicNote(null,null);
|
|
@@ -312,27 +434,29 @@ public class UserChannelContext implements PitchDetectionHandler {
|
|
|
//取出当前处理中的音符信息
|
|
|
NoteAnalysis noteAnalysis = getProcessingNote();
|
|
|
if(noteAnalysis == null){
|
|
|
- noteAnalysis = new NoteAnalysis(musicXmlNote.getMusicalNotesIndex(),musicXmlNote.getMeasureIndex());
|
|
|
+ noteAnalysis = new NoteAnalysis(musicXmlNote.getMusicalNotesIndex(),musicXmlNote.getMeasureIndex(),musicXmlNote.getFrequency());
|
|
|
}
|
|
|
|
|
|
double noteDurationTime = noteAnalysis.getDurationTime() + durationTime;
|
|
|
noteAnalysis.setDurationTime(noteDurationTime);
|
|
|
|
|
|
if(pitch != -1){
|
|
|
- noteAnalysis.setChunks(noteAnalysis.getChunks() + 1);
|
|
|
- noteAnalysis.setTotalPitch(noteAnalysis.getTotalPitch() + pitch);
|
|
|
+ noteAnalysis.getChunkFrequencyList().add((double) pitch);
|
|
|
}
|
|
|
|
|
|
setProcessingNote(noteAnalysis);
|
|
|
|
|
|
- if(noteAnalysis.getIndex() <= getTotalMusicNoteIndex(null) && noteDurationTime >= musicXmlNote.getDuration()){
|
|
|
+ if(noteAnalysis.getMusicalNotesIndex() <= getTotalMusicNoteIndex(null) && noteDurationTime >= musicXmlNote.getDuration()){
|
|
|
|
|
|
- noteAnalysis.setFrequency(noteAnalysis.getTotalPitch()/noteAnalysis.getChunks());
|
|
|
+ noteAnalysis.setPlayFrequency(noteAnalysis.getChunkFrequencyList().stream().mapToDouble(t -> t).sum()/noteAnalysis.getChunkFrequencyList().size());
|
|
|
|
|
|
- LOGGER.info("当前音符下标[{}] 预计频率:{} 实际频率:{} 持续时间:{}", noteAnalysis.getIndex() , musicXmlNote.getFrequency(), noteAnalysis.getFrequency(), noteAnalysis.getDurationTime());
|
|
|
+ LOGGER.info("当前音符下标[{}] 预计频率:{} 实际频率:{} 持续时间:{}", noteAnalysis.getMusicalNotesIndex() , musicXmlNote.getFrequency(), noteAnalysis.getPlayFrequency(), noteAnalysis.getDurationTime());
|
|
|
|
|
|
// 准备处理下一个音符
|
|
|
- setProcessingNote(noteAnalysis = new NoteAnalysis(musicXmlNote.getMusicalNotesIndex() + 1,musicXmlNote.getMeasureIndex()));
|
|
|
+ int nextNoteIndex = musicXmlNote.getMusicalNotesIndex() + 1;
|
|
|
+ NoteAnalysis nextNoteAnalysis = new NoteAnalysis(nextNoteIndex, getMusicSectionIndex(null, nextNoteIndex), getCurrentMusicNote(null,
|
|
|
+ nextNoteIndex).getFrequency());
|
|
|
+ setProcessingNote(nextNoteAnalysis);
|
|
|
}
|
|
|
|
|
|
|
|
@@ -364,4 +488,187 @@ public class UserChannelContext implements PitchDetectionHandler {
|
|
|
}*/
|
|
|
}
|
|
|
|
|
|
+ public int evaluateForSection(int sectionIndex, int subjectId){
|
|
|
+
|
|
|
+ int score = -1;
|
|
|
+ if(doneSectionAnalysisList.size() >= getTotalMusicSectionSize(null)){
|
|
|
+ return score;
|
|
|
+ }
|
|
|
+
|
|
|
+ //取出当前小节的所有音符
|
|
|
+ List<NoteAnalysis> noteAnalysisList = doneNoteAnalysisList.stream().filter(t -> t.getSectionIndex() == sectionIndex).collect(Collectors.toList());
|
|
|
+
|
|
|
+ SectionAnalysis sectionAnalysis = new SectionAnalysis();
|
|
|
+ sectionAnalysis.setIndex(sectionIndex);
|
|
|
+ sectionAnalysis.setNoteNum(noteAnalysisList.size());
|
|
|
+
|
|
|
+ //判断是否需要评分
|
|
|
+ MusicXmlSection musicXmlSection = getCurrentMusicSection(null, sectionIndex);
|
|
|
+ if(noteAnalysisList.size() == musicXmlSection.getNoteNum()){
|
|
|
+ //取出需要评测的音符
|
|
|
+ List<NoteAnalysis> noteList = noteAnalysisList.stream().filter(t -> t.isIgnore() == false).collect(Collectors.toList());
|
|
|
+
|
|
|
+ if(noteList != null && noteList.size() > 0){
|
|
|
+ score = noteList.stream().mapToInt(t -> t.getScore()).sum() / noteList.size();
|
|
|
+ }
|
|
|
+ sectionAnalysis.setDurationTime(noteAnalysisList.stream().mapToDouble(t -> t.getDurationTime()).sum());
|
|
|
+ sectionAnalysis.setScore(score);
|
|
|
+
|
|
|
+ LOGGER.info("小节评分:{}",sectionAnalysis);
|
|
|
+ doneSectionAnalysisList.add(sectionAnalysis);
|
|
|
+ }
|
|
|
+
|
|
|
+ return score;
|
|
|
+ }
|
|
|
+
|
|
|
+ public Map<String, Integer> evaluateForMusic() {
|
|
|
+
|
|
|
+ Map<String, Integer> result = new HashMap<String, Integer>();
|
|
|
+
|
|
|
+ result.put("playTime", (int) doneNoteAnalysisList.stream().mapToDouble(t -> t.getDurationTime()).sum());
|
|
|
+
|
|
|
+ // 取出需要评测的音符
|
|
|
+ List<NoteAnalysis> noteAnalysisList = doneNoteAnalysisList.stream().filter(t -> t.isIgnore() == false).collect(Collectors.toList());
|
|
|
+
|
|
|
+ if (noteAnalysisList != null && noteAnalysisList.size() > 0) {
|
|
|
+ int intonationScore = 0;
|
|
|
+ int tempoScore = 0;
|
|
|
+ int integrityScore = 0;
|
|
|
+ int socre = 0;
|
|
|
+
|
|
|
+ for (NoteAnalysis note : noteAnalysisList) {
|
|
|
+ intonationScore += note.getIntonationScore();
|
|
|
+ tempoScore += note.getTempoScore();
|
|
|
+ integrityScore += note.getIntegrityScore();
|
|
|
+ socre += note.getScore();
|
|
|
+ }
|
|
|
+
|
|
|
+ tempoScore = tempoScore / noteAnalysisList.size();
|
|
|
+ intonationScore = intonationScore / noteAnalysisList.size();
|
|
|
+ integrityScore = integrityScore / noteAnalysisList.size();
|
|
|
+
|
|
|
+ result.put("cadence", tempoScore);
|
|
|
+ result.put("intonation", intonationScore);
|
|
|
+ result.put("integrity", integrityScore);
|
|
|
+
|
|
|
+ int score = socre / noteAnalysisList.size();
|
|
|
+
|
|
|
+ // 平均得分
|
|
|
+ if (getMusicXmlBasicInfo(null).getSubjectId() == 23) {
|
|
|
+ score = tempoScore;
|
|
|
+ }
|
|
|
+ result.put("score", score);
|
|
|
+ }
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+
|
|
|
+ public void evaluateForNote(NoteAnalysis noteAnalysis){
|
|
|
+
|
|
|
+ if (noteAnalysis.getPlayDurationTime() / noteAnalysis.getDurationTime() < 0.1) {
|
|
|
+ noteAnalysis.setMusicalErrorType(NoteErrorType.NOT_PLAY);
|
|
|
+ } else if (noteAnalysis.getPlayDurationTime() / noteAnalysis.getDurationTime() < 0.5) {
|
|
|
+ noteAnalysis.setMusicalErrorType(NoteErrorType.INTEGRITY_WRONG);
|
|
|
+ } else if (!noteAnalysis.isTempo()) {
|
|
|
+ noteAnalysis.setMusicalErrorType(NoteErrorType.CADENCE_WRONG);
|
|
|
+ } else if (Math.abs(noteAnalysis.getFrequency() - noteAnalysis.getPlayFrequency()) > 10) {
|
|
|
+ noteAnalysis.setMusicalErrorType(NoteErrorType.INTONATION_WRONG);
|
|
|
+ } else {
|
|
|
+ noteAnalysis.setMusicalErrorType(NoteErrorType.RIGHT);
|
|
|
+ }
|
|
|
+
|
|
|
+ //计算音分
|
|
|
+ int tempoScore = 0;
|
|
|
+ int integrityScore = 0;
|
|
|
+ int intonationScore = (int) (100 - Math.abs(YINPitchDetector.hertzToAbsoluteCent(noteAnalysis.getPlayFrequency()) - YINPitchDetector.hertzToAbsoluteCent(noteAnalysis.getFrequency()))*10/17);
|
|
|
+ if (intonationScore < 0){
|
|
|
+ intonationScore = 0;
|
|
|
+ }else if(intonationScore > 100){
|
|
|
+ intonationScore = 100;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (noteAnalysis.getMusicalErrorType() == NoteErrorType.NOT_PLAY) {
|
|
|
+ intonationScore = 0;
|
|
|
+ } else {
|
|
|
+
|
|
|
+ if (noteAnalysis.isTempo()) {
|
|
|
+ tempoScore = 100;
|
|
|
+ noteAnalysis.setTempoScore(tempoScore);
|
|
|
+ }
|
|
|
+
|
|
|
+ double durationPercent = noteAnalysis.getPlayDurationTime() / noteAnalysis.getDurationTime();
|
|
|
+ if (durationPercent >= 0.7) {
|
|
|
+ integrityScore = 100;
|
|
|
+ } else if (durationPercent < 0.7 && durationPercent >= 0.5) {
|
|
|
+ integrityScore = 50;
|
|
|
+ }
|
|
|
+ noteAnalysis.setIntegrityScore(integrityScore);
|
|
|
+ }
|
|
|
+ noteAnalysis.setIntonationScore(intonationScore);
|
|
|
+ noteAnalysis.setScore((int)((intonationScore + tempoScore + integrityScore) / 3));
|
|
|
+ }
|
|
|
+
|
|
|
+ private double computeFrequency(List<Double> chunkFrequencyList, int offsetRange) {
|
|
|
+
|
|
|
+ // 排序
|
|
|
+ chunkFrequencyList = chunkFrequencyList.stream().sorted().collect(Collectors.toList());
|
|
|
+
|
|
|
+ double tempFrequency = 0, totalFrequency = 0;
|
|
|
+
|
|
|
+ int maxChunkSize = 0;
|
|
|
+ double frequency = 0;
|
|
|
+ int chunkSize = 1;
|
|
|
+ double avgFrequency = chunkFrequencyList.get(0);
|
|
|
+ for (int i = 1; i < chunkFrequencyList.size(); i++) {
|
|
|
+ tempFrequency = chunkFrequencyList.get(i);
|
|
|
+
|
|
|
+ totalFrequency += tempFrequency;
|
|
|
+
|
|
|
+ if (Math.abs(avgFrequency - tempFrequency) > offsetRange) {
|
|
|
+
|
|
|
+ if (maxChunkSize < chunkSize) {
|
|
|
+ maxChunkSize = chunkSize;
|
|
|
+ frequency = avgFrequency;
|
|
|
+ }
|
|
|
+
|
|
|
+ chunkSize = 1;
|
|
|
+ avgFrequency = tempFrequency;
|
|
|
+ } else {
|
|
|
+ chunkSize++;
|
|
|
+ avgFrequency = (tempFrequency + avgFrequency) / 2;
|
|
|
+ }
|
|
|
+
|
|
|
+ if(i == chunkFrequencyList.size() - 1){
|
|
|
+ if (maxChunkSize < chunkSize) {
|
|
|
+ maxChunkSize = chunkSize;
|
|
|
+ frequency = avgFrequency;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (maxChunkSize * 100 / chunkFrequencyList.size() < 40) {
|
|
|
+ return totalFrequency / chunkFrequencyList.size();
|
|
|
+ }
|
|
|
+
|
|
|
+ return frequency;
|
|
|
+ }
|
|
|
+
|
|
|
+ private double majorityElement(List<Double> nums) {
|
|
|
+ double major = nums.get(0);
|
|
|
+ int count = 1;
|
|
|
+
|
|
|
+ for(int i = 1; i < nums.size(); i++) {
|
|
|
+ double num = nums.get(i);
|
|
|
+ if(count == 0) {
|
|
|
+ count++;
|
|
|
+ major = num;
|
|
|
+ } else if(major == num) {
|
|
|
+ count++;
|
|
|
+ } else {
|
|
|
+ count--;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return major;
|
|
|
+ }
|
|
|
+
|
|
|
}
|