Marcepelaez commited on
Commit
19d2821
1 Parent(s): d5adce2
Files changed (1) hide show
  1. app.py +254 -0
app.py ADDED
@@ -0,0 +1,254 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import numpy as np
2
+ import wave
3
+ import gradio as gr
4
+ import tempfile
5
+ from typing import List, Optional
6
+ import json
7
+
8
+ # Notas musicales comunes con sus frecuencias
9
+ NOTES = {
10
+ "C4": 261.63, "D4": 293.66, "E4": 329.63, "F4": 349.23,
11
+ "G4": 392.00, "A4": 440.00, "B4": 493.88, "C5": 523.25
12
+ }
13
+
14
+ def generate_tone(frequency: float, duration: float, sample_rate: int,
15
+ amplitude: int, wave_type: str, subdivisions: int = 1) -> np.ndarray:
16
+ """
17
+ Genera un tono con parámetros avanzados incluyendo submuestreo.
18
+ """
19
+ total_samples = int(sample_rate * duration)
20
+ samples_per_subdivision = total_samples // subdivisions
21
+
22
+ t = np.linspace(0, duration, total_samples, endpoint=False)
23
+
24
+ try:
25
+ if wave_type == "square":
26
+ tone = amplitude * np.sign(np.sin(2 * np.pi * frequency * t))
27
+ elif wave_type == "sine":
28
+ tone = amplitude * np.sin(2 * np.pi * frequency * t)
29
+ elif wave_type == "triangle":
30
+ tone = amplitude * (2 * np.abs(2 * (frequency * t % 1) - 1) - 1)
31
+ elif wave_type == "sawtooth":
32
+ tone = amplitude * (2 * (frequency * t % 1) - 1)
33
+ else:
34
+ raise ValueError(f"Tipo de onda no válido: {wave_type}")
35
+
36
+ # Aplicar submuestreo
37
+ if subdivisions > 1:
38
+ subsampled_tone = np.zeros_like(tone)
39
+ for i in range(subdivisions):
40
+ start = i * samples_per_subdivision
41
+ end = (i + 1) * samples_per_subdivision
42
+ subsampled_tone[start:end] = tone[start:end] * (1 - abs(i - (subdivisions-1)/2) / ((subdivisions-1)/2))
43
+ tone = subsampled_tone
44
+
45
+ return tone.astype(np.int16)
46
+ except Exception as e:
47
+ raise ValueError(f"Error generando el tono: {str(e)}")
48
+
49
+ def apply_custom_rhythm(tone: np.ndarray, rhythm_pattern: List[int],
50
+ sample_rate: int, beat_duration: float) -> np.ndarray:
51
+ """
52
+ Aplica un patrón rítmico personalizado binario.
53
+ """
54
+ if not rhythm_pattern:
55
+ return tone
56
+
57
+ total_samples = len(tone)
58
+ samples_per_beat = int(sample_rate * beat_duration)
59
+
60
+ # Generar envolvente rítmica
61
+ rhythm_envelope = np.zeros(total_samples)
62
+ current_sample = 0
63
+
64
+ for beat in rhythm_pattern:
65
+ if current_sample >= total_samples:
66
+ break
67
+
68
+ beat_samples = samples_per_beat
69
+ if current_sample + beat_samples > total_samples:
70
+ beat_samples = total_samples - current_sample
71
+
72
+ if beat == 1:
73
+ # Añadir fade in/out
74
+ fade_length = int(beat_samples * 0.1)
75
+ fade_in = np.linspace(0, 1, fade_length)
76
+ fade_out = np.linspace(1, 0, fade_length)
77
+
78
+ beat_envelope = np.ones(beat_samples)
79
+ beat_envelope[:fade_length] *= fade_in
80
+ beat_envelope[-fade_length:] *= fade_out
81
+
82
+ rhythm_envelope[current_sample:current_sample+beat_samples] = beat_envelope
83
+
84
+ current_sample += samples_per_beat
85
+
86
+ return (tone * rhythm_envelope).astype(np.int16)
87
+
88
+ def generate_music(melody: str, counter_melody: str, wave_type: str,
89
+ tempo: int, melody_volume: int, counter_volume: int,
90
+ rhythm_pattern: str, melody_subdivisions: int,
91
+ counter_subdivisions: int) -> str:
92
+ """
93
+ Genera una pieza musical con submuestreo y ritmo personalizado.
94
+ """
95
+ try:
96
+ sample_rate = 44100
97
+ beat_duration = 60 / tempo
98
+
99
+ # Convertir patrón rítmico de texto a lista
100
+ try:
101
+ rhythm_list = [int(x) for x in rhythm_pattern.split(",") if x.strip() in ['0', '1']]
102
+ except:
103
+ rhythm_list = []
104
+
105
+ # Convertir cadenas de notas
106
+ melody_notes = [note.strip() for note in melody.split(",") if note.strip()]
107
+ counter_notes = [note.strip() for note in counter_melody.split(",") if note.strip()]
108
+
109
+ def validate_note(note: str) -> tuple:
110
+ try:
111
+ freq, beats = map(float, note.split(":"))
112
+ if freq <= 0 or beats <= 0:
113
+ raise ValueError
114
+ return freq, beats
115
+ except:
116
+ raise ValueError(f"Formato inválido de nota: {note}")
117
+
118
+ # Generar melodía principal
119
+ main_tone = []
120
+ for note in melody_notes:
121
+ freq, beats = validate_note(note)
122
+ tone = generate_tone(freq, beats * beat_duration, sample_rate,
123
+ melody_volume, wave_type, melody_subdivisions)
124
+ main_tone.append(tone)
125
+ main_tone = np.concatenate(main_tone)
126
+
127
+ # Aplicar ritmo personalizado
128
+ if rhythm_list:
129
+ main_tone = apply_custom_rhythm(main_tone, rhythm_list, sample_rate, beat_duration)
130
+
131
+ # Generar contramelodía
132
+ counter_tone = []
133
+ if counter_notes:
134
+ for note in counter_notes:
135
+ freq, beats = validate_note(note)
136
+ tone = generate_tone(freq, beats * beat_duration, sample_rate,
137
+ counter_volume, wave_type, counter_subdivisions)
138
+ counter_tone.append(tone)
139
+ counter_tone = np.concatenate(counter_tone)
140
+
141
+ # Aplicar ritmo personalizado
142
+ if rhythm_list:
143
+ counter_tone = apply_custom_rhythm(counter_tone, rhythm_list, sample_rate, beat_duration)
144
+ else:
145
+ counter_tone = np.zeros_like(main_tone)
146
+
147
+ # Asegurar misma longitud
148
+ max_length = max(len(main_tone), len(counter_tone))
149
+ main_tone = np.pad(main_tone, (0, max_length - len(main_tone)))
150
+ counter_tone = np.pad(counter_tone, (0, max_length - len(counter_tone)))
151
+
152
+ # Mezclar pistas
153
+ combined_audio = main_tone + counter_tone
154
+ combined_audio = np.clip(combined_audio, -32767, 32767).astype(np.int16)
155
+
156
+ # Guardar audio
157
+ with tempfile.NamedTemporaryFile(delete=False, suffix=".wav") as tmpfile:
158
+ with wave.open(tmpfile.name, 'w') as wf:
159
+ wf.setnchannels(1)
160
+ wf.setsampwidth(2)
161
+ wf.setframerate(sample_rate)
162
+ wf.writeframes(combined_audio.tobytes())
163
+ return tmpfile.name
164
+
165
+ except Exception as e:
166
+ raise gr.Error(f"Error generando la música: {str(e)}")
167
+
168
+ # Interfaz de Gradio
169
+ with gr.Blocks(theme=gr.themes.Soft(primary_hue="indigo", secondary_hue="blue")) as ui:
170
+ gr.Markdown("""
171
+ # 🎵 Generador de Música Avanzado
172
+
173
+ Crea música con ritmos y submuestreo personalizados.
174
+
175
+ ## Formato de Entrada
176
+ - Notas: `frecuencia:duración` (Ej: 440:1)
177
+ - Ritmo: Patrón binario de 0 y 1 (Ej: 1,0,1,0)
178
+ """)
179
+
180
+ with gr.Row():
181
+ with gr.Column(scale=2):
182
+ melody = gr.Textbox(
183
+ label="Melodía Principal",
184
+ placeholder="Ejemplo: 440:1, 494:0.5",
185
+ lines=3
186
+ )
187
+ counter_melody = gr.Textbox(
188
+ label="Contramelodía (opcional)",
189
+ placeholder="Ejemplo: 330:1, 349:0.5",
190
+ lines=3
191
+ )
192
+
193
+ with gr.Row():
194
+ with gr.Column():
195
+ wave_type = gr.Radio(
196
+ ["sine", "square", "triangle", "sawtooth"],
197
+ label="Tipo de Onda",
198
+ value="square"
199
+ )
200
+ rhythm_pattern = gr.Textbox(
201
+ label="Patrón Rítmico (0 y 1)",
202
+ placeholder="Ejemplo: 1,0,1,0",
203
+ value=""
204
+ )
205
+
206
+ with gr.Column():
207
+ melody_subdivisions = gr.Slider(
208
+ minimum=1, maximum=8, step=1,
209
+ label="Subdivisiones de Melodía",
210
+ value=1
211
+ )
212
+ counter_subdivisions = gr.Slider(
213
+ minimum=1, maximum=8, step=1,
214
+ label="Subdivisiones de Contramelodía",
215
+ value=1
216
+ )
217
+
218
+ tempo = gr.Slider(
219
+ minimum=60, maximum=180, step=5,
220
+ label="Tempo (BPM)",
221
+ value=120
222
+ )
223
+
224
+ volume_row = gr.Row()
225
+ with volume_row:
226
+ melody_volume = gr.Slider(
227
+ minimum=0, maximum=30000, step=1000,
228
+ label="Vol. Melodía", value=15000
229
+ )
230
+ counter_volume = gr.Slider(
231
+ minimum=0, maximum=30000, step=1000,
232
+ label="Vol. Contramelodía", value=10000
233
+ )
234
+
235
+ output_audio = gr.Audio(label="Audio Generado")
236
+
237
+ generate_btn = gr.Button("Generar Música", variant="primary")
238
+ generate_btn.click(
239
+ fn=generate_music,
240
+ inputs=[melody, counter_melody, wave_type, tempo,
241
+ melody_volume, counter_volume, rhythm_pattern,
242
+ melody_subdivisions, counter_subdivisions],
243
+ outputs=output_audio
244
+ )
245
+
246
+ gr.Markdown("""
247
+ ### Características Avanzadas
248
+ - Ritmo personalizado con patrón binario
249
+ - Submuestreo para efectos de textura
250
+ - Control independiente de melodía y contramelodía
251
+ """)
252
+
253
+ if __name__ == "__main__":
254
+ ui.launch()