yonge 3 vuotta sitten
vanhempi
commit
f9831e6886

+ 1 - 2
audio-analysis/src/main/java/com/yonge/netty/dto/UserChannelContext.java

@@ -32,7 +32,7 @@ public class UserChannelContext {
 	
 	private final static Logger LOGGER = LoggerFactory.getLogger(UserChannelContext.class);
 	
-	private int offsetMS = 300;
+	private int offsetMS = 350;
 	
 	private String platform;
 	
@@ -152,7 +152,6 @@ public class UserChannelContext {
 		recordId = null;
 		playTime = 0;
 		receivedTime = 0;
-		offsetMS = 350;
 		lastChunkAnalysisList = new ArrayList<ChunkAnalysis>();
 	}
 	

+ 802 - 0
audio-analysis/src/main/java/com/yonge/netty/dto/UserChannelContext2.java

@@ -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));
+	}
+	
+}