yonge 3 年之前
父节点
当前提交
d9248b7b6b
共有 22 个文件被更改,包括 1191 次插入245 次删除
  1. 76 79
      audio-analysis/src/main/java/com/yonge/audio/analysis/Signals.java
  2. 7 1
      audio-analysis/src/main/java/com/yonge/audio/analysis/detector/FrequencyDetector.java
  3. 223 0
      audio-analysis/src/main/java/com/yonge/audio/analysis/detector/YINPitchDetector.java
  4. 7 7
      audio-analysis/src/main/java/com/yonge/audio/utils/ArrayUtil.java
  5. 6 0
      audio-analysis/src/main/java/com/yonge/nettty/dto/ChunkAnalysis.java
  6. 126 20
      audio-analysis/src/main/java/com/yonge/nettty/dto/NoteAnalysis.java
  7. 45 26
      audio-analysis/src/main/java/com/yonge/nettty/dto/SectionAnalysis.java
  8. 385 78
      audio-analysis/src/main/java/com/yonge/nettty/dto/UserChannelContext.java
  9. 67 0
      audio-analysis/src/main/java/com/yonge/nettty/dto/WebSocketResponse.java
  10. 5 2
      audio-analysis/src/main/java/com/yonge/netty/server/NettyChannelManager.java
  11. 1 1
      audio-analysis/src/main/java/com/yonge/netty/server/NettyServer.java
  12. 31 11
      audio-analysis/src/main/java/com/yonge/netty/server/messagehandler/BinaryWebSocketFrameHandler.java
  13. 102 13
      audio-analysis/src/main/java/com/yonge/netty/server/messagehandler/TextWebSocketHandler.java
  14. 23 4
      audio-analysis/src/main/java/com/yonge/netty/server/processor/WaveformWriter.java
  15. 1 1
      audio-analysis/src/main/resources/application.yml
  16. 7 0
      mec-biz/src/main/java/com/ym/mec/biz/service/SysMusicCompareRecordService.java
  17. 13 0
      mec-biz/src/main/java/com/ym/mec/biz/service/impl/SysMusicCompareRecordServiceImpl.java
  18. 2 2
      mec-thirdparty/src/main/java/com/ym/mec/thirdparty/eseal/provider/TsignPlugin.java
  19. 8 0
      mec-thirdparty/src/main/java/com/ym/mec/thirdparty/storage/StoragePlugin.java
  20. 5 0
      mec-thirdparty/src/main/java/com/ym/mec/thirdparty/storage/StoragePluginContext.java
  21. 24 0
      mec-thirdparty/src/main/java/com/ym/mec/thirdparty/storage/provider/AliyunOssStoragePlugin.java
  22. 27 0
      mec-thirdparty/src/main/java/com/ym/mec/thirdparty/storage/provider/KS3StoragePlugin.java

+ 76 - 79
audio-analysis/src/main/java/com/yonge/audio/analysis/Signals.java

@@ -6,57 +6,55 @@ import javax.sound.sampled.DataLine;
 import javax.sound.sampled.LineUnavailableException;
 import javax.sound.sampled.TargetDataLine;
 
