Spaces:
Running
Running
| #!/usr/bin/env python3 | |
| """ | |
| generate_sample_audio.py - Gera os 5 arquivos de áudio de exemplo para o pipeline RVC. | |
| Uso: python generate_sample_audio.py | |
| Os arquivos serão criados em: examples/sample_outputs/ | |
| - entrada.mp3 (áudio original sintético) | |
| - entrada_acapella.mp3 (vocal extraído – simulado) | |
| - entrada_instrumental.mp3 (instrumental simulado) | |
| - saida.mp3 (RVC simulado sobre o original) | |
| - saida_acapella.mp3 (RVC simulado sobre o acapella) | |
| Também gera um arquivo de entrada em examples/sample_inputs/sample_audio.wav | |
| e um vídeo opcional (se moviepy estiver instalado). | |
| """ | |
| import os | |
| import subprocess | |
| import sys | |
| import tempfile | |
| from pathlib import Path | |
| # Tenta importar numpy e scipy; instala automaticamente se faltar | |
| try: | |
| import numpy as np | |
| from scipy.io.wavfile import write as write_wav | |
| from scipy import signal | |
| except ImportError: | |
| print("Instalando dependências necessárias (numpy, scipy)...") | |
| subprocess.check_call([sys.executable, "-m", "pip", "install", "numpy", "scipy"]) | |
| import numpy as np | |
| from scipy.io.wavfile import write as write_wav | |
| from scipy import signal | |
| # Configurações | |
| SAMPLE_RATE = 22050 # Hz | |
| DURATION_SEC = 8 # segundos | |
| BASE_DIR = Path(__file__).parent | |
| INPUTS_DIR = BASE_DIR / "sample_inputs" | |
| OUTPUTS_DIR = BASE_DIR / "sample_outputs" | |
| def generate_voice(duration_sec: float, sample_rate: int) -> np.ndarray: | |
| """ | |
| Gera um sinal de voz sintética (formantes simples + vibrato). | |
| Retorna array float32 no intervalo [-1, 1]. | |
| """ | |
| t = np.linspace(0, duration_sec, int(sample_rate * duration_sec), endpoint=False) | |
| # Frequência fundamental (voz média) | |
| f0 = 180.0 | |
| # Vibrato (modulação de frequência) | |
| vibrato_freq = 5.0 | |
| freq_mod = f0 + 10 * np.sin(2 * np.pi * vibrato_freq * t) | |
| # Gera onda com fase acumulada | |
| phase = 2 * np.pi * np.cumsum(freq_mod) / sample_rate | |
| voice = 0.7 * np.sin(phase) | |
| # Adiciona um harmônico (formante) | |
| voice += 0.3 * np.sin(2 * np.pi * 2 * f0 * t) | |
| # Envelope de amplitude (ataque e decaimento suaves) | |
| envelope = np.ones_like(t) | |
| attack_samples = int(0.1 * sample_rate) | |
| decay_samples = int(0.2 * sample_rate) | |
| envelope[:attack_samples] = np.linspace(0, 1, attack_samples) | |
| envelope[-decay_samples:] = np.linspace(1, 0, decay_samples) | |
| voice *= envelope | |
| return voice.astype(np.float32) | |
| def generate_instrumental(duration_sec: float, sample_rate: int) -> np.ndarray: | |
| """ | |
| Gera um fundo instrumental simples (acorde + ruído leve). | |
| """ | |
| t = np.linspace(0, duration_sec, int(sample_rate * duration_sec), endpoint=False) | |
| # Acorde de Dó maior (262, 330, 392 Hz) | |
| chord = (0.2 * np.sin(2 * np.pi * 262 * t) + | |
| 0.2 * np.sin(2 * np.pi * 330 * t) + | |
| 0.2 * np.sin(2 * np.pi * 392 * t)) | |
| # Ruído branco suave (simula percussão) | |
| noise = np.random.normal(0, 0.05, len(t)) | |
| # Envelope comum | |
| envelope = np.ones_like(t) | |
| attack = int(0.05 * sample_rate) | |
| envelope[:attack] = np.linspace(0, 1, attack) | |
| chord *= envelope | |
| noise *= envelope | |
| instrumental = chord + noise | |
| return instrumental.astype(np.float32) | |
| def apply_pitch_shift(audio: np.ndarray, semitones: float, sample_rate: int) -> np.ndarray: | |
| """ | |
| Aplica um deslocamento de pitch (em semitons) usando remostragem. | |
| Positivo = mais agudo, negativo = mais grave. | |
| """ | |
| if semitones == 0: | |
| return audio | |
| # Fator de remostragem: 2^(semitones/12) | |
| factor = 2 ** (semitones / 12.0) | |
| new_length = int(len(audio) / factor) | |
| shifted = signal.resample(audio, new_length) | |
| # Ajusta para o comprimento original (corta ou completa com zeros) | |
| if len(shifted) > len(audio): | |
| shifted = shifted[:len(audio)] | |
| else: | |
| shifted = np.pad(shifted, (0, len(audio) - len(shifted)), mode='constant') | |
| return shifted.astype(audio.dtype) | |
| def save_audio(file_path: Path, audio_array: np.ndarray, sample_rate: int = SAMPLE_RATE) -> None: | |
| """ | |
| Salva um array como arquivo MP3 usando ffmpeg (via arquivo WAV temporário). | |
| """ | |
| # Salva WAV temporário | |
| with tempfile.NamedTemporaryFile(suffix=".wav", delete=False) as tmp_wav: | |
| tmp_wav_path = tmp_wav.name | |
| write_wav(tmp_wav_path, sample_rate, audio_array) | |
| # Converte para MP3 com ffmpeg | |
| cmd = [ | |
| "ffmpeg", "-y", "-i", tmp_wav_path, | |
| "-acodec", "libmp3lame", "-b:a", "192k", | |
| str(file_path) | |
| ] | |
| subprocess.run(cmd, check=True, capture_output=True) | |
| # Remove o WAV temporário | |
| os.unlink(tmp_wav_path) | |
| print(f" ✅ {file_path.name}") | |
| def main(): | |
| print("=" * 60) | |
| print("🎵 Gerando arquivos de áudio de exemplo para o RVC Full Suite") | |
| print("=" * 60) | |
| # Criar diretórios | |
| INPUTS_DIR.mkdir(parents=True, exist_ok=True) | |
| OUTPUTS_DIR.mkdir(parents=True, exist_ok=True) | |
| print(f"📁 Diretório de entrada: {INPUTS_DIR}") | |
| print(f"📁 Diretório de saída: {OUTPUTS_DIR}") | |
| # 1. Gerar áudio sintético (voz + instrumental) | |
| print("\n🎤 Gerando áudio sintético...") | |
| voice = generate_voice(DURATION_SEC, SAMPLE_RATE) | |
| instrumental = generate_instrumental(DURATION_SEC, SAMPLE_RATE) | |
| # 2. Misturar para obter o áudio original (entrada) | |
| # Normaliza para evitar clipping | |
| original = (voice + instrumental) / 1.2 | |
| original = np.clip(original, -1.0, 1.0) | |
| # Salvar entrada original como WAV (para referência) e MP3 | |
| input_wav = INPUTS_DIR / "sample_audio.wav" | |
| write_wav(str(input_wav), SAMPLE_RATE, original) | |
| print(f"✅ Áudio original salvo em: {input_wav}") | |
| # 3. Gerar os 5 arquivos de saída | |
| print("\n📦 Gerando 5 arquivos de saída (sample_outputs)...") | |
| # entrada.mp3 (original) | |
| entrada_mp3 = OUTPUTS_DIR / "entrada.mp3" | |
| save_audio(entrada_mp3, original) | |
| # entrada_acapella.mp3 (apenas a voz, simulando extração do Demucs) | |
| entrada_acapella = OUTPUTS_DIR / "entrada_acapella.mp3" | |
| save_audio(entrada_acapella, voice) | |
| # entrada_instrumental.mp3 (apenas o instrumental) | |
| entrada_instrumental = OUTPUTS_DIR / "entrada_instrumental.mp3" | |
| save_audio(entrada_instrumental, instrumental) | |
| # saida.mp3 (RVC simulado: pitch shift + leve alteração tímbrica) | |
| # Aplica +3 semitons para simular conversão | |
| shifted_original = apply_pitch_shift(original, semitones=3, sample_rate=SAMPLE_RATE) | |
| saida_mp3 = OUTPUTS_DIR / "saida.mp3" | |
| save_audio(saida_mp3, shifted_original) | |
| # saida_acapella.mp3 (RVC simulado sobre o acapella) | |
| shifted_acapella = apply_pitch_shift(voice, semitones=3, sample_rate=SAMPLE_RATE) | |
| saida_acapella = OUTPUTS_DIR / "saida_acapella.mp3" | |
| save_audio(saida_acapella, shifted_acapella) | |
| # 4. (Opcional) Gerar vídeo de exemplo usando moviepy | |
| try: | |
| from moviepy.video.VideoClip import ColorClip | |
| from moviepy.audio.AudioClip import AudioArrayClip | |
| video_path = INPUTS_DIR / "sample_video.mp4" | |
| if not video_path.exists(): | |
| print("\n🎬 Criando vídeo de exemplo (sample_video.mp4)...") | |
| audio_clip = AudioArrayClip(original, fps=SAMPLE_RATE) | |
| video_clip = ColorClip(size=(640, 480), color=(30, 80, 120), duration=DURATION_SEC) | |
| video_clip = video_clip.with_audio(audio_clip) | |
| video_clip.write_videofile(str(video_path), fps=24, logger=None, verbose=False) | |
| print(f" ✅ {video_path.name}") | |
| except ImportError: | |
| print("\n⚠️ moviepy não instalado. Pulando criação de vídeo.") | |
| print(" Instale com: pip install moviepy") | |
| print("\n" + "=" * 60) | |
| print("✅ Geração concluída!") | |
| print("Arquivos disponíveis em:") | |
| print(f" - Entrada: {INPUTS_DIR}") | |
| print(f" - 5 saídas: {OUTPUTS_DIR}") | |
| print("\nAgora você pode usar esses arquivos para testar a interface Gradio.") | |
| print("=" * 60) | |
| if __name__ == "__main__": | |
| main() |