| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733 |
- package com.ym.mec.biz.handler;
- import be.tarsos.dsp.AudioDispatcher;
- import be.tarsos.dsp.SilenceDetector;
- import be.tarsos.dsp.io.jvm.AudioDispatcherFactory;
- import be.tarsos.dsp.pitch.PitchProcessor;
- import be.tarsos.dsp.util.PitchConverter;
- import com.alibaba.fastjson.JSON;
- import com.alibaba.fastjson.JSONObject;
- import com.ym.mec.biz.dal.dto.MusicPitchDetailDto;
- import com.ym.mec.biz.dal.dto.SoundCompareHelper;
- import com.ym.mec.biz.dal.dto.WavHeader;
- import com.ym.mec.biz.dal.dto.WebSocketInfo;
- import com.ym.mec.biz.service.SoundSocketService;
- import com.ym.mec.biz.service.SysMusicCompareRecordService;
- import com.ym.mec.common.constant.CommonConstants;
- import org.apache.commons.io.FileUtils;
- import org.slf4j.Logger;
- import org.slf4j.LoggerFactory;
- import org.springframework.beans.factory.annotation.Autowired;
- 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 javax.sound.sampled.UnsupportedAudioFileException;
- 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
- * @Date 2021/6/9 0009
- */
- @Service
- public class WebSocketHandler extends AbstractWebSocketHandler {
- private static final Logger LOGGER = LoggerFactory.getLogger(WebSocketHandler.class);
- //存储客户端链接
- public static final Map<String, WebSocketSession> WS_CLIENTS = new ConcurrentHashMap<>();
- private final BigDecimal oneHundred = new BigDecimal(100);
- private final float simpleRate = 44100;
- private final int simpleSize = 1024;
- private final int overlap = 256;
- private final AudioFormat audioFormat = new AudioFormat(simpleRate, 16, 1, true, false);
- private static final PitchProcessor.PitchEstimationAlgorithm algo = PitchProcessor.PitchEstimationAlgorithm.FFT_YIN;
- private final SilenceDetector silenceDetecor = new SilenceDetector();
- private final String tmpDir = FileUtils.getTempDirectoryPath() + "/soundCompare/";
- //用户对应评分信息
- private Map<String, SoundCompareHelper> userSoundInfoMap = new ConcurrentHashMap<>();
- @Autowired
- private SysMusicCompareRecordService sysMusicCompareRecordService;
- public WebSocketHandler() {
- super();
- File soundDir = new File(tmpDir);
- if(!soundDir.exists()){
- soundDir.mkdir();
- }
- }
- @Override
- public void afterConnectionEstablished(WebSocketSession session) throws Exception {
- String phone = session.getPrincipal().getName().split(":")[1];
- LOGGER.info("{}上线", phone);
- WS_CLIENTS.put(phone, session);
- super.afterConnectionEstablished(session);
- }
- @Override
- public void handleMessage(WebSocketSession session, WebSocketMessage<?> message) throws Exception {
- super.handleMessage(session, message);
- }
- @Override
- protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
- String phone = session.getPrincipal().getName().split(":")[1];
- LOGGER.info("{}: {}", phone, message.getPayload());
- WebSocketInfo webSocketInfo = JSON.parseObject(message.getPayload(), WebSocketInfo.class);
- JSONObject bodyObject = (JSONObject) webSocketInfo.getBody();
- String commond = "";
- if(webSocketInfo.getHeader().containsKey(SoundSocketService.COMMOND)){
- commond = webSocketInfo.getHeader().get(SoundSocketService.COMMOND);
- }
- switch (commond){
- case SoundSocketService.MUSIC_XML:
- userSoundInfoMap.put(phone, new SoundCompareHelper());
- List<MusicPitchDetailDto> musicXmlInfos = JSON.parseArray(bodyObject.getString("musicXmlInfos"), MusicPitchDetailDto.class);
- userSoundInfoMap.get(phone).setMusicXmlInfos(musicXmlInfos);
- musicXmlInfos = musicXmlInfos.stream().filter(m->!m.getDontEvaluating()).collect(Collectors.toList());
- userSoundInfoMap.get(phone).setMusicScoreId(bodyObject.getInteger("id"));
- userSoundInfoMap.get(phone).setMeasureXmlInfoMap(musicXmlInfos.stream().collect(Collectors.groupingBy(MusicPitchDetailDto::getMeasureIndex)));
- musicXmlInfos.forEach(e->userSoundInfoMap.get(phone).getMusicalNotePitchMap().put(e.getMusicalNotesIndex(), e.getFrequency()));
- for (Map.Entry<Integer, List<MusicPitchDetailDto>> userMeasureXmlInfoEntry : userSoundInfoMap.get(phone).getMeasureXmlInfoMap().entrySet()) {
- MusicPitchDetailDto firstPitch = userMeasureXmlInfoEntry.getValue().stream().min(Comparator.comparing(MusicPitchDetailDto::getTimeStamp)).get();
- MusicPitchDetailDto lastPitch = userMeasureXmlInfoEntry.getValue().stream().max(Comparator.comparing(MusicPitchDetailDto::getTimeStamp)).get();
- MusicPitchDetailDto musicPitchDetailDto = new MusicPitchDetailDto(firstPitch.getTimeStamp(), lastPitch.getTimeStamp() + lastPitch.getDuration());
- musicPitchDetailDto.setDuration(musicPitchDetailDto.getEndTimeStamp()-musicPitchDetailDto.getTimeStamp());
- userSoundInfoMap.get(phone).getMeasureEndTime().put(userMeasureXmlInfoEntry.getKey(), musicPitchDetailDto);
- }
- break;
- case SoundSocketService.RECORD_START:
- if(!userSoundInfoMap.containsKey(phone)){
- break;
- }
- File file = new File(tmpDir+phone + "_"+ userSoundInfoMap.get(phone).getMusicScoreId() +"_"+ LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss")) +".wav");
- userSoundInfoMap.get(phone).setAccessFile(new RandomAccessFile(file, "rw"));
- break;
- case SoundSocketService.RECORD_END:
- if(!userSoundInfoMap.containsKey(phone)){
- break;
- }
- if(!CollectionUtils.isEmpty(userSoundInfoMap.get(phone).getMeasureEndTime())){
- Integer lastMeasureIndex = userSoundInfoMap.get(phone).getMeasureEndTime().keySet().stream().min(Integer::compareTo).get();
- double recordTime = userSoundInfoMap.get(phone).getAccessFile().length()/(audioFormat.getFrameSize()*audioFormat.getFrameRate())*1000;
- //如果结束时时长大于某小节,则此小节需要评分
- if(recordTime>userSoundInfoMap.get(phone).getMeasureEndTime().get(lastMeasureIndex).getEndTimeStamp()){
- measureCompare(phone, lastMeasureIndex);
- userSoundInfoMap.get(phone).getMeasureEndTime().remove(lastMeasureIndex);
- }
- }
- calTotalScore(phone);
- createHeader(phone);
- break;
- case SoundSocketService.RECORD_CANCEL:
- createHeader(phone);
- break;
- case SoundSocketService.PROXY_MESSAGE:
- // if(bodyObject.containsKey(SoundSocketService.OFFSET_TIME)){
- // int offsetTime = bodyObject.getIntValue(SoundSocketService.OFFSET_TIME);
- // calOffsetTime(phone, offsetTime);
- // }
- break;
- default:
- break;
- }
- }
- @Override
- protected void handleBinaryMessage(WebSocketSession session, BinaryMessage message) throws Exception {
- String phone = session.getPrincipal().getName().split(":")[1];
- if(!userSoundInfoMap.containsKey(phone)){
- return;
- }
- if(Objects.nonNull(userSoundInfoMap.get(phone).getAccessFile())){
- userSoundInfoMap.get(phone).getAccessFile().write(message.getPayload().array());
- }
- AudioDispatcher dispatcher = AudioDispatcherFactory.fromByteArray(message.getPayload().array(), audioFormat, simpleSize, overlap);
- dispatcher.addAudioProcessor(silenceDetecor);
- dispatcher.addAudioProcessor(new PitchProcessor(algo, simpleRate, simpleSize, (pitchDetectionResult, audioEvent) -> {
- int timeStamp = (int) (userSoundInfoMap.get(phone).getMeasureStartTime() + audioEvent.getTimeStamp()*1000);
- float pitch = pitchDetectionResult.getPitch();
- if(pitch>0 && userSoundInfoMap.get(phone).getOffsetTime() == -1){
- calOffsetTime(phone, (int) (CollectionUtils.isEmpty(userSoundInfoMap.get(phone).getRecordMeasurePithInfo())?0:userSoundInfoMap.get(phone).getRecordMeasurePithInfo().get(userSoundInfoMap.get(phone).getRecordMeasurePithInfo().size()-1).getTimeStamp()));
- }
- // LOGGER.info("时间:{}, 频率:{}, 分贝:{}, 音分:{}", timeStamp, pitch, silenceDetecor.currentSPL(), cents);
- userSoundInfoMap.get(phone).getRecordMeasurePithInfo().add(new MusicPitchDetailDto(timeStamp, pitch, silenceDetecor.currentSPL()));
- }));
- dispatcher.run();
- if(Objects.isNull(userSoundInfoMap.get(phone).getAccessFile())){
- return;
- }
- double recordTime = userSoundInfoMap.get(phone).getAccessFile().length()/(audioFormat.getFrameSize()*audioFormat.getFrameRate())*1000;
- userSoundInfoMap.get(phone).setMeasureStartTime(recordTime);
- for (Map.Entry<Integer, MusicPitchDetailDto> userMeasureEndTimeMapEntry : userSoundInfoMap.get(phone).getMeasureEndTime().entrySet()) {
- int ot = (int) (userMeasureEndTimeMapEntry.getValue().getDuration()*0.1);
- if(recordTime>(userMeasureEndTimeMapEntry.getValue().getEndTimeStamp()+ot)){
- measureCompare(phone, userMeasureEndTimeMapEntry.getKey());
- userSoundInfoMap.get(phone).getMeasureEndTime().remove(userMeasureEndTimeMapEntry.getKey());
- break;
- }
- }
- }
- @Override
- protected void handlePongMessage(WebSocketSession session, PongMessage message) throws Exception {
- super.handlePongMessage(session, message);
- LOGGER.info("心跳信息:{}", new String(message.getPayload().array(), "utf-8"));
- }
- @Override
- public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {
- String phone = session.getPrincipal().getName().split(":")[1];
- session.close();
- if(!WS_CLIENTS.containsKey(phone)){
- return;
- }
- exception.printStackTrace();
- LOGGER.info("发生了错误,移除客户端: {}", phone);
- WS_CLIENTS.remove(phone);
- userSoundInfoMap.remove(phone);
- createHeader(phone);
- }
- @Override
- public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
- super.afterConnectionClosed(session, status);
- String phone = session.getPrincipal().getName().split(":")[1];
- LOGGER.info("{}离线", phone);
- WS_CLIENTS.remove(phone);
- userSoundInfoMap.remove(phone);
- createHeader(phone);
- }
- @Override
- public boolean supportsPartialMessages() {
- return super.supportsPartialMessages();
- }
- /**
- * @describe 处理时间偏移
- * @author Joburgess
- * @date 2021/7/5 0005
- * @param phone:
- * @param offsetTime:
- * @return void
- */
- private void calOffsetTime(String phone, int offsetTime){
- userSoundInfoMap.get(phone).setOffsetTime(offsetTime);
- for (Map.Entry<Integer, MusicPitchDetailDto> musicPitchDetailDtoEntry : userSoundInfoMap.get(phone).getMeasureEndTime().entrySet()) {
- musicPitchDetailDtoEntry.getValue().setTimeStamp(musicPitchDetailDtoEntry.getValue().getTimeStamp() + offsetTime);
- musicPitchDetailDtoEntry.getValue().setEndTimeStamp(musicPitchDetailDtoEntry.getValue().getEndTimeStamp() + offsetTime);
- }
- }
- /**
- * @describe 保存录音数据,并生成wav头信息
- * @author Joburgess
- * @date 2021/6/25 0025
- * @param phone:
- * @return void
- */
- private void createHeader(String phone) throws IOException {
- if(!userSoundInfoMap.containsKey(phone)){
- return;
- }
- if(Objects.nonNull(userSoundInfoMap.get(phone).getAccessFile())){
- RandomAccessFile randomAccessFile = userSoundInfoMap.get(phone).getAccessFile();
- LOGGER.info("音频时长:{}", randomAccessFile.length()/(audioFormat.getFrameSize()*audioFormat.getFrameRate())*1000);
- randomAccessFile.seek(0);
- randomAccessFile.write(WavHeader.getWaveHeader(randomAccessFile.length(), (long) audioFormat.getFrameRate(), audioFormat.getSampleSizeInBits()));
- randomAccessFile.close();
- userSoundInfoMap.get(phone).setAccessFile(null);
- }
- // userSoundInfoMap.get(phone).setRecordMeasurePithInfo(null);
- userSoundInfoMap.remove(phone);
- }
- /**
- * @describe 数据比对,生成分数
- * @author Joburgess
- * @date 2021/6/25 0025
- * @param phone:
- * @param measureIndex:
- * @return void
- */
- private void measureCompare(String phone, int measureIndex) throws IOException {
- if (userSoundInfoMap.get(phone).getOffsetTime() == -1){
- userSoundInfoMap.get(phone).setOffsetTime(0);
- }
- //相似度
- BigDecimal intonation = BigDecimal.ZERO;
- //节奏
- BigDecimal cadence = BigDecimal.ZERO;
- //完整度
- BigDecimal integrity = BigDecimal.ZERO;
- try {
- //最低有效频率
- float minValidFrequency = 20;
- //音准分数
- float intonationScore = 0;
- //节奏匹配数量
- float cadenceNum = 0;
- //节奏有效阈值
- float cadenceValidDuty = 0.01f;
- //完整性误差范围
- float integrityRange = 30;
- //完整性分数
- float integrityScore = 0;
- int totalCompareNum = userSoundInfoMap.get(phone).getMeasureXmlInfoMap().get(measureIndex).size();
- for (int i = 0; i < userSoundInfoMap.get(phone).getMeasureXmlInfoMap().get(measureIndex).size(); i++) {
- MusicPitchDetailDto musicXmlInfo = userSoundInfoMap.get(phone).getMeasureXmlInfoMap().get(measureIndex).get(i);
- 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;
- int preMeasureEndTimeStamp = startTimeStamp;
- List<MusicPitchDetailDto> ms = userSoundInfoMap.get(phone).getMusicXmlInfos().stream().filter(m -> m.getMusicalNotesIndex() == musicXmlInfo.getMusicalNotesIndex() - 1).collect(Collectors.toList());
- if(!CollectionUtils.isEmpty(ms)){
- preMeasureEndTimeStamp = ms.get(0).getEndTimeStamp() + userSoundInfoMap.get(phone).getOffsetTime();
- }
- //时间范围内有效节奏数量
- float cadenceValidNum = 0;
- //时间范围内有效音频数量
- float integrityValidNum = 0;
- //时间范围内匹配次数
- float compareNum = 0;
- boolean newMeasure = false;
- float preMusicalNotesPitch = 0;
- if(userSoundInfoMap.get(phone).getMusicalNotePitchMap().containsKey(musicXmlInfo.getMusicalNotesIndex()-1)){
- preMusicalNotesPitch = userSoundInfoMap.get(phone).getMusicalNotePitchMap().get(musicXmlInfo.getMusicalNotesIndex()-1);
- }
- if(userSoundInfoMap.get(phone).getMusicalNotePitchMap().get(musicXmlInfo.getMusicalNotesIndex())==-1){
- newMeasure = true;
- }
- int newNum = 0;
- for (MusicPitchDetailDto recordInfo : userSoundInfoMap.get(phone).getRecordMeasurePithInfo()) {
- if(musicXmlInfo.getMusicalNotesIndex()==0){
- newMeasure = true;
- }
- if(newMeasure){
- break;
- }
- if(recordInfo.getTimeStamp()<preMeasureEndTimeStamp||recordInfo.getTimeStamp()>startTimeStamp){
- continue;
- }
- if(Math.abs(recordInfo.getFrequency()-preMusicalNotesPitch)>10){
- newNum++;
- }else{
- newNum = 0;
- }
- if(newNum>=2){
- newMeasure = true;
- }
- }
- // List<Float> musicalNotesPitchs = new ArrayList<>();
- // List<Float> decibels = new ArrayList<>();
- List<MusicPitchDetailDto> measureSoundPitchInfos = new ArrayList<>();
- 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;
- }
- // musicalNotesPitchs.add(recordInfo.getFrequency());
- // decibels.add(recordInfo.getDecibel());
- measureSoundPitchInfos.add(recordInfo);
- compareNum++;
- if(!newMeasure){
- continue;
- }
- // LOGGER.info("{}频率({}-{}):{}, {}", recordInfo.getTimeStamp(), startTimeStamp, endTimeStamp, musicXmlInfo.getFrequency(), recordInfo.getFrequency());
- //如果在最低有效频率以下则跳过
- if(recordInfo.getFrequency()<minValidFrequency&&musicXmlInfo.getFrequency()!=-1){
- continue;
- }
- cadenceValidNum++;
- if(recordInfo.getTimeStamp()<startTimeStamp||recordInfo.getTimeStamp()>endTimeStamp){
- continue;
- }
- //如果频率差值在节奏误差范围内
- if(Math.abs(recordInfo.getFrequency()-musicXmlInfo.getFrequency())<=integrityRange){
- 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;
- //分贝持续数量
- 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) > 5){
- dnum ++;
- }
- if (dnum > 2){
- cd = musicalNotesPitch.getDecibel();
- dnum = 0;
- decibelChangeNum++;
- }
- }
- userSoundInfoMap.get(phone).getMusicalNotePitchMap().put(musicXmlInfo.getMusicalNotesIndex(), (float) pitch);
- }
- //有效节奏占比
- float cadenceDuty = cadenceValidNum/compareNum;
- //如果频率出现断层或这个音量出现断层,则当前音符节奏无效
- if(errPitchNum>=2 || decibelChangeNum>1){
- cadenceDuty = 0;
- }
- //节奏
- if(cadenceDuty>=cadenceValidDuty){
- cadenceNum++;
- }
- //音准
- if (!CollectionUtils.isEmpty(measureSoundPitchInfos)){
- Double avgPitch = measureSoundPitchInfos.stream().filter(pitch -> Math.abs((pitch.getFrequency()-musicXmlInfo.getFrequency()))<5).collect(Collectors.averagingDouble(pitch -> pitch.getFrequency()));
- //音分
- double recordCents = 0;
- if (avgPitch > 0){
- recordCents = PitchConverter.hertzToAbsoluteCent(avgPitch);
- }
- double cents = PitchConverter.hertzToAbsoluteCent(musicXmlInfo.getFrequency());
- double score = 100 - Math.round(Math.abs(cents - recordCents)) + 3;
- if (score < 0){
- score = 0;
- }else if(score > 100){
- score = 100;
- }
- intonationScore += score;
- musicXmlInfo.setAvgFrequency(avgPitch.floatValue());
- }
- //完成度
- if(integrityValidNum>0){
- integrityValidNum = integrityValidNum + (float) (compareNum * 0.05);
- }
- if(integrityValidNum > compareNum){
- integrityValidNum = compareNum;
- }
- float integrityDuty = integrityValidNum/compareNum;
- integrityScore += integrityDuty;
- }
- BigDecimal measureNum = new BigDecimal(totalCompareNum);
- intonation = new BigDecimal(intonationScore).divide(measureNum, CommonConstants.DECIMAL_PLACE, BigDecimal.ROUND_DOWN).setScale(0, BigDecimal.ROUND_UP);
- cadence = new BigDecimal(cadenceNum).divide(measureNum, CommonConstants.DECIMAL_PLACE, BigDecimal.ROUND_DOWN).multiply(oneHundred).setScale(0, BigDecimal.ROUND_UP);
- integrity = new BigDecimal(integrityScore).divide(measureNum, CommonConstants.DECIMAL_PLACE, BigDecimal.ROUND_DOWN).multiply(oneHundred).setScale(0, BigDecimal.ROUND_UP);
- } catch (ArithmeticException e){
- LOGGER.info("无musicXml信息");
- }
- if(userSoundInfoMap.get(phone).getUserScoreMap().containsKey("intonation")){
- userSoundInfoMap.get(phone).getUserScoreMap().put("intonation", intonation.add(userSoundInfoMap.get(phone).getUserScoreMap().get("intonation")));
- }else{
- userSoundInfoMap.get(phone).getUserScoreMap().put("intonation", intonation);
- }
- if(userSoundInfoMap.get(phone).getUserScoreMap().containsKey("cadence")){
- userSoundInfoMap.get(phone).getUserScoreMap().put("cadence", cadence.add(userSoundInfoMap.get(phone).getUserScoreMap().get("cadence")));
- }else{
- userSoundInfoMap.get(phone).getUserScoreMap().put("cadence", cadence);
- }
- if(userSoundInfoMap.get(phone).getUserScoreMap().containsKey("integrity")){
- userSoundInfoMap.get(phone).getUserScoreMap().put("integrity", integrity.add(userSoundInfoMap.get(phone).getUserScoreMap().get("integrity")));
- }else{
- userSoundInfoMap.get(phone).getUserScoreMap().put("integrity", integrity);
- }
- Map<String, BigDecimal> scoreData = new HashMap<>();
- scoreData.put("intonation", intonation);
- scoreData.put("cadence", cadence);
- scoreData.put("integrity", integrity);
- userSoundInfoMap.get(phone).getUserMeasureScoreMap().put(measureIndex, scoreData);
- WS_CLIENTS.get(phone).sendMessage(new TextMessage(JSON.toJSONString(createPushInfo("measureScore", measureIndex, intonation, cadence, integrity))));
- }
- private void measureCompare2(String phone, MusicPitchDetailDto measureTimeInfo) throws IOException, UnsupportedAudioFileException {
- //小节总时长
- double measureTime = measureTimeInfo.getEndTimeStamp()-measureTimeInfo.getTimeStamp();
- double ot = measureTime * 0.1;
- measureTime += ot;
- //小节时长占用字节数
- int measureByteNum = (int) (measureTime/1000*(audioFormat.getFrameSize()*audioFormat.getFrameRate()));
- List<MusicPitchDetailDto> recordInfo = new ArrayList<>();
- byte[] bytes = new byte[measureByteNum];
- long startOffset = (userSoundInfoMap.get(phone).getAccessFile().length()-measureByteNum);
- userSoundInfoMap.get(phone).getAccessFile().seek(startOffset);
- // userSoundInfoMap.get(phone).getAccessFile().seek(0);
- userSoundInfoMap.get(phone).getAccessFile().readFully(bytes);
- userSoundInfoMap.get(phone).getAccessFile().seek(userSoundInfoMap.get(phone).getAccessFile().length());
- AudioDispatcher dispatcher = AudioDispatcherFactory.fromByteArray(bytes, audioFormat, simpleSize, 128);
- dispatcher.addAudioProcessor(new PitchProcessor(algo, simpleRate, simpleSize, (pitchDetectionResult, audioEvent) -> {
- int timeStamp = (int) (measureTimeInfo.getTimeStamp() - (ot>measureTimeInfo.getTimeStamp()?0:ot) + audioEvent.getTimeStamp()*1000);
- float pitch = pitchDetectionResult.getPitch();
- recordInfo.add(new MusicPitchDetailDto(timeStamp, pitch));
- }));
- dispatcher.run();
- userSoundInfoMap.get(phone).getRecordMeasurePithInfo().addAll(recordInfo);
- LOGGER.info("小节评分频率{}:{}", measureTimeInfo.getMeasureIndex(), JSON.toJSONString(recordInfo));
- scoreCal(phone, measureTimeInfo, recordInfo);
- }
- private void scoreCal(String phone, MusicPitchDetailDto measureTimeInfo, List<MusicPitchDetailDto> recordPitchDetails) throws IOException {
- //相似度
- BigDecimal intonation = BigDecimal.ZERO;
- //节奏
- BigDecimal cadence = BigDecimal.ZERO;
- //完整度
- BigDecimal integrity = BigDecimal.ZERO;
- try {
- //最低有效频率
- float minValidFrequency = 20;
- //音准匹配数量
- float intonationNum = 0;
- //音准匹配误差范围
- float intonationErrRange = 15;
- //音准有效阈值
- float intonationValidDuty = 0.1f;
- //节奏匹配数量
- float cadenceNum = 0;
- //节奏有效阈值
- float cadenceValidDuty = 0.1f;
- //完整性数量
- float integrityNum = 0;
- //完整性误差范围
- float integrityRange = 30;
- //完整性有效阈值
- float integrityValidDuty = 0.5f;
- int totalCompareNum = userSoundInfoMap.get(phone).getMeasureXmlInfoMap().get(measureTimeInfo.getMeasureIndex()).size();
- for (MusicPitchDetailDto musicXmlInfo : userSoundInfoMap.get(phone).getMeasureXmlInfoMap().get(measureTimeInfo.getMeasureIndex())) {
- int ot5 = (int) (musicXmlInfo.getDuration()*0.1);
- int startTimeStamp = musicXmlInfo.getTimeStamp() - ot5;
- int endTimeStamp = musicXmlInfo.getTimeStamp() + ot5;
- //时间范围内有效音准数量
- float recordValidIntonationNum = 0;
- //时间范围内有效节奏数量
- float cadenceValidNum = 0;
- //时间范围内有效音频数量
- float integrityValidNum = 0;
- //时间范围内匹配次数
- float compareNum = 0;
- int faultNum = 0;
- for (int i = 0; i < recordPitchDetails.size(); i++) {
- MusicPitchDetailDto recordInfo = recordPitchDetails.get(i);
- if(recordInfo.getTimeStamp()>(startTimeStamp-ot5)&&Math.abs((recordInfo.getFrequency()-musicXmlInfo.getFrequency()))>20){
- faultNum++;
- }else{
- if(faultNum<6)
- faultNum = 0;
- }
- if(faultNum<6){
- continue;
- }
- //如果在时间范围之外直接跳过
- if(recordInfo.getTimeStamp()<startTimeStamp||recordInfo.getTimeStamp()>endTimeStamp){
- continue;
- }
- // LOGGER.info("{}频率({}-{}):{}, {}", recordInfo.getTimeStamp(), startTimeStamp, endTimeStamp, musicXmlInfo.getFrequency(), recordInfo.getFrequency());
- compareNum++;
- //如果在最低有效频率以下则跳过
- if(recordInfo.getFrequency()<minValidFrequency&&musicXmlInfo.getFrequency()!=-1){
- continue;
- }
- cadenceValidNum++;
- if(recordInfo.getTimeStamp()<startTimeStamp||recordInfo.getTimeStamp()>endTimeStamp){
- continue;
- }
- //如果频率差值在节奏误差范围内
- if(Math.abs(recordInfo.getFrequency()-musicXmlInfo.getFrequency())<=integrityRange){
- integrityValidNum++;
- }
- //如果频率差值在音准误差范围内
- if(Math.abs(recordInfo.getFrequency()-musicXmlInfo.getFrequency())<=intonationErrRange){
- recordValidIntonationNum++;
- }
- }
- //有效音频占比
- float integrityDuty = integrityValidNum/compareNum;
- //有效音高占比
- float intonationDuty = recordValidIntonationNum/compareNum;
- //有效节奏占比
- float cadenceDuty = cadenceValidNum/compareNum;
- //节奏
- if(cadenceDuty>=cadenceValidDuty){
- cadenceNum++;
- if(intonationDuty>=intonationValidDuty){
- intonationNum++;
- }
- if(integrityDuty>=integrityValidDuty){
- integrityNum++;
- }
- }
- }
- 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(integrityNum).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(userSoundInfoMap.get(phone).getUserScoreMap().containsKey("intonation")){
- userSoundInfoMap.get(phone).getUserScoreMap().put("intonation", intonation.add(userSoundInfoMap.get(phone).getUserScoreMap().get("intonation")));
- }else{
- userSoundInfoMap.get(phone).getUserScoreMap().put("intonation", intonation);
- }
- if(userSoundInfoMap.get(phone).getUserScoreMap().containsKey("cadence")){
- userSoundInfoMap.get(phone).getUserScoreMap().put("cadence", cadence.add(userSoundInfoMap.get(phone).getUserScoreMap().get("cadence")));
- }else{
- userSoundInfoMap.get(phone).getUserScoreMap().put("cadence", cadence);
- }
- if(userSoundInfoMap.get(phone).getUserScoreMap().containsKey("integrity")){
- userSoundInfoMap.get(phone).getUserScoreMap().put("integrity", integrity.add(userSoundInfoMap.get(phone).getUserScoreMap().get("integrity")));
- }else{
- userSoundInfoMap.get(phone).getUserScoreMap().put("integrity", integrity);
- }
- Map<String, BigDecimal> scoreData = new HashMap<>();
- scoreData.put("intonation", intonation);
- scoreData.put("cadence", cadence);
- scoreData.put("integrity", integrity);
- userSoundInfoMap.get(phone).getUserMeasureScoreMap().put(measureTimeInfo.getMeasureIndex(), scoreData);
- WS_CLIENTS.get(phone).sendMessage(new TextMessage(JSON.toJSONString(createPushInfo("measureScore", measureTimeInfo.getMeasureIndex(), intonation, cadence, integrity))));
- }
- /**
- * @describe 计算最终评分
- * @author Joburgess
- * @date 2021/6/25 0025
- * @param phone:
- * @return void
- */
- private void calTotalScore(String phone) throws IOException {
- int totalCompareNum = userSoundInfoMap.get(phone).getMeasureXmlInfoMap().keySet().size();
- int currentCompareNum = totalCompareNum-userSoundInfoMap.get(phone).getMeasureEndTime().keySet().size();
- BigDecimal intonation = BigDecimal.ZERO;
- BigDecimal cadence = BigDecimal.ZERO;
- BigDecimal integrity = BigDecimal.ZERO;
- if(currentCompareNum>0){
- 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);
- }
- WS_CLIENTS.get(phone).sendMessage(new TextMessage(JSON.toJSONString(createPushInfo("overall", -1, intonation, cadence, integrity))));
- //存储评分数据
- sysMusicCompareRecordService.saveMusicCompareData(phone, userSoundInfoMap.get(phone).getMusicScoreId(), userSoundInfoMap.get(phone).getUserMeasureScoreMap());
- LOGGER.info("评分数据:{}", JSON.toJSONString(userSoundInfoMap.get(phone)));
- }
- /**
- * @describe 生成评分结果
- * @author Joburgess
- * @date 2021/6/25 0025
- * @param command:
- * @param measureIndex:
- * @param intonation:
- * @param cadence:
- * @param integrity:
- * @return com.ym.mec.biz.dal.dto.WebSocketInfo
- */
- private WebSocketInfo createPushInfo(String command, Integer measureIndex,
- BigDecimal intonation, BigDecimal cadence, BigDecimal integrity){
- WebSocketInfo webSocketInfo = new WebSocketInfo();
- HashMap<String, String> header = new HashMap<>();
- header.put("commond", command);
- webSocketInfo.setHeader(header);
- Map<String, Object> result = new HashMap<>();
- // BigDecimal score = intonation.multiply(new BigDecimal(0.5)).add(cadence.multiply(new BigDecimal(0.5))).setScale(0, BigDecimal.ROUND_HALF_UP);
- BigDecimal score = intonation.add(cadence).add(integrity).divide(new BigDecimal(3), CommonConstants.DECIMAL_PLACE, BigDecimal.ROUND_DOWN).setScale(0, BigDecimal.ROUND_UP);
- // BigDecimal score = integrity.setScale(0, BigDecimal.ROUND_HALF_UP);
- result.put("score", score);
- result.put("intonation", intonation);
- result.put("cadence", cadence);
- result.put("integrity", integrity);
- result.put("measureIndex", measureIndex);
- webSocketInfo.setBody(result);
- LOGGER.info("小节频分:{}", JSON.toJSONString(webSocketInfo));
- return webSocketInfo;
- }
- }
|