Spaces:
Sleeping
Sleeping
Create app.py
Browse files
app.py
ADDED
@@ -0,0 +1,256 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import os
|
2 |
+
import subprocess
|
3 |
+
import sys
|
4 |
+
|
5 |
+
# Liste des dépendances requises avec leurs versions minimales
|
6 |
+
required_packages = {
|
7 |
+
"numpy": "1.20.0",
|
8 |
+
"scipy": "1.6.0",
|
9 |
+
"gradio": "4.44.1",
|
10 |
+
"soundfile": "0.10.0",
|
11 |
+
"pydantic": "1.10.9",
|
12 |
+
"fastapi": "0.95.0"
|
13 |
+
}
|
14 |
+
|
15 |
+
def install_and_check_dependencies(packages):
|
16 |
+
"""
|
17 |
+
Vérifie et installe les dépendances nécessaires.
|
18 |
+
"""
|
19 |
+
for package, version in packages.items():
|
20 |
+
try:
|
21 |
+
module = __import__(package)
|
22 |
+
installed_version = module.__version__
|
23 |
+
if tuple(map(int, installed_version.split('.'))) < tuple(map(int, version.split('.'))):
|
24 |
+
print(f"Mise à jour de {package} vers {version} (actuellement {installed_version})...")
|
25 |
+
subprocess.check_call([sys.executable, "-m", "pip", "install", f"{package}>={version}"])
|
26 |
+
except ImportError:
|
27 |
+
print(f"Installation de {package}...")
|
28 |
+
subprocess.check_call([sys.executable, "-m", "pip", "install", f"{package}>={version}"])
|
29 |
+
|
30 |
+
install_and_check_dependencies(required_packages)
|
31 |
+
|
32 |
+
try:
|
33 |
+
import numpy as np
|
34 |
+
from scipy.signal import fftconvolve
|
35 |
+
from scipy.io.wavfile import write
|
36 |
+
import gradio as gr
|
37 |
+
import soundfile as sf
|
38 |
+
from pydantic import BaseModel
|
39 |
+
except ImportError as e:
|
40 |
+
print(f"Erreur d'importation: {e}")
|
41 |
+
sys.exit(1)
|
42 |
+
|
43 |
+
# Ajout de la configuration pour les modèles Pydantic
|
44 |
+
class ConfigurableModel(BaseModel):
|
45 |
+
class Config:
|
46 |
+
arbitrary_types_allowed = True
|
47 |
+
|
48 |
+
# Gammes musicales enrichies
|
49 |
+
scales = {
|
50 |
+
"Occidentale (par défaut)": [261.63, 293.66, 329.63, 349.23, 392.00, 440.00, 493.88],
|
51 |
+
"Ionienne (Majeur)": [261.63, 293.66, 329.63, 349.23, 392.00, 440.00, 493.88],
|
52 |
+
"Dorienne": [261.63, 293.66, 311.13, 349.23, 392.00, 440.00, 466.16],
|
53 |
+
"Phrygienne": [261.63, 277.18, 311.13, 349.23, 392.00, 415.30, 466.16],
|
54 |
+
"Lydienne": [261.63, 293.66, 329.63, 370.00, 392.00, 440.00, 493.88],
|
55 |
+
"Mixolydienne": [261.63, 293.66, 329.63, 349.23, 392.00, 440.00, 466.16],
|
56 |
+
"Aeolienne (Mineur Naturel)": [261.63, 293.66, 311.13, 349.23, 392.00, 415.30, 466.16],
|
57 |
+
"Locrienne": [261.63, 277.18, 311.13, 349.23, 369.99, 415.30, 466.16],
|
58 |
+
"Arabe": [261.63, 277.18, 329.63, 369.99, 392.00, 415.30, 466.16],
|
59 |
+
"Orientale": [261.63, 293.66, 311.13, 369.99, 392.00, 440.00, 466.16],
|
60 |
+
"Hindoustanie": [261.63, 277.18, 329.63, 369.99, 392.00, 440.00, 466.16],
|
61 |
+
"Japonaise (In-Sen)": [261.63, 277.18, 329.63, 392.00, 415.30],
|
62 |
+
"Chinoise (Pentatonique)": [261.63, 293.66, 329.63, 392.00, 440.00],
|
63 |
+
"Flamenco": [261.63, 277.18, 329.63, 349.23, 369.99, 392.00, 415.30],
|
64 |
+
"Byzantine": [261.63, 277.18, 329.63, 349.23, 392.00, 440.00, 466.16],
|
65 |
+
"Double Harmonique (Arabe)": [261.63, 277.18, 329.63, 349.23, 392.00, 415.30, 466.16],
|
66 |
+
"Chromatique": [261.63, 277.18, 293.66, 311.13, 329.63, 349.23, 369.99, 392.00, 415.30, 440.00, 466.16, 493.88],
|
67 |
+
"Blues Mineur": [261.63, 311.13, 329.63, 349.23, 392.00, 466.16],
|
68 |
+
"Blues Majeur": [261.63, 293.66, 329.63, 370.00, 392.00, 440.00],
|
69 |
+
"Enigmatique": [261.63, 277.18, 329.63, 349.23, 392.00, 440.00, 466.16],
|
70 |
+
"Super-Locrian (Altérée)": [261.63, 277.18, 293.66, 311.13, 329.63, 349.23, 369.99],
|
71 |
+
"Majeur Napolitain": [261.63, 277.18, 329.63, 349.23, 392.00, 440.00, 493.88],
|
72 |
+
"Harmonique Majeure": [261.63, 293.66, 329.63, 349.23, 392.00, 415.30, 466.16],
|
73 |
+
"Harmonique Mineure": [261.63, 293.66, 311.13, 349.23, 392.00, 415.30, 466.16],
|
74 |
+
"Melodique Ascendante": [261.63, 293.66, 311.13, 349.23, 392.00, 440.00, 493.88],
|
75 |
+
"Melodique Descendante (Jazz)": [261.63, 293.66, 311.13, 349.23, 392.00, 415.30, 466.16],
|
76 |
+
"Bebop Dominant": [261.63, 293.66, 329.63, 349.23, 392.00, 440.00, 466.16, 493.88],
|
77 |
+
"Bebop Majeur": [261.63, 293.66, 329.63, 349.23, 392.00, 415.30, 440.00, 466.16],
|
78 |
+
"Gamme Hijaz (Orientale)": [261.63, 277.18, 329.63, 349.23, 392.00, 415.30, 466.16],
|
79 |
+
"Gamme Balinaise (Gamelan)": [261.63, 293.66, 329.63, 369.99, 440.00],
|
80 |
+
"Pentatonique Chinoise": [261.63, 293.66, 329.63, 392.00, 440.00],
|
81 |
+
"Raga Bhairav (Inde)": [261.63, 277.18, 329.63, 349.23, 392.00, 415.30, 466.16],
|
82 |
+
"Raga Yaman": [261.63, 293.66, 329.63, 370.00, 392.00, 440.00, 493.88],
|
83 |
+
"Lydien Augmenté": [261.63, 293.66, 329.63, 370.00, 415.30, 440.00, 493.88],
|
84 |
+
"Phrygien Dominant": [261.63, 277.18, 329.63, 349.23, 392.00, 415.30, 466.16],
|
85 |
+
"Mixolydien b9 b13": [261.63, 277.18, 329.63, 349.23, 392.00, 440.00, 466.16],
|
86 |
+
"Altérée (Super-Locrian)": [261.63, 277.18, 293.66, 311.13, 329.63, 349.23, 369.99],
|
87 |
+
"Gamme Écossaise": [261.63, 293.66, 349.23, 392.00, 440.00],
|
88 |
+
"Gamme Hongroise Mineure": [261.63, 293.66, 311.13, 370.00, 392.00, 440.00, 466.16],
|
89 |
+
"Gamme Klezmer": [261.63, 277.18, 329.63, 349.23, 392.00, 440.00, 466.16],
|
90 |
+
"Enigmatique": [261.63, 277.18, 329.63, 349.23, 392.00, 440.00, 466.16],
|
91 |
+
"Double Harmonique": [261.63, 277.18, 329.63, 349.23, 392.00, 415.30, 466.16],
|
92 |
+
"Majeur Napolitain": [261.63, 277.18, 329.63, 349.23, 392.00, 440.00, 493.88],
|
93 |
+
"Octatonique (Demi-Ton/Ton)": [261.63, 277.18, 311.13, 329.63, 369.99, 392.00, 440.00, 466.16],
|
94 |
+
"Hexatonique (Augmentée)": [261.63, 293.66, 311.13, 349.23, 370.00, 415.30],
|
95 |
+
"Gamme Fibonacci": [261.63, 283.63, 329.63, 392.00, 466.16, 440.00, 493.88],
|
96 |
+
"Gamme Harmoniques Naturels": [261.63, 293.66, 327.03, 349.23, 392.00, 436.04, 490.55],
|
97 |
+
"Gamme Bohémienne": [261.63, 277.18, 311.13, 349.23, 392.00, 415.30, 493.88],
|
98 |
+
"Hexatonique Hongroise": [261.63, 277.18, 311.13, 349.23, 392.00, 440.00],
|
99 |
+
"Gamme Zarlino (Juste)": [261.63, 293.33, 327.03, 349.23, 392.00, 415.30, 490.55],
|
100 |
+
"31-TET": [261.63, 270.00, 278.80, 287.94, 297.44, 307.31, 317.56, 328.20, 339.25, 350.71, 362.60, 374.94, 387.74, 401.02, 414.79, 429.07, 443.87, 459.21, 475.10, 491.57, 508.63, 526.30, 544.60, 563.56, 583.18, 603.49, 624.50, 646.25, 668.74, 692.01, 716.08],
|
101 |
+
"53-TET": [261.63, 265.00, 268.42, 271.89, 275.40, 278.97, 282.59, 286.26, 290.00, 293.78, 297.63, 301.53, 305.50, 309.52, 313.61, 317.75, 321.96, 326.23, 330.56, 334.95, 339.41, 343.94, 348.53, 353.19, 357.91, 362.71, 367.57, 372.50, 377.50, 382.58, 387.73, 392.95, 398.24, 403.61, 409.06, 414.58, 420.17, 425.85, 431.60, 437.43, 443.34, 449.32, 455.39, 461.54, 467.77, 474.08, 480.48, 486.95, 493.51, 500.15, 506.88, 513.68],
|
102 |
+
# Autres gammes...
|
103 |
+
}
|
104 |
+
|
105 |
+
def apply_envelope(signal, sample_rate, attack=0.01, release=0.01):
|
106 |
+
"""Applique une enveloppe d'amplitude avec attaque et relâchement."""
|
107 |
+
num_samples = len(signal)
|
108 |
+
attack_samples = int(attack * sample_rate)
|
109 |
+
release_samples = int(release * sample_rate)
|
110 |
+
|
111 |
+
envelope = np.ones(num_samples)
|
112 |
+
# Création de l'attaque
|
113 |
+
if attack_samples > 0:
|
114 |
+
envelope[:attack_samples] = np.linspace(0, 1, attack_samples)
|
115 |
+
# Création du relâchement
|
116 |
+
if release_samples > 0:
|
117 |
+
envelope[-release_samples:] = np.linspace(1, 0, release_samples)
|
118 |
+
|
119 |
+
return signal * envelope
|
120 |
+
|
121 |
+
def generate_partition(length, pattern_length, pattern_repeats, note_speed_min, note_speed_max, scale):
|
122 |
+
"""
|
123 |
+
Génère une partition complète pour la durée totale, en respectant les patterns et répétitions.
|
124 |
+
"""
|
125 |
+
partition = []
|
126 |
+
current_time = 0
|
127 |
+
|
128 |
+
while current_time < length:
|
129 |
+
if pattern_length > 0:
|
130 |
+
# Générer un pattern
|
131 |
+
pattern = []
|
132 |
+
pattern_current_time = 0
|
133 |
+
while pattern_current_time < pattern_length:
|
134 |
+
interval = np.random.uniform(note_speed_min, note_speed_max)
|
135 |
+
note_duration = np.random.uniform(1.5, 3.0)
|
136 |
+
num_notes = np.random.choice([2, 3, 4])
|
137 |
+
selected_notes = np.random.choice(scale, num_notes, replace=False)
|
138 |
+
|
139 |
+
pattern.append({
|
140 |
+
"start_time": pattern_current_time,
|
141 |
+
"duration": note_duration,
|
142 |
+
"notes": selected_notes.tolist(),
|
143 |
+
"volume": np.random.uniform(0.7, 1.0)
|
144 |
+
})
|
145 |
+
|
146 |
+
pattern_current_time += interval
|
147 |
+
|
148 |
+
# Ajouter les répétitions du pattern à la partition
|
149 |
+
for repeat in range(pattern_repeats):
|
150 |
+
for note in pattern:
|
151 |
+
start_time_global = current_time + repeat * pattern_length + note["start_time"]
|
152 |
+
partition.append({
|
153 |
+
"start_time": start_time_global,
|
154 |
+
"duration": note["duration"],
|
155 |
+
"notes": note["notes"],
|
156 |
+
"volume": note["volume"]
|
157 |
+
})
|
158 |
+
|
159 |
+
current_time += pattern_length * pattern_repeats
|
160 |
+
else:
|
161 |
+
# Pas de pattern défini, ajouter des notes aléatoires
|
162 |
+
interval = np.random.uniform(note_speed_min, note_speed_max)
|
163 |
+
note_duration = np.random.uniform(1.5, 3.0)
|
164 |
+
num_notes = np.random.choice([2, 3, 4])
|
165 |
+
selected_notes = np.random.choice(scale, num_notes, replace=False)
|
166 |
+
|
167 |
+
partition.append({
|
168 |
+
"start_time": current_time,
|
169 |
+
"duration": note_duration,
|
170 |
+
"notes": selected_notes.tolist(),
|
171 |
+
"volume": np.random.uniform(0.7, 1.0)
|
172 |
+
})
|
173 |
+
|
174 |
+
current_time += interval
|
175 |
+
|
176 |
+
return partition
|
177 |
+
|
178 |
+
def synthesize_audio_with_envelope(partition, length=2.0, reverb_length=0.0, stereo_pan=0.0):
|
179 |
+
"""
|
180 |
+
Synthèse avec enveloppe d'amplitude pour éviter les craquements.
|
181 |
+
"""
|
182 |
+
sample_rate = 44100
|
183 |
+
total_length = length + reverb_length
|
184 |
+
t = np.linspace(0, total_length, int(sample_rate * total_length), endpoint=False)
|
185 |
+
wave = np.zeros((len(t), 2)) # Stéréo
|
186 |
+
|
187 |
+
for note in partition:
|
188 |
+
start_sample = int(note["start_time"] * sample_rate)
|
189 |
+
end_sample = start_sample + int(note["duration"] * sample_rate)
|
190 |
+
if end_sample > len(t):
|
191 |
+
end_sample = len(t)
|
192 |
+
|
193 |
+
chord_wave = np.zeros((end_sample - start_sample, 2))
|
194 |
+
for freq in note["notes"]:
|
195 |
+
left_wave = np.sin(2 * np.pi * freq * t[: end_sample - start_sample])
|
196 |
+
right_wave = np.sin(2 * np.pi * (freq + stereo_pan) * t[: end_sample - start_sample])
|
197 |
+
chord_wave[:, 0] += left_wave
|
198 |
+
chord_wave[:, 1] += right_wave
|
199 |
+
|
200 |
+
chord_wave *= note["volume"]
|
201 |
+
# Appliquer l'enveloppe pour éviter des transitions brusques
|
202 |
+
chord_wave[:, 0] = apply_envelope(chord_wave[:, 0], sample_rate)
|
203 |
+
chord_wave[:, 1] = apply_envelope(chord_wave[:, 1], sample_rate)
|
204 |
+
wave[start_sample:end_sample, :] += chord_wave
|
205 |
+
|
206 |
+
# Normalisation finale
|
207 |
+
wave /= np.max(np.abs(wave))
|
208 |
+
return wave
|
209 |
+
|
210 |
+
def generate_audio(length, reverb_length, stereo_pan, note_speed_min, note_speed_max, pattern_length, pattern_repeats, scale_name, output_format):
|
211 |
+
"""
|
212 |
+
Génère le fichier audio simulant un carillon dans le vent, à partir d'une partition.
|
213 |
+
"""
|
214 |
+
scale = scales[scale_name]
|
215 |
+
partition = generate_partition(length, pattern_length, pattern_repeats, note_speed_min, note_speed_max, scale)
|
216 |
+
wave = synthesize_audio_with_envelope(partition, length, reverb_length, stereo_pan)
|
217 |
+
|
218 |
+
# Sauvegarde audio
|
219 |
+
temp_dir = os.environ.get("TEMP", "/tmp")
|
220 |
+
base_output_path = os.path.join(temp_dir, f"stereo_wind_chime.{output_format.lower()}")
|
221 |
+
sf.write(base_output_path, wave, 44100, format=output_format)
|
222 |
+
|
223 |
+
# Génération de la partition texte
|
224 |
+
partition_text = "\n".join(
|
225 |
+
[f"Start: {note['start_time']:.2f}s, Duration: {note['duration']:.2f}s, Notes: {note['notes']}" for note in partition]
|
226 |
+
)
|
227 |
+
|
228 |
+
# Retourne les trois éléments requis
|
229 |
+
return base_output_path, base_output_path, partition_text
|
230 |
+
|
231 |
+
# Interface Gradio
|
232 |
+
iface = gr.Interface(
|
233 |
+
fn=generate_audio,
|
234 |
+
inputs=[
|
235 |
+
gr.Number(label="Longueur du fichier audio (en secondes)", value=60),
|
236 |
+
gr.Number(label="Longueur de la réverbération (en secondes)", value=6),
|
237 |
+
gr.Number(label="Panorama stéréo (différence en Hz)", value=3),
|
238 |
+
gr.Number(label="Rapidité des notes (min, secondes)", value=0.1),
|
239 |
+
gr.Number(label="Rapidité des notes (max, secondes)", value=1),
|
240 |
+
gr.Number(label="Longueur des patterns (en secondes)", value=10),
|
241 |
+
gr.Number(label="Nombre de répétitions des patterns", value=3),
|
242 |
+
gr.Radio(list(scales.keys()), label="Choisissez une gamme musicale", value="Occidentale (par défaut)"),
|
243 |
+
gr.Radio(["FLAC", "WAV"], label="Format de sortie", value="FLAC"),
|
244 |
+
],
|
245 |
+
outputs=[
|
246 |
+
gr.Audio(label="Écouter le fichier audio généré"),
|
247 |
+
gr.File(label="Télécharger le fichier audio généré"),
|
248 |
+
gr.Textbox(label="Partition générée", lines=20, interactive=False),
|
249 |
+
],
|
250 |
+
title="Générateur de Carillon avec Patterns et Répétitions",
|
251 |
+
description="Créez un carillon avec des gammes enrichies, des patterns personnalisables, et écoutez votre création.",
|
252 |
+
)
|
253 |
+
|
254 |
+
iface.launch(share=True)
|
255 |
+
|
256 |
+
input("appuyez sur une touche")
|