오늘 하루종일 AI음악 삽질 6개
커스텀 음악 생성기를 만들기 위해서 리버스 엔지니어링까지 하고 실패해서
다 때려치우려다가 마지막 6번째 어쨋든 성공한 이야기
# MusicLang Injection Attempts (의도 주입 시도 기록)
> 2026-01-07 작성
> MusicLang v2 모델에 사용자 의도(화성, 악기, 분위기)를 주입하려는 모든 시도와 결과를 기록합니다.
---
## 🎯 목표
"Dr. Lacto의 감정 상태에 따라 음악 스타일을 동적으로 제어하기"
- 긴장 상태 → 디스토션 기타, 빠른 템포, 긴장된 화성 (Em-F 반복)
- 평온 상태 → 피아노, 느린 템포, 안정된 화성 (F-C-Am-G)
- 중립 상태 → 자유로운 생성 (C Major)
---
## 📋 시도 목록 및 결과
### ❌ 시도 1: Score 객체 주입 (Seed Injection)
방법: musiclang.library를 사용하여 화성 진행 객체 생성 후 predict(prompt=score) 주입
코드:
```python
from musiclang.library import *
# C Major -> G Major 진행
seed_prompt = (I % I.M) + (V % I.M)
score = predictor.predict(seed_prompt, nb_tokens=100)
```
결과: ❌ 실패
```
ValueError: Invalid token [UNK]
```
원인:
- musiclang 라이브러리가 생성한 Score 객체의 내부 토큰 포맷과 musiclang-predict 모델의 토크나이저 간 버전 불일치
- 모델이 기대하는 토큰 형식instrument__note 형태)과 라이브러리가 생성한 기호가 호환되지 않음
에러 위치:
```
File "musiclang_predict/tokenizers/tokenizer.py", line 794
key, value = token.split('__') # '__'가 없는 토큰이 들어와서 실패
```
---
### ❌ 시도 2: 문자열 화성 주입 predict_chords)
방법: predict_chords("Am C G F") 메서드 사용
코드:
```python
chord_str = "Am C G F"
score = ml.predict_chords(chord_str, time_signature=(4, 4), nb_tokens=100)
```
결과: ❌ 실패
```
Exception: Unknown chord
```
원인:
- predict_chords 메서드가 문자열을 파싱하는 과정에서 chord_repr_to_chord 함수가 "Am", "C" 등을 인식하지 못함
- 내부 파서가 특정 포맷(아마도 MusicLang 전용 표기법)을 기대하는데, 일반적인 코드 이름을 이해하지 못함
---
### ❌ 시도 3: MIDI 파일 로드 후 주입 (Trojan Horse)
방법: Rust로 MIDI 파일 생성 → Score.from_midi() 로드 → predict(prompt=loaded_score) 주입
코드:
```python
# Rust에서 생성한 MIDI 로드
loaded_score = Score.from_midi("/tmp/rust_seed.mid")
score = ml.predict(loaded_score, nb_tokens=200)
```
결과: ❌ 실패
```
AttributeError: type object '_PurePosixPath' has no attribute '_from_parts'
```
원인:
- Score.from_midi()가 의존하는 partitura 라이브러리의 하위 의존성 xmlschema가 Python 3.12와 호환되지 않음
- pathlib 내부 API 변경_from_parts → _load_parts)을 오래된 라이브러리가 따라가지 못함
- 라이브러리 생태계 붕괴: MIDI 파일 자체는 성공적으로 생성되었으나, MusicLang이 이를 읽어들일 수 없음
---
### ❌ 시도 4: Transformers 직접 사용
방법: MusicLangPredictor 우회, transformers.AutoModelForCausalLM 직접 로드
코드:
```python
from transformers import AutoModelForCausalLM, AutoTokenizer
tokenizer = AutoTokenizer.from_pretrained("musiclang/musiclang-v2")
model = AutoModelForCausalLM.from_pretrained("musiclang/musiclang-v2")
```
결과: ⚠️ 부분 성공 / 최종 실패
성공 부분:
- ✅ Tokenizer 로드 성공 (Vocab size: 484)
- ✅ Vocab 내용 확인 성공:
- 129개 악기 토큰 INSTRUMENT_NAME__bassoon, INSTRUMENT_NAME__distortion_guitar 등)
- 23개 화성 토큰 CHORD_DEGREE__5, CHORD_EXTENSION__7 등)
실패 부분:
```
ValueError: Unrecognized model in musiclang/musiclang-v2.
Should have a model_type key in its config.json
```
원인:
- MusicLang v2는 커스텀 아키텍처라서 Hugging Face의 표준 Auto 클래스로 로드 불가
- config.json에 model_type 필드가 없어서 transformers가 어떤 모델 클래스를 사용해야 할지 모름
---
### ✅ 시도 5: 소스 코드 패치 (Prefix Token Injection)
방법: musiclang_predict 패키지 소스 수정, predict() 메서드에 prefix_tokens 파라미터 추가
코드:
```python
# 패치 적용
python patch_musiclang.py
# 사용
score = ml.predict(
nb_tokens=100,
temperature=0.9,
prefix_tokens=[185] # INSTRUMENT_NAME__bassoon
)
```
결과: ✅ 기술적 성공 / 실질적 효과 미미
성공 부분:
- ✅ 패치 적용 성공
- ✅ prefix_tokens 파라미터 인식
- ✅ 에러 없이 음악 생성
실패 부분:
- ❌ 음악 스타일에 큰 변화 없음
- Token ID 185 (bassoon)과 Token ID 149 (tenor_sax) 주입 시 청각적 차이 거의 없음
원인:
- Prefix token은 1600개 토큰 중 첫 1개만 지정
- 나머지 1599개는 모델이 자체 학습 패턴을 따라 생성
- Temperature 0.95로 높아서 모델이 prefix를 무시하고 창의적으로 생성
- Prefix token이 "이 악기만 써라"가 아니라 "이 악기가 등장할 수 있다" 정도의 약한 힌트일 가능성
---
### 🏆 시도 6: Post-Hoc Harmonic Projection (최종 해결책)
"내부에 개입할 수 없다면, 결과를 덮어쓴다"
모델 내부의 토큰 생성 과정에 개입하는 것이 불가능하다면, 생성된 결과물(MIDI)을 우리가 원하는 화성으로 강제 매핑하는 발상의 전환.
방법:
1. MusicLang은 리듬, 프레이즈, 악기 배치 등 "음악적 뼈대"만 자유롭게 생성하도록 둠 (Clean Version)
2. Rust에서 생성된 MIDI 파일을 바이트 레벨에서 파싱 harmonizer.rs)
3. Dr. Lacto의 감정에 따른 Target Chord Progression 정의
4. 각 마디(Bar)의 모든 Note를 Target Chord의 구성음(Chord Tone) 중 가장 가까운 음으로 Pitch Snap
코드 (Rust):
```rust
// Mood에 따른 Chord 정의
match mood {
Mood::Peace => match bar % 4 {
0 => chord("F"),
1 => chord("C"),
2 => chord("Am"),
3 => chord("G"),
},
Mood::Tense => match bar % 2 {
0 => chord("Em"),
1 => chord("F"),
}
}
// Pitch Snap 로직
fn snap_to_chord(pitch: u8, chord: &Chord) -> u8 {
// 가장 가까운 Chord Tone으로 이동
// 예: C 코드(C,E,G)에서 F#이 나오면 G로 이동
}
```
결과: ⭐⭐⭐⭐⭐ 대성공
장점:
1. 100% 확실한 효과: 음 높이를 직접 변경하므로 모델이 무시할 수 없음
2. 의존성 제로: MIDI 표준 포맷만 사용하므로 라이브러리 버전 문제 없음
3. 완벽한 역할 분담:
- AI (MusicLang): 창의성, 리듬, 연주 기법 담당
- Rule-based (Rust): 전체적인 분위기, 조성, 화성 진행 제어
4. 시스템 가치 100배 상승: 단순 "자동 재생기"에서 "상황 반응형 AI 작곡가"로 진화
---
## 📊 시도 요약표
| 시도 | 방법 | 기술적 성공 | 실질적 효과 | 주요 장애물 |
|------|------|------------|------------|------------|
| 1. Score 객체 주입 | predict(prompt=score) | ❌ | - | 토크나이저 버전 불일치 |
| 2. 문자열 화성 | predict_chords("Am C") | ❌ | - | 파서 호환성 문제 |
| 3. MIDI 로드 | Score.from_midi() | ❌ | - | Python 3.12 의존성 충돌 |
| 4. Transformers 직접 | AutoModelForCausalLM | ⚠️ (Tokenizer만) | - | 커스텀 모델 아키텍처 |
| 5. 소스 패치 | prefix_tokens 추가 | ✅ | ❌ | 모델이 prefix 무시 |
| 6. Post-Hoc Projection | MIDI 후처리 (Rust) | ✅ | ⭐⭐⭐⭐⭐ | 없음 (해결됨) |
---
## 💡 최종 교훈
1. "문제를 재정의하라"
- "모델에 의도를 주입해야 한다"는 고정관념에서 벗어나, "모델의 출력을 수정하면 된다"로 문제를 재정의했을 때 해결책이 보임.
2. "되는 기술을 조합하라"
- 최신 초거대 모델(MusicGen 등)을 N100에서 돌리려는 무리수 대신, 가벼운 MusicLang과 강력한 Rust 로직을 결합하여 더 효율적이고 제어 가능한 시스템 구축.
3. "하이브리드 AI의 힘"
- AI(확률적 생성)와 Rule(결정적 제어)의 조화가 순수 AI보다 게임/미디어 환경에서 훨씬 강력함.
---
결론: 우리는 수많은 실패 끝에 MusicLang의 내부를 뜯어고치는 대신, 그 결과를 우리의 의도대로 조각하는 방법을 찾아냈습니다. 이것이 바로 엔지니어링의 승리입니다.