File size: 12,876 Bytes
483bd60
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
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")