import os import subprocess import sys # Liste des dépendances requises avec leurs versions minimales required_packages = { "numpy": "1.20.0", "scipy": "1.6.0", "gradio": "4.44.1", "soundfile": "0.10.0", "pydantic": "1.10.9", "fastapi": "0.95.0" } def install_and_check_dependencies(packages): """ Vérifie et installe les dépendances nécessaires. """ for package, version in packages.items(): try: module = __import__(package) installed_version = module.__version__ if tuple(map(int, installed_version.split('.'))) < tuple(map(int, version.split('.'))): print(f"Mise à jour de {package} vers {version} (actuellement {installed_version})...") subprocess.check_call([sys.executable, "-m", "pip", "install", f"{package}>={version}"]) except ImportError: print(f"Installation de {package}...") subprocess.check_call([sys.executable, "-m", "pip", "install", f"{package}>={version}"]) install_and_check_dependencies(required_packages) try: import numpy as np from scipy.signal import fftconvolve from scipy.io.wavfile import write import gradio as gr import soundfile as sf from pydantic import BaseModel except ImportError as e: print(f"Erreur d'importation: {e}") sys.exit(1) # Ajout de la configuration pour les modèles Pydantic class ConfigurableModel(BaseModel): class Config: arbitrary_types_allowed = True # Gammes musicales enrichies scales = { "Occidentale (par défaut)": [261.63, 293.66, 329.63, 349.23, 392.00, 440.00, 493.88], "Ionienne (Majeur)": [261.63, 293.66, 329.63, 349.23, 392.00, 440.00, 493.88], "Dorienne": [261.63, 293.66, 311.13, 349.23, 392.00, 440.00, 466.16], "Phrygienne": [261.63, 277.18, 311.13, 349.23, 392.00, 415.30, 466.16], "Lydienne": [261.63, 293.66, 329.63, 370.00, 392.00, 440.00, 493.88], "Mixolydienne": [261.63, 293.66, 329.63, 349.23, 392.00, 440.00, 466.16], "Aeolienne (Mineur Naturel)": [261.63, 293.66, 311.13, 349.23, 392.00, 415.30, 466.16], "Locrienne": [261.63, 277.18, 311.13, 349.23, 369.99, 415.30, 466.16], "Arabe": [261.63, 277.18, 329.63, 369.99, 392.00, 415.30, 466.16], "Orientale": [261.63, 293.66, 311.13, 369.99, 392.00, 440.00, 466.16], "Hindoustanie": [261.63, 277.18, 329.63, 369.99, 392.00, 440.00, 466.16], "Japonaise (In-Sen)": [261.63, 277.18, 329.63, 392.00, 415.30], "Chinoise (Pentatonique)": [261.63, 293.66, 329.63, 392.00, 440.00], "Flamenco": [261.63, 277.18, 329.63, 349.23, 369.99, 392.00, 415.30], "Byzantine": [261.63, 277.18, 329.63, 349.23, 392.00, 440.00, 466.16], "Double Harmonique (Arabe)": [261.63, 277.18, 329.63, 349.23, 392.00, 415.30, 466.16], "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], "Blues Mineur": [261.63, 311.13, 329.63, 349.23, 392.00, 466.16], "Blues Majeur": [261.63, 293.66, 329.63, 370.00, 392.00, 440.00], "Enigmatique": [261.63, 277.18, 329.63, 349.23, 392.00, 440.00, 466.16], "Super-Locrian (Altérée)": [261.63, 277.18, 293.66, 311.13, 329.63, 349.23, 369.99], "Majeur Napolitain": [261.63, 277.18, 329.63, 349.23, 392.00, 440.00, 493.88], "Harmonique Majeure": [261.63, 293.66, 329.63, 349.23, 392.00, 415.30, 466.16], "Harmonique Mineure": [261.63, 293.66, 311.13, 349.23, 392.00, 415.30, 466.16], "Melodique Ascendante": [261.63, 293.66, 311.13, 349.23, 392.00, 440.00, 493.88], "Melodique Descendante (Jazz)": [261.63, 293.66, 311.13, 349.23, 392.00, 415.30, 466.16], "Bebop Dominant": [261.63, 293.66, 329.63, 349.23, 392.00, 440.00, 466.16, 493.88], "Bebop Majeur": [261.63, 293.66, 329.63, 349.23, 392.00, 415.30, 440.00, 466.16], "Gamme Hijaz (Orientale)": [261.63, 277.18, 329.63, 349.23, 392.00, 415.30, 466.16], "Gamme Balinaise (Gamelan)": [261.63, 293.66, 329.63, 369.99, 440.00], "Pentatonique Chinoise": [261.63, 293.66, 329.63, 392.00, 440.00], "Raga Bhairav (Inde)": [261.63, 277.18, 329.63, 349.23, 392.00, 415.30, 466.16], "Raga Yaman": [261.63, 293.66, 329.63, 370.00, 392.00, 440.00, 493.88], "Lydien Augmenté": [261.63, 293.66, 329.63, 370.00, 415.30, 440.00, 493.88], "Phrygien Dominant": [261.63, 277.18, 329.63, 349.23, 392.00, 415.30, 466.16], "Mixolydien b9 b13": [261.63, 277.18, 329.63, 349.23, 392.00, 440.00, 466.16], "Altérée (Super-Locrian)": [261.63, 277.18, 293.66, 311.13, 329.63, 349.23, 369.99], "Gamme Écossaise": [261.63, 293.66, 349.23, 392.00, 440.00], "Gamme Hongroise Mineure": [261.63, 293.66, 311.13, 370.00, 392.00, 440.00, 466.16], "Gamme Klezmer": [261.63, 277.18, 329.63, 349.23, 392.00, 440.00, 466.16], "Enigmatique": [261.63, 277.18, 329.63, 349.23, 392.00, 440.00, 466.16], "Double Harmonique": [261.63, 277.18, 329.63, 349.23, 392.00, 415.30, 466.16], "Majeur Napolitain": [261.63, 277.18, 329.63, 349.23, 392.00, 440.00, 493.88], "Octatonique (Demi-Ton/Ton)": [261.63, 277.18, 311.13, 329.63, 369.99, 392.00, 440.00, 466.16], "Hexatonique (Augmentée)": [261.63, 293.66, 311.13, 349.23, 370.00, 415.30], "Gamme Fibonacci": [261.63, 283.63, 329.63, 392.00, 466.16, 440.00, 493.88], "Gamme Harmoniques Naturels": [261.63, 293.66, 327.03, 349.23, 392.00, 436.04, 490.55], "Gamme Bohémienne": [261.63, 277.18, 311.13, 349.23, 392.00, 415.30, 493.88], "Hexatonique Hongroise": [261.63, 277.18, 311.13, 349.23, 392.00, 440.00], "Gamme Zarlino (Juste)": [261.63, 293.33, 327.03, 349.23, 392.00, 415.30, 490.55], "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], "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], # Autres gammes... } def apply_envelope(signal, sample_rate, attack=0.01, release=0.01): """Applique une enveloppe d'amplitude avec attaque et relâchement.""" num_samples = len(signal) attack_samples = int(attack * sample_rate) release_samples = int(release * sample_rate) envelope = np.ones(num_samples) # Création de l'attaque if attack_samples > 0: envelope[:attack_samples] = np.linspace(0, 1, attack_samples) # Création du relâchement if release_samples > 0: envelope[-release_samples:] = np.linspace(1, 0, release_samples) return signal * envelope def generate_partition(length, pattern_length, pattern_repeats, note_speed_min, note_speed_max, scale): """ Génère une partition complète pour la durée totale, en respectant les patterns et répétitions. """ partition = [] current_time = 0 while current_time < length: if pattern_length > 0: # Générer un pattern pattern = [] pattern_current_time = 0 while pattern_current_time < pattern_length: interval = np.random.uniform(note_speed_min, note_speed_max) note_duration = np.random.uniform(1.5, 3.0) num_notes = np.random.choice([2, 3, 4]) selected_notes = np.random.choice(scale, num_notes, replace=False) pattern.append({ "start_time": pattern_current_time, "duration": note_duration, "notes": selected_notes.tolist(), "volume": np.random.uniform(0.7, 1.0) }) pattern_current_time += interval # Ajouter les répétitions du pattern à la partition for repeat in range(pattern_repeats): for note in pattern: start_time_global = current_time + repeat * pattern_length + note["start_time"] partition.append({ "start_time": start_time_global, "duration": note["duration"], "notes": note["notes"], "volume": note["volume"] }) current_time += pattern_length * pattern_repeats else: # Pas de pattern défini, ajouter des notes aléatoires interval = np.random.uniform(note_speed_min, note_speed_max) note_duration = np.random.uniform(1.5, 3.0) num_notes = np.random.choice([2, 3, 4]) selected_notes = np.random.choice(scale, num_notes, replace=False) partition.append({ "start_time": current_time, "duration": note_duration, "notes": selected_notes.tolist(), "volume": np.random.uniform(0.7, 1.0) }) current_time += interval return partition def synthesize_audio_with_envelope(partition, length=2.0, reverb_length=0.0, stereo_pan=0.0): """ Synthèse avec enveloppe d'amplitude pour éviter les craquements. """ sample_rate = 44100 total_length = length + reverb_length t = np.linspace(0, total_length, int(sample_rate * total_length), endpoint=False) wave = np.zeros((len(t), 2)) # Stéréo for note in partition: start_sample = int(note["start_time"] * sample_rate) end_sample = start_sample + int(note["duration"] * sample_rate) if end_sample > len(t): end_sample = len(t) chord_wave = np.zeros((end_sample - start_sample, 2)) for freq in note["notes"]: left_wave = np.sin(2 * np.pi * freq * t[: end_sample - start_sample]) right_wave = np.sin(2 * np.pi * (freq + stereo_pan) * t[: end_sample - start_sample]) chord_wave[:, 0] += left_wave chord_wave[:, 1] += right_wave chord_wave *= note["volume"] # Appliquer l'enveloppe pour éviter des transitions brusques chord_wave[:, 0] = apply_envelope(chord_wave[:, 0], sample_rate) chord_wave[:, 1] = apply_envelope(chord_wave[:, 1], sample_rate) wave[start_sample:end_sample, :] += chord_wave # Normalisation finale wave /= np.max(np.abs(wave)) return wave def generate_audio(length, reverb_length, stereo_pan, note_speed_min, note_speed_max, pattern_length, pattern_repeats, scale_name, output_format): """ Génère le fichier audio simulant un carillon dans le vent, à partir d'une partition. """ scale = scales[scale_name] partition = generate_partition(length, pattern_length, pattern_repeats, note_speed_min, note_speed_max, scale) wave = synthesize_audio_with_envelope(partition, length, reverb_length, stereo_pan) # Sauvegarde audio temp_dir = os.environ.get("TEMP", "/tmp") base_output_path = os.path.join(temp_dir, f"stereo_wind_chime.{output_format.lower()}") sf.write(base_output_path, wave, 44100, format=output_format) # Génération de la partition texte partition_text = "\n".join( [f"Start: {note['start_time']:.2f}s, Duration: {note['duration']:.2f}s, Notes: {note['notes']}" for note in partition] ) # Retourne les trois éléments requis return base_output_path, base_output_path, partition_text # Interface Gradio iface = gr.Interface( fn=generate_audio, inputs=[ gr.Number(label="Longueur du fichier audio (en secondes)", value=60), gr.Number(label="Longueur de la réverbération (en secondes)", value=6), gr.Number(label="Panorama stéréo (différence en Hz)", value=3), gr.Number(label="Rapidité des notes (min, secondes)", value=0.1), gr.Number(label="Rapidité des notes (max, secondes)", value=1), gr.Number(label="Longueur des patterns (en secondes)", value=10), gr.Number(label="Nombre de répétitions des patterns", value=3), gr.Radio(list(scales.keys()), label="Choisissez une gamme musicale", value="Occidentale (par défaut)"), gr.Radio(["FLAC", "WAV"], label="Format de sortie", value="FLAC"), ], outputs=[ gr.Audio(label="Écouter le fichier audio généré"), gr.File(label="Télécharger le fichier audio généré"), gr.Textbox(label="Partition générée", lines=20, interactive=False), ], title="Générateur de Carillon avec Patterns et Répétitions", description="Créez un carillon avec des gammes enrichies, des patterns personnalisables, et écoutez votre création.", ) iface.launch(share=True) input("appuyez sur une touche")