Skip to content

haechanmoon/java-codemusic

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

85 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

java-codemusic

[오픈 미션] 코드를 음악으로

실행 영상

▶️ 실행 영상 보러가기 (Google Drive)

프로젝트 제작 동기

이번 오픈 미션에서는 저의 가장 큰 정체성인 음악과, 프리코스 3주차간의 열심히 학습한 내용을 접목해보고 싶었습니다. 지원서에도 작성했지만 저는 음악을 하다가 소프트웨어 전공 전과했습니다. 따라서 오픈 미션에서 제가 잘 할 수 있는 것은 음악분야를 코드와 접목시키는 것이었습니다. 이 프로젝트는 제가 과거에 했던 음악과 미래에 하게될 코딩, 그 다리가 되어주는 것 같습니다.

프로젝트 소개

자신이 작성한 코드를 넣으면 그 코드에 맞는 음악을 만들어 줍니다.

  • 오른손 / 왼손 2개의 코드들를 준비합니다.
  • 마지막 줄에 "done"을 입력하면 입력이 완료됩니다.
  • 템포는 1(느림)~9(빠름)까지 있습니다.
  • 평소 듣는 음악과 매우 다를 수 있습니다.
  • 제가 만들었지만 들으면서 소름이 돋아 많이 재생하진 않았습니다.
  • main/java/resources 에 아름다운 Canon 을 저장해두었습니다.

변환 규칙

1. 오른손 (Melody)

  • a ~ y (25자): "4옥타브 도(C4)"부터 "6옥타브 도(C6)"까지 25개 음에 순서대로 1:1 매핑
  • z: 쉼표(REST)
  • 그 외: 1, 2번에 해당하지 않는 모든 문자(한글, 숫자, 특수문자, 공백 등)는 쉼표(REST)로 변환
  • 오른손 코드의 길이가 왼손 코드의 길이보다 짧을 때 왼손 코드 길이를 자름.

2. 왼손 (Bass)

  • a ~ y (q 제외): 오른손 규칙의 음을 기준으로 2옥타브 낮춰 변환 (예: C4 -> C2)
  • z, y: 쉼표(REST)
  • 그 외: 정의되지 않은 문자는 모두 쉼표(REST)로 변환
  • 왼손 코드의 길이가 오른손 코드 길이보다 짧을 때 왼손코드는 순환

3. 공통 및 특수 기능

  • 다이나믹 (Dynamics)
  • 왼손 대문자 입력: f (forte, 강하게)
  • 왼손 소문자 입력: p (piano, 약하게)
  • 화음 (Chord) - 오른손만 적용
    • public, if, while 등 특정 자바 키워드 감지 시 단음이 아닌 3화음(예: C-E-G)으로 변환
    • 키워드 길이만큼 문자열 인덱스를 건너뛰고 변환 진행
  • 제거
    • 오른손, 왼손 둘 다 "REST"일 경우 그 마디 제거

기능 목록 정리

1. 입력 기능

  • 사용자가 오른손 멜로디로 변환할 코드 문자열(String)을 입력
  • 사용자가 왼손 베이스로 변환할 코드 문자열(String)을 입력
  • 템포 조절(1~9) 입력
  • 입력값이 없을 경우 에러 메시지 출력 후 재입력 받기

2. 핵심 변환 기능 ([enum] Note)

  • Map 자료구조를 이용해, 지정된 알파벳(예: a-y)을 4옥타브 기준의 음표(예: "C4")로 변환
  • Map에 정의되지 않은 글자(한글, 숫자, 특수문자, 공백 등)는 쉼표("REST")로 처리
  • Map 구조 대신 Enum사용
  • 왼손 파트로 요청된 변환은, 오른손 기준보다 2옥타브 낮은 음표(예: "C4" -> "C2")로 변환
  • 정의되지 않은 글자(한글, 숫자, 특수문자, 공백 등)는 쉼표("REST")로 처리

3. 연주 조합 기능 (CodeMusicGame)

  • 오른손 문자열의 길이를 기준으로, 한 글자씩 오른손과 왼손 음표를 조합
  • 왼손 문자열은 오른손 문자열이 끝날 때까지 계속 순환(반복) (예: 오른손 1000자, 왼손 250자 -> 왼손 4번 반복)

