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