Skip to content
Original file line number Diff line number Diff line change
@@ -1,16 +1,33 @@
package com.sosaw.sosaw.domain.basicsound.entity.enums;

import lombok.Getter;
import lombok.RequiredArgsConstructor;

import java.util.Arrays;

@Getter
@RequiredArgsConstructor
public enum BasicSoundType {
DOG_BARK, // 강아지 짓는 소리
CAT_MEOW, // 고양이 소리
HUMAN_LAUGH, // 사람 웃는 소리
BABY_CRY, // 아기 우는 소리
PHONE_RING, // 전화벨 소리
DOORBELL, // 초인종 소리
DOOR_OPEN_CLOSE, // 문 여닫는 소리
KNOCK, // 노크 소리
FIRE_ALARM, // 화재 경보기 소리
MICROWAVE, // 전자레인지 소리
CAR_HORN, // 경적 소리
SIREN // 비상 경보음 소리
DOG_BARK("Dog Bark"), // 강아지 짓는 소리
CAT_MEOW("Cat Meow"), // 고양이 소리
HUMAN_LAUGH("Laughing"), // 사람 웃는 소리
BABY_CRY("Baby Cry"), // 아기 우는 소리
PHONE_RING("Phone Ring"), // 전화벨 소리
DOORBELL("Doorbell"), // 초인종 소리
DOOR_OPEN_CLOSE("Door In-Use"), // 문 여닫는 소리
KNOCK("Knocking"), // 노크 소리
FIRE_ALARM("Fire/Smoke Alarm"), // 화재 경보기 소리
MICROWAVE("Microwave"), // 전자레인지 소리
CAR_HORN("Car Honk"), // 경적 소리
SIREN("Siren"), // 비상 경보음 소리
UNKNOWN("Unknown");

private final String label;

public static BasicSoundType fromLabel(String find) {
return Arrays.stream(values())
.filter(e -> e.label.equalsIgnoreCase(find))
.findFirst()
.orElse(UNKNOWN); // 매칭 없으면 null
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.sosaw.sosaw.domain.basicsound.exception;

import com.sosaw.sosaw.global.response.code.BaseResponseCode;
import lombok.AllArgsConstructor;
import lombok.Getter;

import static com.sosaw.sosaw.global.constant.StaticValue.NOT_FOUND;

@Getter
@AllArgsConstructor
public enum BasicSoundErrorCode implements BaseResponseCode {
NOT_FOUND_BASIC_SOUND_404("BASIC_SOUND_404_1", NOT_FOUND, "해당 기본 소리를 찾을 수 없습니다.");

private final String code;
private final int httpStatus;
private final String message;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.sosaw.sosaw.domain.basicsound.exception;

import com.sosaw.sosaw.global.exception.BaseException;

public class NotFoundBasicSoundException extends BaseException {
public NotFoundBasicSoundException() {
super(BasicSoundErrorCode.NOT_FOUND_BASIC_SOUND_404);
}
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
package com.sosaw.sosaw.domain.basicsound.repository;

import com.sosaw.sosaw.domain.basicsound.entity.BasicSound;
import com.sosaw.sosaw.domain.basicsound.entity.enums.BasicSoundType;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

import java.util.Optional;

@Repository
public interface BasicSoundRepository extends JpaRepository<BasicSound, Long> {

Optional<BasicSound> findBySoundType(BasicSoundType sound);
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ public enum CustomSoundErrorCode implements BaseResponseCode {
UNSUPPORTED_EXTENSION_415("CUSTOM_SOUND_415", UNSUPPORTED_MEDIA_TYPE, "지원하지 않는 파일 형식입니다. (.wav만 허용)"),
FILE_PROCESS_FAILED_500("CUSTOM_SOUND_500_1", INTERNAL_SERVER_ERROR, "파일 처리 중 오류가 발생했습니다."),
FASTAPI_CALL_FAILED_500("CUSTOM_SOUND_500_2", INTERNAL_SERVER_ERROR, "FastAPI MFCC 호출 실패"),
NOT_FOUND_SOUND_404("CUSTOM_SOUND_404_1", NOT_FOUND, "해당 소리를 찾을 수 없습니다.");
NOT_FOUND_CUSTOM_SOUND_404("CUSTOM_SOUND_404_1", NOT_FOUND, "해당 커스텀 소리를 찾을 수 없습니다.");

private final String code;
private final int httpStatus;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.sosaw.sosaw.domain.customsound.exception;

import com.sosaw.sosaw.global.exception.BaseException;

public class NotFoundCustomSoundException extends BaseException {
public NotFoundCustomSoundException() {
super(CustomSoundErrorCode.NOT_FOUND_CUSTOM_SOUND_404);
}
}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@

public interface AudioFeatureExtractor {
float[] extractMfcc(MultipartFile file);
String predict(MultipartFile file);
}
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ public interface CustomSoundRepository extends JpaRepository<CustomSound, Long>
ORDER BY c.mfcc <=> (:mfcc)::vector ASC
LIMIT 1
""", nativeQuery = true)
Optional<SoundMatchRow> findTopMatchByUserIdWithSimilarity(
SoundMatchRow findTopMatchByUserIdWithSimilarity(
@Param("userId") Long userId,
@Param("mfcc") String mfccVectorLiteral // "[0.12, -0.03, ...]" 형태의 문자열
);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
package com.sosaw.sosaw.domain.customsound.service;

import com.sosaw.sosaw.domain.basicsound.entity.BasicSound;
import com.sosaw.sosaw.domain.basicsound.entity.enums.BasicSoundType;
import com.sosaw.sosaw.domain.basicsound.exception.NotFoundBasicSoundException;
import com.sosaw.sosaw.domain.basicsound.repository.BasicSoundRepository;
import com.sosaw.sosaw.domain.customsound.entity.CustomSound;
import com.sosaw.sosaw.domain.customsound.exception.NotFoundSoundException;
import com.sosaw.sosaw.domain.customsound.exception.NotFoundCustomSoundException;
import com.sosaw.sosaw.domain.customsound.port.AudioFeatureExtractor;
import com.sosaw.sosaw.domain.customsound.repository.CustomSoundRepository;
import com.sosaw.sosaw.domain.customsound.web.dto.SoundMatchRes;
Expand Down Expand Up @@ -32,6 +36,7 @@ public class CustomSoundServiceImpl implements CustomSoundService{
private final AudioFeatureExtractor audioFeatureExtractor; // 포트 주입
private final SoundSettingRepository soundSettingRepository;
private final UserRepository userRepository;
private final BasicSoundRepository basicSoundRepository;


@Override
Expand All @@ -54,7 +59,7 @@ public void upload(SoundUploadReq req, User user) {
public void delete(Long customSoundId) {
customSoundRepository.findById(customSoundId).ifPresentOrElse(
customSoundRepository::delete,
() -> { throw new NotFoundSoundException(); }
() -> { throw new NotFoundCustomSoundException(); }
);
}

Expand All @@ -71,7 +76,7 @@ public List<SoundsRes> getAllSounds(User user) {
@Transactional
public void modify(SoundUploadReq req, Long customSoundId) {
CustomSound sound = customSoundRepository.findById(customSoundId)
.orElseThrow(NotFoundSoundException::new);
.orElseThrow(NotFoundCustomSoundException::new);
float[] mfcc = audioFeatureExtractor.extractMfcc(req.getFile());
sound.replace(req, mfcc);
}
Expand All @@ -86,11 +91,34 @@ public SoundMatchRes match(User user, MultipartFile file) {
String literal = FloatArrayVectorConverter.toLiteral(mfcc);

SoundMatchRow row = customSoundRepository
.findTopMatchByUserIdWithSimilarity(user.getUserId(), literal)
.orElseThrow(NotFoundSoundException::new);
//TODO: 일상생활 소리 탐지와 사용자 소리 어떤 것을 반환할지, 단일 컷 정확도 기준 필요
// 일상생활 소리 탐지 부분에서 서비스에서 재공되는 9개의 소리가 아닌 다른 소리 분류시 제외 시켜야 함.
return SoundMatchRes.from(row);
.findTopMatchByUserIdWithSimilarity(user.getUserId(), literal);

log.info("커스텀 소리 유사도: {}", row.getSimilarity());

//1. 커스텀 유사도
if (row != null && row.getSimilarity() >= 0.997) {
return SoundMatchRes.from(row);
}

//2. tf모델 예측
String label = audioFeatureExtractor.predict(file);

log.info("매핑된 사운드 종류 : {}", label);

BasicSoundType sound = BasicSoundType.fromLabel(label);

//커스텀 유사도가 낮고 tf에서도 예측불가능
if (sound == BasicSoundType.UNKNOWN) {
return SoundMatchRes.unknown();
}

BasicSound basicSound = basicSoundRepository.findBySoundType(sound)
.orElseThrow(NotFoundBasicSoundException::new);

SoundSetting setting = soundSettingRepository.findByUserUserIdAndBasicSound(user.getUserId(), basicSound)
.orElse(SoundSetting.createForBasic(user, basicSound));

return SoundMatchRes.fromBasicSound(sound, setting);
}

}
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package com.sosaw.sosaw.domain.customsound.web.dto;

import com.sosaw.sosaw.domain.basicsound.entity.enums.BasicSoundType;
import com.sosaw.sosaw.domain.customsound.entity.enums.Color;
import com.sosaw.sosaw.domain.customsound.repository.projection.SoundMatchRow;
import com.sosaw.sosaw.domain.soundsetting.entity.SoundSetting;

public record SoundMatchRes(
String soundName,
Expand All @@ -21,4 +23,19 @@ public static SoundMatchRes from(SoundMatchRow row){
row.getVibration()
);
}

public static SoundMatchRes fromBasicSound(BasicSoundType sound, SoundSetting setting) {
return new SoundMatchRes(
sound.getLabel(),
null,
null,
-1.0,
setting.isAlarmEnabled(),
setting.getVibrationLevel()
);
}

public static SoundMatchRes unknown(){
return new SoundMatchRes("Unknown", null,null,0.0, false,0);
}
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
package com.sosaw.sosaw.domain.soundsetting.repository;

import com.sosaw.sosaw.domain.basicsound.entity.BasicSound;
import com.sosaw.sosaw.domain.soundsetting.entity.SoundSetting;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

import java.util.List;
import java.util.Optional;

@Repository
Expand All @@ -15,4 +17,7 @@ public interface SoundSettingRepository extends JpaRepository<SoundSetting, Long
Optional<SoundSetting> findByUserUserIdAndCustomSoundId(Long userId, Long customId);
Optional<SoundSetting> findByUserUserIdAndBasicSoundId(Long userId, Long basicId);

List<SoundSetting> findByUserUserId(Long userId);

Optional<SoundSetting> findByUserUserIdAndBasicSound(Long userId, BasicSound basicSound);
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,18 @@
package com.sosaw.sosaw.domain.soundsetting.service;

import com.sosaw.sosaw.domain.soundsetting.web.dto.SoundAlarmUpdateReq;
import com.sosaw.sosaw.domain.soundsetting.web.dto.SoundSettingRes;
import com.sosaw.sosaw.domain.soundsetting.web.dto.SoundVibrationUpdateReq;
import com.sosaw.sosaw.domain.user.entity.User;
import com.sosaw.sosaw.global.security.CustomUserDetails;

import java.util.List;

public interface SoundSettingService {

// 알림 설정 조회
List<SoundSettingRes> getAllSoundSetting(User user);

// 알람 유무 수정
void updateAlarm(User user, SoundAlarmUpdateReq req);
void updateCustomAlarm(User user, SoundAlarmUpdateReq req);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,64 @@

import com.sosaw.sosaw.domain.basicsound.entity.BasicSound;
import com.sosaw.sosaw.domain.basicsound.repository.BasicSoundRepository;
import com.sosaw.sosaw.domain.customsound.exception.NotFoundSoundException;
import com.sosaw.sosaw.domain.customsound.exception.NotFoundCustomSoundException;
import com.sosaw.sosaw.domain.customsound.entity.CustomSound;
import com.sosaw.sosaw.domain.customsound.repository.CustomSoundRepository;
import com.sosaw.sosaw.domain.soundsetting.entity.SoundSetting;
import com.sosaw.sosaw.domain.soundsetting.repository.SoundSettingRepository;
import com.sosaw.sosaw.domain.soundsetting.web.dto.SoundAlarmUpdateReq;
import com.sosaw.sosaw.domain.soundsetting.web.dto.SoundSettingRes;
import com.sosaw.sosaw.domain.soundsetting.web.dto.SoundVibrationUpdateReq;
import com.sosaw.sosaw.domain.user.entity.User;
import com.sosaw.sosaw.domain.user.exception.UserNotFoundException;
import com.sosaw.sosaw.domain.user.repository.UserRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;

@Service
@RequiredArgsConstructor
public class SoundSettingServiceImpl implements SoundSettingService {

private final BasicSoundRepository basicSoundRepository;
private final CustomSoundRepository customSoundRepository;
private final SoundSettingRepository soundSettingRepository;
private final UserRepository userRepository;

@Override
@Transactional
public List<SoundSettingRes> getAllSoundSetting(User user) {
userRepository.findById(user.getUserId())
.orElseThrow(UserNotFoundException::new);

//모든 사운드 설정 조회
List<SoundSetting> settings = soundSettingRepository.findByUserUserId(user.getUserId());

//기본 사운드 설정 조회
List<BasicSound> basicSounds = basicSoundRepository.findAll();

//기본 사운드 존재 확인
for(BasicSound basicSound : basicSounds) {
boolean exists = false;
for(SoundSetting setting : settings) {
if(setting.getBasicSound() != null &&
setting.getBasicSound().getId().equals(basicSound.getId())) {
exists = true;
break;
}
}
if(!exists) {
SoundSetting newSetting = SoundSetting.createForBasic(user, basicSound);
soundSettingRepository.save(newSetting);
settings.add(newSetting);
}
}


return settings.stream().map(SoundSettingRes::from).toList();
}

@Override
@Transactional
Expand All @@ -46,7 +85,7 @@ public void updateCustomAlarm(User user, SoundAlarmUpdateReq req) {
// 1) 내 커스텀 사운드인지 검증
CustomSound customSound = customSoundRepository
.findByIdAndUserUserId(req.getSoundId(), user.getUserId())
.orElseThrow(NotFoundSoundException::new);
.orElseThrow(NotFoundCustomSoundException::new);

// 2) 설정 조회 or 생성
SoundSetting setting = soundSettingRepository
Expand All @@ -66,7 +105,7 @@ public void updateCustomVibration(User user, SoundVibrationUpdateReq req) {
// 1) 내 커스텀 사운드인지 검증
CustomSound customSound = customSoundRepository
.findByIdAndUserUserId(req.getSoundId(), user.getUserId())
.orElseThrow(NotFoundSoundException::new);
.orElseThrow(NotFoundCustomSoundException::new);

// 2) 설정 조회 or 생성
SoundSetting setting = soundSettingRepository
Expand All @@ -84,7 +123,7 @@ public void updateCustomVibration(User user, SoundVibrationUpdateReq req) {
public void updateDefaultAlarm(User user, SoundAlarmUpdateReq req) {
// 1) 기본 사운드 검증
BasicSound basicSound = basicSoundRepository.findById(req.getSoundId())
.orElseThrow(NotFoundSoundException::new);
.orElseThrow(NotFoundCustomSoundException::new);

// 2) 설정 조회 or 생성
SoundSetting setting = soundSettingRepository
Expand All @@ -102,7 +141,7 @@ public void updateDefaultAlarm(User user, SoundAlarmUpdateReq req) {
public void updateDefaultVibration(User user, SoundVibrationUpdateReq req) {
// 1) 기본 사운드 검증
BasicSound basicSound = basicSoundRepository.findById(req.getSoundId())
.orElseThrow(NotFoundSoundException::new);
.orElseThrow(NotFoundCustomSoundException::new);

// 2) 설정 조회 or 생성
SoundSetting setting = soundSettingRepository
Expand Down
Loading