Browse Source

feat:小节评分

Joburgess 4 years ago
parent
commit
308d140a83
1 changed files with 179 additions and 24 deletions
  1. 179 24
      mec-teacher/src/main/java/com/ym/mec/teacher/handler/WebSocketHandler.java

+ 179 - 24
mec-teacher/src/main/java/com/ym/mec/teacher/handler/WebSocketHandler.java

@@ -9,19 +9,24 @@ import com.ym.mec.biz.dal.dto.MusicPitchDetailDto;
 import com.ym.mec.biz.dal.dto.WavHeader;
 import com.ym.mec.biz.dal.dto.WebSocketInfo;
 import com.ym.mec.biz.service.SoundSocketService;
-import org.apache.commons.lang3.RandomUtils;
+import com.ym.mec.common.constant.CommonConstants;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.stereotype.Service;
+import org.springframework.util.CollectionUtils;
 import org.springframework.web.socket.*;
 import org.springframework.web.socket.handler.AbstractWebSocketHandler;
 
 import javax.sound.sampled.AudioFormat;
-import java.io.*;
+import java.io.File;
+import java.io.IOException;
+import java.io.RandomAccessFile;
+import java.math.BigDecimal;
 import java.time.LocalDateTime;
 import java.time.format.DateTimeFormatter;
 import java.util.*;
 import java.util.concurrent.ConcurrentHashMap;
