package com.yonge.netty.dto;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;

import javax.sound.sampled.AudioFormat;

import com.yonge.cooleshow.common.enums.HardLevelEnum;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import be.tarsos.dsp.pitch.FastYin;

import com.yonge.audio.analysis.Signals;
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 UserChannelContext {
	
	private final static Logger LOGGER = LoggerFactory.getLogger(UserChannelContext.class);
	
	//打击乐
	private final static List<Integer> percussionList = Arrays.asList(23, 113);
	
	private FastYin detector;
	
	private String user;
	
	private double standardFrequecy = 440;
	
	private float deviceDelayTimeMS;
	
	private double dynamicOffset;
	
	private String platform;
	
	private Long recordId;
	
	private Integer subjectId;
	
	private float beatDuration;
	
	private boolean startProcessed;
	
	// 曲目与musicxml对应关系
	private ConcurrentHashMap<Long, MusicXmlBasicInfo> songMusicXmlMap = new ConcurrentHashMap<Long, 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> totalChunkAnalysisList = new ArrayList<ChunkAnalysis>();
	
	private byte[] channelBufferBytes = new byte[0];
	
	private double playTime;
	
	private HardLevelEnum hardLevel = HardLevelEnum.ADVANCED;
	
	private boolean handlerSwitch;
	
	private NotePlayResult queryNoteFrequency(MusicXmlNote xmlNote, double playFrequency) {

		NotePlayResult result = new NotePlayResult();

		if (Math.round(xmlNote.getFrequency()) == Math.round(playFrequency)) {
			result.setStatus(0);
			result.setDeviationRate(0);
			result.setDeviationCentValue(0);
			
			return result;
		}

		int status = 0;
		
		NoteFrequencyRange noteFrequencyRange = new NoteFrequencyRange(standardFrequecy, xmlNote.getFrequency());

		if (noteFrequencyRange.getMinFrequency() > playFrequency ) {
			status = 1;//偏低
		} else if( playFrequency > noteFrequencyRange.getMaxFrequency()){
			status = 2;//偏高
		}

		result.setStatus(status);
		result.setDeviationCentValue(getCents(playFrequency, xmlNote.getFrequency()));
		result.setDeviationHertzValue(playFrequency - xmlNote.getFrequency());
		result.setDeviationRate(result.getDeviationCentValue()/(17*100));

		return result;
	}
	
	/**
	 * 获取2个频率之间的音分差值
	 * @param srcFrequency
	 * @param targetFrequency
	 * @return
	 */
	private double getCents(double srcFrequency, double targetFrequency) {
		if (targetFrequency < 0) {
			return 0;
		}
		if(srcFrequency < 0){
			return 17 * 10;
		}
		return Math.log(srcFrequency / targetFrequency) / Math.log(2) * 1200;
	}
	
	public void init(MusicXmlBasicInfo musicXmlBasicInfo,float sampleRate, int bufferSize) {
		this.platform = musicXmlBasicInfo.getPlatform();
		this.subjectId = musicXmlBasicInfo.getSubjectId();
		this.beatDuration = musicXmlBasicInfo.getBeatLength() + musicXmlBasicInfo.getReactionTimeMs();
		hardLevel = HardLevelEnum.valueOf(musicXmlBasicInfo.getHeardLevel());
		if(detector == null){
			detector = new FastYin(sampleRate, bufferSize);
		}
		this.standardFrequecy = musicXmlBasicInfo.getHertz();
	}
	
	public void setUser(String user) {
		this.user = user;
	}

	public Long getRecordId() {
		return recordId;
	}

	public void setRecordId(Long recordId) {
		this.recordId = recordId;
	}

	public boolean getHandlerSwitch() {
		return handlerSwitch;
	}

	public void setHandlerSwitch(boolean handlerSwitch) {
		this.handlerSwitch = handlerSwitch;
	}

	public float getDeviceDelayTimeMS() {
		return deviceDelayTimeMS;
	}

	public void setDeviceDelayTimeMS(float deviceDelayTimeMS) {
		this.deviceDelayTimeMS = deviceDelayTimeMS;
	}

	public float getBeatDuration() {
		return beatDuration;
	}

	public void setBeatDuration(float beatDuration) {
		this.beatDuration = beatDuration;
	}

	public HardLevelEnum getHardLevel() {
		return hardLevel;
	}

	public ConcurrentHashMap<Long, 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() {
		waveFileProcessor = null;
		processingNote = new NoteAnalysis(0,0,-1);
		evaluatingSectionIndex = new AtomicInteger(0);
		channelBufferBytes = new byte[0];
		doneNoteAnalysisList = new ArrayList<NoteAnalysis>();
		doneSectionAnalysisList = new ArrayList<SectionAnalysis>();
		totalChunkAnalysisList = new ArrayList<ChunkAnalysis>();
		recordId = null;
		playTime = 0;
		startProcessed = false;
		deviceDelayTimeMS = 0;
		dynamicOffset = 0;
		handlerSwitch = false;
		standardFrequecy = 440;
	}
	
	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);

		int totalNoteIndex = getTotalMusicNoteIndex(null);
		if (musicXmlBasicInfo != null && index <= totalNoteIndex) {
			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 playFrequency = -1;
		if(!percussionList.contains(subjectId)){
			playFrequency = (int)detector.getPitch(samples).getPitch();
		}
		
		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);
		
		playTime += durationTime;
		
		// 获取当前音符信息
		MusicXmlNote musicXmlNote = getCurrentMusicNote(null,null);

		if (musicXmlNote == null) {
			return;
		}
		
		//取出当前处理中的音符信息
		NoteAnalysis noteAnalysis = getProcessingNote();
		if(noteAnalysis == null || noteAnalysis.getDurationTime() == 0) {
			noteAnalysis = new NoteAnalysis(musicXmlNote.getMeasureRenderIndex(), musicXmlNote.getMusicalNotesIndex(), musicXmlNote.getMeasureIndex(), (int)musicXmlNote.getFrequency(), musicXmlNote.getDuration());
		}
		
		evaluatingSectionIndex.set(noteAnalysis.getSectionIndex());
		
		if (noteAnalysis.getMusicalNotesIndex() >= 0 && noteAnalysis.getMusicalNotesIndex() <= getTotalMusicNoteIndex(null)) {
			
			ChunkAnalysis chunkAnalysis = new ChunkAnalysis(playTime - durationTime, playTime, playFrequency, splDb, power, amplitude);
			
			if(totalChunkAnalysisList.size() > 0){
				if(totalChunkAnalysisList.get(totalChunkAnalysisList.size() - 1).getAmplitude() + 2 < chunkAnalysis.getAmplitude()){
					chunkAnalysis.setPeak(true);//只针对打击乐
				}
			}
			totalChunkAnalysisList.add(chunkAnalysis);
			
			boolean flag = false; //是否收到有效信号
			if(percussionList.contains(subjectId)){
				flag = chunkAnalysis.getAmplitude() > hardLevel.getAmplitudeThreshold();
			}else{
				flag = chunkAnalysis.getFrequency() > MusicalInstrumentsPitchRange.get(subjectId, standardFrequecy).getMinPitch() && chunkAnalysis.getFrequency() < MusicalInstrumentsPitchRange.get(subjectId, standardFrequecy).getMaxPitch();
			}
			
			if(startProcessed == false && flag){
				
				startProcessed = true;
				
				//计算延迟偏移值
				//playTime = musicXmlNote.getTimeStamp() + durationTime;
				dynamicOffset = chunkAnalysis.getStartTime() - musicXmlNote.getTimeStamp();
				if(100 * dynamicOffset / musicXmlNote.getDuration() > hardLevel.getIntegrityRange()){
					dynamicOffset = 0;
				}
			}
			
			LOGGER.debug("user:{}  startProcessed:{}  dynamicOffset:{}  Frequency:{}  splDb:{}  amplitude:{}  time:{}", user, startProcessed, dynamicOffset, playFrequency, splDb, amplitude, playTime);
			
			if (playTime >= (musicXmlNote.getDuration() + musicXmlNote.getTimeStamp() + dynamicOffset)) {

				if (musicXmlNote.getDontEvaluating()) {
					noteAnalysis.setIgnore(true);
				}
				
				//判断节奏(音符持续时间内有不间断的音高，就节奏正确)
				// 节奏  0：正常  1：错误 2：节奏慢 3：节奏快
				int tempo = 0;
				if (percussionList.contains(subjectId)) {
					noteAnalysis.setPlayFrequency(-1);
					tempo = computeTempoWithAmplitude2(musicXmlNote);
				}else{
					noteAnalysis.setPlayFrequency(computeFrequency(musicXmlNote));
					tempo = computeTempoWithFrequency(musicXmlNote);
				}
				
				noteAnalysis.setTempo(tempo);
				
				evaluateForNote(musicXmlNote, noteAnalysis);//对当前音符评分

				LOGGER.debug("当前音符下标[{}] 预计频率：{} 实际频率：{} 节奏：{}", noteAnalysis.getMusicalNotesIndex(), musicXmlNote.getFrequency(), noteAnalysis.getPlayFrequency(),
						noteAnalysis.getTempo());
				
				doneNoteAnalysisList.add(noteAnalysis);
				
				// 准备处理下一个音符
				int nextNoteIndex = musicXmlNote.getMusicalNotesIndex() + 1;
				float nextNoteFrequence = -1;
				double standDuration = 0;
				int measureRenderIndex = 0;
				MusicXmlNote nextMusicXmlNote = getCurrentMusicNote(null, nextNoteIndex);
				if(nextMusicXmlNote != null){
					nextNoteFrequence = nextMusicXmlNote.getFrequency();
					standDuration = nextMusicXmlNote.getDuration();
					measureRenderIndex = nextMusicXmlNote.getMeasureRenderIndex();
				}
				
				NoteAnalysis nextNoteAnalysis = new NoteAnalysis(measureRenderIndex, nextNoteIndex, getMusicSectionIndex(null, nextNoteIndex), (int)nextNoteFrequence, standDuration);

				noteAnalysis = nextNoteAnalysis;

			}

			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()){
			sectionAnalysis.setMeasureRenderIndex(noteAnalysisList.stream().findFirst().get().getMeasureRenderIndex());
			//取出需要评测的音符
			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.debug("小节评分：{}",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());
        result.put("recordId", recordId.intValue());
		
		// 取出需要评测的音符
		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 || getMusicXmlBasicInfo(null).getSubjectId() == 113) {
				score = tempoScore;
			}
			result.put("score", score);
		} else {
			result.put("cadence", 0);
			result.put("intonation", 0);
			result.put("integrity", 0);
			result.put("score", 0);
		}
		return result;
	}
	

	public void evaluateForNote(MusicXmlNote musicXmlNote, NoteAnalysis noteAnalysis) {
		
		double floatingRange = musicXmlNote.getDuration() * hardLevel.getTempoEffectiveRange(musicXmlNote.getDenominator(), musicXmlNote.getDuration()) / 100;
		
		double endTime = musicXmlNote.getTimeStamp() + dynamicOffset + floatingRange;
		double startTime = musicXmlNote.getTimeStamp() + dynamicOffset - floatingRange;
		
		List<ChunkAnalysis> chunkAnalysisList = totalChunkAnalysisList.stream().filter(t -> Double.doubleToLongBits(t.getStartTime()) >= Double.doubleToLongBits(startTime) && Double.doubleToLongBits(t.getEndTime()) <= Double.doubleToLongBits(endTime)).collect(Collectors.toList());
		
		double correctedStartTime = queryFirstNoteStartTime(chunkAnalysisList, musicXmlNote, floatingRange);
		double correctedEndTime = correctedStartTime + musicXmlNote.getDuration();
		
		chunkAnalysisList = totalChunkAnalysisList.stream().filter(t -> Double.doubleToLongBits(t.getStartTime()) >= Double.doubleToLongBits(correctedStartTime) && Double.doubleToLongBits(t.getEndTime()) <= Double.doubleToLongBits(correctedEndTime)).collect(Collectors.toList());
		
		double durationTime = chunkAnalysisList.get(chunkAnalysisList.size() - 1).getEndTime() - chunkAnalysisList.get(0).getStartTime();
		
		double playDurationTime = 0;
		
		if (percussionList.contains(subjectId)) {
			if (noteAnalysis.getFrequency() == -1) { // 休止符
				if (noteAnalysis.getTempo() != 0 ) {
					noteAnalysis.setMusicalErrorType(setMusicalErrorTempo(noteAnalysis.getTempo()));
				} else {
					noteAnalysis.setMusicalErrorType(NoteErrorType.RIGHT);
				}
			}else{
				int beatTimes = (int) chunkAnalysisList.stream().filter(t -> t.getAmplitude() > hardLevel.getAmplitudeThreshold()).count();
				if(beatTimes == 0){
					noteAnalysis.setMusicalErrorType(NoteErrorType.NOT_PLAY);
				}else if (noteAnalysis.getTempo() != 0) {
					noteAnalysis.setMusicalErrorType(setMusicalErrorTempo(noteAnalysis.getTempo()));
				} else {
					noteAnalysis.setMusicalErrorType(NoteErrorType.RIGHT);
				}
			}
		} else {
			
			NotePlayResult notePlayResult = queryNoteFrequency(musicXmlNote, noteAnalysis.getPlayFrequency());
			
			if (noteAnalysis.getFrequency() == -1) {// 休止符

				playDurationTime = chunkAnalysisList.stream().filter(t -> t.getFrequency() <= MusicalInstrumentsPitchRange.get(subjectId, standardFrequecy).getMinPitch()).mapToDouble(t -> t.getDurationTime()).sum();

				if (noteAnalysis.getTempo() != 0) {
					noteAnalysis.setMusicalErrorType(setMusicalErrorTempo(noteAnalysis.getTempo()));
				} else if (playDurationTime * 100 / durationTime < hardLevel.getIntegrityRange()) {
					noteAnalysis.setMusicalErrorType(NoteErrorType.INTEGRITY_WRONG);
				} else if (notePlayResult.getStatus() != 0) {
					noteAnalysis.setMusicalErrorType(setMusicalErrorStatus(notePlayResult.getStatus()));
				} else {
					noteAnalysis.setMusicalErrorType(NoteErrorType.RIGHT);
				}
			} else {
				playDurationTime = chunkAnalysisList.stream().filter(t -> t.getFrequency() > MusicalInstrumentsPitchRange.get(subjectId, standardFrequecy).getMinPitch() && t.getFrequency() < MusicalInstrumentsPitchRange.get(subjectId, standardFrequecy).getMaxPitch())
						.mapToDouble(t -> t.getDurationTime()).sum();

				if (playDurationTime * 100 / durationTime < hardLevel.getNotPlayRange()) {
					noteAnalysis.setMusicalErrorType(NoteErrorType.NOT_PLAY);
					LOGGER.debug("未演奏：{}", playDurationTime * 100 / durationTime);
				} else if (playDurationTime * 100 / durationTime < hardLevel.getIntegrityRange()) {
					noteAnalysis.setMusicalErrorType(NoteErrorType.INTEGRITY_WRONG);
					LOGGER.debug("完整度不足：{}", playDurationTime * 100 / durationTime);
				} else if (noteAnalysis.getTempo() != 0) {
					noteAnalysis.setMusicalErrorType(setMusicalErrorTempo(noteAnalysis.getTempo()));
				} else if (notePlayResult.getStatus() != 0) {
					noteAnalysis.setMusicalErrorType(setMusicalErrorStatus(notePlayResult.getStatus()));
				} else {
					noteAnalysis.setMusicalErrorType(NoteErrorType.RIGHT);
				}
			}
			
		}

		// 计算音分
		int tempoScore = 0;
		int integrityScore = 0;
		double cents = getCents(noteAnalysis.getPlayFrequency(), noteAnalysis.getFrequency());
		int intonationScore = 100 - new BigDecimal(Math.abs(cents)).multiply(new BigDecimal(10)).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.getTempo() == 0) {
				tempoScore = 100;
				noteAnalysis.setTempoScore(tempoScore);
			}

			integrityScore = (int) (playDurationTime * 100 * 100 / hardLevel.getIntegrityRange() / durationTime);
			if (integrityScore > 100) {
				integrityScore = 100;
			}
			noteAnalysis.setIntegrityScore(integrityScore);
		}
		noteAnalysis.setIntonationScore(intonationScore);
		if (percussionList.contains(subjectId)) {
			noteAnalysis.setScore(tempoScore);
		} else {
			noteAnalysis.setScore(new BigDecimal(intonationScore + tempoScore + integrityScore).divide(new BigDecimal(3), 2).setScale(0, BigDecimal.ROUND_UP)
					.intValue());
		}
	}

	// 设置 音高
	private NoteErrorType setMusicalErrorStatus(int status) {
		if (status == 1) {
			return NoteErrorType.INTONATION_LOW;
		} else if (status ==2) {
			return NoteErrorType.INTONATION_HIGH;
		}
		return null;
	}

	// 设置节奏
	private NoteErrorType setMusicalErrorTempo(int tempo) {
		if (tempo == 1) {
			return NoteErrorType.CADENCE_WRONG;
		} else if (tempo == 2) {
			return NoteErrorType.CADENCE_SLOW;
		} else if (tempo ==3) {

			return NoteErrorType.CADENCE_FAST;
		} else {
			return NoteErrorType.RIGHT;
		}
	}

	private int computeFrequency(MusicXmlNote musicXmlNote) {
		
		double floatingRange = musicXmlNote.getDuration() * hardLevel.getTempoEffectiveRange(musicXmlNote.getDenominator(), musicXmlNote.getDuration()) / 100;
		
		double endTime = musicXmlNote.getTimeStamp() + dynamicOffset + floatingRange;
		double startTime = musicXmlNote.getTimeStamp() + dynamicOffset - floatingRange;
		
		LOGGER.debug("------Pitch------standard start time:{}  floatingRange:{}  modified [ {} - {} ]------------", musicXmlNote.getTimeStamp() + dynamicOffset, floatingRange, startTime, endTime);
		
		List<ChunkAnalysis> chunkAnalysisList = totalChunkAnalysisList.stream().filter(t -> Double.doubleToLongBits(t.getStartTime()) >= Double.doubleToLongBits(startTime) && Double.doubleToLongBits(t.getEndTime()) <= Double.doubleToLongBits(endTime)).collect(Collectors.toList());
		
		double correctedStartTime = queryFirstNoteStartTime(chunkAnalysisList, musicXmlNote, floatingRange);
		double correctedEndTime = correctedStartTime + musicXmlNote.getDuration();
		
		//重新计算延迟
		dynamicOffset = correctedStartTime - musicXmlNote.getTimeStamp();
		dynamicOffset = dynamicOffset < 0 ? 0 : dynamicOffset;
		
		chunkAnalysisList = totalChunkAnalysisList.stream().filter(t -> Double.doubleToLongBits(t.getStartTime()) >= Double.doubleToLongBits(correctedStartTime) && Double.doubleToLongBits(t.getEndTime()) <= Double.doubleToLongBits(correctedEndTime)).collect(Collectors.toList());

		//根据完整度取部分有效信号
		int elementSize = chunkAnalysisList.size() * hardLevel.getIntegrityRange() / 100;
		chunkAnalysisList = chunkAnalysisList.subList(0, elementSize);
		if(chunkAnalysisList == null || chunkAnalysisList.size() == 0){
			return -1;
		}
		
		ChunkAnalysis firstChunkAnalysis = chunkAnalysisList.get(0);
		
		LOGGER.debug("------Pitch------startTime:{}  endTime:{}------", firstChunkAnalysis.getStartTime(), chunkAnalysisList.get(chunkAnalysisList.size() - 1)
				.getEndTime());
		
		List<ChunkAnalysis> chunkList = new ArrayList<ChunkAnalysis>(chunkAnalysisList);
		
		List<Double> chunkFrequencyList = chunkList.stream().map(t -> t.getFrequency()).filter(t -> t.doubleValue() > MusicalInstrumentsPitchRange.get(subjectId, standardFrequecy).getMinPitch() && t.doubleValue() < MusicalInstrumentsPitchRange.get(subjectId, standardFrequecy).getMaxPitch())
				.collect(Collectors.toList());
		
		if (chunkFrequencyList.size() == 0) {
			return -1;
		}

		int frequency = (int) (chunkFrequencyList.stream().mapToDouble(t -> t).sum() / chunkFrequencyList.size());

		return frequency;
	}
	
	/**
	 * 时值范围内有且只有一个音，且不能间断，且在合理范围内需开始演奏
	 * 与上一个音相同时，2个音之间需要间断
	 * @param musicXmlNote
	 * @return
	 */
	private int computeTempoWithFrequency(MusicXmlNote musicXmlNote){
		
		double floatingRange = musicXmlNote.getDuration() * hardLevel.getTempoEffectiveRange(musicXmlNote.getDenominator(), musicXmlNote.getDuration()) / 100;
		
		double endTime = musicXmlNote.getTimeStamp() + dynamicOffset + floatingRange;
		double startTime = musicXmlNote.getTimeStamp() + dynamicOffset - floatingRange;
		
		List<ChunkAnalysis> chunkAnalysisList = totalChunkAnalysisList.stream().filter(t -> Double.doubleToLongBits(t.getStartTime()) >= Double.doubleToLongBits(startTime) && Double.doubleToLongBits(t.getEndTime()) <= Double.doubleToLongBits(endTime)).collect(Collectors.toList());
		
		double correctedStartTime = queryFirstNoteStartTime(chunkAnalysisList, musicXmlNote, floatingRange);
		double correctedEndTime = correctedStartTime + musicXmlNote.getDuration();
		
		chunkAnalysisList = totalChunkAnalysisList.stream().filter(t -> Double.doubleToLongBits(t.getStartTime()) >= Double.doubleToLongBits(correctedStartTime) && Double.doubleToLongBits(t.getEndTime()) <= Double.doubleToLongBits(correctedEndTime)).collect(Collectors.toList());
		
		//根据完整度取部分有效信号，取开头的
		int elementSize = chunkAnalysisList.size() * hardLevel.getIntegrityRange() / 100;
		
		//int startIndex = (chunkAnalysisList.size() - elementSize) / 2 - 1;
		int startIndex = 0;
		startIndex = startIndex < 1 ? 0 : startIndex;
		List<ChunkAnalysis> chunkList = chunkAnalysisList.subList(startIndex, elementSize + startIndex);
		
		if(chunkList == null || chunkList.size() == 0){
			return 1;
		}
		
		if (musicXmlNote.getFrequency() == -1) {// 休止符
			return chunkList.stream().filter(t -> t.getFrequency() > MusicalInstrumentsPitchRange.get(subjectId, standardFrequecy).getMinPitch()).count() <= 1? 0:1;
		}

		ChunkAnalysis firstChunkAnalysis = chunkAnalysisList.get(0);
		//取出前面忽略的块
		List<ChunkAnalysis> ignoreHeaderList = chunkAnalysisList.subList(0, startIndex);
		if(ignoreHeaderList != null && ignoreHeaderList.size() > 0){
			for(ChunkAnalysis ca : ignoreHeaderList){
				if(ca.getFrequency() < MusicalInstrumentsPitchRange.get(subjectId, standardFrequecy).getMinPitch()){
					firstChunkAnalysis.setFrequency(-1);
					break;
				}
			}
		}
		
		double firstChunkStartTime = firstChunkAnalysis.getStartTime();
		
		Optional<ChunkAnalysis> chunkAnalysisOptional = totalChunkAnalysisList.stream().filter(t -> Double.doubleToLongBits(t.getEndTime()) < Double.doubleToLongBits(firstChunkStartTime)).findFirst();

		ChunkAnalysis lastChunkAnalysis = null;
		if (chunkAnalysisOptional.isPresent()) {
			lastChunkAnalysis = chunkAnalysisOptional.get();
		}
		
		if(lastChunkAnalysis == null){
			lastChunkAnalysis = new ChunkAnalysis(0, 0, -1, 0, 0, 0);
		}
		
		/*List<ChunkAnalysis> chunkList = new ArrayList<ChunkAnalysis>(chunkAnalysisList);
		
		if(chunkList.size() == 0){
			return false;
		}*/
		
		NoteFrequencyRange noteFrequencyRange = null;
		ChunkAnalysis chunkAnalysis = null;
		int tempo = 0;
		//boolean isContinue = true;
		//int unplayedSize = 0;
		int firstPeakIndex = -1;
		
		//将信号分堆归类
		Map<NoteFrequencyRange, Integer> signalGrouping = new HashMap<NoteFrequencyRange, Integer>();
		
		for (int i = 0; i < chunkList.size(); i++) {
			chunkAnalysis = chunkList.get(i);
			if (chunkAnalysis != null) {
				if (chunkAnalysis.getFrequency() > MusicalInstrumentsPitchRange.get(subjectId, standardFrequecy).getMinPitch() || firstPeakIndex > -1) {
					
					if (firstPeakIndex == -1) {
						firstPeakIndex = i;
					}
					
					noteFrequencyRange = new NoteFrequencyRange(standardFrequecy, chunkAnalysis.getFrequency());
					
					if (signalGrouping.containsKey(noteFrequencyRange)) {
						signalGrouping.put(noteFrequencyRange, signalGrouping.get(noteFrequencyRange) + 1);
					} else {
						signalGrouping.put(noteFrequencyRange, 1);
					}
				}
			}
		}
		
		Integer maxTimes = 0, totalTimes = 0;
		
		for (Entry<NoteFrequencyRange, Integer> entry : signalGrouping.entrySet()) {
			if (entry.getValue() > maxTimes) {
				maxTimes = entry.getValue();
			}
			totalTimes = totalTimes + entry.getValue();
		}
		
		if(totalTimes == 0){
			totalTimes = chunkList.size();
		}
		
		/*if (maxTimes * 100 / totalTimes < hardLevel.getIntegrityRange()) {
			tempo = 1;
			LOGGER.debug("节奏错误原因：信号分堆后的最大数量不足指定的完成比例");
		}*/
		
		/**
		for (int i = 0; i < chunkList.size(); i++) {
			chunkAnalysis = chunkList.get(i);
			if (chunkAnalysis != null) {
				if (chunkAnalysis.getFrequency() > MIN_FREQUECY) {
					
					tempo = true;
					if (firstPeakIndex == -1) {
						firstPeakIndex = i;
						noteFrequencyRange = new NoteFrequencyRange(standardFrequecy, chunkAnalysis.getFrequency());
					} else if (noteFrequencyRange.getMinFrequency() > chunkAnalysis.getFrequency()
							|| chunkAnalysis.getFrequency() > noteFrequencyRange.getMaxFrequency()) {// 判断是否是同一个音
						//是否是低八度或高八度
						if(!((noteFrequencyRange.getMinFrequency() < chunkAnalysis.getFrequency() * 2 && chunkAnalysis.getFrequency() * 2 < noteFrequencyRange.getMaxFrequency())
								|| (noteFrequencyRange.getMinFrequency() < chunkAnalysis.getFrequency() / 2 && chunkAnalysis.getFrequency() / 2 < noteFrequencyRange.getMaxFrequency()))){
							tempo = false;
							LOGGER.debug("节奏错误原因：不是同一个音[{}]:{}-{}", chunkAnalysis.getFrequency(), noteFrequencyRange.getMinFrequency(), noteFrequencyRange.getMaxFrequency());
							break;
						}
					}
					if (isContinue == false) {
						if ((i + 1) / chunkAnalysisList.size() < hardLevel.getIntegrityRange()) {
							if (unplayedSize > 0) {
								tempo = false;
								LOGGER.debug("节奏错误原因：信号不连续");
								break;
							}
						}
					}
				} else {
					if (tempo == true) {
						isContinue = false;
						unplayedSize++;
					}
				}
			}
		}
		*/
		
		if (tempo == 0) {
			// 判断进入时间点
			if(firstPeakIndex * 100 /chunkList.size() > hardLevel.getTempoEffectiveRange(musicXmlNote.getDenominator(), musicXmlNote.getDuration())){
				// 节奏慢
				tempo = 2;
				LOGGER.debug("节奏错误原因：进入时间点太晚");
			}else{
				//判断是否与上一个音延续下来的
				if(firstChunkAnalysis.getFrequency() > MusicalInstrumentsPitchRange.get(subjectId, standardFrequecy).getMinPitch() && lastChunkAnalysis.getFrequency() > MusicalInstrumentsPitchRange.get(subjectId, standardFrequecy).getMinPitch()){
					tempo = new NoteFrequencyRange(standardFrequecy, firstChunkAnalysis.getFrequency()).equals(new NoteFrequencyRange(standardFrequecy, lastChunkAnalysis.getFrequency())) == false?0:1;
					if(tempo == 1){
						LOGGER.debug("节奏错误原因：上一个音[{}]延续下来导致的", lastChunkAnalysis.getFrequency());
					}
				}
			}
		}
		
		return tempo;
	}
	
	private int computeTempoWithAmplitude2(MusicXmlNote musicXmlNote) {

		double floatingRange = musicXmlNote.getDuration() * hardLevel.getTempoEffectiveRange(musicXmlNote.getDenominator(), musicXmlNote.getDuration()) / 100;
		
		double endTime = musicXmlNote.getTimeStamp() + dynamicOffset + floatingRange;
		double startTime = musicXmlNote.getTimeStamp() + dynamicOffset - floatingRange;
		
		LOGGER.debug("------Tempo------standard start time:{}  floatingRange:{}  modified [ {} - {} ]------------", musicXmlNote.getTimeStamp(), floatingRange, startTime, endTime);
		
		List<ChunkAnalysis> chunkAnalysisList = totalChunkAnalysisList.stream().filter(t -> Double.doubleToLongBits(t.getStartTime()) >= Double.doubleToLongBits(startTime) && Double.doubleToLongBits(t.getEndTime()) <= Double.doubleToLongBits(endTime)).collect(Collectors.toList());
		
		double correctedStartTime = queryFirstNoteStartTime(chunkAnalysisList, musicXmlNote, floatingRange);
		double correctedEndTime = correctedStartTime + musicXmlNote.getDuration();
		
		chunkAnalysisList = totalChunkAnalysisList.stream().filter(t -> Double.doubleToLongBits(t.getStartTime()) >= Double.doubleToLongBits(correctedStartTime) && Double.doubleToLongBits(t.getEndTime()) <= Double.doubleToLongBits(correctedEndTime)).collect(Collectors.toList());
		
		//根据完整度取部分有效信号
		int elementSize = chunkAnalysisList.size() * hardLevel.getIntegrityRange() / 100;
		List<ChunkAnalysis> chunkList = chunkAnalysisList.subList(0, elementSize);
		
		if(chunkList == null || chunkList.size() == 0){
			return 1;
		}
		
		ChunkAnalysis firstChunkAnalysis = chunkAnalysisList.get(0);
		
		LOGGER.debug("------Tempo------startTime:{}  endTime:{}------", firstChunkAnalysis.getStartTime(), chunkList.get(chunkList.size() - 1)
				.getEndTime());

		if (musicXmlNote.getFrequency() == -1) {// 休止符
			
			LOGGER.debug("--pause note Amplitude:{}  Denominator:{}",chunkList.stream().map(t -> t).collect(Collectors.toList()), musicXmlNote.getDenominator());
			return chunkList.stream().filter(t -> t.getAmplitude() > hardLevel.getAmplitudeThreshold()).count() <= 0 ? 0:1;
		}
		
		Optional<ChunkAnalysis> chunkAnalysisOptional = totalChunkAnalysisList.stream().filter(t -> Double.doubleToLongBits(t.getEndTime()) < Double.doubleToLongBits(firstChunkAnalysis.getStartTime())).findFirst();

		ChunkAnalysis lastChunkAnalysis = null;
		if (chunkAnalysisOptional.isPresent()) {
			lastChunkAnalysis = chunkAnalysisOptional.get();
		}
		if(lastChunkAnalysis == null){
			lastChunkAnalysis = new ChunkAnalysis(0, 0, -1, 0, 0, 0);
		}
		
		List<Integer> chunkAmplitudeList = chunkList.stream().map(ChunkAnalysis::getAmplitude).collect(Collectors.toList());

		chunkAmplitudeList.add(0, lastChunkAnalysis.getAmplitude());
		
		LOGGER.debug("--Amplitude:{}  Denominator:{}",chunkAmplitudeList.stream().map(t -> t).collect(Collectors.toList()), musicXmlNote.getDenominator());
		
		// 检测是否有多个波峰
		int tempo = 1;
		boolean isContinue = true;
		int firstPeakIndex = -1;
		int peakSize = 0;
		for (int i = 1; i < chunkAmplitudeList.size(); i++) {
			if (Math.abs(chunkAmplitudeList.get(i) - chunkAmplitudeList.get(i - 1)) < hardLevel.getAmplitudeThreshold()) {
				continue;
			}
			if (chunkAmplitudeList.get(i) > hardLevel.getAmplitudeThreshold() && chunkAmplitudeList.get(i) > chunkAmplitudeList.get(i - 1)) {
				tempo = 0;
				if(firstPeakIndex == -1){
					firstPeakIndex = i;
					peakSize++;
				}
				if (isContinue == false) {
					tempo = 1;
					peakSize++;
					break;
				}
			} else {
				if (tempo == 0) {
					isContinue = false;
				}
			}
		}
		
		if(peakSize == 0){
			tempo = lastChunkAnalysis.isPeak() ?0:1;
		}else if(peakSize == 1){
			tempo = 0;
		}else{
			tempo = 1;
		}
		
		if (tempo == 0) {
			// 判断进入时间点
			if((firstPeakIndex - 1) * 100 /chunkAmplitudeList.size() > hardLevel.getTempoEffectiveRange(musicXmlNote.getDenominator(), musicXmlNote.getDuration()) * 2){
				LOGGER.debug("超过范围：{}", (firstPeakIndex - 1) * 100 /chunkAmplitudeList.size());
				tempo = 1;
			}
		}
		
		return tempo;
	}
	
	private double queryFirstNoteStartTime(List<ChunkAnalysis> chunkAnalysisList, MusicXmlNote musicXmlNote, double floatingRange) {
		
		if(chunkAnalysisList == null || chunkAnalysisList.size() == 0){
			LOGGER.debug("[查询第一个音]找不到数据,correctedStartTime:{}", musicXmlNote.getTimeStamp() + dynamicOffset);
			return musicXmlNote.getTimeStamp() + dynamicOffset;
		}
		
		if (percussionList.contains(subjectId)) {
			Optional<ChunkAnalysis> optional = chunkAnalysisList.stream().filter(t -> t.getAmplitude() > hardLevel.getAmplitudeThreshold()).findFirst();
			if(optional.isPresent()){
				LOGGER.debug("[查询第一个音]范围内查询到信号,correctedStartTime:{}", optional.get().getStartTime());
				return optional.get().getStartTime();
			}else{
				LOGGER.debug("[查询第一个音]范围内未查询到信号,correctedStartTime:{}", musicXmlNote.getTimeStamp() + dynamicOffset);
				return musicXmlNote.getTimeStamp() + dynamicOffset;
			}
		}
		
		//判断是否与上一个音是同一个音符
		if(musicXmlNote.getMusicalNotesIndex() > 0){
			MusicXmlNote preMusicXmlNote = getCurrentMusicNote(null, musicXmlNote.getMusicalNotesIndex() - 1);
			if((int)preMusicXmlNote.getFrequency() == (int)musicXmlNote.getFrequency()){
				Optional<ChunkAnalysis> optional = chunkAnalysisList.stream().filter(t -> t.getFrequency() <= MusicalInstrumentsPitchRange.get(subjectId, standardFrequecy).getMinPitch()).findFirst();
				if (optional.isPresent()) {// 与上一个音同音，有断开
					
					double restNotes = optional.get().getEndTime();

					optional = chunkAnalysisList
							.stream()
							.filter(t -> t.getStartTime() >= restNotes && t.getFrequency() > MusicalInstrumentsPitchRange.get(subjectId, standardFrequecy).getMinPitch()
									&& t.getFrequency() < MusicalInstrumentsPitchRange.get(subjectId, standardFrequecy).getMaxPitch()).findFirst();
					if(optional.isPresent()){
						LOGGER.debug("[查询第一个音]与上一个音同音，有断开,correctedStartTime:{}", optional.get().getStartTime());
						return optional.get().getStartTime();
					}else{
						
						if((int)musicXmlNote.getFrequency() == -1){
							LOGGER.debug("[查询第一个音]与上一个音同音，有断开,correctedStartTime:{}", musicXmlNote.getTimeStamp() + dynamicOffset);
							return musicXmlNote.getTimeStamp() + dynamicOffset;
						}else{
							LOGGER.debug("[查询第一个音]与上一个音同音，有断开,correctedStartTime:{}", musicXmlNote.getTimeStamp() + floatingRange + dynamicOffset);
							return musicXmlNote.getTimeStamp() + floatingRange + dynamicOffset;
						}
					}

				} else {
					LOGGER.debug("[查询第一个音]与上一个音同音，未断开,correctedStartTime:{}", musicXmlNote.getTimeStamp() + dynamicOffset);
					return musicXmlNote.getTimeStamp() + dynamicOffset;
				}
			}
		}

		NoteFrequencyRange standardNote = new NoteFrequencyRange(standardFrequecy, musicXmlNote.getFrequency());

		NoteFrequencyRange noteFrequencyRange = null;

		for (ChunkAnalysis ca : chunkAnalysisList) {
			noteFrequencyRange = new NoteFrequencyRange(standardFrequecy, ca.getFrequency());
			if (standardNote.equals(noteFrequencyRange)) {
				LOGGER.debug("[查询第一个音]范围内查询到信号,correctedStartTime:{}", ca.getStartTime());
				return ca.getStartTime();
			}
		}
		
		LOGGER.debug("[查询第一个音]范围内未查询到信号,correctedStartTime:{}", musicXmlNote.getTimeStamp() + dynamicOffset);

		//return chunkAnalysisList.get(chunkAnalysisList.size() - 1).getEndTime();
		return musicXmlNote.getTimeStamp() + dynamicOffset;
	}
	
	public static void main(String[] args) {
		double[] midi = new double[128];;
		int standardPitch = 440; // a is 440 hz...
		for (int x = 0; x < midi.length; ++x)
		{
			//转调
		   midi[x] = new BigDecimal(standardPitch).multiply(new BigDecimal(Math.pow(2, new BigDecimal(x-69).divide(new BigDecimal(12),6,BigDecimal.ROUND_HALF_UP).doubleValue()))).doubleValue();
		   System.out.println("x=" + x +"  "+ midi[x]);
		}
		UserChannelContext test= new UserChannelContext();
		System.out.println(test.getCents(440, 442));
	}
	
}