-public class Signals 
-{
-	public static float mean( float[] signal )
-	{
+public class Signals {
+	public static float mean(float[] signal) {
 		float mean = 0;
-		for( int i = 0; i < signal.length; i++ )
-			mean+=signal[i];
+		for (int i = 0; i < signal.length; i++)
+			mean += signal[i];
 		mean /= signal.length;
 		return mean;
 	}
-	
-	public static float energy( float[] signal )
-	{
+
+	public static float energy(float[] signal) {
 		float totalEnergy = 0;
-		for( int i = 0; i < signal.length; i++ )		
+		for (int i = 0; i < signal.length; i++)
 			totalEnergy += Math.pow(signal[i], 2);
 		return totalEnergy;
 	}
 
-	public static float power(float[] signal ) 
-	{	
-		return energy( signal ) / signal.length;
+	public static float power(float[] signal) {
+		return energy(signal) / signal.length;
 	}
-	
-	public static float norm( float[] signal )
-	{
-		return (float)Math.sqrt( energy(signal) );
+
+	public static float norm(float[] signal) {
+		return (float) Math.sqrt(energy(signal));
 	}
-	
-	public static float minimum( float[] signal )
-	{
+
+	public static float minimum(float[] signal) {
 		float min = Float.POSITIVE_INFINITY;
-		for( int i = 0; i < signal.length; i++ )
-			min = Math.min( min, signal[i] );
+		for (int i = 0; i < signal.length; i++)
+			min = Math.min(min, signal[i]);
 		return min;
 	}
-	
-	public static float maximum( float[] signal )
-	{
+
+	public static float maximum(float[] signal) {
 		float max = Float.NEGATIVE_INFINITY;
-		for( int i = 0; i < signal.length; i++ )
-			max = Math.max( max, signal[i] );
+		for (int i = 0; i < signal.length; i++)
+			max = Math.max(max, signal[i]);
 		return max;
 	}
-	
-	public static void scale( float[] signal, float scale )
-	{
-		for( int i = 0; i < signal.length; i++ )
+
+	public static void scale(float[] signal, float scale) {
+		for (int i = 0; i < signal.length; i++) {
 			signal[i] *= scale;
+			if (signal[i] > 32767) {
+				signal[i] = 32767;
+			} else if (signal[i] < -32768) {
+				signal[i] = -32768;
+			}
+		}
 	}
-	
+
 	public static float rms(float[] samples) {
 		// 均方根 (RMS) 功率
 		return (float) Math.sqrt(power(samples));
@@ -69,7 +67,7 @@ public class Signals
 		// 计算声强级(Sound Pressure Level)
 		return (20.0 * Math.log10(rms));
 	}
-	
+
 	public static int decibels(float[] samples) {
 		// 声音的分贝范围
 		double minDecibels = 0, db = 0, maxDecibels = 127;
@@ -88,57 +86,56 @@ public class Signals
 
 		return (int) db;
 	}
-	
+
 	public static void main(String[] args) throws LineUnavailableException {
-		
+
 		float sampleRate = 44100;
-		
+
 		AudioFormat audioFormat = new AudioFormat(sampleRate, 16, 1, true, false);
-		
+
 		DataLine.Info dataLineInfo = new DataLine.Info(TargetDataLine.class, audioFormat);
-		
+
 		TargetDataLine targetDataLine = (TargetDataLine) AudioSystem.getLine(dataLineInfo);
-		
+
 		targetDataLine.open(audioFormat);
-        targetDataLine.start();
-        
-        AudioFloatConverter converter = AudioFloatConverter.getConverter(audioFormat);
-        
-        byte[] buffer = new byte[1024 * 8];
-
-        while(true){
-        	targetDataLine.read(buffer, 0, buffer.length);
-        	
-        	float[] sampleFloats = new float[buffer.length /2];
-        	converter.toFloatArray(buffer,sampleFloats);
-        	
-        	
-        	//计算声强级(Sound Pressure Level)
-        	double splDb = soundPressureLevel(sampleFloats);
-        	
-        	int db = decibels(sampleFloats);
-        	
-        	
-        	Complex[] complex = new Complex[sampleFloats.length];
-        	
-        	for(int i = 0;i<sampleFloats.length;i++){
-        		complex[i] = new Complex(sampleFloats[i], 0);
-        	}
-        	Complex[] result = FFT.fft(complex);
-        	
-        	double maxMagnitude = result[0].abs();
-        	int maxIndex = 0;
-        	
-        	for(int i = 1;i<result.length / 2;i++){
-        		if(maxMagnitude < result[i].abs()){
-        			maxMagnitude = result[i].abs();
-        			maxIndex = i;
-        		}
-        	}
-        	
-        	double f = maxIndex * sampleRate / result.length;
-        	
-        	System.out.println("db:" + db + "	power:" + power(sampleFloats) + "	splDb: " + splDb + "	frequency: " + f);
-        }
+		targetDataLine.start();
+
+		AudioFloatConverter converter = AudioFloatConverter.getConverter(audioFormat);
+
+		byte[] buffer = new byte[1024 * 8];
+
+		while (true) {
+			targetDataLine.read(buffer, 0, buffer.length);
+
+			float[] sampleFloats = new float[buffer.length / 2];
+			converter.toFloatArray(buffer, sampleFloats);
+
+			// 计算声强级(Sound Pressure Level)
+			double splDb = soundPressureLevel(sampleFloats);
+
+			int db = decibels(sampleFloats);
+
+			Complex[] complex = new Complex[sampleFloats.length];
+
+			for (int i = 0; i < sampleFloats.length; i++) {
+				complex[i] = new Complex(sampleFloats[i], 0);
+			}
+			Complex[] result = FFT.fft(complex);
+
+			double maxMagnitude = result[0].abs();
+			int maxIndex = 0;
+
+			for (int i = 1; i < result.length / 2; i++) {
+				if (maxMagnitude < result[i].abs()) {
+					maxMagnitude = result[i].abs();
+					maxIndex = i;
+				}
+			}
+
+			double f = maxIndex * sampleRate / result.length;
+
+			System.out.println("db:" + db + "  energy:" + energy(sampleFloats) + "	power:" + power(sampleFloats) + "  rms:" + rms(sampleFloats) + "	splDb: "
+					+ splDb + "	frequency: " + f);
+		}
 	}
 }

+ 7 - 1
audio-analysis/src/main/java/com/yonge/audio/analysis/detector/FrequencyDetector.java

@@ -21,7 +21,7 @@ public class FrequencyDetector {
 
 		if (isUseHanmingWindow) {
 			// 加汉明窗
-			// hamming(samples);
+			hamming(samples);
 		}
 
 		Complex[] complex = new Complex[samples.length];
@@ -43,4 +43,10 @@ public class FrequencyDetector {
 
 		return maxIndex * sampleRate / result.length;
 	}
+
+	private void hamming(float[] samples) {
+		for (int i = 0; i < samples.length; i++) {
+			samples[i] *= (0.54f - 0.46f * Math.cos((2 * Math.PI) * i / (samples.length - 1)));
+		}
+	}
 }

+ 223 - 0
audio-analysis/src/main/java/com/yonge/audio/analysis/detector/YINPitchDetector.java

@@ -0,0 +1,223 @@
+package com.yonge.audio.analysis.detector;
+
+/**
+ * A {@link PitchDetector} implementation that uses a YIN algorithm to determine the frequency of
+ * the provided waveform data. The YIN algorithm is similar to the Auto-correlation Function used
+ * for pitch detection but adds additional steps to better the accuracy of the results. Each step
+ * lowers the error rate further. The following implementation was inspired by
+ * <a href="https://github.com/JorenSix/TarsosDSP/blob/master/src/core/be/tarsos/dsp/pitch/Yin.java">TarsosDsp</a>
+ * and
+ * <a href="http://recherche.ircam.fr/equipes/pcm/cheveign/ps/2002_JASA_YIN_proof.pdf">this YIN paper</a>.
+ * The six steps in the YIN algorithm are (according to the YIN paper):
+ * <p>
+ * <ol>
+ * <li>Auto-correlation Method</li>
+ * <li>Difference Function</li>
+ * <li>Cumulative Mean Normalized Difference Function</li>
+ * <li>Absolute Threshold</li>
+ * <li>Parabolic Interpolation</li>
+ * <li>Best Local Estimate</li>
+ * </ol>
+ * </p>
+ * The first two steps, the Auto-correlation Method and the Difference Function, can seemingly be
+ * combined into a single difference function step according to the YIN paper.
+ */
+public class YINPitchDetector {
+    // According to the YIN Paper, the threshold should be between 0.10 and 0.15
+    private static final float ABSOLUTE_THRESHOLD = 0.125f;
+
+	/**
+	 * C-1 = 16.35 / 2 Hz.
+	 */
+	private static final double REF_FREQ = 8.17579892;
+
+	/**
+	 * Cache LOG 2 calculation.
+	 */
+	private static final double LOG_TWO = Math.log(2.0);
+
+    private final double sampleRate;
+    private final float[] resultBuffer;
+
+    public YINPitchDetector(int bufferSize, float sampleRate) {
+        this.sampleRate = sampleRate;
+        this.resultBuffer = new float[bufferSize/2];
+    }
+    
+    /**
+	 * The reference frequency is configured. The default reference frequency is
+	 * 16.35Hz. This is C0 on a piano keyboard with A4 tuned to 440 Hz. This
+	 * means that 0 cents is C0; 1200 is C1; 2400 is C2; ... also -1200 cents is
+	 * C-1
+	 * 
+	 * @param hertzValue
+	 *            The pitch in Hertz.
+	 * @return The value in absolute cents using the configured reference
+	 *         frequency
+	 */
+	public static double hertzToAbsoluteCent(final double hertzValue) {
+		double pitchInAbsCent = 0.0;
+		if (hertzValue > 0) {
+			pitchInAbsCent = 1200 * Math.log(hertzValue / REF_FREQ) / LOG_TWO;
+		}
+		return pitchInAbsCent;
+	}
+
+    public double getFrequency(float[] wave) {
+        int tau;
+
+        // First, perform the functions to normalize the wave data
+
+        // The first and second steps in the YIN algorithm
+        autoCorrelationDifference(wave);
+
+        // The third step in the YIN algorithm
+        cumulativeMeanNormalizedDifference();
+
+        // Then perform the functions to retrieve the tau (the approximate period)
+
+        // The fourth step in the YIN algorithm
+        tau = absoluteThreshold();
+
+        // The fifth step in the YIN algorithm
+        float betterTau = parabolicInterpolation(tau);
+
+        // TODO implement the sixth and final step of the YIN algorithm
+        // (it isn't implemented in the Tarsos DSP project but is briefly explained in the YIN
+        // paper).
+
+        // The fundamental frequency (note frequency) is the sampling rate divided by the tau (index
+        // within the resulting buffer array that marks the period).
+        // The period is the duration (or index here) of one cycle.
+        // Frequency = 1 / Period, with respect to the sampling rate, Frequency = Sample Rate / Period
+        return sampleRate / betterTau;
+    }
+
+    /**
+     * Performs the first and second step of the YIN Algorithm on the provided array buffer values.
+     * This is a "combination" of the AutoCorrelation Method and the Difference Function. The
+     * AutoCorrelation Method multiplies the array value at the specified index with the array value
+     * at the specified index plus the "tau" (greek letter used in the formula). Whereas the
+     * Difference Function takes the square of the difference of the two values. This is supposed to
+     * provide a more accurate result (from about 10% to about 1.95% error rate). Note that this
+     * formula is a riemann sum, meaning the operation specified above is performed and accumulated
+     * for every value in the array. The result of this function is stored in a global array,
+     * {@link #resultBuffer}, which the subsequent steps of the algorithm should use.
+     *
+     * @param wave The waveform data to perform the AutoCorrelation Difference function on.
+     */
+    private void autoCorrelationDifference(final float[] wave) {
+        // Note this algorithm is currently slow (O(n^2)). Should look for any possible optimizations.
+        int length = resultBuffer.length;
+        int i, j;
+
+        for (j = 1; j < length; j++) {
+            for (i = 0; i < length; i++) {
+                // d sub t (tau) = (x(i) - x(i - tau))^2, from i = 1 to result buffer size
+                resultBuffer[j] += Math.pow((wave[i] - wave[i + j]), 2);
+            }
+        }
+    }
+
+    /**
+     * Performs the third step in the YIN Algorithm on the {@link #resultBuffer}. The result of this
+     * function yields an even lower error rate (about 1.69% from 1.95%). The {@link #resultBuffer}
+     * is updated when this function is performed.
+     */
+    private void cumulativeMeanNormalizedDifference() {
+        // newValue = oldValue / (runningSum / tau)
+        // == (oldValue / 1) * (tau / runningSum)
+        // == oldValue * (tau / runningSum)
+
+        // Here we're using index i as the "tau" in the equation
+        int i;
+        int length = resultBuffer.length;
+        float runningSum = 0;
+
+        // Set the first value in the result buffer to the value of one
+        resultBuffer[0] = 1;
+
+        for (i = 1; i < length; i++) {
+            // The sum of this value plus all the previous values in the buffer array
+            runningSum += resultBuffer[i];
+
+            // The current value is updated to be the current value multiplied by the index divided by the running sum value
+            resultBuffer[i] *= i / runningSum;
+        }
+    }
+
+    /**
+     * Performs step four of the YIN Algorithm on the {@link #resultBuffer}. This is the first step
+     * in the algorithm to attempt finding the period of the wave data. When attempting to determine
+     * the period of a wave, it's common to search for the high or low peaks or dips of the wave.
+     * This will allow you to determine the length of a cycle or its period. However, especially
+     * with a natural sound sample, it is possible to have false dips. This makes determining the
+     * period more difficult. This function attempts to resolve this issue by introducing a
+     * threshold. The result of this function yields an even lower rate (about 0.78% from about
+     * 1.69%).
+     *
+     * @return The tau indicating the approximate period.
+     */
+    private int absoluteThreshold() {
+        int tau;
+        int length = resultBuffer.length;
+
+        // The first two values in the result buffer should be 1, so start at the third value
+        for (tau = 2; tau < length; tau++) {
+            // If we are less than the threshold, continue on until we find the lowest value
+            // indicating the lowest dip in the wave since we first crossed the threshold.
+            if (resultBuffer[tau] < ABSOLUTE_THRESHOLD) {
+                while (tau + 1 < length && resultBuffer[tau + 1] < resultBuffer[tau]) {
+                    tau++;
+                }
+
+                // We have the approximate tau value, so break the loop
+                break;
+            }
+        }
+
+        // Some implementations of this algorithm set the tau value to -1 to indicate no correct tau
+        // value was found. This implementation will just return the last tau.
+        tau = tau >= length ? length - 1 : tau;
+
+        return tau;
+    }
+
+    /**
+     * Further lowers the error rate by using parabolas to smooth the wave between the minimum and
+     * maximum points. Especially helps to detect higher frequencies more precisely. The result of
+     * this function results in only a small error rate decline from about 0.78% to about 0.77%.
+     */
+    private float parabolicInterpolation(final int currentTau) {
+        // Finds the points to fit the parabola between
+        int x0 = currentTau < 1 ? currentTau : currentTau - 1;
+        int x2 = currentTau + 1 < resultBuffer.length ? currentTau + 1 : currentTau;
+
+        // Finds the better tau estimate
+        float betterTau;
+
+        if (x0 == currentTau) {
+            if (resultBuffer[currentTau] <= resultBuffer[x2]) {
+                betterTau = currentTau;
+            } else {
+                betterTau = x2;
+            }
+        } else if (x2 == currentTau) {
+            if (resultBuffer[currentTau] <= resultBuffer[x0]) {
+                betterTau = currentTau;
+            } else {
+                betterTau = x0;
+            }
+        } else {
+            // Fit the parabola between the first point, current tau, and the last point to find a
+            // better tau estimate.
+            float s0 = resultBuffer[x0];
+            float s1 = resultBuffer[currentTau];
+            float s2 = resultBuffer[x2];
+
+            betterTau = currentTau + (s2 - s0) / (2 * (2 * s1 - s2 - s0));
+        }
+
+        return betterTau;
+    }
+}

+ 7 - 7
audio-analysis/src/main/java/com/yonge/audio/utils/ArrayUtil.java

@@ -27,7 +27,7 @@ public class ArrayUtil {
 	}
 
 	/**
-	 * 根据指定的起始、结束为止提取数组中的数据,并返回
+	 * 根据指定的起始、结束为止提取数组中的数据(起止都包含),并返回
 	 * @param src
 	 * @param startIndex
 	 * @param endIndex
@@ -39,8 +39,8 @@ public class ArrayUtil {
 			throw new RuntimeException("结束索引[" + endIndex + "]不能小于起始索引[" + startIndex + "]");
 		}
 
-		byte[] target = new byte[endIndex - startIndex];
-		System.arraycopy(src, startIndex, target, 0, endIndex - startIndex);
+		byte[] target = new byte[endIndex - startIndex + 1];
+		System.arraycopy(src, startIndex, target, 0, target.length);
 
 		return target;
 	}
@@ -70,7 +70,7 @@ public class ArrayUtil {
 	}
 
 	/**
-	 * 根据指定的起始、结束为止提取数组中的数据,并返回
+	 * 根据指定的起始、结束为止提取数组中的数据(起止都包含),并返回
 	 * @param src
 	 * @param startIndex
 	 * @param endIndex
@@ -81,8 +81,8 @@ public class ArrayUtil {
 			throw new RuntimeException("结束索引[" + endIndex + "]不能小于起始索引[" + startIndex + "]");
 		}
 
-		float[] target = new float[endIndex - startIndex];
-		System.arraycopy(src, startIndex, target, 0, endIndex - startIndex);
+		float[] target = new float[endIndex - startIndex + 1];
+		System.arraycopy(src, startIndex, target, 0, target.length);
 
 		return target;
 	}
@@ -90,7 +90,7 @@ public class ArrayUtil {
 	public static void main(String[] args) {
 		byte[] b1 = { 1, 2, 3, 4, 5 };
 		byte[] b2 = { 3, 2, 1 };
-		byte[] r = mergeByte(b1, b2);
+		byte[] r = extractByte(b1, 0, 4);
 		for (int i = 0; i < r.length; i++) {
 			System.out.println(r[i]);
 		}

+ 6 - 0
audio-analysis/src/main/java/com/yonge/nettty/dto/ChunkAnalysis.java

@@ -23,6 +23,12 @@ public class ChunkAnalysis {
 		this.durationTime = endTime - startTime;
 	}
 
+	public ChunkAnalysis(double frequency, double splDb, float power) {
+		this.frequency = frequency;
+		this.splDb = splDb;
+		this.power = power;
+	}
+
 	public double getStartTime() {
 		return startTime;
 	}

+ 126 - 20
audio-analysis/src/main/java/com/yonge/nettty/dto/NoteAnalysis.java

@@ -1,42 +1,84 @@
 package com.yonge.nettty.dto;
 
+import java.util.ArrayList;
+import java.util.List;
+
+import com.ym.mec.common.enums.BaseEnum;
+
 public class NoteAnalysis {
 
+	public enum NoteErrorType implements BaseEnum<String, NoteErrorType> {
+		RIGHT("演奏正确"), CADENCE_WRONG("节奏错误"), INTONATION_WRONG("音准错误"), INTEGRITY_WRONG("完整度不足"), NOT_PLAY("未演奏");
+
+		private String msg;
+
+		NoteErrorType(String msg) {
+			this.msg = msg;
+		}
+
+		public String getMsg() {
+			return msg;
+		}
+
+		@Override
+		public String getCode() {
+			return this.name();
+		}
+	}
+
 	private int index;
 
+	private int sectionIndex;
+
 	private double startTime;
 
 	private double endTime;
+	
+	private double standardDurationTime;
 
 	private double durationTime;
 
 	private double frequency;
 
-	private int sectionIndex;
+	private double playFrequency = -1;
 
-	private double totalPitch;
+	private boolean tempo = true;
 
-	private int chunks;
+	private NoteErrorType noteErrorType = NoteErrorType.RIGHT;
+
+	private List<Double> chunkFrequencyList = new ArrayList<Double>();
+
+	private double playDurationTime;
+	
+	private int score;
+	
+	private int intonationScore;
+	
+	private int tempoScore;
+	
+	private int integrityScore;
 
 	private boolean ignore;
 
-	public NoteAnalysis(int index, int sectionIndex) {
+	public NoteAnalysis(int index, int sectionIndex, double frequency, double standardDurationTime) {
+		this.standardDurationTime = standardDurationTime;
 		this.index = index;
 		this.sectionIndex = sectionIndex;
+		this.frequency = frequency;
 	}
 
-	public NoteAnalysis(double startTime, double endTime, double frequency) {
+	public NoteAnalysis(double startTime, double endTime, double playFrequency) {
 		this.startTime = startTime;
 		this.endTime = endTime;
 		this.durationTime = endTime - startTime;
-		this.frequency = frequency;
+		this.playFrequency = playFrequency;
 	}
 
-	public int getIndex() {
+	public int getMusicalNotesIndex() {
 		return index;
 	}
 
-	public void setIndex(int index) {
+	public void setMusicalNotesIndex(int index) {
 		this.index = index;
 	}
 
@@ -64,6 +106,22 @@ public class NoteAnalysis {
 		this.durationTime = durationTime;
 	}
 
+	public double getStandardDurationTime() {
+		return standardDurationTime;
+	}
+
+	public void setStandardDurationTime(double standardDurationTime) {
+		this.standardDurationTime = standardDurationTime;
+	}
+
+	public double getPlayFrequency() {
+		return playFrequency;
+	}
+
+	public void setPlayFrequency(double playFrequency) {
+		this.playFrequency = playFrequency;
+	}
+
 	public double getFrequency() {
 		return frequency;
 	}
@@ -72,6 +130,14 @@ public class NoteAnalysis {
 		this.frequency = frequency;
 	}
 
+	public boolean isTempo() {
+		return tempo;
+	}
+
+	public void setTempo(boolean tempo) {
+		this.tempo = tempo;
+	}
+
 	public int getSectionIndex() {
 		return sectionIndex;
 	}
@@ -80,28 +146,68 @@ public class NoteAnalysis {
 		this.sectionIndex = sectionIndex;
 	}
 
-	public double getTotalPitch() {
-		return totalPitch;
+	public boolean isIgnore() {
+		return ignore;
 	}
 
-	public void setTotalPitch(double totalPitch) {
-		this.totalPitch = totalPitch;
+	public void setIgnore(boolean ignore) {
+		this.ignore = ignore;
 	}
 
-	public int getChunks() {
-		return chunks;
+	public double getPlayDurationTime() {
+		return playDurationTime;
 	}
 
-	public void setChunks(int chunks) {
-		this.chunks = chunks;
+	public void setPlayDurationTime(double playDurationTime) {
+		this.playDurationTime = playDurationTime;
 	}
 
-	public boolean isIgnore() {
-		return ignore;
+	public List<Double> getChunkFrequencyList() {
+		return chunkFrequencyList;
 	}
 
-	public void setIgnore(boolean ignore) {
-		this.ignore = ignore;
+	public void setChunkFrequencyList(List<Double> chunkFrequencyList) {
+		this.chunkFrequencyList = chunkFrequencyList;
+	}
+
+	public NoteErrorType getMusicalErrorType() {
+		return noteErrorType;
+	}
+
+	public void setMusicalErrorType(NoteErrorType noteErrorType) {
+		this.noteErrorType = noteErrorType;
+	}
+
+	public int getScore() {
+		return score;
+	}
+
+	public void setScore(int score) {
+		this.score = score;
+	}
+
+	public int getIntonationScore() {
+		return intonationScore;
+	}
+
+	public void setIntonationScore(int intonationScore) {
+		this.intonationScore = intonationScore;
+	}
+
+	public int getTempoScore() {
+		return tempoScore;
+	}
+
+	public void setTempoScore(int tempoScore) {
+		this.tempoScore = tempoScore;
+	}
+
+	public int getIntegrityScore() {
+		return integrityScore;
+	}
+
+	public void setIntegrityScore(int integrityScore) {
+		this.integrityScore = integrityScore;
 	}
 
 }

+ 45 - 26
audio-analysis/src/main/java/com/yonge/nettty/dto/SectionAnalysis.java

@@ -1,48 +1,67 @@
 package com.yonge.nettty.dto;
 
+import org.apache.commons.lang3.builder.ToStringBuilder;
+
 public class SectionAnalysis {
+
+	// 小节下标
+	private int measureIndex;
+
+	// 音符数
+	private int noteNum;
+
+	// 持续时长
+	private double durationTime;
+
+	// 得分
+	private float score;
 	
-	//小节下标
-	private int index;
-	
-	//音准
-	private float intonation;
-	
-	//节奏
-	private float tempo;
-	
-	//完整性
-	private float integrity;
+	public SectionAnalysis() {
+		// TODO Auto-generated constructor stub
+	}
+
+	public SectionAnalysis(int index, int noteNum, float durationTime, float score) {
+		this.measureIndex = index;
+		this.noteNum = noteNum;
+		this.durationTime = durationTime;
+		this.score = score;
+	}
 
 	public int getIndex() {
-		return index;
+		return measureIndex;
 	}
 
-	public void setIndex(int index) {
-		this.index = index;
+	public void setIndex(int measureIndex) {
+		this.measureIndex = measureIndex;
 	}
 
-	public float getIntonation() {
-		return intonation;
+	public int getNoteNum() {
+		return noteNum;
 	}
 
-	public void setIntonation(float intonation) {
-		this.intonation = intonation;
+	public void setNoteNum(int noteNum) {
+		this.noteNum = noteNum;
 	}
 
-	public float getTempo() {
-		return tempo;
+	public double getDurationTime() {
+		return durationTime;
 	}
 
-	public void setTempo(float tempo) {
-		this.tempo = tempo;
+	public void setDurationTime(double durationTime) {
+		this.durationTime = durationTime;
 	}
 
-	public float getIntegrity() {
-		return integrity;
+	public float getScore() {
+		return score;
 	}
 
-	public void setIntegrity(float integrity) {
-		this.integrity = integrity;
+	public void setScore(float score) {
+		this.score = score;
 	}
+	
+	@Override
+	public String toString() {
+		return ToStringBuilder.reflectionToString(this);
+	}
+
 }

+ 385 - 78
audio-analysis/src/main/java/com/yonge/nettty/dto/UserChannelContext.java

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

+ 67 - 0
audio-analysis/src/main/java/com/yonge/nettty/dto/WebSocketResponse.java

@@ -0,0 +1,67 @@
+package com.yonge.nettty.dto;
+
+import org.springframework.http.HttpStatus;
+
+public class WebSocketResponse<T> {
+
+	private Head header = new Head();
+
+	private T body;
+
+	public WebSocketResponse(Head header, T body) {
+		this.header = header;
+		this.body = body;
+	}
+
+	public WebSocketResponse(String command, T body) {
+		this.header = new Head(command, HttpStatus.OK.value());
+		this.body = body;
+	}
+
+	public Head getHeader() {
+		return header;
+	}
+
+	public void setHeader(Head header) {
+		this.header = header;
+	}
+
+	public T getBody() {
+		return body;
+	}
+
+	public void setBody(T body) {
+		this.body = body;
+	}
+
+	public static class Head {
+		private int status = HttpStatus.OK.value();
+		private String commond = "";
+
+		public Head() {
+
+		}
+
+		public Head(String commond, int status) {
+			this.commond = commond;
+			this.status = status;
+		}
+
+		public int getStatus() {
+			return status;
+		}
+
+		public void setStatus(int status) {
+			this.status = status;
+		}
+
+		public String getCommond() {
+			return commond;
+		}
+
+		public void setCommond(String commond) {
+			this.commond = commond;
+		}
+
+	}
+}

+ 5 - 2
audio-analysis/src/main/java/com/yonge/netty/server/NettyChannelManager.java

@@ -2,6 +2,7 @@ package com.yonge.netty.server;
 
 import io.netty.channel.Channel;
 import io.netty.channel.ChannelId;
+import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
 import io.netty.util.AttributeKey;
 
 import java.util.concurrent.ConcurrentHashMap;
@@ -11,6 +12,8 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.stereotype.Component;
 
+import com.ym.mec.util.json.JsonUtil;
+
 @Component
 public class NettyChannelManager {
 
@@ -98,7 +101,7 @@ public class NettyChannelManager {
 	 * @param user 用户
 	 * @param message 消息体
 	 */
-	public void send(String user, Object message) {
+	public void sendTextMessage(String user, Object message) {
 		// 获得用户对应的 Channel
 		Channel channel = userChannels.get(user);
 		if (channel == null) {
@@ -110,7 +113,7 @@ public class NettyChannelManager {
 			return;
 		}
 		// 发送消息
-		channel.writeAndFlush(message);
+		channel.writeAndFlush(new TextWebSocketFrame(JsonUtil.toJSONString(message)));
 	}
 
 	/**

+ 1 - 1
audio-analysis/src/main/java/com/yonge/netty/server/NettyServer.java

@@ -103,7 +103,7 @@ public class NettyServer {
 				/*
 				 * 说明: 1、对应webSocket,它的数据是以帧(frame)的形式传递 2、浏览器请求时 ws://localhost:58080/xxx 表示请求的uri 3、核心功能是将http协议升级为ws协议,保持长连接
 				 */
-				channelPipeline.addLast(new WebSocketServerProtocolHandler(webSocketPath, WEBSOCKET_PROTOCOL, true, 65536 * 10, false, true));
+				channelPipeline.addLast(new WebSocketServerProtocolHandler(webSocketPath, WEBSOCKET_PROTOCOL, true, 65536 * 1000, false, true));
 
 				// 自定义的handler,处理业务逻辑
 				channelPipeline.addLast(nettyServerHandler);

+ 31 - 11
audio-analysis/src/main/java/com/yonge/netty/server/messagehandler/BinaryWebSocketFrameHandler.java

@@ -8,26 +8,25 @@ import io.netty.channel.ChannelHandlerContext;
 import io.netty.channel.SimpleChannelInboundHandler;
 import io.netty.handler.codec.http.websocketx.BinaryWebSocketFrame;
 
-import java.applet.AudioClip;
 import java.io.File;
 import java.text.SimpleDateFormat;
 import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
 
 import javax.sound.sampled.AudioFormat;
 
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Component;
 
-import be.tarsos.dsp.AudioDispatcher;
-import be.tarsos.dsp.io.TarsosDSPAudioFloatConverter;
-import be.tarsos.dsp.io.TarsosDSPAudioFormat;
-import be.tarsos.dsp.io.jvm.AudioDispatcherFactory;
 import be.tarsos.dsp.pitch.PitchProcessor;
 import be.tarsos.dsp.pitch.PitchProcessor.PitchEstimationAlgorithm;
 
 import com.yonge.audio.analysis.AudioFloatConverter;
 import com.yonge.audio.utils.ArrayUtil;
 import com.yonge.nettty.dto.UserChannelContext;
+import com.yonge.nettty.dto.WebSocketResponse;
+import com.yonge.nettty.entity.MusicXmlBasicInfo;
 import com.yonge.netty.server.NettyChannelManager;
 import com.yonge.netty.server.processor.WaveformWriter;
 import com.yonge.netty.server.service.UserChannelContextService;
@@ -60,7 +59,7 @@ public class BinaryWebSocketFrameHandler extends SimpleChannelInboundHandler<Bin
 	/**
 	 * @describe 采样大小
 	 */
-	private int bufferSize = 1024 * 8;
+	private int bufferSize = 1024 * 4;
 	/**
 	 * @describe 帧覆盖大小
 	 */
@@ -133,20 +132,41 @@ public class BinaryWebSocketFrameHandler extends SimpleChannelInboundHandler<Bin
 			int totalLength = channelContext.getChannelBufferBytes().length;
 			
 			while(totalLength >= bufferSize){
-				byte[] bufferData = ArrayUtil.extractByte(channelContext.getChannelBufferBytes(), 0, bufferSize);
-				channelContext.setChannelBufferBytes(ArrayUtil.extractByte(channelContext.getChannelBufferBytes(), bufferSize - 1, totalLength - 1));
+				byte[] bufferData = ArrayUtil.extractByte(channelContext.getChannelBufferBytes(), 0, bufferSize - 1);
+				
+				if(bufferSize != totalLength){
+					channelContext.setChannelBufferBytes(ArrayUtil.extractByte(channelContext.getChannelBufferBytes(), bufferSize, totalLength - 1));
+				}else{
+					channelContext.setChannelBufferBytes(new byte[0]);
+				}
 				
 				float[] sampleFloats = new float[bufferSize/2];
 				
 				converter.toFloatArray(bufferData,sampleFloats);
 				
 				channelContext.handle1(sampleFloats, audioFormat);
-				
-				totalLength = channelContext.getChannelBufferBytes().length;
 
-				/*AudioDispatcher dispatcher = AudioDispatcherFactory.fromByteArray(bufferData, audioFormat, bufferSize, overlap);
+				/*AudioDispatcher dispatcher = AudioDispatcherFactory.fromFloatArray(sampleFloats, (int)audioFormat.getSampleRate(), bufferSize, overlap);
 				dispatcher.addAudioProcessor(new PitchProcessor(algorithm, sampleRate, bufferSize, channelContext));
 				dispatcher.run();*/
+				
+				MusicXmlBasicInfo musicXmlBasicInfo = channelContext.getMusicXmlBasicInfo(null);
+				int sectionIndex = channelContext.getEvaluatingSectionIndex().get();
+				
+				//评分
+				int score = channelContext.evaluateForSection(sectionIndex, musicXmlBasicInfo.getSubjectId());
+				if(score >= 0){
+					
+					Map<String,Object> params = new HashMap<String, Object>();
+					params.put("score", score);
+					params.put("measureIndex", sectionIndex);
+					
+					WebSocketResponse<Map<String,Object>> resp = new WebSocketResponse<Map<String,Object>>("measureScore",params);
+					
+					nettyChannelManager.sendTextMessage(user, resp);
+				}
+				
+				totalLength = channelContext.getChannelBufferBytes().length;
 			}
 
 		} finally {

+ 102 - 13
audio-analysis/src/main/java/com/yonge/netty/server/messagehandler/TextWebSocketHandler.java

@@ -6,19 +6,36 @@ import io.netty.channel.ChannelHandlerContext;
 import io.netty.channel.SimpleChannelInboundHandler;
 import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
 
+import java.math.BigDecimal;
+import java.util.Comparator;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.stream.Collectors;
+
 import org.apache.commons.lang3.StringUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Component;
 
+import com.alibaba.fastjson.JSON;
 import com.alibaba.fastjson.JSONObject;
 import com.alibaba.fastjson.JSONPath;
 import com.ym.mec.biz.dal.entity.SysMusicCompareRecord;
+import com.ym.mec.biz.dal.enums.DeviceTypeEnum;
 import com.ym.mec.biz.dal.enums.FeatureType;
 import com.ym.mec.biz.service.SysMusicCompareRecordService;
+import com.ym.mec.thirdparty.storage.StoragePluginContext;
+import com.ym.mec.thirdparty.storage.provider.KS3StoragePlugin;
+import com.ym.mec.util.upload.UploadUtil;
+import com.yonge.nettty.dto.SectionAnalysis;
 import com.yonge.nettty.dto.UserChannelContext;
+import com.yonge.nettty.dto.WebSocketResponse;
 import com.yonge.nettty.entity.MusicXmlBasicInfo;
+import com.yonge.nettty.entity.MusicXmlNote;
+import com.yonge.netty.server.NettyChannelManager;
 import com.yonge.netty.server.processor.WaveformWriter;
 import com.yonge.netty.server.service.UserChannelContextService;
 
@@ -31,27 +48,35 @@ public class TextWebSocketHandler extends SimpleChannelInboundHandler<TextWebSoc
 	@Autowired
 	private SysMusicCompareRecordService sysMusicCompareRecordService;
 
+    @Autowired
+    private StoragePluginContext storagePluginContext;
+
 	@Autowired
 	private UserChannelContextService userChannelContextService;
 
+	@Autowired
+	private NettyChannelManager nettyChannelManager;
+
 	@Override
 	protected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame frame) throws Exception {
 
 		Channel channel = ctx.channel();
 
 		String jsonMsg = frame.text();
-		String commond = (String) JSONPath.extract(jsonMsg, "$.header.commond");
+		String command = (String) JSONPath.extract(jsonMsg, "$.header.commond");
 
 		JSONObject dataObj = (JSONObject) JSONPath.extract(jsonMsg, "$.body");
 
-		LOGGER.info("接收到客户端的指令[{}]:{}", commond, dataObj);
+		LOGGER.info("接收到客户端的指令[{}]:{}", command, dataObj);
 
 		UserChannelContext channelContext = userChannelContextService.getChannelContext(channel);
+		
+		MusicXmlBasicInfo musicXmlBasicInfo = null;
 
-		switch (commond) {
+		switch (command) {
 		case "musicXml": // 同步music xml信息
 
-			MusicXmlBasicInfo musicXmlBasicInfo = JSONObject.toJavaObject(dataObj, MusicXmlBasicInfo.class);
+			musicXmlBasicInfo = JSONObject.toJavaObject(dataObj, MusicXmlBasicInfo.class);
 
 			userChannelContextService.remove(channel);
 
@@ -60,18 +85,34 @@ public class TextWebSocketHandler extends SimpleChannelInboundHandler<TextWebSoc
 			}
 
 			channelContext.getSongMusicXmlMap().put(musicXmlBasicInfo.getExamSongId(), musicXmlBasicInfo);
+			channelContext.init();
 
 			userChannelContextService.register(channel, channelContext);
 
 			break;
 		case "recordStart": // 开始评测
 
-			SysMusicCompareRecord sysMusicCompareRecord = new SysMusicCompareRecord(FeatureType.CLOUD_STUDY_EVALUATION);
-			sysMusicCompareRecordService.insert(sysMusicCompareRecord);
-			
-			//清空缓存信息
+			// 清空缓存信息
 			channelContext.resetUserInfo();
-
+			
+			musicXmlBasicInfo = channelContext.getMusicXmlBasicInfo(null);
+
+			if (musicXmlBasicInfo != null) {
+				Date date = new Date();
+				SysMusicCompareRecord sysMusicCompareRecord = new SysMusicCompareRecord(FeatureType.CLOUD_STUDY_EVALUATION);
+				sysMusicCompareRecord.setCreateTime(date);
+				sysMusicCompareRecord.setUserId(Integer.parseInt(nettyChannelManager.getUser(channel)));
+				sysMusicCompareRecord.setSysMusicScoreId(musicXmlBasicInfo.getExamSongId());
+				sysMusicCompareRecord.setBehaviorId(musicXmlBasicInfo.getBehaviorId());
+				//sysMusicCompareRecord.setClientId();
+				sysMusicCompareRecord.setDeviceType(DeviceTypeEnum.valueOf(musicXmlBasicInfo.getPlatform()));
+				sysMusicCompareRecord.setSpeed(musicXmlBasicInfo.getSpeed());
+				
+				MusicXmlNote musicXmlNote = musicXmlBasicInfo.getMusicXmlInfos().stream().max(Comparator.comparing(MusicXmlNote::getTimeStamp)).get();
+				sysMusicCompareRecord.setSourceTime((float) ((musicXmlNote.getTimeStamp()+musicXmlNote.getDuration())/1000));
+				sysMusicCompareRecordService.insert(sysMusicCompareRecord);
+				channelContext.setRecordId(sysMusicCompareRecord.getId());
+			}
 			break;
 		case "recordEnd": // 结束评测
 		case "recordCancel": // 取消评测
@@ -84,14 +125,62 @@ public class TextWebSocketHandler extends SimpleChannelInboundHandler<TextWebSoc
 				// 写文件头
 				waveFileProcessor.processingFinished();
 			}
-			
-			//清空缓存信息
-			channelContext.resetUserInfo();
 
-			if (StringUtils.equals(commond, "recordEnd")) {
+			if (StringUtils.equals(command, "recordEnd")) {
 				// 生成评测报告
+				Map<String, Object> params = new HashMap<String, Object>();
+
+				Map<String, Integer> scoreMap = channelContext.evaluateForMusic();
+				for (Entry<String, Integer> entry : scoreMap.entrySet()) {
+					params.put(entry.getKey(), entry.getValue());
+				}
+				
+				//保存评测结果
+				Long recordId = channelContext.getRecordId();
+				SysMusicCompareRecord sysMusicCompareRecord = sysMusicCompareRecordService.get(recordId);
+				if(sysMusicCompareRecord != null){
+					musicXmlBasicInfo = channelContext.getMusicXmlBasicInfo(null);
+					
+					sysMusicCompareRecord.setScore(new BigDecimal(scoreMap.get("score")));
+					sysMusicCompareRecord.setIntonation(new BigDecimal(scoreMap.get("intonation")));
+					sysMusicCompareRecord.setIntegrity(new BigDecimal(scoreMap.get("integrity")));
+					sysMusicCompareRecord.setCadence(new BigDecimal(scoreMap.get("cadence")));
+					sysMusicCompareRecord.setFeature(FeatureType.CLOUD_STUDY_EVALUATION);
+					sysMusicCompareRecord.setPlayTime(scoreMap.get("playTime")/1000);
+
+		            String url = null;
+		            try {
+		                String folder = UploadUtil.getFileFloder();
+		                url = storagePluginContext.asyncUploadFile(KS3StoragePlugin.PLUGIN_NAME,"soundCompare/" + folder, waveFileProcessor.getFile());
+		            } catch (Exception e) {
+		                LOGGER.error("录音文件上传失败:{}", e);
+		            }
+					sysMusicCompareRecord.setRecordFilePath(url);
+					//sysMusicCompareRecord.setVideoFilePath(videoFilePath);
+
+					Map<String, Object> scoreData = new HashMap<>();
+					scoreData.put("userMeasureScore", channelContext.getDoneSectionAnalysisList().stream().collect(Collectors.toMap(SectionAnalysis :: getIndex, t -> t)));
+
+					Map<String, Object> musicalNotesPlayStats = new HashMap<>();
+					musicalNotesPlayStats.put("detailId", musicXmlBasicInfo.getDetailId());
+					musicalNotesPlayStats.put("examSongId", musicXmlBasicInfo.getExamSongId());
+					musicalNotesPlayStats.put("xmlUrl", musicXmlBasicInfo.getXmlUrl());
+					
+					musicalNotesPlayStats.put("notesData", channelContext.getDoneNoteAnalysisList());
+					scoreData.put("musicalNotesPlayStats", musicalNotesPlayStats);
+					sysMusicCompareRecord.setScoreData(JSON.toJSONString(scoreData));
+					
+					sysMusicCompareRecordService.saveMusicCompareData(sysMusicCompareRecord);
+				}
+				
+				WebSocketResponse<Map<String, Object>> resp = new WebSocketResponse<Map<String, Object>>("overall", params);
+
+				nettyChannelManager.sendTextMessage(nettyChannelManager.getUser(channel), resp);
 			}
 
+			// 清空缓存信息
+			channelContext.resetUserInfo();
+
 			break;
 		case "proxyMessage": // ???
 

+ 23 - 4
audio-analysis/src/main/java/com/yonge/netty/server/processor/WaveformWriter.java

@@ -24,6 +24,7 @@
 package com.yonge.netty.server.processor;
 
 import java.io.ByteArrayOutputStream;
+import java.io.File;
 import java.io.IOException;
 import java.io.RandomAccessFile;
 
@@ -37,6 +38,8 @@ import be.tarsos.dsp.writer.WaveHeader;
  */
 public class WaveformWriter {
 
+	private static final Logger LOGGER = LoggerFactory.getLogger(WaveformWriter.class);
+
 	private RandomAccessFile randomAccessFile;
 
 	private final String fileName;
@@ -49,8 +52,6 @@ public class WaveformWriter {
 
 	private static final int HEADER_LENGTH = 44;
 
-	private static final Logger LOGGER = LoggerFactory.getLogger(WaveformWriter.class);
-
 	public WaveformWriter(String fileName) {
 
 		this.fileName = fileName;
@@ -78,8 +79,9 @@ public class WaveformWriter {
 
 	public void processingFinished() {
 		try {
-			WaveHeader waveHeader = new WaveHeader(WaveHeader.FORMAT_PCM, channelNum, sampleRate, bitsPerSample, (int) randomAccessFile.length() - HEADER_LENGTH);// 16
-																																					// is
+			WaveHeader waveHeader = new WaveHeader(WaveHeader.FORMAT_PCM, channelNum, sampleRate, bitsPerSample, (int) randomAccessFile.length()
+					- HEADER_LENGTH);
+
 			ByteArrayOutputStream header = new ByteArrayOutputStream();
 			waveHeader.write(header);
 			randomAccessFile.seek(0);
@@ -90,4 +92,21 @@ public class WaveformWriter {
 			e.printStackTrace();
 		}
 	}
+
+	public File getFile() {
+		return new File(fileName);
+	}
+
+	public long getFileLength(boolean isSubHeadLength) {
+		try {
+			if (isSubHeadLength) {
+				return randomAccessFile.length() - HEADER_LENGTH;
+			}
+			return randomAccessFile.length();
+		} catch (IOException e) {
+			LOGGER.error("读取WAV文件出现异常[{}]:{}", fileName, e.getMessage());
+			e.printStackTrace();
+		}
+		return 0;
+	}
 }

+ 1 - 1
audio-analysis/src/main/resources/application.yml

@@ -26,7 +26,7 @@ spring:
     
   datasource:
     name: test
-    url: jdbc:mysql://47.114.1.200:3306/mec_dev?useUnicode=true&characterEncoding=UTF8&serverTimezone=Asia/Shanghai
+    url: jdbc:mysql://47.114.1.200:3306/mec_test?useUnicode=true&characterEncoding=UTF8&serverTimezone=Asia/Shanghai&allowMultiQueries=true
     username: mec_dev
     password: dayaDataOnline@2019
     # 使用druid数据源

+ 7 - 0
mec-biz/src/main/java/com/ym/mec/biz/service/SysMusicCompareRecordService.java

@@ -24,6 +24,13 @@ public interface SysMusicCompareRecordService extends BaseService<Long, SysMusic
     void saveMusicCompareData(String phone, SoundCompareHelper soundCompareInfo);
 
     /**
+     * @describe 保存用户评测记录
+     * @param sysMusicCompareRecord
+     * @return void
+     */
+    void saveMusicCompareData(SysMusicCompareRecord sysMusicCompareRecord);
+
+    /**
      * @describe 用户最后一次评测数据
      * @author Joburgess
      * @date 2021/8/23 0023

+ 13 - 0
mec-biz/src/main/java/com/ym/mec/biz/service/impl/SysMusicCompareRecordServiceImpl.java

@@ -29,6 +29,7 @@ import com.ym.mec.util.date.DateUtil;
 import org.apache.commons.lang3.StringUtils;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
 import org.springframework.util.CollectionUtils;
 
 import java.math.BigDecimal;
@@ -131,6 +132,18 @@ public class SysMusicCompareRecordServiceImpl extends BaseServiceImpl<Long, SysM
 	}
 
 	@Override
+	@Transactional
+	public void saveMusicCompareData(SysMusicCompareRecord sysMusicCompareRecord) {
+		
+		Integer userId = sysMusicCompareRecord.getUserId();
+		
+		sysMusicCompareRecordDao.update(sysMusicCompareRecord);
+		studentDao.addStudentCloudStudySequenceDays(userId);
+		sysMusicCompareWeekDataService
+				.updateUserWeekTrainData(userId, LocalDate.now().with(DateUtil.weekFields.dayOfWeek(), DayOfWeek.MONDAY.getValue()));
+	}
+
+	@Override
 	public Object getLastEvaluationMusicalNotesPlayStats(Integer userId, Long recordId) {
 		SysMusicCompareRecord userLastEvaluationData;
 		if(Objects.nonNull(recordId)){

+ 2 - 2
mec-thirdparty/src/main/java/com/ym/mec/thirdparty/eseal/provider/TsignPlugin.java

@@ -58,12 +58,12 @@ public class TsignPlugin implements ESealPlugin, InitializingBean, DisposableBea
         projectconfig.setItsmApiUrl(apisUrl);
         Result result = ServiceClientManager.registClient(projectconfig, null, null);
         if (result.getErrCode() != 0) {
-            throw new ThirdpartyException("e签宝客户端注册失败:{}", result.getMsg());
+            //throw new ThirdpartyException("e签宝客户端注册失败:{}", result.getMsg());
         }
 
         serviceClient = ServiceClientManager.get(projectId);
         if (serviceClient == null) {
-            throw new ThirdpartyException("获取e签宝客户端失败");
+            //throw new ThirdpartyException("获取e签宝客户端失败");
         }
     }
 

+ 8 - 0
mec-thirdparty/src/main/java/com/ym/mec/thirdparty/storage/StoragePlugin.java

@@ -16,6 +16,14 @@ public interface StoragePlugin {
 	String uploadFile(String folderName, File file);
 
 	/**
+	 * 上传文件
+	 * @param folderName 文件夹
+	 * @param file 需要上传的文件
+	 * @return 返回文件路径
+	 */
+	String asyncUploadFile(String folderName, File file);
+
+	/**
 	 * 下载文件
 	 * @param folderName 文件夹
 	 * @param fileName 文件名称

+ 5 - 0
mec-thirdparty/src/main/java/com/ym/mec/thirdparty/storage/StoragePluginContext.java

@@ -24,6 +24,11 @@ public class StoragePluginContext {
 		StoragePlugin StoragePlugin = getStoragePlugin(storagePluginName);
 		return StoragePlugin.uploadFile(folderName, file);
 	}
+	
+	public String asyncUploadFile(String storagePluginName, String folderName, File file){
+		StoragePlugin StoragePlugin = getStoragePlugin(storagePluginName);
+		return StoragePlugin.asyncUploadFile(folderName, file);
+	}
 
 	private StoragePlugin getStoragePlugin(String storagePluginName) {
 		StoragePlugin storagePlugin = mapper.get(storagePluginName);

+ 24 - 0
mec-thirdparty/src/main/java/com/ym/mec/thirdparty/storage/provider/AliyunOssStoragePlugin.java

@@ -80,6 +80,30 @@ public class AliyunOssStoragePlugin implements StoragePlugin, InitializingBean,
 	}
 
 	@Override
+	public String asyncUploadFile(String folderName, File file) {
+		if (!file.exists()) {
+			throw new ThirdpartyException("需要上传的文件[{}]不存在", file.getAbsolutePath());
+		}
+
+		if (folderName.endsWith("/")) {
+			folderName = folderName.substring(0, folderName.length() - 1);
+		}
+		
+		final String dir = folderName;
+		
+		Thread thread = new Thread(new Runnable() {
+			
+			@Override
+			public void run() {
+				ossClient.putObject(bucketName, dir + "/" + file.getName(), file);
+			}
+		});
+		thread.start();
+
+		return "https://" + bucketName + "." + endpoint + "/" + folderName + "/" + file.getName();
+	}
+
+	@Override
 	public byte[] getFile(String folderName, String fileName) throws IOException {
 		OSSObject ossObject = ossClient.getObject(bucketName, folderName + "/" + fileName);
 		try {

+ 27 - 0
mec-thirdparty/src/main/java/com/ym/mec/thirdparty/storage/provider/KS3StoragePlugin.java

@@ -93,6 +93,33 @@ public class KS3StoragePlugin implements StoragePlugin, InitializingBean, Dispos
 	}
 
 	@Override
+	public String asyncUploadFile(String folderName, File file) {
+		if (!file.exists()) {
+			throw new ThirdpartyException("需要上传的文件[{}]不存在", file.getAbsolutePath());
+		}
+
+		if (folderName.endsWith("/")) {
+			folderName = folderName.substring(0, folderName.length() - 1);
+		}
+
+		PutObjectRequest request = new PutObjectRequest(bucketName, folderName + "/" + file.getName(), file);
+
+		// 上传一个公开文件
+		request.setCannedAcl(CannedAccessControlList.PublicRead);
+		
+		Thread thread = new Thread(new Runnable() {
+			
+			@Override
+			public void run() {
+				client.putObject(request);
+			}
+		});
+		thread.start();
+
+		return "https://" + bucketName + "." + endpoint + "/" + folderName + "/" + file.getName();
+	}
+
+	@Override
 	public byte[] getFile(String folderName, String fileName) throws IOException {
 		GetObjectRequest request = new GetObjectRequest(bucketName, folderName + "/" + fileName);
 		GetObjectResult result = client.getObject(request);