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