|
@@ -0,0 +1,802 @@
|
|
|
+package com.yonge.netty.dto;
|
|
|
+
|
|
|
+import java.math.BigDecimal;
|
|
|
+import java.util.ArrayList;
|
|
|
+import java.util.Comparator;
|
|
|
+import java.util.HashMap;
|
|
|
+import java.util.Iterator;
|
|
|
+import java.util.List;
|
|
|
+import java.util.Map;
|
|
|
+import java.util.concurrent.ConcurrentHashMap;
|
|
|
+import java.util.concurrent.atomic.AtomicInteger;
|
|
|
+import java.util.stream.Collectors;
|
|
|
+
|
|
|
+import javax.sound.sampled.AudioFormat;
|
|
|
+
|
|
|
+import org.slf4j.Logger;
|
|
|
+import org.slf4j.LoggerFactory;
|
|
|
+
|
|
|
+import com.yonge.audio.analysis.Signals;
|
|
|
+import com.yonge.audio.analysis.detector.YINPitchDetector;
|
|
|
+import com.yonge.audio.utils.ArrayUtil;
|
|
|
+import com.yonge.netty.dto.NoteAnalysis.NoteErrorType;
|
|
|
+import com.yonge.netty.entity.MusicXmlBasicInfo;
|
|
|
+import com.yonge.netty.entity.MusicXmlNote;
|
|
|
+import com.yonge.netty.entity.MusicXmlSection;
|
|
|
+import com.yonge.netty.server.processor.WaveformWriter;
|
|
|
+
|
|
|
+/**
|
|
|
+ * 用户通道上下文
|
|
|
+ */
|
|
|
+public class UserChannelContext2 {
|
|
|
+
|
|
|
+ private final static Logger LOGGER = LoggerFactory.getLogger(UserChannelContext2.class);
|
|
|
+
|
|
|
+ private int offsetMS = 350;
|
|
|
+
|
|
|
+ private String platform;
|
|
|
+
|
|
|
+ private Long recordId;
|
|
|
+
|
|
|
+ private Integer subjectId;
|
|
|
+
|
|
|
+ private int beatDuration;
|
|
|
+
|
|
|
+ private int beatByteLength;
|
|
|
+
|
|
|
+ // 曲目与musicxml对应关系
|
|
|
+ private ConcurrentHashMap<Integer, MusicXmlBasicInfo> songMusicXmlMap = new ConcurrentHashMap<Integer, MusicXmlBasicInfo>();
|
|
|
+
|
|
|
+ private WaveformWriter waveFileProcessor;
|
|
|
+
|
|
|
+ private NoteAnalysis processingNote = new NoteAnalysis(0, 0, -1);
|
|
|
+
|
|
|
+ private AtomicInteger evaluatingSectionIndex = new AtomicInteger(0);
|
|
|
+
|
|
|
+ private List<NoteAnalysis> doneNoteAnalysisList = new ArrayList<NoteAnalysis>();
|
|
|
+
|
|
|
+ private List<SectionAnalysis> doneSectionAnalysisList = new ArrayList<SectionAnalysis>();
|
|
|
+
|
|
|
+ private List<ChunkAnalysis> chunkAnalysisList = new ArrayList<ChunkAnalysis>();
|
|
|
+
|
|
|
+ private List<ChunkAnalysis> totalChunkAnalysisList = new ArrayList<ChunkAnalysis>();
|
|
|
+
|
|
|
+ private byte[] channelBufferBytes = new byte[0];
|
|
|
+
|
|
|
+ private double playTime;
|
|
|
+
|
|
|
+ private double receivedTime;
|
|
|
+
|
|
|
+ private List<ChunkAnalysis> lastChunkAnalysisList = new ArrayList<ChunkAnalysis>();
|
|
|
+
|
|
|
+ private HardLevelEnum hardLevel = HardLevelEnum.ADVANCED;
|
|
|
+
|
|
|
+ public void init(String platform, String heardLevel, int subjectId, int beatDuration) {
|
|
|
+ this.platform = platform;
|
|
|
+ this.subjectId = subjectId;
|
|
|
+ this.beatDuration = beatDuration;
|
|
|
+ this.beatByteLength = WaveformWriter.SAMPLE_RATE * WaveformWriter.BITS_PER_SAMPLE / 8 * beatDuration / 1000;
|
|
|
+ hardLevel = HardLevelEnum.valueOf(heardLevel);
|
|
|
+ }
|
|
|
+
|
|
|
+ public byte[] skipMetronome(byte[] datas) {
|
|
|
+ if (beatByteLength > 0) {
|
|
|
+ if (datas.length <= beatByteLength) {
|
|
|
+ beatByteLength -= datas.length;
|
|
|
+ return new byte[0];
|
|
|
+ }
|
|
|
+ if(beatByteLength % 2 != 0){
|
|
|
+ beatByteLength++;
|
|
|
+ }
|
|
|
+ datas = ArrayUtil.extractByte(datas, beatByteLength, datas.length - 1);
|
|
|
+ beatByteLength = 0;
|
|
|
+ }
|
|
|
+ return datas;
|
|
|
+ }
|
|
|
+
|
|
|
+ public Long getRecordId() {
|
|
|
+ return recordId;
|
|
|
+ }
|
|
|
+
|
|
|
+ public void setRecordId(Long recordId) {
|
|
|
+ this.recordId = recordId;
|
|
|
+ }
|
|
|
+
|
|
|
+ public int getOffsetMS() {
|
|
|
+ return offsetMS;
|
|
|
+ }
|
|
|
+
|
|
|
+ public void setOffsetMS(int offsetMS) {
|
|
|
+ this.offsetMS = offsetMS;
|
|
|
+ }
|
|
|
+
|
|
|
+ public HardLevelEnum getHardLevel() {
|
|
|
+ return hardLevel;
|
|
|
+ }
|
|
|
+
|
|
|
+ public ConcurrentHashMap<Integer, MusicXmlBasicInfo> getSongMusicXmlMap() {
|
|
|
+ return songMusicXmlMap;
|
|
|
+ }
|
|
|
+
|
|
|
+ public WaveformWriter getWaveFileProcessor() {
|
|
|
+ return waveFileProcessor;
|
|
|
+ }
|
|
|
+
|
|
|
+ public void setWaveFileProcessor(WaveformWriter waveFileProcessor) {
|
|
|
+ this.waveFileProcessor = waveFileProcessor;
|
|
|
+ }
|
|
|
+
|
|
|
+ public NoteAnalysis getProcessingNote() {
|
|
|
+ return processingNote;
|
|
|
+ }
|
|
|
+
|
|
|
+ public void setProcessingNote(NoteAnalysis processingNote) {
|
|
|
+ this.processingNote = processingNote;
|
|
|
+ }
|
|
|
+
|
|
|
+ public List<SectionAnalysis> getDoneSectionAnalysisList() {
|
|
|
+ return doneSectionAnalysisList;
|
|
|
+ }
|
|
|
+
|
|
|
+ public List<NoteAnalysis> getDoneNoteAnalysisList() {
|
|
|
+ return doneNoteAnalysisList;
|
|
|
+ }
|
|
|
+
|
|
|
+ public void resetUserInfo() {
|
|
|
+ beatByteLength = WaveformWriter.SAMPLE_RATE * WaveformWriter.BITS_PER_SAMPLE / 8 * beatDuration / 1000;
|
|
|
+ waveFileProcessor = null;
|
|
|
+ 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>();
|
|
|
+ totalChunkAnalysisList = new ArrayList<ChunkAnalysis>();
|
|
|
+ recordId = null;
|
|
|
+ playTime = 0;
|
|
|
+ receivedTime = 0;
|
|
|
+ lastChunkAnalysisList = new ArrayList<ChunkAnalysis>();
|
|
|
+ }
|
|
|
+
|
|
|
+ public MusicXmlBasicInfo getMusicXmlBasicInfo(Integer songId){
|
|
|
+ MusicXmlBasicInfo musicXmlBasicInfo = null;
|
|
|
+ if (songId == null) {
|
|
|
+ musicXmlBasicInfo = songMusicXmlMap.values().stream().findFirst().get();
|
|
|
+ } else {
|
|
|
+ musicXmlBasicInfo = songMusicXmlMap.get(songId);
|
|
|
+ }
|
|
|
+ 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) {
|
|
|
+ if (songMusicXmlMap.size() == 0) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ if(noteIndex == null){
|
|
|
+ noteIndex = processingNote.getMusicalNotesIndex();
|
|
|
+ }
|
|
|
+ final int index = noteIndex;
|
|
|
+ MusicXmlBasicInfo musicXmlBasicInfo = getMusicXmlBasicInfo(songId);
|
|
|
+
|
|
|
+ if (musicXmlBasicInfo != null && index <= getTotalMusicNoteIndex(null)) {
|
|
|
+ return musicXmlBasicInfo.getMusicXmlInfos().stream().filter(t -> t.getMusicalNotesIndex() == index).findFirst().get();
|
|
|
+ }
|
|
|
+
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ public int getTotalMusicNoteIndex(Integer songId) {
|
|
|
+ if (songMusicXmlMap.size() == 0) {
|
|
|
+ return -1;
|
|
|
+ }
|
|
|
+ MusicXmlBasicInfo musicXmlBasicInfo = getMusicXmlBasicInfo(songId);
|
|
|
+
|
|
|
+ if (musicXmlBasicInfo != null) {
|
|
|
+ return musicXmlBasicInfo.getMusicXmlInfos().stream().map(t -> t.getMusicalNotesIndex()).distinct().max(Integer::compareTo).get();
|
|
|
+ }
|
|
|
+
|
|
|
+ return -1;
|
|
|
+ }
|
|
|
+
|
|
|
+ public List<MusicXmlNote> getCurrentMusicSection(Integer songId, Integer sectionIndex) {
|
|
|
+ if (songMusicXmlMap.size() == 0) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ if(sectionIndex == null){
|
|
|
+ sectionIndex = processingNote.getSectionIndex();
|
|
|
+ }
|
|
|
+ final int index = sectionIndex;
|
|
|
+ MusicXmlBasicInfo musicXmlBasicInfo = getMusicXmlBasicInfo(songId);
|
|
|
+
|
|
|
+ if (musicXmlBasicInfo != null) {
|
|
|
+ return musicXmlBasicInfo.getMusicXmlInfos().stream().filter(t -> t.getMusicalNotesIndex() == index)
|
|
|
+ .sorted(Comparator.comparing(MusicXmlNote::getMusicalNotesIndex)).collect(Collectors.toList());
|
|
|
+ }
|
|
|
+
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ public int getTotalMusicSectionSize(Integer songId) {
|
|
|
+ if (songMusicXmlMap.size() == 0) {
|
|
|
+ return -1;
|
|
|
+ }
|
|
|
+ MusicXmlBasicInfo musicXmlBasicInfo = getMusicXmlBasicInfo(songId);
|
|
|
+
|
|
|
+ if (musicXmlBasicInfo != null) {
|
|
|
+ return (int) musicXmlBasicInfo.getMusicXmlInfos().stream().map(t -> t.getMeasureIndex()).distinct().count();
|
|
|
+ }
|
|
|
+
|
|
|
+ return -1;
|
|
|
+ }
|
|
|
+
|
|
|
+ public int getMusicSectionIndex(Integer songId, int musicXmlNoteIndex) {
|
|
|
+ if (songMusicXmlMap.size() == 0) {
|
|
|
+ return -1;
|
|
|
+ }
|
|
|
+
|
|
|
+ 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();
|
|
|
+ }
|
|
|
+
|
|
|
+ return -1;
|
|
|
+ }
|
|
|
+
|
|
|
+ public byte[] getChannelBufferBytes() {
|
|
|
+ return channelBufferBytes;
|
|
|
+ }
|
|
|
+
|
|
|
+ public void setChannelBufferBytes(byte[] channelBufferBytes) {
|
|
|
+ this.channelBufferBytes = channelBufferBytes;
|
|
|
+ }
|
|
|
+
|
|
|
+ public AtomicInteger getEvaluatingSectionIndex() {
|
|
|
+ return evaluatingSectionIndex;
|
|
|
+ }
|
|
|
+
|
|
|
+ public void handle(float[] samples, AudioFormat audioFormat){
|
|
|
+
|
|
|
+ YINPitchDetector frequencyDetector = new YINPitchDetector(samples.length , audioFormat.getSampleRate());
|
|
|
+
|
|
|
+ int playFrequency = (int) frequencyDetector.getFrequency(samples);
|
|
|
+ int splDb = (int) Signals.soundPressureLevel(samples);
|
|
|
+ int power = (int) Signals.power(samples);
|
|
|
+ int amplitude = (int) Signals.norm(samples);
|
|
|
+ float rms = Signals.rms(samples);
|
|
|
+
|
|
|
+ double durationTime = 1000 * (samples.length * 2) / audioFormat.getSampleRate() / (audioFormat.getSampleSizeInBits() / 8);
|
|
|
+
|
|
|
+ receivedTime += durationTime;
|
|
|
+
|
|
|
+ /*if(offsetMS == 0){
|
|
|
+ return;
|
|
|
+ }*/
|
|
|
+
|
|
|
+ if(receivedTime < offsetMS){
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ playTime += durationTime;
|
|
|
+
|
|
|
+ // 获取当前音符信息
|
|
|
+ MusicXmlNote musicXmlNote = getCurrentMusicNote(null,null);
|
|
|
+
|
|
|
+ if (musicXmlNote == null) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ //取出当前处理中的音符信息
|
|
|
+ NoteAnalysis noteAnalysis = getProcessingNote();
|
|
|
+ if(noteAnalysis == null || noteAnalysis.getDurationTime() == 0) {
|
|
|
+ noteAnalysis = new NoteAnalysis(musicXmlNote.getMusicalNotesIndex(), musicXmlNote.getMeasureIndex(), (int)musicXmlNote.getFrequency(), musicXmlNote.getDuration());
|
|
|
+ }
|
|
|
+
|
|
|
+ evaluatingSectionIndex.set(noteAnalysis.getSectionIndex());
|
|
|
+
|
|
|
+ if (noteAnalysis.getMusicalNotesIndex() >= 0 && noteAnalysis.getMusicalNotesIndex() <= getTotalMusicNoteIndex(null)) {
|
|
|
+
|
|
|
+ ChunkAnalysis lastChunkAnalysis = new ChunkAnalysis(playTime - durationTime, playTime, playFrequency, splDb, power, amplitude);
|
|
|
+
|
|
|
+ if(totalChunkAnalysisList.size() > 0){
|
|
|
+ if(totalChunkAnalysisList.get(totalChunkAnalysisList.size() - 1).getAmplitude() + 2 < lastChunkAnalysis.getAmplitude()){
|
|
|
+ lastChunkAnalysis.setPeak(true);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ totalChunkAnalysisList.add(lastChunkAnalysis);
|
|
|
+
|
|
|
+ if (playTime >= (musicXmlNote.getDuration() + musicXmlNote.getTimeStamp())) {
|
|
|
+
|
|
|
+ LOGGER.info("------ Frequency:{} splDb:{} Power:{} amplitude:{} time:{}------", playFrequency, splDb, power, amplitude, playTime);
|
|
|
+
|
|
|
+ if(Math.abs(chunkAnalysisList.get(chunkAnalysisList.size() - 1).getFrequency() - lastChunkAnalysis.getFrequency()) > hardLevel.getFrequencyThreshold()){
|
|
|
+ lastChunkAnalysis.setFrequency(-1);
|
|
|
+ }
|
|
|
+
|
|
|
+ //每个音符最后一个块
|
|
|
+ lastChunkAnalysisList.add(lastChunkAnalysis);
|
|
|
+ if(noteAnalysis.getMusicalNotesIndex() > 0){
|
|
|
+ lastChunkAnalysis = lastChunkAnalysisList.get(noteAnalysis.getMusicalNotesIndex() - 1);
|
|
|
+ }else{
|
|
|
+ lastChunkAnalysis = new ChunkAnalysis(0, 0, -1, 0, 0, 0);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (musicXmlNote.getDontEvaluating()) {
|
|
|
+ noteAnalysis.setIgnore(true);
|
|
|
+ }
|
|
|
+
|
|
|
+ if(chunkAnalysisList.size() == 0){// 延音线
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ noteAnalysis.setPlayFrequency(computeFrequency(chunkAnalysisList, lastChunkAnalysis, hardLevel.getFrequencyThreshold()));
|
|
|
+
|
|
|
+ //判断节奏(音符持续时间内有不间断的音高,就节奏正确)
|
|
|
+ boolean tempo = true;
|
|
|
+ if (subjectId == 23 || subjectId == 113) {
|
|
|
+ if (musicXmlNote.getFrequency() == -1) {// 休止符
|
|
|
+ tempo = chunkAnalysisList.stream().filter(t -> t.getAmplitude() > hardLevel.getAmplitudeThreshold()).count() <= 0;
|
|
|
+ }else{
|
|
|
+ tempo = computeTempoWithAmplitude2(musicXmlNote, chunkAnalysisList, lastChunkAnalysis);
|
|
|
+ }
|
|
|
+ }else{
|
|
|
+ if (musicXmlNote.getFrequency() == -1) {// 休止符
|
|
|
+ tempo = chunkAnalysisList.stream().filter(t -> t.getFrequency() > 100).count() <= 1;
|
|
|
+ }else{
|
|
|
+ tempo = computeTempoWithFrequency(musicXmlNote, chunkAnalysisList, lastChunkAnalysis);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ noteAnalysis.setDurationTime(chunkAnalysisList.stream().mapToDouble(t -> t.getDurationTime()).sum());
|
|
|
+
|
|
|
+ noteAnalysis.setTempo(tempo);
|
|
|
+
|
|
|
+ evaluateForNote(noteAnalysis);
|
|
|
+
|
|
|
+ LOGGER.info("当前音符下标[{}] 预计频率:{} 实际频率:{} 节奏:{}", noteAnalysis.getMusicalNotesIndex(), musicXmlNote.getFrequency(), noteAnalysis.getPlayFrequency(),
|
|
|
+ noteAnalysis.isTempo());
|
|
|
+
|
|
|
+ doneNoteAnalysisList.add(noteAnalysis);
|
|
|
+
|
|
|
+ //lastChunkAnalysis = chunkAnalysisList.get(chunkAnalysisList.size() - 1);
|
|
|
+
|
|
|
+ chunkAnalysisList.clear();
|
|
|
+
|
|
|
+ // 准备处理下一个音符
|
|
|
+ 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), (int)nextNoteFrequence, standDuration);
|
|
|
+
|
|
|
+ noteAnalysis = nextNoteAnalysis;
|
|
|
+
|
|
|
+ } else {
|
|
|
+
|
|
|
+ LOGGER.info("Frequency:{} splDb:{} Power:{} amplitude:{} rms:{}", playFrequency, splDb, power, amplitude, rms);
|
|
|
+
|
|
|
+ chunkAnalysisList.add(lastChunkAnalysis);
|
|
|
+ }
|
|
|
+
|
|
|
+ setProcessingNote(noteAnalysis);
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ 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());
|
|
|
+
|
|
|
+ long ignoreSize = noteAnalysisList.stream().filter(t -> t.isIgnore()).count();
|
|
|
+
|
|
|
+ SectionAnalysis sectionAnalysis = new SectionAnalysis();
|
|
|
+ sectionAnalysis.setIndex(sectionIndex);
|
|
|
+ sectionAnalysis.setNoteNum(noteAnalysisList.size());
|
|
|
+ sectionAnalysis.setIsIngore(ignoreSize == 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);
|
|
|
+ result.put("recordId", recordId.intValue());
|
|
|
+
|
|
|
+ int score = socre / noteAnalysisList.size();
|
|
|
+
|
|
|
+ // 平均得分
|
|
|
+ if (getMusicXmlBasicInfo(null).getSubjectId() == 23 || getMusicXmlBasicInfo(null).getSubjectId() == 113) {
|
|
|
+ score = tempoScore;
|
|
|
+ }
|
|
|
+ result.put("score", score);
|
|
|
+ }
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ public void evaluateForNote(NoteAnalysis noteAnalysis) {
|
|
|
+
|
|
|
+ double playDurationTime = 0;
|
|
|
+
|
|
|
+ if (subjectId == 23 || subjectId == 113) {
|
|
|
+ if (noteAnalysis.getFrequency() == -1) {// 休止符
|
|
|
+ if (!noteAnalysis.isTempo()) {
|
|
|
+ noteAnalysis.setMusicalErrorType(NoteErrorType.CADENCE_WRONG);
|
|
|
+ } else {
|
|
|
+ noteAnalysis.setMusicalErrorType(NoteErrorType.RIGHT);
|
|
|
+ }
|
|
|
+ }else{
|
|
|
+ int beatTimes = (int) chunkAnalysisList.stream().filter(t -> t.getAmplitude() > hardLevel.getAmplitudeThreshold()).count();
|
|
|
+ LOGGER.info("Amplitude:{} beatTimes:{}",chunkAnalysisList.stream().map(t -> t.getAmplitude()).collect(Collectors.toList()),beatTimes);
|
|
|
+ if(beatTimes == 0){
|
|
|
+ noteAnalysis.setMusicalErrorType(NoteErrorType.NOT_PLAY);
|
|
|
+ }else if (!noteAnalysis.isTempo()) {
|
|
|
+ noteAnalysis.setMusicalErrorType(NoteErrorType.CADENCE_WRONG);
|
|
|
+ } else {
|
|
|
+ noteAnalysis.setMusicalErrorType(NoteErrorType.RIGHT);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ if (noteAnalysis.getFrequency() == -1) {// 休止符
|
|
|
+
|
|
|
+ playDurationTime = chunkAnalysisList.stream().filter(t -> t.getFrequency() <= 100).mapToDouble(t -> t.getDurationTime()).sum();
|
|
|
+
|
|
|
+ if (!noteAnalysis.isTempo()) {
|
|
|
+ noteAnalysis.setMusicalErrorType(NoteErrorType.CADENCE_WRONG);
|
|
|
+ } else if (playDurationTime * 100 / noteAnalysis.getDurationTime() < hardLevel.getIntegrityRange()) {
|
|
|
+ noteAnalysis.setMusicalErrorType(NoteErrorType.INTEGRITY_WRONG);
|
|
|
+ } else if (Math.abs(noteAnalysis.getFrequency() - noteAnalysis.getPlayFrequency()) > hardLevel.getFrequencyThreshold()) {
|
|
|
+ noteAnalysis.setMusicalErrorType(NoteErrorType.INTONATION_WRONG);
|
|
|
+ } else {
|
|
|
+ noteAnalysis.setMusicalErrorType(NoteErrorType.RIGHT);
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ playDurationTime = chunkAnalysisList.stream().filter(t -> t.getFrequency() > 100 && t.getFrequency() < 2000)
|
|
|
+ .mapToDouble(t -> t.getDurationTime()).sum();
|
|
|
+
|
|
|
+ if (playDurationTime * 100 / noteAnalysis.getDurationTime() < hardLevel.getNotPlayRange()) {
|
|
|
+ noteAnalysis.setMusicalErrorType(NoteErrorType.NOT_PLAY);
|
|
|
+ } else if (playDurationTime * 100 / noteAnalysis.getDurationTime() < hardLevel.getIntegrityRange()) {
|
|
|
+ noteAnalysis.setMusicalErrorType(NoteErrorType.INTEGRITY_WRONG);
|
|
|
+ } else if (!noteAnalysis.isTempo()) {
|
|
|
+ noteAnalysis.setMusicalErrorType(NoteErrorType.CADENCE_WRONG);
|
|
|
+ } else if (Math.abs(noteAnalysis.getFrequency() - noteAnalysis.getPlayFrequency()) > hardLevel.getFrequencyThreshold()) {
|
|
|
+ noteAnalysis.setMusicalErrorType(NoteErrorType.INTONATION_WRONG);
|
|
|
+ } else {
|
|
|
+ noteAnalysis.setMusicalErrorType(NoteErrorType.RIGHT);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 计算音分
|
|
|
+ int tempoScore = 0;
|
|
|
+ int integrityScore = 0;
|
|
|
+ int intonationScore = 100 - new BigDecimal(Math.abs(YINPitchDetector.hertzToAbsoluteCent(noteAnalysis.getPlayFrequency())
|
|
|
+ - YINPitchDetector.hertzToAbsoluteCent(noteAnalysis.getFrequency()))).multiply(new BigDecimal(20)).divide(new BigDecimal(17), BigDecimal.ROUND_UP)
|
|
|
+ .setScale(0, BigDecimal.ROUND_UP).intValue();
|
|
|
+ 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 = playDurationTime / noteAnalysis.getDurationTime();
|
|
|
+ if (durationPercent >= 0.7) {
|
|
|
+ integrityScore = 100;
|
|
|
+ } else if (durationPercent < 0.7 && durationPercent >= 0.5) {
|
|
|
+ integrityScore = 50;
|
|
|
+ }
|
|
|
+ noteAnalysis.setIntegrityScore(integrityScore);
|
|
|
+ }
|
|
|
+ noteAnalysis.setIntonationScore(intonationScore);
|
|
|
+ if (subjectId == 23 || subjectId == 113) {
|
|
|
+ noteAnalysis.setScore(tempoScore);
|
|
|
+ } else {
|
|
|
+ noteAnalysis.setScore(new BigDecimal(intonationScore + tempoScore + integrityScore).divide(new BigDecimal(3), 2).setScale(0, BigDecimal.ROUND_UP)
|
|
|
+ .intValue());
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private int computeFrequency(List<ChunkAnalysis> chunkAnalysisList, ChunkAnalysis lastChunkAnalysis, int offsetRange) {
|
|
|
+
|
|
|
+ List<ChunkAnalysis> chunkList = new ArrayList<ChunkAnalysis>(chunkAnalysisList);
|
|
|
+
|
|
|
+ int tenutoSize = 0;
|
|
|
+ // 剔除上一个音延续下来的信号
|
|
|
+ if (lastChunkAnalysis != null) {
|
|
|
+ int lastFrequency = lastChunkAnalysis.getFrequency();
|
|
|
+ Iterator<ChunkAnalysis> iterable = chunkList.iterator();
|
|
|
+ while (iterable.hasNext()) {
|
|
|
+ if (Math.abs(lastFrequency - iterable.next().getFrequency()) > offsetRange) {
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ iterable.remove();
|
|
|
+ tenutoSize++;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (chunkList.size() == 0) {
|
|
|
+ return lastFrequency < 100 ? -1 : lastFrequency;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ List<Integer> chunkFrequencyList = chunkList.stream().map(t -> t.getFrequency()).filter(t -> t.doubleValue() > 100 && t.doubleValue() < 2000)
|
|
|
+ .collect(Collectors.toList());
|
|
|
+
|
|
|
+ if (chunkFrequencyList.size() == 0) {
|
|
|
+ return -1;
|
|
|
+ }
|
|
|
+
|
|
|
+ if(tenutoSize * 100 / chunkAnalysisList.size() > 50){
|
|
|
+ return lastChunkAnalysis.getFrequency();
|
|
|
+ }
|
|
|
+
|
|
|
+ // 排序
|
|
|
+ chunkFrequencyList = chunkFrequencyList.stream().sorted().collect(Collectors.toList());
|
|
|
+
|
|
|
+ int tempFrequency = chunkFrequencyList.get(0), totalFrequency = chunkFrequencyList.get(0);
|
|
|
+
|
|
|
+ int maxChunkSize = 0;
|
|
|
+ int frequency = chunkFrequencyList.get(0);
|
|
|
+ int chunkSize = 1;
|
|
|
+ int avgFrequency = chunkFrequencyList.get(0);
|
|
|
+ for (int i = 1; i < chunkFrequencyList.size(); i++) {
|
|
|
+ tempFrequency = chunkFrequencyList.get(i);
|
|
|
+
|
|
|
+ if (Math.abs(avgFrequency - tempFrequency) > offsetRange) {
|
|
|
+
|
|
|
+ avgFrequency = totalFrequency / chunkSize;
|
|
|
+
|
|
|
+ if (maxChunkSize < chunkSize) {
|
|
|
+ maxChunkSize = chunkSize;
|
|
|
+ frequency = avgFrequency;
|
|
|
+ }
|
|
|
+
|
|
|
+ chunkSize = 1;
|
|
|
+ avgFrequency = tempFrequency;
|
|
|
+ totalFrequency = tempFrequency;
|
|
|
+ } else {
|
|
|
+ chunkSize++;
|
|
|
+ totalFrequency += tempFrequency;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (i == chunkFrequencyList.size() - 1) {
|
|
|
+ if (maxChunkSize <= chunkSize) {
|
|
|
+ maxChunkSize = chunkSize;
|
|
|
+ frequency = totalFrequency / chunkSize;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (chunkFrequencyList.size() < 3) {
|
|
|
+ frequency = (int)chunkFrequencyList.get(chunkFrequencyList.size() - 1);
|
|
|
+ }
|
|
|
+
|
|
|
+ if(frequency < 100){
|
|
|
+ frequency = -1;
|
|
|
+ }
|
|
|
+
|
|
|
+ return frequency;
|
|
|
+ }
|
|
|
+
|
|
|
+ private boolean computeTempoWithFrequency(MusicXmlNote musicXmlNote, List<ChunkAnalysis> chunkAnalysisList, ChunkAnalysis lastChunkAnalysis){
|
|
|
+
|
|
|
+ List<ChunkAnalysis> chunkList = new ArrayList<ChunkAnalysis>(chunkAnalysisList);
|
|
|
+
|
|
|
+ // 剔除上一个音延续下来的信号
|
|
|
+ if (lastChunkAnalysis != null) {
|
|
|
+ double lastFrequency = lastChunkAnalysis.getFrequency();
|
|
|
+ Iterator<ChunkAnalysis> iterable = chunkList.iterator();
|
|
|
+ while (iterable.hasNext()) {
|
|
|
+ if (Math.abs(lastFrequency - iterable.next().getFrequency()) > hardLevel.getFrequencyThreshold()) {
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ iterable.remove();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if(chunkList.size() == 0){
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ ChunkAnalysis chunkAnalysis = null;
|
|
|
+ boolean tempo = false;
|
|
|
+ boolean isContinue = true;
|
|
|
+ int unplayedSize = 0;
|
|
|
+ int firstPeakIndex = -1;
|
|
|
+ for (int i = 0; i < chunkList.size(); i++) {
|
|
|
+ chunkAnalysis = chunkList.get(i);
|
|
|
+ if (chunkAnalysis != null) {
|
|
|
+ if (chunkAnalysis.getFrequency() > 100) {
|
|
|
+ tempo = true;
|
|
|
+ if(firstPeakIndex == -1){
|
|
|
+ firstPeakIndex = i;
|
|
|
+ }
|
|
|
+ if (isContinue == false) {
|
|
|
+ if (chunkAnalysisList.size() < 5) {
|
|
|
+ if (unplayedSize > 0) {
|
|
|
+ tempo = false;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ if ((unplayedSize * 100 / chunkAnalysisList.size()) > hardLevel.getNotPlayRange() || unplayedSize > 1) {
|
|
|
+ tempo = false;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ if (tempo == true) {
|
|
|
+ isContinue = false;
|
|
|
+ unplayedSize++;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (tempo) {
|
|
|
+ // 判断进入时间点
|
|
|
+ if((chunkAnalysisList.size() - chunkList.size() + firstPeakIndex) * 100 /chunkAnalysisList.size() > hardLevel.getTempoEffectiveRange(musicXmlNote.getDenominator())){
|
|
|
+ tempo = false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return tempo;
|
|
|
+ }
|
|
|
+
|
|
|
+ private boolean computeTempoWithAmplitude2(MusicXmlNote musicXmlNote, List<ChunkAnalysis> chunkAnalysisList, ChunkAnalysis lastChunkAnalysis) {
|
|
|
+
|
|
|
+ double endTime = (musicXmlNote.getDuration() + musicXmlNote.getTimeStamp());
|
|
|
+ double startTime = musicXmlNote.getTimeStamp() * (100 - hardLevel.getTempoEffectiveRange(musicXmlNote.getDenominator())) / 100;
|
|
|
+
|
|
|
+ chunkAnalysisList = totalChunkAnalysisList.stream().filter(t -> t.getStartTime() >= startTime && t.getEndTime() <= endTime).collect(Collectors.toList());
|
|
|
+
|
|
|
+ if(chunkAnalysisList == null || chunkAnalysisList.size() == 0){
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ ChunkAnalysis chunkAnalysis = chunkAnalysisList.get(0);
|
|
|
+
|
|
|
+ lastChunkAnalysis = totalChunkAnalysisList.stream().filter(t -> t.getEndTime() == chunkAnalysis.getStartTime()).findFirst().get();
|
|
|
+ if(lastChunkAnalysis == null){
|
|
|
+ lastChunkAnalysis = new ChunkAnalysis(0, 0, -1, 0, 0, 0);
|
|
|
+ }
|
|
|
+
|
|
|
+ List<Integer> chunkAmplitudeList = chunkAnalysisList.stream().map(ChunkAnalysis::getAmplitude).collect(Collectors.toList());
|
|
|
+
|
|
|
+ chunkAmplitudeList.add(0, lastChunkAnalysis.getAmplitude());
|
|
|
+
|
|
|
+ // 检测是否有多个波峰
|
|
|
+ boolean tempo = false;
|
|
|
+ boolean isContinue = true;
|
|
|
+ int firstPeakIndex = -1;
|
|
|
+ int peakSize = 0;
|
|
|
+ for (int i = 1; i < chunkAmplitudeList.size(); i++) {
|
|
|
+ if (chunkAmplitudeList.get(i) > hardLevel.getAmplitudeThreshold() && chunkAmplitudeList.get(i) > chunkAmplitudeList.get(i - 1) + 2) {
|
|
|
+ tempo = true;
|
|
|
+ if(firstPeakIndex == -1){
|
|
|
+ firstPeakIndex = i;
|
|
|
+ peakSize++;
|
|
|
+ }
|
|
|
+ if (isContinue == false) {
|
|
|
+ tempo = false;
|
|
|
+ peakSize++;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ if (tempo == true) {
|
|
|
+ isContinue = false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if(peakSize == 0){
|
|
|
+ tempo = lastChunkAnalysis.isPeak();
|
|
|
+ }else if(peakSize == 1){
|
|
|
+ tempo = true;
|
|
|
+ }else{
|
|
|
+ tempo = false;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (tempo) {
|
|
|
+ // 判断进入时间点
|
|
|
+ if((firstPeakIndex - 1) * 100 /chunkAmplitudeList.size() > (hardLevel.getTempoEffectiveRange(musicXmlNote.getDenominator()) * 2 * 100 / (100 + hardLevel.getTempoEffectiveRange(musicXmlNote.getDenominator())))){
|
|
|
+ tempo = false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return tempo;
|
|
|
+ }
|
|
|
+
|
|
|
+ public static void main(String[] args) {
|
|
|
+ UserChannelContext2 context = new UserChannelContext2();
|
|
|
+
|
|
|
+ //int[] frequencys = {286,291,291,291,291,291,291};
|
|
|
+ int[] frequencys = {312,43,295,294,294,295};
|
|
|
+
|
|
|
+ ChunkAnalysis lastChunkAnalysis = new ChunkAnalysis(624, 0, 0);
|
|
|
+
|
|
|
+ List<ChunkAnalysis> chunkAnalysisList = new ArrayList<ChunkAnalysis>();
|
|
|
+ for(int f : frequencys) {
|
|
|
+ chunkAnalysisList.add(new ChunkAnalysis(f, 0, 0));
|
|
|
+ }
|
|
|
+
|
|
|
+ MusicXmlNote musicXmlNote = new MusicXmlNote();
|
|
|
+ musicXmlNote.setDenominator(1);
|
|
|
+
|
|
|
+ //System.out.println(context.computeFrequency(chunkAnalysisList, lastChunkAnalysis, 5));
|
|
|
+ System.out.println(context.computeTempoWithFrequency(musicXmlNote, chunkAnalysisList, lastChunkAnalysis));
|
|
|
+ }
|
|
|
+
|
|
|
+}
|