WindChimes / app.py
Ribot's picture
Create app.py
483bd60 verified
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")