Spaces:
Running
on
A10G
Running
on
A10G
| """ | |
| Audio Effect Chain | |
| ================== | |
| ์ค์ ์ค๋์ค์ ์ดํํธ๋ฅผ ์ ์ฉํ๋ ์ฒ๋ฆฌ ์ฒด์ธ | |
| pedalboard ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์ฌ์ฉ (Spotify์์ ๋ง๋ ์ค๋์ค ํ๋ฌ๊ทธ์ธ ๋ผ์ด๋ธ๋ฌ๋ฆฌ) | |
| - ๊ณ ํ์ง VST ์์ค์ ์ดํํธ | |
| - Python์์ ์ฝ๊ฒ ์ฌ์ฉ ๊ฐ๋ฅ | |
| - ์ค์๊ฐ ์ฒ๋ฆฌ๋ ๊ฐ๋ฅ | |
| """ | |
| import numpy as np | |
| from pathlib import Path | |
| from typing import Dict, Any, List | |
| import soundfile as sf | |
| # pedalboard - ์ค๋์ค ์ดํํธ ๋ผ์ด๋ธ๋ฌ๋ฆฌ | |
| from pedalboard import ( | |
| Pedalboard, | |
| Compressor, | |
| Gain, | |
| LowShelfFilter, | |
| HighShelfFilter, | |
| PeakFilter, | |
| Delay, | |
| Reverb, | |
| Distortion, | |
| Limiter, | |
| HighpassFilter, | |
| LowpassFilter | |
| ) | |
| from pedalboard.io import AudioFile | |
| class EffectChain: | |
| """์ค๋์ค ์ดํํธ ์ฒ๋ฆฌ ์ฒด์ธ""" | |
| AVAILABLE_EFFECTS = [ | |
| "eq_lowshelf", | |
| "eq_highshelf", | |
| "eq_peak1", | |
| "eq_peak2", | |
| "compressor", | |
| "distortion", | |
| "delay", | |
| "reverb", | |
| "limiter" | |
| ] | |
| def __init__(self): | |
| """์ดํํธ ์ฒด์ธ ์ด๊ธฐํ""" | |
| pass | |
| def get_available_effects(self) -> List[str]: | |
| """์ฌ์ฉ ๊ฐ๋ฅํ ์ดํํธ ๋ชฉ๋ก ๋ฐํ""" | |
| return self.AVAILABLE_EFFECTS.copy() | |
| def process( | |
| self, | |
| input_path: str, | |
| output_path: str, | |
| parameters: Dict[str, float] | |
| ) -> None: | |
| """ | |
| ์ค๋์ค ํ์ผ์ ์ดํํธ ์ฒด์ธ ์ ์ฉ | |
| Args: | |
| input_path: ์ ๋ ฅ ์ค๋์ค ํ์ผ ๊ฒฝ๋ก | |
| output_path: ์ถ๋ ฅ ์ค๋์ค ํ์ผ ๊ฒฝ๋ก | |
| parameters: ์ดํํฐ ํ๋ผ๋ฏธํฐ ๋์ ๋๋ฆฌ | |
| """ | |
| # ์ค๋์ค ํ์ผ ์ฝ๊ธฐ | |
| audio, sample_rate = sf.read(input_path) | |
| # ๋ชจ๋ ธ๋ฉด ์คํ ๋ ์ค๋ก ๋ณํ (์ผ๋ถ ์ดํํธ๊ฐ ์คํ ๋ ์ค ํ์) | |
| if len(audio.shape) == 1: | |
| audio = np.column_stack([audio, audio]) | |
| # float32๋ก ๋ณํ | |
| audio = audio.astype(np.float32) | |
| # ์ดํํธ ์ฒด์ธ ๊ตฌ์ฑ | |
| board = self._build_pedalboard(parameters, sample_rate) | |
| # ์ดํํธ ์ ์ฉ | |
| processed = board(audio, sample_rate) | |
| # Wet/Dry ๋ฏน์ค ์ ์ฉ | |
| wet_mix = parameters.get("final_wet_mix", 0.5) | |
| final_audio = (1 - wet_mix) * audio + wet_mix * processed | |
| # ํด๋ฆฌํ ๋ฐฉ์ง | |
| final_audio = np.clip(final_audio, -1.0, 1.0) | |
| # ์ถ๋ ฅ ํ์ผ ์ ์ฅ | |
| sf.write(output_path, final_audio, sample_rate) | |
| print(f"[EffectChain] ์ฒ๋ฆฌ ์๋ฃ: {output_path}") | |
| def _build_pedalboard( | |
| self, | |
| params: Dict[str, float], | |
| sample_rate: int | |
| ) -> Pedalboard: | |
| """ | |
| ํ๋ผ๋ฏธํฐ๋ก๋ถํฐ pedalboard ์ดํํธ ์ฒด์ธ ๊ตฌ์ฑ | |
| """ | |
| effects = [] | |
| # === EQ Section === | |
| # Low Shelf EQ | |
| if params.get("eq_lowshelf_gain", 0) != 0: | |
| effects.append( | |
| LowShelfFilter( | |
| cutoff_frequency_hz=params.get("eq_lowshelf_freq", 200), | |
| gain_db=params.get("eq_lowshelf_gain", 0), | |
| q=0.707 | |
| ) | |
| ) | |
| # High Shelf EQ | |
| if params.get("eq_highshelf_gain", 0) != 0: | |
| effects.append( | |
| HighShelfFilter( | |
| cutoff_frequency_hz=params.get("eq_highshelf_freq", 8000), | |
| gain_db=params.get("eq_highshelf_gain", 0), | |
| q=0.707 | |
| ) | |
| ) | |
| # Peak EQ 1 | |
| if params.get("eq_peak1_gain", 0) != 0: | |
| effects.append( | |
| PeakFilter( | |
| cutoff_frequency_hz=params.get("eq_peak1_freq", 1000), | |
| gain_db=params.get("eq_peak1_gain", 0), | |
| q=params.get("eq_peak1_q", 1.0) | |
| ) | |
| ) | |
| # Peak EQ 2 | |
| if params.get("eq_peak2_gain", 0) != 0: | |
| effects.append( | |
| PeakFilter( | |
| cutoff_frequency_hz=params.get("eq_peak2_freq", 3000), | |
| gain_db=params.get("eq_peak2_gain", 0), | |
| q=params.get("eq_peak2_q", 1.0) | |
| ) | |
| ) | |
| # === Dynamics Section === | |
| # Compressor | |
| threshold = params.get("compressor_threshold", -24) | |
| ratio = params.get("compressor_ratio", 4.0) | |
| if ratio > 1.0: | |
| effects.append( | |
| Compressor( | |
| threshold_db=threshold, | |
| ratio=ratio, | |
| attack_ms=params.get("compressor_attack", 5), | |
| release_ms=params.get("compressor_release", 50) | |
| ) | |
| ) | |
| # Makeup Gain | |
| makeup = params.get("compressor_makeup", 0) | |
| if makeup != 0: | |
| effects.append(Gain(gain_db=makeup)) | |
| # === Distortion Section === | |
| distortion_amount = params.get("distortion_amount", 0) | |
| if distortion_amount > 0: | |
| # pedalboard์ Distortion์ 0-100 ๋ฒ์ | |
| effects.append( | |
| Distortion(drive_db=distortion_amount * 40) # 0-1 -> 0-40dB | |
| ) | |
| # Distortion ํ ํค ์กฐ์ (Tone = LPF) | |
| tone = params.get("distortion_tone", 0.5) | |
| lpf_freq = 2000 + tone * 10000 # 2kHz ~ 12kHz | |
| effects.append( | |
| LowpassFilter(cutoff_frequency_hz=lpf_freq) | |
| ) | |
| # === Time-based Effects Section === | |
| # Delay | |
| delay_mix = params.get("delay_mix", 0) | |
| if delay_mix > 0: | |
| delay_time_ms = params.get("delay_time", 250) | |
| effects.append( | |
| Delay( | |
| delay_seconds=delay_time_ms / 1000, | |
| feedback=params.get("delay_feedback", 0.3), | |
| mix=delay_mix | |
| ) | |
| ) | |
| # Reverb | |
| reverb_wet = params.get("reverb_wet_dry", 0) | |
| if reverb_wet > 0: | |
| effects.append( | |
| Reverb( | |
| room_size=params.get("reverb_room_size", 0.5), | |
| damping=params.get("reverb_damping", 0.5), | |
| wet_level=reverb_wet, | |
| dry_level=1 - reverb_wet, | |
| width=1.0 | |
| ) | |
| ) | |
| # === Output Section === | |
| # Limiter (ํด๋ฆฌํ ๋ฐฉ์ง) | |
| effects.append( | |
| Limiter( | |
| threshold_db=-1.0, | |
| release_ms=100 | |
| ) | |
| ) | |
| return Pedalboard(effects) | |
| def process_realtime( | |
| self, | |
| audio_chunk: np.ndarray, | |
| sample_rate: int, | |
| parameters: Dict[str, float] | |
| ) -> np.ndarray: | |
| """ | |
| ์ค์๊ฐ ์ค๋์ค ์ฒญํฌ ์ฒ๋ฆฌ (์คํธ๋ฆฌ๋ฐ์ฉ) | |
| Args: | |
| audio_chunk: ์ค๋์ค ๋ฐ์ดํฐ ๋ฐฐ์ด | |
| sample_rate: ์ํ๋ ์ดํธ | |
| parameters: ์ดํํฐ ํ๋ผ๋ฏธํฐ | |
| Returns: | |
| ์ฒ๋ฆฌ๋ ์ค๋์ค ์ฒญํฌ | |
| """ | |
| if len(audio_chunk.shape) == 1: | |
| audio_chunk = np.column_stack([audio_chunk, audio_chunk]) | |
| audio_chunk = audio_chunk.astype(np.float32) | |
| board = self._build_pedalboard(parameters, sample_rate) | |
| processed = board(audio_chunk, sample_rate) | |
| wet_mix = parameters.get("final_wet_mix", 0.5) | |
| final = (1 - wet_mix) * audio_chunk + wet_mix * processed | |
| return np.clip(final, -1.0, 1.0) | |