RVC-CH / examples /generate_sample_audio.py
LosCaquitos's picture
Update examples/generate_sample_audio.py
a2bc837 verified
raw
history blame
7.97 kB
#!/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()