4. 출력 기능 (OutputView)

  • 조합된 오른손 음표, 왼손 음표, 다이나믹을 보기 쉬운 형식으로 콘솔에 한 줄씩 출력 (출력 예시
    • 오른손: [C4] | [F4] | [A5] | [B4]
    • 왼손    : [C2] | [D2] | [C2] | [D2]
    • 다이나믹: p  |   p   |     p   |  f
  • 출력물을 한 줄에 BEATS_PER_LINE(상수)씩. 한 마디를 MAX_NOTE_WIDTH(상수)로 설정

5. 보너스 기능 (BonusFeatures)

  • public, static, if 등 특정 자바 키워드를 인식하고, 이를 단음이 아닌 화음(예: "C4-E4-G4")으로 변환
  • 키워드를 변환했을 경우, 해당 키워드의 길이만큼 문자열을 건너뛰고 다음 변환을 진행
  • 왼손을 기준으로 대문자에서 소문자로 p(piano) , 소문자에서 대문자로 f(forte) 다이나믹 기능 추가
  • java.sound.midi 학습 후 CodeMusicGame과 연결

예외 처리

  • 빈칸으로 입력 시 [ERROR] 출력 후, 다시 입력받기
  • 템포 입력시 1~9 가 아닐경우 [ERROR] 출력 후, 다시 입력받기

테스트 목록

단위 테스트

  • Validator

    • 오른손/왼손 : 빈 값 입력 시 예외 발생 검증
    • 템포 : 1~9 정수만 받도록 설정
  • Note (Enum)

    • 알파벳(a~y) 입력 시 정상적인 음표 상수 반환 검증
    • 예외 문자(z, 특수문자 등) 입력 시 REST 반환 검증
    • 왼손/오른손 MIDI 번호 계산 로직 검증
  • Chord (Enum)

    • 문자열 시작 부분의 키워드(public 등) 매칭 여부 검증
    • 매칭된 키워드에 따른 화음(MIDI 배열) 생성 검증

통합 테스트

  • CodeMusicGame
    • 긴 오른손 코드와 짧은 왼손 코드 입력 시, 왼손의 반복(Loop) 동작 검증
    • 키워드 포함 문자열 입력 시, 인덱스 스킵 및 화음 생성 동작 검증
    • 대소문자 섞인 입력에 대한 다이나믹(p, f) 리스트 생성 검증
    • 오른손과 왼손이 모두 쉼표(REST)일 경우 악보에 추가하지 않고 건너뛰는지 검증

핵심 학습 내용/고민한 점

  • 메서드 분리를 통한 가독성 향상: 초기 구현 단계에서는 CodeMusicGamestart() 메서드 안에 입력, 변환, 출력, 연주 로직이 모두 들어있어 코드가 매우 길었습니다. 이를 해결하기 위해 processChord, processSingleNote, addScoreIfValid와 같이 기능 단위로 메서드를 분리했습니다. 결과적으로 모든 메서드가 15라인 이하로 유지되었고, 각 메서드가 하나의 역할만 수행하게 되어 코드의 의도를 파악하기 훨씬 쉬워졌습니다.
  • else 예약어 제거와 클린 코드: 2,3주차 요구사항이자 피드백이었던 else 사용 금지를 실천하기 위해 노력했습니다. 분기 처리가 필요한 곳에서 if (...) return; 패턴을 적극적으로 사용하여 불필요한 들여쓰기를 줄이고 로직을 단순화했습니다.
  • Enum의 활용과 책임 분리: 문자열을 음표로 변환하는 복잡한 로직을 CodeMusicGame에서 처리하지 않고, NoteChord라는 Enum에게 위임했습니다. Chord Enum은 자신의 키워드(public 등)를 알고 있고, 화음을 생성하는 계산 로직까지 스스로 담당합니다. 덕분에 메인 로직은 단순히 Enum에게 질문하고 결과를 받기만 하면 되는 깔끔한 구조가 되었습니다.
  • 상수 관리 (Messages): 코드 곳곳에 흩어져 있던 출력 메시지와 에러 메시지를 Messages 클래스로 모아 상수로 관리했습니다. 이를 통해 매직 스트링을 제거하고, 문구 수정이 필요할 때 한 곳만 수정하면 되는 유지보수성을 확보했습니다.

어려웠던 점 및 해결 과정

1. 도메인(음악)을 코드로 구현하는 난관 (Java Sound API & 음악 이론)

  • java.sound.midi 학습: MIDI 시스템이 낯설어 소리를 내는 기본 구조(Synthesizer, Channel)를 이해하는 데 시간이 걸렸습니다. 공식 문서를 참고하여 NoteOn, NoteOff 메시지로 음을 제어하는 MidiPlayer를 구현해냈습니다.
  • 12음계와 26개 알파벳의 매핑: 음악은 12음계인데 알파벳은 26개라 1:1 매핑이 불가능했습니다. 이를 해결하기 위해 옥타브를 확장(C4~C6)하여 25개 음을 배치하고, 남은 문자와 특수문자는 쉼표(REST)로 처리하는 규칙을 세워 논리적인 모순을 해결했습니다.
  • 화음과 박자 구성: 단순히 음을 나열하는 것을 넘어 음악답게 들리게 하려면 화음과 리듬이 필요했습니다. Chord Enum을 만들어 장조/단조 규칙에 따라 3화음을 계산하도록 로직을 구현하고, 스레드( Thread.sleep)를 활용해 템포를 조절하는 방식으로 템포를 구현했습니다.

2. 객체지향 설계와 책임 분배

  • 역할과 책임의 분리: 처음에는 CodeMusicGame 클래스가 입력, 계산, 연주를 모두 담당하여 비대했습니다. 이를 해결하기 위해 InputView(입력), Validator(검증), Note/Chord(변환 로직), MidiPlayer(연주)로 클래스를 분리하여 단일 책임 원칙(SRP) 을 지키려 노력했습니다.
  • Enum의 재발견: 단순히 상수를 나열하는 것을 넘어, NoteChord Enum이 직접 MIDI 번호를 계산하고 키워드를 찾는 기능(메서드)을 갖도록 설계했습니다. 덕분에 메인 비즈니스 로직이 훨씬 간결해졌습니다.

3. 코드 리펙토링과 테스트의 한계 극복

  • 15라인 제한 준수: 메서드가 길어지는 것은 "너무 많은 일을 하고 있다"는 신호라는 것을 배웠습니다. while문 내부의 복잡한 분기 처리를 processChord, processSingleNote 등으로 잘게 쪼개어 가독성을 높이고 제약 사항을 준수했습니다.
  • 테스트하기 어려운 코드 분리: CodeMusicGame 내부에서 Random을 직접 사용하여 결과를 생성할 경우, 매번 결과가 달라져 단위 테스트가 불가능했습니다. 이를 해결하기 위해 랜덤을 결정하는 영역과 계산하는 영역을 분리했습니다.
    • Chord Enum은 스스로 랜덤을 생성하지 않고, 외부에서 주입된 rootNoteisMajor(장조 여부) 값에 따라 순수하게 계산만 수행하도록 설계했습니다. 덕분에 Chord의 로직을 100% 테스트할 수 있었습니다.

설계 과정

  • InputView (입력 및 재시도): 3주차 미션에서 사용한 while(true) - try - catch 패턴을 적용하여, 사용자가 잘못된 값을 입력하더라도 프로그램이 종료되지 않고 에러 메시지 출력 후 다시 입력받도록 설계했습니다.
  • Validator (검증): 입력값의 빈 문자열 여부, 템포 범위 확인 등의 검증 로직을 별도 클래스로 분리하여 InputView가 검증 책임까지 떠안지 않도록 했습니다.
  • CodeMusicGame (지휘자): 이 클래스는 직접 복잡한 계산을 수행하기보다, InputView에서 입력을 받고, Note/Chord를 통해 악보(MusicScore)를 만들고, MidiPlayer에게 연주를 지시하는 역할을 담당하도록 설계했습니다.
  • MidiPlayer (연주): Java Sound API를 사용하는 로직은 복잡하고 예외 처리가 필요하므로 별도 클래스로 격리했습니다. 외부에서는 단순히 play()만 호출하면 내부적으로 소리를 재생하도록 캡슐화했습니다.

About

[우테코 오픈 미션] 자바 코드뮤직

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages