Spaces:
Sleeping
Sleeping
import numpy as np | |
import wave | |
import gradio as gr | |
import tempfile | |
from typing import List, Optional | |
import json | |
# Notas musicales comunes con sus frecuencias | |
NOTES = { | |
"C4": 261.63, "D4": 293.66, "E4": 329.63, "F4": 349.23, | |
"G4": 392.00, "A4": 440.00, "B4": 493.88, "C5": 523.25 | |
} | |
# Melodías predefinidas | |
PREDEFINED_MELODIES = { | |
"Melodía Simple": { | |
"melody": "261.63:1, 329.63:0.5, 392.00:1, 440.00:0.5", | |
"counter_melody": "130.81:1, 164.81:0.5, 196.00:1, 220.00:0.5", | |
"wave_type": "square", | |
"rhythm_pattern": "1,0,1,0", | |
"tempo": 120, | |
"melody_subdivisions": 2, | |
"counter_subdivisions": 1 | |
}, | |
"Onda Triangular": { | |
"melody": "440.00:0.5, 493.88:0.5, 523.25:1, 493.88:0.5", | |
"counter_melody": "329.63:0.5, 349.23:0.5, 392.00:1, 349.23:0.5", | |
"wave_type": "triangle", | |
"rhythm_pattern": "1,1,0,0", | |
"tempo": 140, | |
"melody_subdivisions": 3, | |
"counter_subdivisions": 2 | |
}, | |
"Ritmo Complejo": { | |
"melody": "261.63:0.25, 329.63:0.25, 392.00:0.5, 440.00:0.25, 493.88:0.25", | |
"counter_melody": "130.81:0.25, 164.81:0.25, 196.00:0.5, 220.00:0.25, 246.94:0.25", | |
"wave_type": "sawtooth", | |
"rhythm_pattern": "1,0,1,1,0", | |
"tempo": 180, | |
"melody_subdivisions": 4, | |
"counter_subdivisions": 3 | |
} | |
} | |
def generate_tone(frequency: float, duration: float, sample_rate: int, | |
amplitude: int, wave_type: str, subdivisions: int = 1) -> np.ndarray: | |
""" | |
Genera un tono con parámetros avanzados incluyendo submuestreo. | |
""" | |
total_samples = int(sample_rate * duration) | |
samples_per_subdivision = total_samples // subdivisions | |
t = np.linspace(0, duration, total_samples, endpoint=False) | |
try: | |
if wave_type == "square": | |
tone = amplitude * np.sign(np.sin(2 * np.pi * frequency * t)) | |
elif wave_type == "sine": | |
tone = amplitude * np.sin(2 * np.pi * frequency * t) | |
elif wave_type == "triangle": | |
tone = amplitude * (2 * np.abs(2 * (frequency * t % 1) - 1) - 1) | |
elif wave_type == "sawtooth": | |
tone = amplitude * (2 * (frequency * t % 1) - 1) | |
else: | |
raise ValueError(f"Tipo de onda no válido: {wave_type}") | |
# Aplicar submuestreo | |
if subdivisions > 1: | |
subsampled_tone = np.zeros_like(tone) | |
for i in range(subdivisions): | |
start = i * samples_per_subdivision | |
end = (i + 1) * samples_per_subdivision | |
subsampled_tone[start:end] = tone[start:end] * (1 - abs(i - (subdivisions-1)/2) / ((subdivisions-1)/2)) | |
tone = subsampled_tone | |
return tone.astype(np.int16) | |
except Exception as e: | |
raise ValueError(f"Error generando el tono: {str(e)}") | |
def apply_custom_rhythm(tone: np.ndarray, rhythm_pattern: List[int], | |
sample_rate: int, beat_duration: float) -> np.ndarray: | |
""" | |
Aplica un patrón rítmico personalizado binario. | |
""" | |
if not rhythm_pattern: | |
return tone | |
total_samples = len(tone) | |
samples_per_beat = int(sample_rate * beat_duration) | |
# Generar envolvente rítmica | |
rhythm_envelope = np.zeros(total_samples) | |
current_sample = 0 | |
for beat in rhythm_pattern: | |
if current_sample >= total_samples: | |
break | |
beat_samples = samples_per_beat | |
if current_sample + beat_samples > total_samples: | |
beat_samples = total_samples - current_sample | |
if beat == 1: | |
# Añadir fade in/out | |
fade_length = int(beat_samples * 0.1) | |
fade_in = np.linspace(0, 1, fade_length) | |
fade_out = np.linspace(1, 0, fade_length) | |
beat_envelope = np.ones(beat_samples) | |
beat_envelope[:fade_length] *= fade_in | |
beat_envelope[-fade_length:] *= fade_out | |
rhythm_envelope[current_sample:current_sample+beat_samples] = beat_envelope | |
current_sample += samples_per_beat | |
return (tone * rhythm_envelope).astype(np.int16) | |
def generate_music(melody: str, counter_melody: str, wave_type: str, | |
tempo: int, melody_volume: int, counter_volume: int, | |
rhythm_pattern: str, melody_subdivisions: int, | |
counter_subdivisions: int) -> str: | |
""" | |
Genera una pieza musical con submuestreo y ritmo personalizado. | |
""" | |
try: | |
sample_rate = 44100 | |
beat_duration = 60 / tempo | |
# Convertir patrón rítmico de texto a lista | |
try: | |
rhythm_list = [int(x) for x in rhythm_pattern.split(",") if x.strip() in ['0', '1']] | |
except: | |
rhythm_list = [] | |
# Convertir cadenas de notas | |
melody_notes = [note.strip() for note in melody.split(",") if note.strip()] | |
counter_notes = [note.strip() for note in counter_melody.split(",") if note.strip()] | |
def validate_note(note: str) -> tuple: | |
try: | |
freq, beats = map(float, note.split(":")) | |
if freq <= 0 or beats <= 0: | |
raise ValueError | |
return freq, beats | |
except: | |
raise ValueError(f"Formato inválido de nota: {note}") | |
# Generar melodía principal | |
main_tone = [] | |
for note in melody_notes: | |
freq, beats = validate_note(note) | |
tone = generate_tone(freq, beats * beat_duration, sample_rate, | |
melody_volume, wave_type, melody_subdivisions) | |
main_tone.append(tone) | |
main_tone = np.concatenate(main_tone) | |
# Aplicar ritmo personalizado | |
if rhythm_list: | |
main_tone = apply_custom_rhythm(main_tone, rhythm_list, sample_rate, beat_duration) | |
# Generar contramelodía | |
counter_tone = [] | |
if counter_notes: | |
for note in counter_notes: | |
freq, beats = validate_note(note) | |
tone = generate_tone(freq, beats * beat_duration, sample_rate, | |
counter_volume, wave_type, counter_subdivisions) | |
counter_tone.append(tone) | |
counter_tone = np.concatenate(counter_tone) | |
# Aplicar ritmo personalizado | |
if rhythm_list: | |
counter_tone = apply_custom_rhythm(counter_tone, rhythm_list, sample_rate, beat_duration) | |
else: | |
counter_tone = np.zeros_like(main_tone) | |
# Asegurar misma longitud | |
max_length = max(len(main_tone), len(counter_tone)) | |
main_tone = np.pad(main_tone, (0, max_length - len(main_tone))) | |
counter_tone = np.pad(counter_tone, (0, max_length - len(counter_tone))) | |
# Mezclar pistas | |
combined_audio = main_tone + counter_tone | |
combined_audio = np.clip(combined_audio, -32767, 32767).astype(np.int16) | |
# Guardar audio | |
with tempfile.NamedTemporaryFile(delete=False, suffix=".wav") as tmpfile: | |
with wave.open(tmpfile.name, 'w') as wf: | |
wf.setnchannels(1) | |
wf.setsampwidth(2) | |
wf.setframerate(sample_rate) | |
wf.writeframes(combined_audio.tobytes()) | |
return tmpfile.name | |
except Exception as e: | |
raise gr.Error(f"Error generando la música: {str(e)}") | |
def create_note_buttons(): | |
"""Crea botones para las notas predefinidas.""" | |
return [gr.Button(note) for note in NOTES.keys()] | |
def add_note(note: str, current_melody: str) -> str: | |
"""Añade una nota al campo de melodía.""" | |
freq = NOTES[note] | |
if current_melody: | |
return f"{current_melody}, {freq}:1" | |
return f"{freq}:1" | |
def load_predefined_melody(melody_name: str): | |
""" | |
Carga los parámetros de una melodía predefinida. | |
""" | |
melody_params = PREDEFINED_MELODIES.get(melody_name, {}) | |
return ( | |
melody_params.get('melody', ''), | |
melody_params.get('counter_melody', ''), | |
melody_params.get('wave_type', 'square'), | |
melody_params.get('tempo', 120), | |
melody_params.get('rhythm_pattern', ''), | |
melody_params.get('melody_subdivisions', 1), | |
melody_params.get('counter_subdivisions', 1) | |
) | |
# Interfaz de Gradio | |
with gr.Blocks(theme=gr.themes.Soft(primary_hue="indigo", secondary_hue="blue")) as ui: | |
gr.Markdown(""" | |
# 🎵 Generador de Música Avanzado | |
Crea música con ritmos y submuestreo personalizados. | |
## Formato de Entrada | |
- Notas: `frecuencia:duración` (Ej: 440:1) | |
- Ritmo: Patrón binario de 0 y 1 (Ej: 1,0,1,0) | |
""") | |
with gr.Row(): | |
with gr.Column(scale=2): | |
melody = gr.Textbox( | |
label="Melodía Principal", | |
placeholder="Ejemplo: 440:1, 494:0.5", | |
lines=3 | |
) | |
counter_melody = gr.Textbox( | |
label="Contramelodía (opcional)", | |
placeholder="Ejemplo: 330:1, 349:0.5", | |
lines=3 | |
) | |
with gr.Column(scale=1): | |
gr.Markdown("### Notas Predefinidas") | |
with gr.Row(): | |
note_buttons = create_note_buttons() | |
for btn in note_buttons: | |
btn.click(fn=add_note, inputs=[btn, melody], outputs=melody) | |
with gr.Accordion("Melodías Predefinidas", open=False): | |
predefined_melody_dropdown = gr.Dropdown( | |
list(PREDEFINED_MELODIES.keys()), | |
label="Seleccionar Melodía Predefinida" | |
) | |
load_melody_btn = gr.Button("Cargar Melodía") | |
with gr.Row(): | |
with gr.Column(): | |
wave_type = gr.Radio( | |
["sine", "square", "triangle", "sawtooth"], | |
label="Tipo de Onda", | |
value="square" | |
) | |
rhythm_pattern = gr.Textbox( | |
label="Patrón Rítmico (0 y 1)", | |
placeholder="Ejemplo: 1,0,1,0", | |
value="" | |
) | |
with gr.Column(): | |
melody_subdivisions = gr.Slider( | |
minimum=1, maximum=8, step=1, | |
label="Subdivisiones de Melodía", | |
value=1 | |
) | |
counter_subdivisions = gr.Slider( | |
minimum=1, maximum=8, step=1, | |
label="Subdivisiones de Contramelodía", | |
value=1 | |
) | |
tempo = gr.Slider( | |
minimum=60, maximum=180, step=5, | |
label="Tempo (BPM)", | |
value=120 | |
) | |
volume_row = gr.Row() | |
with volume_row: | |
melody_volume = gr.Slider( | |
minimum=0, maximum=30000, step=1000, | |
label="Vol. Melodía", value=15000 | |
) | |
counter_volume = gr.Slider( | |
minimum=0, maximum=30000, step=1000, | |
label="Vol. Contramelodía", value=10000 | |
) | |
output_audio = gr.Audio(label="Audio Generado") | |
generate_btn = gr.Button("Generar Música", variant="primary") | |
# Conexión de eventos | |
load_melody_btn.click( | |
fn=load_predefined_melody, | |
inputs=predefined_melody_dropdown, | |
outputs=[ | |
melody, | |
counter_melody, | |
wave_type, | |
tempo, | |
rhythm_pattern, | |
melody_subdivisions, | |
counter_subdivisions | |
] | |
) | |
generate_btn.click( | |
fn=generate_music, | |
inputs=[melody, counter_melody, wave_type, tempo, | |
melody_volume, counter_volume, rhythm_pattern, | |
melody_subdivisions, counter_subdivisions], | |
outputs=output_audio | |
) | |
gr.Markdown(""" | |
### Características Avanzadas | |
- Ritmo personalizado con patrón binario | |
- Submuestreo para efectos de textura | |
- Control independiente de melodía y contramelodía | |
""") | |
if __name__ == "__main__": | |
ui.launch() | |
# requirements.txt content | |
# numpy==1.23.5 | |
# gradio==3.50.2 | |
# wave==0.0.2 |