오디오 AI Hello World — 사전 훈련 모델로 5분 만에 해보는 7가지 프로젝트
pip install 한 줄 + 코드 3줄로 음원 분리, 음성 인식, TTS, 피치 감지, 음악 생성까지
전부 직접 돌릴 수 있는 코드다. Python 3.10+ 필요.
0. 근본부터 — PyTorch로 직접 구현하는 오디오 분리 파이프라인
CLI 도구를 호출하는 건 "사용"이지 "이해"가 아니다. 실제로 Demucs나 Mel-Band RoFormer 안에서 일어나는 일을 PyTorch로 직접 짜보면 이렇다.
핵심 흐름:
raw audio → STFT → 스펙트로그램 → 신경망(마스크 예측) → 원본 × 마스크 → ISTFT → 분리된 audio
Step 1 — 오디오 로드:
import torch
import torchaudio
wav, sr = torchaudio.load("song.mp3") # [channels, samples]
print(f"채널: {wav.shape[0]}, 샘플: {wav.shape[1]}, 샘플레이트: {sr}")
# → 채널: 2, 샘플: 4410000, 샘플레이트: 44100 (= 100초 스테레오)
wav는 1D 파형이다. x축이 시간, y축이 진폭(-1.0~1.0). 이 상태로는 "어디가 보컬이고 어디가 드럼인지" 알 수 없다.
Step 2 — STFT로 주파수 분해:
n_fft = 2048
hop_length = 441 # 44100/441 = 100fps
window = torch.hann_window(n_fft)
# 파형 → 복소 스펙트로그램
spec = torch.stft(wav, n_fft=n_fft, hop_length=hop_length,
window=window, return_complex=True)
print(f"스펙트로그램: {spec.shape}")
# → [2, 1025, 10001] (2채널, 1025 주파수빈, 10001 시간프레임)
# 크기(magnitude)와 위상(phase) 분리
magnitude = spec.abs() # 어떤 주파수가 얼마나 강한지
phase = spec.angle() # 파형의 위상 (복원에 필요)
1025개 주파수 빈은 0Hz~22050Hz를 커버한다. magnitude[채널, 100, 500]이면 "500번째 시간프레임에서 100번째 주파수 빈의 강도"다.
Step 3 — 신경망으로 마스크 예측:
이게 핵심이다. Demucs든 RoFormer든, 모델이 하는 일은 결국 "각 주파수×시간 위치가 보컬인 확률"을 나타내는 마스크를 출력하는 것이다.
가장 단순한 마스크 예측 네트워크:
import torch.nn as nn
class SimpleVocalMask(nn.Module):
"""가장 단순한 음원 분리 — 학습 없이 구조만 이해하기 위한 코드"""
def __init__(self, freq_bins=1025, hidden=512):
super().__init__()
self.net = nn.Sequential(
nn.Linear(freq_bins, hidden),
nn.ReLU(),
nn.Linear(hidden, hidden),
nn.ReLU(),
nn.Linear(hidden, freq_bins),
nn.Sigmoid() # 0~1 마스크 출력
)
def forward(self, magnitude):
# magnitude: [batch, channels, freq_bins, time_frames]
# 시간 프레임마다 주파수 벡터를 입력으로 마스크를 예측
mask = self.net(magnitude.permute(0, 1, 3, 2)) # → [B, C, T, F]
return mask.permute(0, 1, 3, 2) # → [B, C, F, T]
model = SimpleVocalMask()
mask = model(magnitude.unsqueeze(0)) # 배치 차원 추가
print(f"마스크: {mask.shape}, 범위: {mask.min():.3f}~{mask.max():.3f}")
# → 마스크: [1, 2, 1025, 10001], 범위: 0.023~0.977
이 마스크의 의미: 값이 1에 가까우면 "이 주파수·시간은 보컬", 0에 가까우면 "보컬이 아님".
실제 Demucs나 RoFormer는 이 SimpleVocalMask 자리에 수천만 파라미터의 Transformer가 들어간다. 근데 역할은 동일하다 — 마스크를 만드는 것.
Step 4 — 마스크 적용으로 소스 분리:
# 보컬 = 원본 × 마스크
vocal_magnitude = magnitude * mask.squeeze(0)
# 반주 = 원본 × (1 - 마스크)
instrumental_magnitude = magnitude * (1 - mask.squeeze(0))
# 위상은 원본 그대로 사용 (마스크는 크기만 조절)
vocal_spec = vocal_magnitude * torch.exp(1j * phase)
instrumental_spec = instrumental_magnitude * torch.exp(1j * phase)
마스크가 정확하면 vocal_spec에는 보컬 주파수만, instrumental_spec에는 반주 주파수만 남는다.
Step 5 — ISTFT로 파형 복원:
# 스펙트로그램 → 파형
vocals_wav = torch.istft(vocal_spec, n_fft=n_fft, hop_length=hop_length,
window=window, length=wav.shape[1])
instrumental_wav = torch.istft(instrumental_spec, n_fft=n_fft, hop_length=hop_length,
window=window, length=wav.shape[1])
# 저장
torchaudio.save("vocals.wav", vocals_wav, sr)
torchaudio.save("instrumental.wav", instrumental_wav, sr)
print("분리 완료!")
전체 흐름 요약:
song.mp3
↓ torchaudio.load()
wav [2, 4410000] ← raw 파형 (시간 도메인)
↓ torch.stft()
spec [2, 1025, 10001] ← 복소 스펙트로그램 (주파수 도메인)
↓ .abs() / .angle()
magnitude + phase ← 크기와 위상 분리
↓ model(magnitude)
mask [2, 1025, 10001] ← 보컬 마스크 (0~1)
↓ magnitude × mask
vocal_magnitude ← 보컬 주파수만 남음
↓ × exp(j × phase)
vocal_spec ← 복소 스펙트로그램 복원
↓ torch.istft()
vocals.wav ← 분리된 보컬 (시간 도메인)
이 SimpleVocalMask는 훈련을 안 했으니 랜덤 마스크를 출력한다 — 당연히 결과가 엉망이다. 근데 구조는 Demucs와 동일하다. Demucs가 80MB, RoFormer가 913MB인 이유는 이 마스크를 더 정확하게 예측하기 위해 더 크고 정교한 신경망을 쓰기 때문이다.
실제 모델과의 차이
| SimpleVocalMask (위 코드) | Demucs htdemucs | Mel-Band RoFormer | |
|---|---|---|---|
| 마스크 예측 네트워크 | Linear 3층 | 시간+주파수 하이브리드 Transformer | 멜 밴드 계층적 Transformer |
| 파라미터 수 | ~1.5M | 26.3M | 84.2M |
| 주파수 분해 | 단순 STFT | STFT + 시간 도메인 병렬 | STFT + 멜 밴드 60개 |
| 마스크 타입 | 실수 (0~1) | 복소 마스크 | 복소 마스크 |
| 청킹/오버랩 | 없음 | ~10초 청크, 25% 오버랩 | ~11초 청크, 4× 오버랩 |
핵심 차이: 실제 모델은 복소 마스크(크기+위상 모두 예측)를 쓰고, 오디오를 청크로 나눠서 처리하고, 오버랩-애드로 이어붙인다. 근데 뼈대는 위 코드와 같다.
1. 음원 분리 — 노래에서 보컬 빼기
모델: Demucs htdemucs (Meta, ~80MB)
기대 결과: MP3 하나 넣으면 vocals.wav + no_vocals.wav 2개가 나온다
pip install demucs torchcodec
demucs song.mp3 --two-stems vocals
# → separated/htdemucs/song/vocals.wav, no_vocals.wav
코드로 하려면:
import subprocess
subprocess.run(["demucs", "song.mp3", "--two-stems", "vocals"])
다른 모델 옵션:
| 모델 | 명령 | 결과 |
|------|------|------|
| htdemucs | demucs song.mp3 | 4-스템 (보컬/드럼/베이스/기타) |
| htdemucs | demucs song.mp3 --two-stems vocals | 2-스템 (보컬/반주) |
| htdemucs_6s | demucs song.mp3 -n htdemucs_6s | 6-스템 (+피아노, 기타 분리) |
| UVR Karaoke | audio-separator song.mp3 --model_filename mel_band_roformer_karaoke_aufr33_viperx_sdr_10.1956.ckpt | 2-스템 (리드 보컬/카라오케 반주, 코러스 유지) |
2. 음성 인식(STT) — 음성을 텍스트로
모델: OpenAI Whisper large-v3 (~3GB) 또는 tiny (~75MB)
기대 결과: 오디오 파일 넣으면 텍스트 + 타임스탬프가 나온다
pip install openai-whisper
import whisper
model = whisper.load_model("tiny") # tiny=75MB, base=142MB, large-v3=3GB
result = model.transcribe("audio.mp3")
print(result["text"])
# → "안녕하세요 오늘 날씨가 좋네요"
for seg in result["segments"]:
print(f"[{seg['start']:.1f}s ~ {seg['end']:.1f}s] {seg['text']}")
# → [0.0s ~ 2.3s] 안녕하세요
# → [2.3s ~ 4.1s] 오늘 날씨가 좋네요
다른 모델 옵션:
| 모델 | 크기 | 속도 | 품질 |
|------|------|------|------|
| tiny | 75 MB | 매우 빠름 | 낮음 |
| base | 142 MB | 빠름 | 보통 |
| small | 466 MB | 보통 | 좋음 |
| medium | 1.5 GB | 느림 | 매우 좋음 |
| large-v3 | 3 GB | 매우 느림 | 최고 |
3. 음성 합성(TTS) — 텍스트를 음성으로
모델: Coqui TTS XTTS-v2 (~1.8GB)
기대 결과: 텍스트 넣으면 자연스러운 음성 WAV가 나온다. 참조 음성으로 목소리 클론도 가능.
pip install TTS
from TTS.api import TTS
tts = TTS("tts_models/multilingual/multi-dataset/xtts_v2")
tts.tts_to_file(
text="안녕하세요, 오디오 AI의 세계에 오신 것을 환영합니다.",
language="ko",
file_path="output.wav"
)
# → output.wav (한국어 음성)
# 목소리 클론 (참조 음성 6초 이상 필요)
tts.tts_to_file(
text="이 목소리로 말해보세요.",
speaker_wav="reference_voice.wav",
language="ko",
file_path="cloned.wav"
)
더 가벼운 대안:
pip install pyttsx3 # 오프라인, 설치 즉시 작동, 품질 낮음
import pyttsx3
engine = pyttsx3.init()
engine.say("Hello World")
engine.runAndWait()
4. 피치 감지 — 음의 높낮이 추출
모델: CREPE (TensorFlow, ~25MB) 또는 pyin (librosa, 모델 불필요)
기대 결과: 오디오에서 시간별 주파수(Hz)와 음이름이 나온다
pip install librosa numpy
import librosa
import numpy as np
y, sr = librosa.load("vocal.wav", sr=22050)
# pyin 알고리즘으로 피치 추출 (모델 다운로드 불필요)
f0, voiced_flag, voiced_probs = librosa.pyin(
y, fmin=80, fmax=1000, sr=sr
)
# Hz → 음이름 변환
times = librosa.times_like(f0, sr=sr)
for t, freq in zip(times[::10], f0[::10]): # 10프레임마다
if not np.isnan(freq):
note = librosa.hz_to_note(freq)
print(f"{t:.2f}s: {freq:.1f}Hz ({note})")
# → 0.50s: 440.0Hz (A4)
# → 0.60s: 493.9Hz (B4)
5. 음악 태깅 — 장르/무드/악기 자동 분류
모델: MERT (HuggingFace, ~1.2GB)
기대 결과: 오디오 넣으면 장르(rock, jazz, pop...), 무드(happy, sad...), 악기(guitar, piano...) 태그가 나온다
pip install transformers librosa
from transformers import pipeline
# 사전 훈련된 오디오 분류 모델
classifier = pipeline(
"audio-classification",
model="MIT/ast-finetuned-audioset-10-10-0.4593"
)
result = classifier("audio.wav")
for item in result[:5]:
print(f"{item['label']}: {item['score']:.2%}")
# → Music: 95.23%
# → Speech: 3.12%
# → Singing: 1.45%
6. 소리 분류 — 이게 무슨 소리인지
모델: AST (Audio Spectrogram Transformer, MIT, ~350MB)
기대 결과: 환경음 넣으면 "개 짖는 소리", "비 오는 소리", "자동차 경적" 등 527개 카테고리로 분류
from transformers import pipeline
classifier = pipeline(
"audio-classification",
model="MIT/ast-finetuned-audioset-10-10-0.4593"
)
result = classifier("dog_bark.wav")
print(result[0])
# → {'label': 'Dog', 'score': 0.9834}
위의 음악 태깅과 같은 모델인데, 입력에 따라 다른 결과가 나온다. AudioSet 527개 클래스를 커버한다.
7. 음악 생성 — 텍스트로 음악 만들기
모델: MusicGen (Meta, small=300MB ~ large=3.3GB)
기대 결과: "upbeat electronic dance music" 같은 프롬프트 넣으면 음악이 생성된다
pip install transformers scipy
from transformers import pipeline
import scipy
generator = pipeline(
"text-to-audio",
model="facebook/musicgen-small" # 300MB
)
music = generator(
"upbeat electronic dance music with heavy bass",
forward_params={"max_new_tokens": 256} # ~5초
)
scipy.io.wavfile.write(
"generated.wav",
rate=music["sampling_rate"],
data=music["audio"][0].T
)
# → generated.wav (AI가 만든 음악)
사전 훈련 모델 정리
| 용도 | 모델 | 크기 | pip 패키지 | 난이도 |
|---|---|---|---|---|
| 음원 분리 (범용) | Demucs htdemucs | 80 MB | demucs |
쉬움 |
| 음원 분리 (카라오케) | UVR Mel-Band RoFormer | 913 MB | audio-separator |
쉬움 |
| 음성 인식 (STT) | Whisper tiny~large | 75MB~3GB | openai-whisper |
쉬움 |
| 음성 합성 (TTS) | XTTS-v2 | 1.8 GB | TTS |
보통 |
| 피치 감지 | librosa pyin | 0 MB | librosa |
쉬움 |
| 소리 분류 | AST (MIT) | 350 MB | transformers |
쉬움 |
| 음악 생성 | MusicGen (Meta) | 300MB~3.3GB | transformers |
쉬움 |
| 음성 클론 | GPT-SoVITS | ~1GB | 별도 설치 | 어려움 |
가장 간단한 첫 프로젝트 추천
"내 노래 노래방으로 만들기" — Demucs 한 줄이면 된다:
pip install demucs torchcodec && demucs my_song.mp3 --two-stems vocals
"회의 녹음 텍스트로 변환" — Whisper 3줄:
import whisper
model = whisper.load_model("base")
print(model.transcribe("meeting.mp3")["text"])
"텍스트를 음성으로 읽기" — Coqui TTS 3줄:
from TTS.api import TTS
tts = TTS("tts_models/multilingual/multi-dataset/xtts_v2")
tts.tts_to_file(text="안녕하세요", language="ko", file_path="hello.wav")
전부 GPU 없이 CPU에서 돌아간다. 느리지만 결과는 나온다.
핵심 개념
음원 분리 — pip install demucs + 1줄 명령으로 보컬/반주 분리
음성 인식 — Whisper 모델 로드 + transcribe() 한 줄로 텍스트 변환
음성 합성 — Coqui TTS로 텍스트 → 자연스러운 음성 WAV 생성
피치 감지 — librosa.pyin()으로 모델 없이 실시간 음높이 추출
음악 생성 — MusicGen에 텍스트 프롬프트 넣으면 음악이 나온다