|
@@ -6,7 +6,6 @@ import be.tarsos.dsp.pitch.PitchProcessor;
|
|
|
import be.tarsos.dsp.util.PitchConverter;
|
|
|
import com.alibaba.fastjson.JSON;
|
|
|
import com.alibaba.fastjson.JSONObject;
|
|
|
-import com.alibaba.fastjson.serializer.SerializerFeature;
|
|
|
import com.ym.mec.biz.dal.config.SoundCompareConfig;
|
|
|
import com.ym.mec.biz.dal.dao.SysMusicScoreAccompanimentDao;
|
|
|
import com.ym.mec.biz.dal.dto.*;
|
|
@@ -32,6 +31,7 @@ import org.springframework.web.socket.WebSocketSession;
|
|
|
|
|
|
import javax.sound.sampled.UnsupportedAudioFileException;
|
|
|
import java.io.File;
|
|
|
+import java.io.FileNotFoundException;
|
|
|
import java.io.IOException;
|
|
|
import java.io.RandomAccessFile;
|
|
|
import java.math.BigDecimal;
|
|
@@ -50,7 +50,7 @@ import static com.ym.mec.biz.service.SoundSocketService.VIDEO_UPDATE;
|
|
|
@Service
|
|
|
public class SoundCompareHandler implements WebSocketEventHandler {
|
|
|
|
|
|
- public static final Logger LOGGER = LoggerFactory.getLogger(SoundCompareHandler.class);
|
|
|
+ private final Logger LOGGER = LoggerFactory.getLogger(SoundCompareHandler.class);
|
|
|
|
|
|
private BigDecimal oneHundred = new BigDecimal(100);
|
|
|
|
|
@@ -99,9 +99,6 @@ public class SoundCompareHandler implements WebSocketEventHandler {
|
|
|
userSoundInfoMap.get(phone).setMusicXmlInfos(musicXmlInfos);
|
|
|
musicXmlInfos = musicXmlInfos.stream().filter(m->!m.getDontEvaluating()).collect(Collectors.toList());
|
|
|
userSoundInfoMap.get(phone).setMusicScoreId(bodyObject.getInteger("id"));
|
|
|
- if(bodyObject.containsKey("beatLength")){
|
|
|
- userSoundInfoMap.get(phone).setFirstMeasureStartBytes((long) (bodyObject.getLong("beatLength")/1000f*(soundCompareConfig.audioFormat.getFrameSize()*soundCompareConfig.audioFormat.getFrameRate())));
|
|
|
- }
|
|
|
if(bodyObject.containsKey("platform")){
|
|
|
userSoundInfoMap.get(phone).setDeviceType(DeviceTypeEnum.valueOf(bodyObject.getString("platform")));
|
|
|
}
|
|
@@ -151,9 +148,8 @@ public class SoundCompareHandler implements WebSocketEventHandler {
|
|
|
File file = new File(tmpDir+phone + "_"+ userSoundInfoMap.get(phone).getMusicScoreId() +"_"+ LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss")) +".wav");
|
|
|
userSoundInfoMap.get(phone).setFile(file);
|
|
|
userSoundInfoMap.get(phone).setAccessFile(new RandomAccessFile(file, "rw"));
|
|
|
- userSoundInfoMap.get(phone).getAccessFile().seek(44);
|
|
|
userSoundInfoMap.get(phone).setRecordFilePath(file.getAbsolutePath());
|
|
|
- } catch (IOException e) {
|
|
|
+ } catch (FileNotFoundException e) {
|
|
|
throw new BizException("文件创建失败:", e);
|
|
|
}
|
|
|
break;
|
|
@@ -182,10 +178,10 @@ public class SoundCompareHandler implements WebSocketEventHandler {
|
|
|
createHeader(phone, false);
|
|
|
break;
|
|
|
case SoundSocketService.PROXY_MESSAGE:
|
|
|
-// if(DeviceTypeEnum.IOS.equals(userSoundInfoMap.get(phone).getDeviceType())&&bodyObject.containsKey(SoundSocketService.OFFSET_TIME)){
|
|
|
-// int offsetTime = bodyObject.getIntValue(SoundSocketService.OFFSET_TIME);
|
|
|
-// calOffsetTime(phone, offsetTime);
|
|
|
-// }
|
|
|
+ if(DeviceTypeEnum.IOS.equals(userSoundInfoMap.get(phone).getDeviceType())&&bodyObject.containsKey(SoundSocketService.OFFSET_TIME)){
|
|
|
+ int offsetTime = bodyObject.getIntValue(SoundSocketService.OFFSET_TIME);
|
|
|
+ calOffsetTime(phone, offsetTime);
|
|
|
+ }
|
|
|
break;
|
|
|
case VIDEO_UPDATE:
|
|
|
SysMusicCompareRecord update = null;
|
|
@@ -211,27 +207,24 @@ public class SoundCompareHandler implements WebSocketEventHandler {
|
|
|
if(!userSoundInfoMap.containsKey(phone)){
|
|
|
return;
|
|
|
}
|
|
|
- userSoundInfoMap.get(phone).setRecordBytes(userSoundInfoMap.get(phone).getRecordBytes()+message.getPayloadLength());
|
|
|
- if(userSoundInfoMap.get(phone).getRecordBytes()<userSoundInfoMap.get(phone).getFirstMeasureStartBytes()){
|
|
|
- return;
|
|
|
- }
|
|
|
try {
|
|
|
if(Objects.nonNull(userSoundInfoMap.get(phone).getAccessFile())){
|
|
|
userSoundInfoMap.get(phone).getAccessFile().write(message.getPayload().array());
|
|
|
- }else{
|
|
|
- return;
|
|
|
}
|
|
|
|
|
|
AudioDispatcher dispatcher = AudioDispatcherFactory.fromByteArray(message.getPayload().array(), soundCompareConfig.audioFormat, soundCompareConfig.simpleSize, soundCompareConfig.overlap);
|
|
|
+
|
|
|
dispatcher.addAudioProcessor(userSoundInfoMap.get(phone).silenceDetector);
|
|
|
dispatcher.addAudioProcessor(new PitchProcessor(soundCompareConfig.algo, soundCompareConfig.simpleRate, soundCompareConfig.simpleSize, userSoundInfoMap.get(phone)));
|
|
|
dispatcher.run();
|
|
|
+ if(Objects.isNull(userSoundInfoMap.get(phone).getAccessFile())){
|
|
|
+ return;
|
|
|
+ }
|
|
|
|
|
|
double recordTime = userSoundInfoMap.get(phone).getAccessFile().length()/(soundCompareConfig.audioFormat.getFrameSize()*soundCompareConfig.audioFormat.getFrameRate())*1000;
|
|
|
userSoundInfoMap.get(phone).setMeasureStartTime(recordTime);
|
|
|
-
|
|
|
for (Map.Entry<Integer, MusicPitchDetailDto> userMeasureEndTimeMapEntry : userSoundInfoMap.get(phone).getMeasureEndTime().entrySet()) {
|
|
|
- if(recordTime>(userMeasureEndTimeMapEntry.getValue().getEndTimeStamp()+100)){
|
|
|
+ if(recordTime>(userMeasureEndTimeMapEntry.getValue().getEndTimeStamp())){
|
|
|
if(userMeasureEndTimeMapEntry.getValue().getDontEvaluating()){
|
|
|
continue;
|
|
|
}else{
|
|
@@ -268,7 +261,6 @@ public class SoundCompareHandler implements WebSocketEventHandler {
|
|
|
musicPitchDetailDtoEntry.getValue().setTimeStamp(musicPitchDetailDtoEntry.getValue().getTimeStamp() + offsetTime);
|
|
|
musicPitchDetailDtoEntry.getValue().setEndTimeStamp(musicPitchDetailDtoEntry.getValue().getEndTimeStamp() + offsetTime);
|
|
|
}
|
|
|
- userSoundInfoMap.get(phone).getMusicXmlInfos().forEach(e->e.setTimeStamp(e.getTimeStamp()+offsetTime));
|
|
|
}
|
|
|
|
|
|
/**
|
|
@@ -355,32 +347,111 @@ public class SoundCompareHandler implements WebSocketEventHandler {
|
|
|
for (int i = 0; i < userSoundInfoMap.get(phone).getMeasureXmlInfoMap().get(measureIndex).size(); i++) {
|
|
|
MusicPitchDetailDto musicXmlInfo = userSoundInfoMap.get(phone).getMeasureXmlInfoMap().get(measureIndex).get(i);
|
|
|
|
|
|
- int startTimeStamp = musicXmlInfo.getTimeStamp();
|
|
|
- int endTimeStamp = musicXmlInfo.getTimeStamp() + musicXmlInfo.getDuration();
|
|
|
+ int ot5 = (int) (musicXmlInfo.getDuration()*0.1);
|
|
|
+ int startTimeStamp = musicXmlInfo.getTimeStamp() + userSoundInfoMap.get(phone).getOffsetTime() + ot5;
|
|
|
+ int endTimeStamp = musicXmlInfo.getTimeStamp() + userSoundInfoMap.get(phone).getOffsetTime() + musicXmlInfo.getDuration() - ot5;
|
|
|
+
|
|
|
+ //时间范围内有效节奏数量
|
|
|
+ float cadenceValidNum = 0;
|
|
|
+ //时间范围内有效音频数量
|
|
|
+ float integrityValidNum = 0;
|
|
|
+ //时间范围内匹配次数
|
|
|
+ float compareNum = 0;
|
|
|
|
|
|
- int ot5 = (int) (musicXmlInfo.getDuration()*0.22<70?70:musicXmlInfo.getDuration()*0.22);
|
|
|
- int rightTimeRange = ot5>200?200:ot5;
|
|
|
+ List<MusicPitchDetailDto> measureSoundPitchInfos = new ArrayList<>();
|
|
|
|
|
|
- List<MusicPitchDetailDto> recordPitchs = userSoundInfoMap.get(phone).getRecordMeasurePitchInfos().stream().filter(m -> m.getTimeStamp()>=startTimeStamp-rightTimeRange && m.getTimeStamp() < endTimeStamp-rightTimeRange).collect(Collectors.toList());
|
|
|
+ for (int j = 0; j < userSoundInfoMap.get(phone).getRecordMeasurePithInfo().size(); j++) {
|
|
|
+ MusicPitchDetailDto recordInfo = userSoundInfoMap.get(phone).getRecordMeasurePithInfo().get(j);
|
|
|
+ //如果在时间范围之外直接跳过
|
|
|
+ if(recordInfo.getTimeStamp()<startTimeStamp||recordInfo.getTimeStamp()>endTimeStamp){
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ measureSoundPitchInfos.add(recordInfo);
|
|
|
+ compareNum++;
|
|
|
+ //如果在最低有效频率以下则跳过
|
|
|
+ if(recordInfo.getFrequency()<soundCompareConfig.validFrequency&&musicXmlInfo.getFrequency()!=-1){
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ cadenceValidNum++;
|
|
|
+ //如果频率差值在节奏误差范围内
|
|
|
+// if(Math.abs(recordInfo.getFrequency()-musicXmlInfo.getFrequency())<=soundCompareConfig.integrityFrequencyRange){
|
|
|
+// integrityValidNum++;
|
|
|
+// }
|
|
|
+ }
|
|
|
+
|
|
|
+ //非正常频率次数
|
|
|
+ int errPitchNum = 0;
|
|
|
+ //分贝变化次数
|
|
|
+ int decibelChangeNum = 0;
|
|
|
+
|
|
|
+ if(CollectionUtils.isEmpty(measureSoundPitchInfos)){
|
|
|
+ userSoundInfoMap.get(phone).getMusicalNotePitchMap().put(musicXmlInfo.getMusicalNotesIndex(), (float) 0);
|
|
|
+ }else{
|
|
|
+ Map<Integer, Long> collect = measureSoundPitchInfos.stream().map(pitch -> (int)pitch.getFrequency()).collect(Collectors.groupingBy(Integer::intValue, Collectors.counting()));
|
|
|
+ //出现次数最多的频率
|
|
|
+ Integer pitch = collect.entrySet().stream().max(Comparator.comparing(e -> e.getValue())).get().getKey();
|
|
|
+ //当前频率
|
|
|
+ double cf = -1;
|
|
|
+ //频率持续数量
|
|
|
+ int fnum = 0;
|
|
|
+ //是否演奏中
|
|
|
+ boolean ing = false;
|
|
|
+ //当前分贝
|
|
|
+ double cd = 0;
|
|
|
+ //分贝变化方向,-1变小,1变大
|
|
|
+ int dcd = -1;
|
|
|
+ //分贝持续数量
|
|
|
+ int dnum = 0;
|
|
|
+ for (MusicPitchDetailDto musicalNotesPitch : measureSoundPitchInfos) {
|
|
|
+ //计算频率断层次数
|
|
|
+ if (Math.abs(musicalNotesPitch.getFrequency() - cf) > 20){
|
|
|
+ fnum ++;
|
|
|
+ }
|
|
|
+ if (fnum>=5){
|
|
|
+ cf = musicalNotesPitch.getFrequency();
|
|
|
+ fnum = 0;
|
|
|
+ if (cf != -1){
|
|
|
+ errPitchNum ++;
|
|
|
+ ing = true;
|
|
|
+ cd = musicalNotesPitch.getDecibel();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ //计算声音大小断层册数
|
|
|
+ if(ing && Math.abs(musicalNotesPitch.getDecibel() - cd) > 10){
|
|
|
+ dnum ++;
|
|
|
+ }
|
|
|
+ if (dnum > 2){
|
|
|
+ int tdcd = cd > musicalNotesPitch.getDecibel() ? -1 : 1;
|
|
|
+ cd = musicalNotesPitch.getDecibel();
|
|
|
+ dnum = 0;
|
|
|
+ if (tdcd != dcd) {
|
|
|
+ decibelChangeNum++;
|
|
|
+ }
|
|
|
+ dcd = tdcd;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ userSoundInfoMap.get(phone).getMusicalNotePitchMap().put(musicXmlInfo.getMusicalNotesIndex(), (float) pitch);
|
|
|
+ }
|
|
|
|
|
|
boolean cadenceRight = false;
|
|
|
boolean intonationRight = false;
|
|
|
boolean integrityRight = false;
|
|
|
|
|
|
- float integrityDuty = 0;
|
|
|
- if(recordPitchs.size()>0){
|
|
|
- integrityDuty = recordPitchs.get(0).getDuration()/(float)musicXmlInfo.getDuration();
|
|
|
+ //有效节奏占比
|
|
|
+ float cadenceDuty = cadenceValidNum/compareNum;
|
|
|
+ //如果频率出现断层或这个音量出现断层,则当前音符节奏无效
|
|
|
+ if(errPitchNum>=2 || decibelChangeNum>1){
|
|
|
+ cadenceDuty = 0;
|
|
|
}
|
|
|
- integrityDuty = scoreMapping(integrityDuty, userSoundInfoMap.get(phone).getHeardLevel().getIntegrityRange(), 1);
|
|
|
//节奏
|
|
|
- if(recordPitchs.size()==1){
|
|
|
+ if(cadenceDuty>=userSoundInfoMap.get(phone).getHeardLevel().getCadenceRange()){
|
|
|
cadenceNum++;
|
|
|
cadenceRight = true;
|
|
|
}
|
|
|
//音准、完成度
|
|
|
- if (cadenceRight){
|
|
|
+ if (cadenceRight && !CollectionUtils.isEmpty(measureSoundPitchInfos)){
|
|
|
//音准
|
|
|
- float avgPitch = recordPitchs.get(0).getFrequency();
|
|
|
+ Double avgPitch = measureSoundPitchInfos.stream().filter(pitch -> Math.abs((pitch.getFrequency()-musicXmlInfo.getFrequency()))<15).collect(Collectors.averagingDouble(pitch -> pitch.getFrequency()));
|
|
|
//音分
|
|
|
double recordCents = 0;
|
|
|
if (avgPitch > 0){
|
|
@@ -390,40 +461,29 @@ public class SoundCompareHandler implements WebSocketEventHandler {
|
|
|
if(musicXmlInfo.getFrequency()>0){
|
|
|
cents = PitchConverter.hertzToAbsoluteCent(musicXmlInfo.getFrequency());
|
|
|
}
|
|
|
- double score = 100 - Math.round(Math.abs(cents - recordCents)) + 3;
|
|
|
+ double score = 100 - Math.round(Math.abs(cents - recordCents)) + userSoundInfoMap.get(phone).getHeardLevel().getIntonationCentsRange();
|
|
|
if (score < 0){
|
|
|
score = 0;
|
|
|
}else if(score > 100){
|
|
|
score = 100;
|
|
|
}
|
|
|
-
|
|
|
- score = Math.pow(score/100f, userSoundInfoMap.get(phone).getHeardLevel().getIntonationCentsRange())*100;
|
|
|
-
|
|
|
- if(Objects.nonNull(userSoundInfoMap.get(phone).getSubjectId())&&userSoundInfoMap.get(phone).getSubjectId()==23){
|
|
|
- score = 100;
|
|
|
- integrityDuty = 1;
|
|
|
- }
|
|
|
-
|
|
|
intonationScore += score;
|
|
|
- musicXmlInfo.setAvgFrequency(avgPitch);
|
|
|
+ musicXmlInfo.setAvgFrequency(avgPitch.floatValue());
|
|
|
intonationRight = score>70;
|
|
|
|
|
|
- integrityScore += integrityDuty;
|
|
|
- integrityRight = integrityDuty>0.7;
|
|
|
+ if(score>0){
|
|
|
+ integrityValidNum = measureSoundPitchInfos.stream().filter(pitch -> Math.abs((pitch.getFrequency()-musicXmlInfo.getFrequency()))<15).count();
|
|
|
+ }else{
|
|
|
+ integrityValidNum = 0;
|
|
|
+ }
|
|
|
}
|
|
|
-
|
|
|
- //如果当前音符不需要演奏
|
|
|
- if (musicXmlInfo.getFrequency()<0&&recordPitchs.size()<=0){
|
|
|
- cadenceNum++;
|
|
|
- cadenceRight = true;
|
|
|
-
|
|
|
- intonationScore += 100;
|
|
|
- musicXmlInfo.setAvgFrequency(-1);
|
|
|
- intonationRight = true;
|
|
|
-
|
|
|
- integrityScore += 1;
|
|
|
- integrityRight = true;
|
|
|
+ //完成度
|
|
|
+ if(integrityValidNum > compareNum){
|
|
|
+ integrityValidNum = compareNum;
|
|
|
}
|
|
|
+ float integrityDuty = integrityValidNum/compareNum;
|
|
|
+ integrityScore += integrityDuty;
|
|
|
+ integrityRight = integrityDuty>0.7;
|
|
|
|
|
|
if(!cadenceRight){
|
|
|
userSoundInfoMap.get(phone).getMusicalNotesPlayStats().add(new MusicalNotesPlayStatDto(musicXmlInfo.getMusicalNotesIndex(), MusicalErrorTypeEnum.CADENCE_WRONG));
|
|
@@ -468,11 +528,6 @@ public class SoundCompareHandler implements WebSocketEventHandler {
|
|
|
createPushInfo(phone, "measureScore", measureIndex, intonation, cadence, integrity);
|
|
|
}
|
|
|
|
|
|
- private float scoreMapping(float score, float divisor, float maxValue){
|
|
|
- score = score*divisor;
|
|
|
- return score>maxValue?maxValue:score;
|
|
|
- }
|
|
|
-
|
|
|
/**
|
|
|
* @describe 计算最终评分
|
|
|
* @author Joburgess
|
|
@@ -488,15 +543,15 @@ public class SoundCompareHandler implements WebSocketEventHandler {
|
|
|
BigDecimal integrity = BigDecimal.ZERO;
|
|
|
|
|
|
if(currentCompareNum>0){
|
|
|
- intonation = userSoundInfoMap.get(phone).getUserScoreMap().get("intonation").divide(new BigDecimal(currentCompareNum), 0, BigDecimal.ROUND_UP);
|
|
|
- cadence = userSoundInfoMap.get(phone).getUserScoreMap().get("cadence").divide(new BigDecimal(currentCompareNum), 0, BigDecimal.ROUND_UP);
|
|
|
- integrity = userSoundInfoMap.get(phone).getUserScoreMap().get("integrity").divide(new BigDecimal(currentCompareNum), 0, BigDecimal.ROUND_UP);
|
|
|
+ intonation = userSoundInfoMap.get(phone).getUserScoreMap().get("intonation").divide(new BigDecimal(currentCompareNum), 0, BigDecimal.ROUND_DOWN);
|
|
|
+ cadence = userSoundInfoMap.get(phone).getUserScoreMap().get("cadence").divide(new BigDecimal(currentCompareNum), 0, BigDecimal.ROUND_DOWN);
|
|
|
+ integrity = userSoundInfoMap.get(phone).getUserScoreMap().get("integrity").divide(new BigDecimal(currentCompareNum), 0, BigDecimal.ROUND_DOWN);
|
|
|
}
|
|
|
|
|
|
//计算分数并推送
|
|
|
createPushInfo(phone, "overall", -1, intonation, cadence, integrity);
|
|
|
|
|
|
- LOGGER.info("评分数据:{}", JSON.toJSONString(userSoundInfoMap.get(phone), SerializerFeature.DisableCircularReferenceDetect));
|
|
|
+ LOGGER.info("评分数据:{}", JSON.toJSONString(userSoundInfoMap.get(phone)));
|
|
|
}
|
|
|
|
|
|
/**
|
|
@@ -515,8 +570,12 @@ public class SoundCompareHandler implements WebSocketEventHandler {
|
|
|
WebSocketInfo webSocketInfo = new WebSocketInfo();
|
|
|
webSocketInfo.setHeader(new WebSocketInfo.Head(command));
|
|
|
Map<String, Object> result = new HashMap<>(5);
|
|
|
- BigDecimal score = intonation.add(cadence).add(integrity).divide(new BigDecimal(3), CommonConstants.DECIMAL_PLACE, BigDecimal.ROUND_DOWN).setScale(0, BigDecimal.ROUND_UP);
|
|
|
-
|
|
|
+ //打击乐只看节奏分
|
|
|
+ BigDecimal score = cadence;
|
|
|
+ //非打击乐总分为平均分
|
|
|
+ if(Objects.isNull(userSoundInfoMap.get(phone).getSubjectId())||userSoundInfoMap.get(phone).getSubjectId()!=23){
|
|
|
+ score = intonation.add(cadence).add(integrity).divide(new BigDecimal(3), CommonConstants.DECIMAL_PLACE, BigDecimal.ROUND_DOWN).setScale(0, BigDecimal.ROUND_UP);
|
|
|
+ }
|
|
|
result.put("score", score);
|
|
|
result.put("intonation", intonation);
|
|
|
result.put("cadence", cadence);
|