+import java.util.stream.Collectors;
 
 /**
  * @Author Joburgess
@@ -34,12 +39,18 @@ public class WebSocketHandler extends AbstractWebSocketHandler {
 
     public static final Map<String, WebSocketSession> WS_CLIENTS = new ConcurrentHashMap<>();
 
+    private BigDecimal oneHundred = new BigDecimal(100);
+
     AudioFormat audioFormat = new AudioFormat(44100, 16, 1, true, false);
     PitchProcessor.PitchEstimationAlgorithm algo = PitchProcessor.PitchEstimationAlgorithm.FFT_YIN;
 
     private Map<String, RandomAccessFile> userRandomAccessFileMap = new ConcurrentHashMap<>();
-    private Map<String, List<MusicPitchDetailDto>> userXmlInfoMap = new ConcurrentHashMap<>();
+    private Map<String, Map<Integer, List<MusicPitchDetailDto>>> userMeasureXmlInfoMap = new ConcurrentHashMap<>();
+    private Map<String, Map<Integer, Integer>> userMeasureEndTimeMap = new ConcurrentHashMap<>();
     private Map<String, Set<Integer>> userMeasureMap = new ConcurrentHashMap<>();
+    private Map<String, Double> userMeasureStartTimeMap = new ConcurrentHashMap<>();
+    private Map<String, List<MusicPitchDetailDto>> userLastMeasurePithInfoMap = new ConcurrentHashMap<>();
+    private Map<String, Map<String, BigDecimal>> userTotalScoreMap = new ConcurrentHashMap<>();
 
     public WebSocketHandler() {
         super();
@@ -72,14 +83,27 @@ public class WebSocketHandler extends AbstractWebSocketHandler {
         }
         switch (commond){
             case SoundSocketService.MUSIC_XML:
-                userXmlInfoMap.put(phone, JSON.parseArray(bodyObject.getString("musicXmlInfos"), MusicPitchDetailDto.class));
+                List<MusicPitchDetailDto> musicXmlInfos = JSON.parseArray(bodyObject.getString("musicXmlInfos"), MusicPitchDetailDto.class);
+                userLastMeasurePithInfoMap.put(phone, new ArrayList<>());
+                userTotalScoreMap.put(phone, new HashMap<>());
+                userMeasureXmlInfoMap.put(phone, musicXmlInfos.stream().collect(Collectors.groupingBy(MusicPitchDetailDto::getMeasureIndex)));
+                userMeasureEndTimeMap.put(phone, new HashMap<>());
+                for (Map.Entry<Integer, List<MusicPitchDetailDto>> userMeasureXmlInfoEntry : userMeasureXmlInfoMap.get(phone).entrySet()) {
+                    MusicPitchDetailDto musicPitchDetailDto = userMeasureXmlInfoEntry.getValue().stream().max(Comparator.comparing(MusicPitchDetailDto::getTimeStamp)).get();
+                    userMeasureEndTimeMap.get(phone).put(userMeasureXmlInfoEntry.getKey(), musicPitchDetailDto.getTimeStamp()+musicPitchDetailDto.getDuration());
+                }
                 break;
             case SoundSocketService.RECORD_START:
                 File file = new File("E:\\Temp\\record"+phone +"-"+ LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss")) +".wav");
                 userRandomAccessFileMap.put(phone, new RandomAccessFile(file, "rw"));
                 userMeasureMap.put(phone, new HashSet<>());
+                userMeasureStartTimeMap.put(phone, (double) 0);
                 break;
             case SoundSocketService.RECORD_END:
+                if(!CollectionUtils.isEmpty(userMeasureEndTimeMap.get(phone))){
+                    measureCompare(phone, userMeasureEndTimeMap.get(phone).keySet().stream().max(Integer::compareTo).get());
+                }
+                calTotalScore(phone);
                 createHeader(phone);
                 break;
             default:
@@ -94,34 +118,26 @@ public class WebSocketHandler extends AbstractWebSocketHandler {
         if(userRandomAccessFileMap.containsKey(phone)){
             userRandomAccessFileMap.get(phone).write(message.getPayload().array());
         }
+        List<MusicPitchDetailDto> recordInfo = new ArrayList<>();
         AudioDispatcher dispatcher = AudioDispatcherFactory.fromByteArray(message.getPayload().array(), audioFormat, 256, 128);
         dispatcher.addAudioProcessor(new PitchProcessor(algo, 44100, 256, (pitchDetectionResult, audioEvent) -> {
-            double timeStamp = audioEvent.getTimeStamp();
+            int timeStamp = (int) (userMeasureStartTimeMap.get(phone) + audioEvent.getTimeStamp()*1000);
             float pitch = pitchDetectionResult.getPitch();
-//            LOGGER.info("时间:{},频率:{}", timeStamp, pitch);
+            LOGGER.info("时间:{},频率:{}", timeStamp, pitch);
+            recordInfo.add(new MusicPitchDetailDto(timeStamp, pitch));
         }));
         dispatcher.run();
         double recordTime = userRandomAccessFileMap.get(phone).length()/(audioFormat.getFrameSize()*audioFormat.getFrameRate())*1000;
-//        LOGGER.info("时长:{}", recordTime);
-        for (MusicPitchDetailDto musicPitchDetailDto : userXmlInfoMap.get(phone)) {
-            if(!userMeasureMap.get(phone).contains(musicPitchDetailDto.getMeasureIndex())&&recordTime>musicPitchDetailDto.getTimeStamp()+musicPitchDetailDto.getDuration()){
-                WebSocketInfo webSocketInfo = new WebSocketInfo();
-                HashMap<String, String> header = new HashMap<>();
-                header.put("commond", "measureScore");
-                webSocketInfo.setHeader(header);
-                Map<String, Double> result = new HashMap<>();
-                result.put("score", RandomUtils.nextDouble(0,100));
-                result.put("intonation", RandomUtils.nextDouble(0,100));
-                result.put("cadence", RandomUtils.nextDouble(0,100));
-                result.put("integrity", RandomUtils.nextDouble(0,100));
-                result.put("integrity", RandomUtils.nextDouble(0,100));
-                result.put("measureIndex", (double) musicPitchDetailDto.getMeasureIndex());
-                webSocketInfo.setBody(result);
-                WS_CLIENTS.get(phone).sendMessage(new TextMessage(JSON.toJSONString(webSocketInfo)));
-                userMeasureMap.get(phone).add(musicPitchDetailDto.getMeasureIndex());
-                LOGGER.info("推送评分结果:{}", phone);
+        userMeasureStartTimeMap.put(phone, recordTime);
+        userLastMeasurePithInfoMap.get(phone).addAll(recordInfo);
+        for (Map.Entry<Integer, Integer> userMeasureEndTimeMapEntry : userMeasureEndTimeMap.get(phone).entrySet()) {
+            if(recordTime>userMeasureEndTimeMapEntry.getValue()){
+                measureCompare(phone, userMeasureEndTimeMapEntry.getKey());
+                userMeasureEndTimeMap.get(phone).remove(userMeasureEndTimeMapEntry.getKey());
+                break;
             }
         }
+
     }
 
     @Override
@@ -166,4 +182,143 @@ public class WebSocketHandler extends AbstractWebSocketHandler {
         randomAccessFile.close();
         userRandomAccessFileMap.remove(phone);
     }
+
+    private void measureCompare(String phone, int measureIndex) throws IOException {
+        //总分
+        BigDecimal score = BigDecimal.ZERO;
+        //相似度
+        BigDecimal intonation = BigDecimal.ZERO;
+        //节奏
+        BigDecimal cadence = BigDecimal.ZERO;
+        //完整度
+        BigDecimal integrity = BigDecimal.ZERO;
+
+        try {
+            //最低有效频率
+            float minValidFrequency = 20;
+
+            //有效音频数量
+            float validNum = 0;
+            //音频有效阈值
+            float validDuty = 0.7f;
+
+            //音准匹配数量
+            float intonationNum = 0;
+            //音准匹配误差范围
+            float intonationErrRange = 10;
+            //音准有效阈值
+            float intonationValidDuty = 0.8f;
+
+            //节奏匹配数量
+            float cadenceNum = 0;
+            //节奏匹配误差范围
+            float cadenceErrRange = 100;
+            //节奏有效阈值
+            float cadenceValidDuty = 0.7f;
+
+            int totalCompareNum = userMeasureXmlInfoMap.get(phone).get(measureIndex).size();
+
+            for (MusicPitchDetailDto musicXmlInfo : userMeasureXmlInfoMap.get(phone).get(measureIndex)) {
+                int startTimeStamp = musicXmlInfo.getTimeStamp();
+                int endTimeStamp = musicXmlInfo.getTimeStamp()+musicXmlInfo.getDuration();
+
+                //时间范围内有效音准数量
+                float recordValidIntonationNum = 0;
+                //时间范围内有效节奏数量
+                float cadenceValidNum = 0;
+                //时间范围内有效音频数量
+                float recordValidNum = 0;
+                //时间范围内匹配次数
+                float compareNum = 0;
+                for (MusicPitchDetailDto recordInfo : userLastMeasurePithInfoMap.get(phone)) {
+                    //如果在时间范围之外直接跳过
+                    if(recordInfo.getTimeStamp()<startTimeStamp||recordInfo.getTimeStamp()>endTimeStamp){
+                        continue;
+                    }
+                    compareNum++;
+                    //如果在最低有效频率以下则跳过
+                    if(recordInfo.getFrequency()<minValidFrequency){
+                        continue;
+                    }
+                    recordValidNum++;
+                    //如果频率差值在节奏误差范围内
+                    if(recordInfo.getFrequency()-musicXmlInfo.getFrequency()<=cadenceErrRange){
+                        cadenceValidNum++;
+                    }
+                    //如果频率差值在音准误差范围内
+                    if(recordInfo.getFrequency()-musicXmlInfo.getFrequency()<=intonationErrRange){
+                        recordValidIntonationNum++;
+                    }
+                }
+                //有效音频占比
+                float recordValidDuty = recordValidNum/compareNum;
+                if(recordValidDuty<validDuty){
+                    continue;
+                }
+                validNum++;
+                //有效音高占比
+                float intonationDuty = recordValidIntonationNum/compareNum;
+                //有效节奏占比
+                float cadenceDuty = cadenceValidNum/compareNum;
+                if(intonationDuty>=intonationValidDuty){
+                    intonationNum++;
+                }
+                if(cadenceDuty>=cadenceValidDuty){
+                    cadenceNum++;
+                }
+            }
+
+            intonation = new BigDecimal(intonationNum).divide(new BigDecimal(totalCompareNum), CommonConstants.DECIMAL_PLACE, BigDecimal.ROUND_DOWN).multiply(oneHundred).setScale(0, BigDecimal.ROUND_HALF_UP);
+            cadence = new BigDecimal(cadenceNum).divide(new BigDecimal(totalCompareNum), CommonConstants.DECIMAL_PLACE, BigDecimal.ROUND_DOWN).multiply(oneHundred).setScale(0, BigDecimal.ROUND_HALF_UP);
+            integrity = new BigDecimal(validNum).divide(new BigDecimal(totalCompareNum), CommonConstants.DECIMAL_PLACE, BigDecimal.ROUND_DOWN).multiply(oneHundred).setScale(0, BigDecimal.ROUND_HALF_UP);
+        } catch (ArithmeticException e){
+            LOGGER.info("无musicXml信息");
+        }
+
+        if(userTotalScoreMap.get(phone).containsKey("intonation")){
+            userTotalScoreMap.get(phone).put("intonation", intonation.add(userTotalScoreMap.get(phone).get("intonation")));
+        }else{
+            userTotalScoreMap.get(phone).put("intonation", intonation);
+        }
+
+        if(userTotalScoreMap.get(phone).containsKey("cadence")){
+            userTotalScoreMap.get(phone).put("cadence", intonation.add(userTotalScoreMap.get(phone).get("cadence")));
+        }else{
+            userTotalScoreMap.get(phone).put("cadence", cadence);
+        }
+
+        if(userTotalScoreMap.get(phone).containsKey("integrity")){
+            userTotalScoreMap.get(phone).put("integrity", intonation.add(userTotalScoreMap.get(phone).get("integrity")));
+        }else{
+            userTotalScoreMap.get(phone).put("integrity", integrity);
+        }
+        WS_CLIENTS.get(phone).sendMessage(new TextMessage(JSON.toJSONString(createPushInfo("measureScore", measureIndex, score, intonation, cadence, integrity))));
+//                LOGGER.info("推送评分结果:{}", phone);
+    }
+
+    private void calTotalScore(String phone) throws IOException {
+        int totalCompareNum = userMeasureXmlInfoMap.get(phone).keySet().size();
+        BigDecimal intonation = userTotalScoreMap.get(phone).get("intonation").divide(new BigDecimal(totalCompareNum), 0, BigDecimal.ROUND_DOWN);
+        BigDecimal cadence = userTotalScoreMap.get(phone).get("cadence").divide(new BigDecimal(totalCompareNum), 0, BigDecimal.ROUND_DOWN);
+        BigDecimal integrity = userTotalScoreMap.get(phone).get("integrity").divide(new BigDecimal(totalCompareNum), 0, BigDecimal.ROUND_DOWN);
+        BigDecimal score = intonation.multiply(new BigDecimal(0.9)).add(cadence.multiply(new BigDecimal(0.9))).add(integrity.multiply(new BigDecimal(0.1))).setScale(0, BigDecimal.ROUND_HALF_UP);
+
+        WS_CLIENTS.get(phone).sendMessage(new TextMessage(JSON.toJSONString(createPushInfo("overall", -1, score, intonation, cadence, integrity))));
+    }
+
+    private WebSocketInfo createPushInfo(String commond, Integer measureIndex,
+                                         BigDecimal score, BigDecimal intonation, BigDecimal cadence, BigDecimal integrity){
+        WebSocketInfo webSocketInfo = new WebSocketInfo();
+        HashMap<String, String> header = new HashMap<>();
+        header.put("commond", commond);
+        webSocketInfo.setHeader(header);
+        Map<String, Object> result = new HashMap<>();
+        result.put("score", score);
+        result.put("intonation", intonation);
+        result.put("cadence", cadence);
+        result.put("integrity", integrity);
+        result.put("measureIndex", measureIndex);
+        webSocketInfo.setBody(result);
+        return webSocketInfo;
+    }
 }