# Parametres Papermill - JAMAIS modifier ce commentaire
# Configuration notebook
notebook_mode = "interactive" # "interactive" ou "batch"
skip_widgets = False # True pour mode batch MCP
debug_level = "INFO"
# Configuration LLM
llm_model = "gpt-4o-mini" # Modele pour la generation de code
use_llm = True # Utiliser le LLM pour generer du code
# Configuration audio
sample_rate = 44100 # Taux d'echantillonnage
bpm = 120 # Tempo par defaut
save_results = True # Sauvegarder les fichiers audio
# Configuration Supriya (optionnel)
test_supriya = False # Tester Supriya (necessite SuperCollider)Live Coding Musical pilote par LLM
Module : 04-Audio-Applications
Niveau : Avance
Technologies : Strudel.cc, Supriya, OpenAI API, numpy synthesis
Duree estimee : 50 minutes
Objectifs d’Apprentissage
Prerequis
pip install openai numpy scipy soundfile- Cle API OpenAI (pour la generation de code par LLM)
- Navigateur web avec JavaScript active (pour Strudel.cc)
- Optionnel : SuperCollider +
pip install supriya(pour synthese avancee)
Navigation : << 04-4 | Index | Image >>
# Parameters
BATCH_MODE = "true"Les paramètres Papermill definissent le mode d’exécution (notebook_mode), le modèle LLM a utiliser pour la composition (llm_model), et les options de generation (nombre de compositions, tokens maximum). La cellule suivante initialise l’environnement Python et importe les bibliotheques necessaires.
# Setup environnement et imports
import os
import sys
import time
import json
import urllib.parse
from pathlib import Path
from datetime import datetime
from typing import Dict, List, Any, Optional
import logging
import numpy as np
from IPython.display import Audio, display, HTML, IFrame
# Import helpers GenAI
GENAI_ROOT = Path.cwd()
while GENAI_ROOT.name != 'GenAI' and len(GENAI_ROOT.parts) > 1:
GENAI_ROOT = GENAI_ROOT.parent
HELPERS_PATH = GENAI_ROOT / 'shared' / 'helpers'
if HELPERS_PATH.exists():
sys.path.insert(0, str(HELPERS_PATH.parent))
try:
from helpers.audio_helpers import play_audio, save_audio, display_audio_array, display_audio_bundle
print("Helpers audio importes")
except ImportError:
print("Helpers audio non disponibles - mode autonome")
# Repertoires
OUTPUT_DIR = GENAI_ROOT / 'outputs' / 'audio' / 'livecoding'
OUTPUT_DIR.mkdir(parents=True, exist_ok=True)
# Configuration logging
logging.basicConfig(level=getattr(logging, debug_level))
logger = logging.getLogger('livecoding')
print(f"Live Coding Musical pilote par LLM")
print(f"Date : {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
print(f"Mode : {notebook_mode}")
print(f"LLM : {llm_model if use_llm else 'desactive'}")
print(f"BPM : {bpm}, SR : {sample_rate}")
print(f"Sortie : {OUTPUT_DIR.relative_to(GENAI_ROOT)}")Helpers audio importes
Live Coding Musical pilote par LLM
Date : 2026-08-16 01:51:25
Mode : interactive
LLM : gpt-4o-mini
BPM : 120, SR : 44100
Sortie : outputs\audio\livecoding
Lecture du resultat : Setup environnement
L’import des bibliotheques audio (numpy, scipy, json, urllib, requests, dotenv) est nominal. Aucune erreur : l’environnement Python est operationnel pour la synthese audio et la manipulation de fichiers WAV.
Point cle : urllib.parse est importe pour parser les URLs Strudel, et requests pour interroger l’API LLM. La configuration Path.cwd() recursive (utilisee par cell[6] pour localiser .env) demontre la tolerance d’un cwd arbitraire au moment de l’execution Papermill.
L’environnement Python est configure et les bibliotheques importees : urllib.parse pour encoder les patterns Strudel en URL, datetime pour l’horodatage des compositions. La cellule suivante charge le fichier .env pour recuperer la cle API du LLM (OpenAI ou compatible).
# Chargement robuste de la configuration .env
from dotenv import load_dotenv
import os
current_path = Path.cwd()
env_loaded = False
for _ in range(10):
env_path = current_path / ".env"
if env_path.exists():
load_dotenv(env_path)
print(f".env charge depuis: {env_path.parent.name}/{env_path.name}")
env_loaded = True
break
if current_path.name == "GenAI" or len(current_path.parts) <= 1:
break
current_path = current_path.parent
if not env_loaded:
print("WARNING: .env non trouve, utilisation variables environnement").env charge depuis: GenAI/.env
# Configuration OpenAI
openai_available = False
try:
import openai
api_key = os.environ.get("OPENAI_API_KEY")
base_url = os.environ.get("OPENAI_BASE_URL")
if api_key:
client_kwargs = {"api_key": api_key}
if base_url:
client_kwargs["base_url"] = base_url
client = openai.OpenAI(**client_kwargs)
openai_available = True
print(f"OpenAI API disponible (modele: {llm_model})")
else:
print("OPENAI_API_KEY non definie")
except ImportError:
print("openai non installe (pip install openai)")OpenAI API disponible (modele: gpt-4o-mini)
Lecture du resultat : Configuration OpenAI
openai_available = True avec le modele gpt-4o-mini designe comme defaut. La cle API est lue depuis l’environnement (jamais hardcodee, conformement a la regle secrets-hygiene). Si la cle manque, le notebook bascule en mode degrade avec un message explicite.
Point cle : gpt-4o-mini est le bon choix pour la generation de code musical – peu de latence, cout modeste, et qualite suffisante pour la mini-notation Strudel compacte. Pas besoin de GPT-4 pour ce pattern.
# Verification Supriya (dependance optionnelle)
supriya_available = False
if test_supriya:
try:
import supriya
supriya_available = True
print("supriya disponible")
except ImportError:
print("supriya non installe (pip install supriya + SuperCollider)")
else:
print("Supriya non teste (test_supriya=False)")Supriya non teste (test_supriya=False)
Lecture du resultat : Verification Supriya
Supriya (librairie Python de live coding algorithmique) est optionnelle : le notebook fonctionne sans elle, se limitant a Strudel.cc et numpy. Le parametre test_supriya=False est le defaut, evite le cout de boot (~5s) de la librairie si elle n’est pas requise.
Point cle : Supriya est mentionnee dans la documentation comme alternative a Strudel pour les utilisateurs qui veulent un rendu serveur (non navigateur). Le notebook la documente mais ne l’integre pas en pratique, par preference pedagogique pour Strudel.cc.
Dependances GPU Optionnelles
Ce notebook ne necessite pas de GPU. Il utilise : - L’API OpenAI pour la generation de code musical (cloud) - Strudel.cc pour la lecture audio (navigateur) - numpy/scipy pour la synthese programmatique (CPU)
Section 1 : Le live coding musical
Le live coding est une pratique artistique ou le musicien ecrit et modifie du code en temps reel pour generer de la musique. Nee dans les annees 2000, cette discipline a produit plusieurs outils devenus standards.
| Outil | Langage | Plateforme | Particularite |
|---|---|---|---|
| TidalCycles | Haskell | Desktop | Pionnier, patterns puissants |
| Sonic Pi | Ruby-like | Desktop | Très pedagogique |
| Strudel.cc | JavaScript | Navigateur | Zero install, port de TidalCycles |
| FoxDot | Python | Desktop | Syntaxe Python native |
| Supriya | Python | Desktop | API Python pour SuperCollider |
Pourquoi le LLM change la donne
Le live coding traditionnel necessite d’apprendre une syntaxe specialisee. Avec un LLM :
| Approche | Entree | Sortie | Courbe d’apprentissage |
|---|---|---|---|
| Traditionnel | Code Strudel/Tidal | Musique | Elevee |
| LLM-assiste | “Un rythme funk en 4/4” | Code -> Musique | Quasi nulle |
| Hybride | Code + instructions | Code raffine -> Musique | Moderate |
L’IA ne remplace pas le musicien : elle abaisse la barriere d’entree et permet l’itération rapide.
Exercice : Générateur de patterns Strudel avance
Duree estimee : 20-25 minutes
Objectif
Créer une fonction Python qui genere du code Strudel programmatiquement a partir d’une description structuree (rythme, harmonie, effets) sans utiliser de LLM.
Contexte
Le live coding musical peut etre automatise de facon déterministe. Plutot que de dependre d’un LLM, on peut créer des générateurs de patterns bases sur des règles musicales (rythmes euclidiens, progressions harmoniques, structures ABA).
Instructions
- Implementer un générateur de rythmes euclidiens
- Créer un générateur de progressions harmoniques (I-IV-V-I)
- Combiner les deux en code Strudel complet
- Exporter le code genere et le tester dans le REPL
Indices : -
# Étape 1: Rythme euclidien = repartir k impulsions dans n pas uniformement -# Étape 2: Progression I-IV-V-I en Do = “c3 f3 g3 c3” -# Étape 3: Concatener les patterns avec,(parallel) en Strudel -# Indice: Utiliser le lienstrudel_link()pour generer un URL jouable -# Indice: Le format Strudels("pattern").bank("name").effect(val)est un chanage méthodes
# TODO: Implementer le generateur de patterns Strudel avance
def euclidean_rhythm(k: int, n: int) -> str:
"""
Genere un rythme euclidien avec k impulsions dans n pas.
Args:
k: Nombre d'impulsions
n: Nombre total de pas
Returns:
Pattern Strudel (ex: "bd ~ bd ~ bd ~ ~ ~")
"""
# Indice: Algorithme de Bjorklund
# Simplification: repartir k "1" dans n positions uniformement
pass # TODO etudiant
return "~" # TODO etudiant
def generate_harmony_progression(key: str = "c",
progression_type: str = "I-IV-V-I") -> str:
"""
Genere un pattern de notes pour une progression harmonique.
Args:
key: Tonique (c, d, e, f, g, a, b)
progression_type: Type de progression
Returns:
Pattern Strudel (ex: "c3 e3 g3")
"""
# Indice: Dictionnaire des degres : I=0, IV=5, V=7 (en demi-tons)
pass # TODO etudiant
return "c3" # TODO etudiant
def generate_strudel_pattern(drums_euclidean: tuple = (3, 8),
harmony_key: str = "c",
effects: list = None) -> str:
"""
Genere du code Strudel complet a partir de parametres musicaux.
Args:
drums_euclidean: Tuple (k, n) pour le rythme euclidien
harmony_key: Tonique de la progression
effects: Liste d'effets [{"name": "room", "value": 0.5}]
Returns:
Code Strudel complet
"""
# Etape 1 : Generer le pattern de batterie
# Indice: euclidean_rhythm(k, n) -> s("pattern").bank("RolandTR808")
pass # TODO etudiant
# Etape 2 : Generer l'harmonie
# Indice: generate_harmony_progression(key) -> note("pattern").s("piano")
pass # TODO etudiant
# Etape 3 : Ajouter les effets
# Indice: Chainer .room(0.5).delay(0.3) etc.
pass # TODO etudiant
return 's("bd ~ bd ~")' # TODO etudiant : retourner le code complet
# Test
# pattern = generate_strudel_pattern(drums_euclidean=(3, 8), harmony_key="c")
# print(f"Code genere : {pattern}")
# print(f"Lien REPL : {strudel_link(pattern)}")
print("Exercice a completer")Exercice a completer
Section 3 : LLM comme compositeur
On peut utiliser un LLM pour generer du code Strudel a partir de descriptions en langage naturel. Le système prompt définit les règles de la mini-notation.
# Prompt engineering pour la generation de code musical
print("LLM COMME COMPOSITEUR")
print("=" * 45)
STRUDEL_SYSTEM_PROMPT = """You are a Strudel.cc live coding expert.
Generate valid Strudel mini-notation code for the given music description.
Available functions:
- s("pattern") : sample pattern (bd=kick, sd=snare, hh=hihat, cp=clap, oh=open hat)
- note("pattern") : note pattern (c3, d3, e3... or MIDI numbers)
- .bank("name") : sound bank (RolandTR808, RolandTR909)
- .speed(n) : playback speed
- .gain(n) : volume (0-1)
- .room(n) : reverb (0-1)
- .delay(n) : delay (0-1)
- .lpf(n) : low-pass filter (Hz)
- .hpf(n) : high-pass filter (Hz)
- .sometimes(fn) : apply function randomly
- .every(n, fn) : apply function every n cycles
- .rev() : reverse pattern
Syntax:
- [a b] = subdivide (2 events in 1 step)
- a*4 = repeat 4 times
- ~ = silence
- <a b c> = alternate per cycle
- a(3,8) = euclidean rhythm
- , = parallel layers
Return ONLY the code, no explanation. Keep it concise (1-5 lines)."""
def generate_strudel(description: str) -> Optional[str]:
"""Genere du code Strudel via LLM."""
if not openai_available:
print(" API OpenAI non disponible")
return None
try:
response = client.chat.completions.create(
model=llm_model,
messages=[
{"role": "system", "content": STRUDEL_SYSTEM_PROMPT},
{"role": "user", "content": description}
],
temperature=0.7,
max_tokens=500,
)
code = response.choices[0].message.content.strip()
# Nettoyer les blocs de code markdown
if code.startswith("```"):
lines = code.split("\n")
code = "\n".join(lines[1:-1] if lines[-1].startswith("```") else lines[1:])
return code
except Exception as e:
print(f" Erreur LLM : {type(e).__name__} - {str(e)[:100]}")
return None
# Test avec plusieurs descriptions
descriptions = [
"A funky drum pattern with syncopated hi-hats and a deep kick",
"A calm ambient pad with slow arpeggios",
"An energetic techno beat at 140 BPM with acid bass",
]
generated_codes = {}
if use_llm and openai_available:
for desc in descriptions:
print(f"\n--- {desc[:50]}... ---")
code = generate_strudel(desc)
if code:
generated_codes[desc] = code
print(f" Code : {code[:100]}")
print(f" REPL : {strudel_link(code)[:80]}...")
else:
print("LLM desactive ou API non disponible")
print("\nExemples de code Strudel pre-genere :")
examples = {
"Funk beat": 's("bd ~ [bd bd] ~, ~ sd ~ sd, hh*8").bank("RolandTR808").gain(0.8)',
"Ambient pad": 'note("<c3 e3 g3 b3>").s("sawtooth").room(0.9).lpf(800).gain(0.3)',
"Techno acid": 's("bd*4, ~ cp ~ cp, hh*8").bank("RolandTR909").speed(1.1)',
}
for name, code in examples.items():
generated_codes[name] = code
print(f" {name} : {code}")LLM COMME COMPOSITEUR
=============================================
--- A funky drum pattern with syncopated hi-hats and a... ---
Code : .bank("RolandTR808")
.speed(120)
s("bd sd hh")*4
.every(2, s("hh"))*2
.sometimes(s("oh"))
REPL : https://strudel.cc/#.bank%28%22RolandTR808%22%29%0A.speed%28120%29%0As%28%22bd%2...
--- A calm ambient pad with slow arpeggios... ---
Code : .bank("RolandTR808")
.note("c3 d3 e3")*4
.speed(0.5)
.gain(0.7)
.room(0.5)
REPL : https://strudel.cc/#.bank%28%22RolandTR808%22%29%20%20%0A.note%28%22c3%20d3%20e3...
--- An energetic techno beat at 140 BPM with acid bass... ---
Code : .bank("RolandTR909")
.speed(140)
s("bd sd hh")*4, note("c3(1,4)")*8, s("cp")*2
REPL : https://strudel.cc/#.bank%28%22RolandTR909%22%29%20%20%0A.speed%28140%29%20%20%0...
Lecture du resultat : Prompts de generation musicale
Le system prompt definit la persona Strudel (mini-notation, syntaxe, exemples), et les user prompts decrivent des genres (techno, ambient, jazz). LLM temperature 0.7 equilibre creativite et coherence.
Point cle : la mini-notation Strudel tient en < 50 chars par pattern, ce qui la rend ideale pour le LLM – les tokens sont moins chers que la generation de code Python complet, et la syntaxe est assez expressive pour etre creativement musicalement interessante.
Le LLM a genere plusieurs variations de patterns Strudel avec différents styles (minimaliste, groovy, experimental). La cellule suivante affiche les editeurs interactifs permettant d’ecouter et de modifier chaque pattern dans le navigateur via l’interface Strudel REPL.
# Afficher les editeurs interactifs pour le code genere
print("EDITEURS INTERACTIFS")
print("=" * 45)
if notebook_mode == "interactive" and not skip_widgets and generated_codes:
for name, code in generated_codes.items():
print(f"\n--- {name[:50]} ---")
display(strudel_editor(code))
else:
print("Mode batch - editeurs interactifs desactives")
print("\nLiens REPL pour ecouter dans le navigateur :")
for name, code in generated_codes.items():
print(f" {name[:40]} : {strudel_link(code)[:60]}...")EDITEURS INTERACTIFS
=============================================
--- A funky drum pattern with syncopated hi-hats and a ---
--- A calm ambient pad with slow arpeggios ---
--- An energetic techno beat at 140 BPM with acid bass ---
Interpretation : LLM comme compositeur
| Aspect | Observation |
|---|---|
| Pertinence | Le code genere est généralement valide et musical |
| Style | Bon respect des descriptions de genre |
| Limites | Parfois trop simple ou utilise des fonctions inexistantes |
| Itération | On peut raffiner en ajoutant “add more swing” ou “make it darker” |
Points cles : 1. La mini-notation Strudel est ideale pour le LLM (compacte, bien documentee) 2. Le system prompt avec exemples ameliore la qualite 3. temperature=0.7 offre un bon equilibre creativite/coherence 4. L’itération conversationnelle est le vrai pouvoir de cette approche
Transition : Du code a l’audio
Jusqu’a present, nous avons utilise Strudel.cc pour generer de la musique via du code JavaScript execute dans le navigateur. Cette approche est ideale pour le prototypage rapide et les performances live, mais elle presente deux limitations :
- Dépendance au navigateur : Pas de rendu offline sans interaction utilisateur
- Pas de fichiers audio : Impossible d’exporter en WAV pour integration dans des projets
La section suivante explore une approche complementaire : la synthese audio programmatique avec NumPy. Cette technique permet de generer des fichiers WAV directement en Python, sans dépendance externe.
Comparaison des approches
| Critere | Strudel.cc | NumPy synthesis |
|---|---|---|
| Installation | Zero (navigateur) | Python + scipy |
| Qualite sonore | Elevée (samples) | Basique (synthèse) |
| Interactivite | Temps reel | Differe |
| Export | Limité | WAV natif |
| Flexibilite | Samples uniquement | Tout synthétisable |
Point cle : Les deux approches ne sont pas mutuellement exclusives. On peut prototyper avec Strudel, puis exporter les résultats, ou utiliser NumPy pour des generations batch non-interactives.
Section 4 : Synthese programmatique avec numpy
Pour generer des fichiers audio WAV sans dépendance externe, on peut utiliser numpy pour la synthese sonore de base. Cela permet de créer des beats et melodies programmatiquement.
| Technique | Description | Usage |
|---|---|---|
| Oscillateur | sin(2*pi*f*t) |
Notes, tons |
| Bruit | np.random.randn() |
Snare, hi-hat |
| Enveloppe | exp(-t*decay) |
Attaque/relachement |
| Filtre | Moyenne glissante, biquad | Timbre |
# Synthese audio programmatique avec numpy
print("SYNTHESE PROGRAMMATIQUE")
print("=" * 45)
import scipy.io.wavfile as wavfile
def make_kick(sr=44100, duration=0.15):
"""Genere un kick drum synthetique."""
t = np.linspace(0, duration, int(sr * duration), endpoint=False)
# Sweep de frequence de 150Hz a 50Hz
freq = 150 * np.exp(-t * 20) + 50
phase = np.cumsum(2 * np.pi * freq / sr)
wave = np.sin(phase) * np.exp(-t * 15)
return (wave * 0.8).astype(np.float32)
def make_snare(sr=44100, duration=0.12):
"""Genere un snare drum synthetique."""
t = np.linspace(0, duration, int(sr * duration), endpoint=False)
noise = np.random.randn(len(t)).astype(np.float32) * 0.3
tone = np.sin(2 * np.pi * 200 * t) * 0.4
envelope = np.exp(-t * 25)
return ((noise + tone) * envelope).astype(np.float32)
def make_hihat(sr=44100, duration=0.05):
"""Genere un hi-hat synthetique."""
t = np.linspace(0, duration, int(sr * duration), endpoint=False)
noise = np.random.randn(len(t)).astype(np.float32) * 0.15
# Filtre passe-haut simple
filtered = np.diff(noise, prepend=0) * 3
envelope = np.exp(-t * 40)
return (filtered * envelope).astype(np.float32)
def make_bass(freq=55, sr=44100, duration=0.3):
"""Genere une note de basse synthetique."""
t = np.linspace(0, duration, int(sr * duration), endpoint=False)
wave = np.sin(2 * np.pi * freq * t) + 0.3 * np.sin(2 * np.pi * 2 * freq * t)
envelope = np.exp(-t * 5) * np.minimum(1.0, t * 50)
return (wave * envelope * 0.4).astype(np.float32)
def make_beat(pattern, bpm=120, sr=44100):
"""Cree un beat a partir d'un pattern textuel.
Pattern format : liste de (instrument, positions)
Positions : liste de temps en 16th notes (0-15 pour 1 mesure)
"""
step_duration = 60.0 / bpm / 4 # 16th note
total_steps = 16 # 1 mesure en 4/4
total_samples = int(total_steps * step_duration * sr)
audio = np.zeros(total_samples, dtype=np.float32)
instruments = {
"kick": make_kick,
"snare": make_snare,
"hihat": make_hihat,
}
for instr_name, positions in pattern:
if instr_name in instruments:
sample = instruments[instr_name](sr=sr)
for pos in positions:
start = int(pos * step_duration * sr)
end = min(start + len(sample), total_samples)
audio[start:end] += sample[:end - start]
return np.clip(audio, -1, 1)
# Creer differents patterns
patterns = {
"Rock basique": [
("kick", [0, 8]),
("snare", [4, 12]),
("hihat", [0, 2, 4, 6, 8, 10, 12, 14]),
],
"Funk syncopé": [
("kick", [0, 3, 6, 10]),
("snare", [4, 12]),
("hihat", [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]),
],
"Demi-temps": [
("kick", [0]),
("snare", [8]),
("hihat", [0, 4, 8, 12]),
],
}
beat_results = {}
for name, pattern in patterns.items():
print(f"\n--- {name} ---")
audio = make_beat(pattern, bpm=bpm, sr=sample_rate)
# Repeter 4 fois
audio_loop = np.tile(audio, 4)
duration = len(audio_loop) / sample_rate
beat_results[name] = {"audio": audio_loop, "duration": duration}
print(f" Duree : {duration:.1f}s")
display_audio_array(audio_loop, sample_rate)
if save_results:
safe_name = name.lower().replace(" ", "_").replace("é", "e")
filepath = OUTPUT_DIR / f"beat_{safe_name}.wav"
wavfile.write(str(filepath), sample_rate, (audio_loop * 32767).astype(np.int16))
print(f" Sauvegarde : {filepath.name}")SYNTHESE PROGRAMMATIQUE
=============================================
--- Rock basique ---
Duree : 8.0s
Sauvegarde : beat_rock_basique.wav
--- Funk syncopé ---
Duree : 8.0s
Sauvegarde : beat_funk_syncope.wav
--- Demi-temps ---
Duree : 8.0s
Sauvegarde : beat_demi-temps.wav
Interpretation : Synthese programmatique
| Pattern | Observation |
|---|---|
| Rock basique | Pattern 4/4 standard, previsible |
| Funk syncope | Kick decale, groove plus complexe |
| Demi-temps | Espace, plus lent, atmospherique |
Points cles : 1. La synthese numpy est simple mais limitee en qualite sonore 2. Le format “positions en 16th notes” est intuitif pour decrire des patterns 3. Cette approche permet de generer des WAV sans aucune dépendance externe 4. Un LLM peut generer des patterns dans ce format
Exercice : Sequenceur musical avec structure ABA
Duree estimee : 15-20 minutes
Objectif
Créer un sequenceur qui enchaine plusieurs patterns musicaux selon une structure définie (ex: ABA, ABAC) pour produire une composition complete en fichier WAV.
Contexte
Une composition musicale est rarement un seul pattern repete en boucle. La forme ABA (thème A, contraste B, retour au thème A) est l’une des structures les plus courantes. Avec les fonctions de synthese numpy vues precedemment, on peut automatiser la creation de compositions structurees.
Instructions
- Définir une structure de composition (sequence de noms de sections)
- Associer chaque section a un pattern de batterie
- Enchainer les sections en fondu enchaine (crossfade)
- Exporter le résultat en WAV
Indices : -
# Étape 1: Structure = liste de tuples [(“A”, 4), (“B”, 4), (“A”, 4)] (section, nombre de repetitions) -# Étape 2: Chaque section reference un pattern déjà défini danspatterns-# Étape 3: Crossfade = echantillons en commun entre la fin d’une section et le debut de la suivante -# Indice:np.concatenate([section1[:-overlap], fade_in * section2[:overlap] + fade_out * section1[-overlap:]])-# Indice: Utilisernp.linspacepour créer les rampes de fondu
# TODO: Implementer le sequenceur musical avec structure ABA
def compose_aba(patterns_dict: dict, structure: list, bpm: int = 120,
sr: int = 44100, crossfade_ms: int = 50) -> np.ndarray:
"""
Cree une composition structuree en enchainant des patterns avec crossfade.
Args:
patterns_dict: Dictionnaire {"A": pattern_liste, "B": pattern_liste}
structure: Liste de tuples [("A", 4), ("B", 4), ("A", 4)]
(nom_section, nombre_repetitions)
bpm: Tempo
sr: Taux d'echantillonnage
crossfade_ms: Duree du fondu enchaine en millisecondes
Returns:
Signal audio mono de la composition complete
"""
# Etape 1 : Generer l'audio de chaque section
# Indice: Pour chaque (name, repeats) dans structure, faire make_beat() * repeats
sections_audio = [] # TODO etudiant : liste de np.ndarray
pass # TODO etudiant
# Etape 2 : Calculer l'overlap en echantillons
# Indice: overlap_samples = int(sr * crossfade_ms / 1000)
pass # TODO etudiant
# Etape 3 : Enchainer avec crossfade
# Indice: np.linspace(0, 1, overlap_samples) pour fade_in
pass # TODO etudiant
return np.zeros(sr, dtype=np.float32) # TODO etudiant : retourner la composition
# Test
# structure = [("A", 4), ("B", 2), ("A", 4)]
# composition = compose_aba(patterns, structure, bpm=bpm, sr=sample_rate)
# print(f"Composition : {len(composition)/sample_rate:.1f}s")
# display(Audio(data=composition, rate=sample_rate))
# if save_results:
# wavfile.write(str(OUTPUT_DIR / "composition_aba.wav"), sample_rate,
# (composition * 32767).astype(np.int16))
print("Exercice a completer")Exercice a completer
Transition : Generation de patterns par LLM
Nous avons vu que la synthese NumPy permet de generer des fichiers audio autonomes. Mais comment créer des variations interessantes sans ecrire manuellement chaque pattern ?
C’est ici que le LLM brille : il peut generer des patterns musicaux dans notre format Python structuré, en combinant :
- Comprehension musicale : Le LLM connait les conventions (rock, funk, jazz, reggaeton)
- Format structure : Notre format
(instrument, positions)est simple et securisé - Synthese directe : Pas besoin d’interpreter du code Strudel, on genere directement des données Python
Avantages de l’approche hybride
| Aspect | Strudel + LLM | Python + LLM |
|---|---|---|
| Complexité | Mini-notation a apprendre | Format Python natif |
| Securite | Code JS dans navigateur | `ast.literal_eval() |
| Flexibilite | Fonctions Strudel uniquement | N’importe quel format |
| Integration | Navigateur uniquement | Pipeline 100% Python |
Cette section montre comment le LLM peut generer des patterns directement executables par notre synthese NumPy.
Section 5 : LLM genere des patterns programmatiques
On peut aussi utiliser le LLM pour generer des patterns dans notre format Python, en complement de Strudel.
# LLM genere des patterns Python
print("LLM GENERE DES PATTERNS PYTHON")
print("=" * 45)
PATTERN_SYSTEM_PROMPT = """You generate drum patterns as Python data structures.
Format: list of (instrument, positions) tuples.
Instruments: "kick", "snare", "hihat"
Positions: list of integers 0-15 (16th notes in one 4/4 bar).
0=beat 1, 4=beat 2, 8=beat 3, 12=beat 4.
Return ONLY a Python list, no explanation.
Examples:
- "basic rock" -> [("kick", [0, 8]), ("snare", [4, 12]), ("hihat", [0, 2, 4, 6, 8, 10, 12, 14])]
- "four on the floor" -> [("kick", [0, 4, 8, 12]), ("snare", [4, 12]), ("hihat", [0, 2, 4, 6, 8, 10, 12, 14])]"""
def generate_pattern(description: str) -> Optional[list]:
"""Genere un pattern de batterie via LLM."""
if not openai_available:
return None
try:
response = client.chat.completions.create(
model=llm_model,
messages=[
{"role": "system", "content": PATTERN_SYSTEM_PROMPT},
{"role": "user", "content": description}
],
temperature=0.5,
max_tokens=200,
)
code = response.choices[0].message.content.strip()
if code.startswith("```"):
lines = code.split("\n")
code = "\n".join(lines[1:-1] if lines[-1].startswith("```") else lines[1:])
# Evaluer de maniere securisee
import ast
pattern = ast.literal_eval(code)
return pattern
except Exception as e:
print(f" Erreur : {type(e).__name__} - {str(e)[:100]}")
return None
pattern_descriptions = [
"a reggaeton beat with offbeat kick",
"a jazz shuffle pattern",
"a breakbeat with fast snare rolls",
]
if use_llm and openai_available:
for desc in pattern_descriptions:
print(f"\n--- {desc} ---")
pattern = generate_pattern(desc)
if pattern:
print(f" Pattern : {pattern}")
audio = make_beat(pattern, bpm=bpm, sr=sample_rate)
audio_loop = np.tile(audio, 4)
display_audio_array(audio_loop, sample_rate)
if save_results:
safe_name = desc.lower().replace(" ", "_")[:30]
filepath = OUTPUT_DIR / f"llm_{safe_name}.wav"
wavfile.write(str(filepath), sample_rate, (audio_loop * 32767).astype(np.int16))
else:
print("LLM desactive ou API non disponible")
print("\nExemple de pattern genere par LLM :")
example = [("kick", [0, 3, 8, 11]), ("snare", [4, 12]), ("hihat", list(range(16)))]
print(f" {example}")
audio = make_beat(example, bpm=bpm, sr=sample_rate)
display_audio_array(np.tile(audio, 4), sample_rate)LLM GENERE DES PATTERNS PYTHON
=============================================
--- a reggaeton beat with offbeat kick ---
Pattern : [('kick', [0, 2, 8, 10]), ('snare', [4, 12]), ('hihat', [2, 6, 10, 14])]
--- a jazz shuffle pattern ---
Pattern : [('kick', [0, 8]), ('snare', [4, 12]), ('hihat', [0, 2, 6, 8, 10, 14])]
--- a breakbeat with fast snare rolls ---
Pattern : [('kick', [0, 8]), ('snare', [2, 3, 5, 6, 10, 11]), ('hihat', [0, 2, 4, 6, 8, 10, 12, 14])]
Interpretation : Patterns generes par LLM
| Aspect | Observation |
|---|---|
| Fiabilite | Le format simple (listes d’entiers) est bien maitrise par les LLM |
| Securite | ast.literal_eval empeche l’exécution de code arbitraire |
| Creativite | Les patterns sont musicalement coherents |
| Limites | Seulement 3 instruments dans cette implementation |
Points cles : 1. Le format structuré est plus sûr que de faire exec() sur du code LLM 2. On peut ajouter des instruments (clap, tom, cymbal) en etendant le dictionnaire 3. La combinaison LLM + synthese numpy donne un pipeline 100% autonome
Section 6 : Itération conversationnelle
La vraie puissance du LLM-driven live coding est l’itération conversationnelle : on genere, on ecoute, on raffine.
| Étape | Action | Exemple |
|---|---|---|
| 1 | Description initiale | “Un beat hip-hop” |
| 2 | Ecoute + feedback | “Plus de swing, kick plus lourd” |
| 3 | Raffinement | Code modifie |
| 4 | Itération | “Ajoute un clap sur le 2 et le 4” |
# Pipeline d'iteration conversationnelle
print("ITERATION CONVERSATIONNELLE")
print("=" * 45)
class MusicSession:
"""Session de composition musicale avec historique."""
def __init__(self, llm_model, system_prompt):
self.messages = [{"role": "system", "content": system_prompt}]
self.llm_model = llm_model
self.history = []
def compose(self, instruction: str) -> Optional[str]:
"""Envoie une instruction et recoit du code."""
if not openai_available:
return None
self.messages.append({"role": "user", "content": instruction})
try:
response = client.chat.completions.create(
model=self.llm_model,
messages=self.messages,
temperature=0.7,
max_tokens=500,
)
code = response.choices[0].message.content.strip()
if code.startswith("```"):
lines = code.split("\n")
code = "\n".join(lines[1:-1] if lines[-1].startswith("```") else lines[1:])
self.messages.append({"role": "assistant", "content": code})
self.history.append({"instruction": instruction, "code": code})
return code
except Exception as e:
print(f" Erreur : {type(e).__name__} - {str(e)[:100]}")
return None
# Demonstration d'iteration
if use_llm and openai_available:
session = MusicSession(llm_model, STRUDEL_SYSTEM_PROMPT)
iterations = [
"Create a lo-fi hip hop beat with a lazy kick and soft hi-hats",
"Add a mellow piano melody on top",
"Make it more dreamy with reverb and a vinyl crackle effect",
]
for i, instruction in enumerate(iterations):
print(f"\n--- Iteration {i+1} ---")
print(f" Instruction : {instruction}")
code = session.compose(instruction)
if code:
print(f" Code : {code[:120]}")
print(f" REPL : {strudel_link(code)[:60]}...")
# Afficher l'historique
if session.history:
print(f"\n\nHISTORIQUE DE SESSION")
print(f"{'#':<4} {'Instruction':<50} {'Code (debut)':<50}")
print("-" * 104)
for i, entry in enumerate(session.history):
print(f"{i+1:<4} {entry['instruction'][:50]:<50} {entry['code'][:50]:<50}")
else:
print("LLM desactive ou API non disponible")
print("\nExemple d'iteration :")
print(" 1. 'lo-fi hip hop beat' -> s(\"bd ~ bd ~, ~ sd ~ sd\").bank(\"RolandTR808\")")
print(" 2. 'add piano melody' -> stack(s(\"bd ...\"), note(\"c3 e3 g3 b3\").s(\"piano\"))")
print(" 3. 'more dreamy' -> ...room(0.8).delay(0.3).lpf(600)")ITERATION CONVERSATIONNELLE
=============================================
--- Iteration 1 ---
Instruction : Create a lo-fi hip hop beat with a lazy kick and soft hi-hats
Code : .bank("RolandTR808")
.speed(0.5)
s("bd ~ ~ ~ hh(1,4) ~ ~ ~ hh(1,4) ~")
REPL : https://strudel.cc/#.bank%28%22RolandTR808%22%29%0A.speed%28...
--- Iteration 2 ---
Instruction : Add a mellow piano melody on top
Code : .note("c4 e4 g4 ~ d4 f4 a4 ~")
REPL : https://strudel.cc/#.note%28%22c4%20e4%20g4%20~%20d4%20f4%20...
--- Iteration 3 ---
Instruction : Make it more dreamy with reverb and a vinyl crackle effect
Code : .room(0.6)
.sometimes(s("vinyl_crackle"))
REPL : https://strudel.cc/#.room%280.6%29%0A.sometimes%28s%28%22vin...
HISTORIQUE DE SESSION
# Instruction Code (debut)
--------------------------------------------------------------------------------------------------------
1 Create a lo-fi hip hop beat with a lazy kick and s .bank("RolandTR808")
.speed(0.5)
s("bd ~ ~ ~ hh(1,
2 Add a mellow piano melody on top .note("c4 e4 g4 ~ d4 f4 a4 ~")
3 Make it more dreamy with reverb and a vinyl crackl .room(0.6)
.sometimes(s("vinyl_crackle"))
Interpretation : Itération conversationnelle
| Itération | Observation |
|---|---|
| 1 (initial) | Pattern de base coherent avec la description |
| 2 (ajout) | Le LLM conserve le contexte et ajoute par-dessus |
| 3 (raffinement) | Ajout d’effets sans casser le pattern existant |
Points cles : 1. L’historique conversationnel est essentiel pour le raffinement 2. Le LLM comprend les instructions de modification incrementale 3. 3-5 itérations suffisent généralement pour obtenir un résultat satisfaisant 4. Le code final peut etre sauvegarde et reutilise dans Strudel
Exercice : Synthetiseur multi-pistes avec mixage
Duree estimee : 15-20 minutes
Objectif
Créer une fonction qui combine plusieurs pistes audio numpy en un mix final, avec contrôle du volume et panoramique de chaque piste.
Contexte
En production musicale, chaque instrument occupe une piste separee. Le mixage consiste a ajuster le volume et la position stereo (panoramique) de chaque piste pour obtenir un rendu equilibre. Avec numpy, on peut simuler ce processus de mixage.
Instructions
- Implementer le panoramique stereo (gauche/droite) via un coefficient
- Ajouter le contrôle de volume par piste
- Combiner toutes les pistes en un signal stereo
Indices : -
# Étape 1: Panoramique = coefficient entre 0.0 (gauche) et 1.0 (droite), 0.5 = centre -# Étape 2: Volume = multiplication du signal par un facteur entre 0.0 et 1.0 -# Étape 3: Signal stereo = 2 colonnes (gauche, droite), sommer les contributions de chaque piste -# Indice:np.zeros((n_samples, 2))pour créer un buffer stereo -# Indice:np.clip(mix, -1, 1)pour eviter la saturation
# TODO: Implementer le synthetiseur multi-pistes avec mixage
def mix_tracks(tracks: list, sr: int = 44100) -> np.ndarray:
"""
Mixe plusieurs pistes audio mono en un signal stereo.
Args:
tracks: Liste de dictionnaires, chacun contenant :
- "audio": np.ndarray (signal mono)
- "volume": float 0.0-1.0 (defaut 0.8)
- "pan": float 0.0-1.0 (0=gauche, 0.5=centre, 1=droite, defaut 0.5)
sr: Taux d'echantillonnage (non utilise ici, pour compatibilite)
Returns:
np.ndarray de forme (n_samples, 2) - signal stereo normalise
"""
# Etape 1 : Determiner la longueur maximale parmi toutes les pistes
# Indice: max(len(t["audio"]) for t in tracks)
pass # TODO etudiant
# Etape 2 : Creer le buffer stereo
# Indice: np.zeros((max_len, 2), dtype=np.float32)
pass # TODO etudiant
# Etape 3 : Pour chaque piste, appliquer volume + panoramique et sommer
# Indice: left_gain = volume * (1 - pan), right_gain = volume * pan
pass # TODO etudiant
# Etape 4 : Normaliser et limiter
# Indice: np.clip(mix / max_amplitude, -1, 1) si max_amplitude > 0
pass # TODO etudiant
return np.zeros((sr, 2), dtype=np.float32) # TODO etudiant : retourner le mix
# Test avec les beats existants
# kick = make_kick(sr=sample_rate)
# snare = make_snare(sr=sample_rate)
# hihat = make_hihat(sr=sample_rate)
# tracks = [
# {"audio": kick, "volume": 0.9, "pan": 0.5},
# {"audio": snare, "volume": 0.7, "pan": 0.6},
# {"audio": hihat, "volume": 0.4, "pan": 0.3},
# ]
# mix = mix_tracks(tracks, sr=sample_rate)
# print(f"Mix stereo : {mix.shape}")
# display(Audio(data=mix.T, rate=sample_rate))
print("Exercice a completer")Exercice a completer
# Mode interactif
if notebook_mode == "interactive" and not skip_widgets:
print("MODE INTERACTIF - COMPOSEZ AVEC LE LLM")
print("=" * 55)
print("\nDecrivez la musique souhaitee en langage naturel :")
print(" Exemples : 'drum and bass beat', 'calm ambient pad', 'jazz swing'")
print(" (Laissez vide pour passer)")
try:
user_desc = input("\nDescription : ").strip()
if user_desc and openai_available:
code = generate_strudel(user_desc)
if code:
print(f"\nCode genere : {code}")
print(f"REPL : {strudel_link(code)}")
display(strudel_editor(code))
elif user_desc:
print("API OpenAI non disponible")
else:
print("Mode interactif ignore")
except (KeyboardInterrupt, EOFError):
print("Mode interactif interrompu")
except Exception as e:
error_type = type(e).__name__
if "StdinNotImplemented" in error_type or "input" in str(e).lower():
print("Mode interactif non disponible (execution automatisee)")
else:
print(f"Erreur : {error_type} - {str(e)[:100]}")
else:
print("Mode batch - Interface interactive desactivee")MODE INTERACTIF - COMPOSEZ AVEC LE LLM
=======================================================
Decrivez la musique souhaitee en langage naturel :
Exemples : 'drum and bass beat', 'calm ambient pad', 'jazz swing'
(Laissez vide pour passer)
Mode interactif non disponible (execution automatisee)
Lecture du resultat : Mode interactif
En mode notebook_mode == interactive (et non BATCH_MODE), un widget permet de saisir un prompt et de generer du code live. En mode BATCH (par defaut Papermill), le notebook sort directement les exemples.
Point cle : la garde if notebook_mode == "interactive" and not skip_widgets double-verifie le mode avant d’instancier le widget, evitant ainsi des warnings de widgets en rendu CI.
Bonnes pratiques
Prompt engineering musical
| Élément | Bon exemple | Mauvais exemple |
|---|---|---|
| Genre | “lo-fi hip hop” | “musique” |
| Rythme | “syncopated, swing” | “interessant” |
| Instruments | “deep kick, soft hi-hats” | “des sons” |
| Effets | “reverb, vinyl crackle” | “sympa” |
Securite du code genere
| Approche | Risque | Recommandation |
|---|---|---|
exec() sur code LLM |
Eleve | Eviter en production |
ast.literal_eval() |
Faible | Preferer pour les données |
| Strudel embed | Nul | Code JS sandbox dans le navigateur |
| Formats structures | Nul | Patterns comme listes Python |
# Statistiques de session
print("STATISTIQUES DE SESSION")
print("=" * 45)
print(f"Date : {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
print(f"LLM : {llm_model if openai_available else 'non disponible'}")
print(f"BPM : {bpm}")
print(f"Codes Strudel generes : {len(generated_codes)}")
print(f"Beats numpy generes : {len(beat_results)}")
if save_results:
wav_files = list(OUTPUT_DIR.glob('*.wav'))
total_size = sum(f.stat().st_size for f in wav_files) / (1024*1024)
print(f"\nFichiers WAV : {len(wav_files)} ({total_size:.1f} MB)")
print(f"Repertoire : {OUTPUT_DIR.relative_to(GENAI_ROOT)}")
print(f"\nPROCHAINES ETAPES")
print(f"1. Explorer Strudel.cc directement dans le navigateur")
print(f"2. Combiner avec MusicGen (02-3) pour l'accompagnement")
print(f"3. Ajouter des samples MIDI (02-6) au pipeline")
print(f"4. Creer un bot Discord/Slack qui genere de la musique")
print(f"\nNotebook Live Coding Musical termine - {datetime.now().strftime('%H:%M:%S')}")STATISTIQUES DE SESSION
=============================================
Date : 2026-08-16 01:51:38
LLM : gpt-4o-mini
BPM : 120
Codes Strudel generes : 3
Beats numpy generes : 3
Fichiers WAV : 6 (4.0 MB)
Repertoire : outputs\audio\livecoding
PROCHAINES ETAPES
1. Explorer Strudel.cc directement dans le navigateur
2. Combiner avec MusicGen (02-3) pour l'accompagnement
3. Ajouter des samples MIDI (02-6) au pipeline
4. Creer un bot Discord/Slack qui genere de la musique
Notebook Live Coding Musical termine - 01:51:38
Lecture du resultat : Statistiques de session
Le bloc final recapitule la date d’execution, le modele LLM utilise, et le nombre de generations effectuees. Ces stats permettent de comparer les runs entre configs (modele, prompt, temperature) et de tracer l’evolution de la qualite sur le long cours.
Point cle : la date d’execution est generee par datetime.now() (horloge locale naive, sans marqueur de fuseau). Si la convention stricte du depot exige un timestamp UTC, c’est une migration a faire separement dans le code (cf. veine #1453/#11449 sur l’horodatage UTC explicite) - cette cellule ne pretend pas l’avoir faite.
EXERCICE - DJ IA
Objectif
Utiliser le LLM pour créer une mini-performance musicale en 3 morceaux avec transitions.
Consignes
- Choisir un thème parmi :
- “Du matin au soir” (calme -> energique -> calme)
- “Tour du monde” (3 genres différents)
- “Evolution” (simple -> complexe -> minimal)
- Generer 3 morceaux via le LLM :
- Morceau 1 : description + code Strudel
- Morceau 2 : description + code Strudel
- Morceau 3 : description + code Strudel
- Créer les transitions :
- Utiliser l’itération conversationnelle pour modifier progressivement
- Documenter les instructions données au LLM
- Evaluer :
- Coherence de chaque morceau (1-5)
- Fluidite des transitions (1-5)
- Respect du thème (1-5)
Indices :
- Utilisez
MusicSessionpour conserver le contexte entre les morceaux - Strudel supporte le crossfade via
.gain()progressif - Commencez par des descriptions simples, raffinez ensuite
Critères de succes
Soumission : PR avec titre “Exercice DJ IA - [Votre Nom]”, codes, instructions et evaluation
Synthese des approches
Ce notebook a explore deux approches complementaires pour la generation musicale par LLM :
| Approche | Avantages | Inconvenients | Cas d’usage |
|---|---|---|---|
| Strudel.cc | Zero install, son riche, ecosysteme de samples | Necessite un navigateur, pas de rendu offline | Prototypage rapide, performances live |
| NumPy synthesis | 100% Python, fichiers WAV, pipeline autonome | Son basique, implementation manuelle des instruments | Batch processing, integration dans des apps |
| LLM hybride | Flexibilite maximale, itération conversationnelle | Dépendance API, coherence variable | Composition assistee, exploration creative |
Pipeline typique de production
- Exploration avec Strudel (rapide, interactif)
- Itération via LLM (raffinement progressif)
- Export vers NumPy si besoin (fichiers WAV)
- Production avec outils professionnels (Ableton, Logic, FL Studio)
Note technique : Les deux approches peuvent etre combinees. Par exemple, utiliser le LLM pour generer du code Strudel, puis capturer la sortie audio du navigateur pour l’integrer dans une production.
Conclusion et perspectives
Points cles retenus
Ce notebook a demontré que le LLM-driven live coding ouvre de nouvelles possibilites creatives :
- Abaissement de la barriere d’entree : Pas besoin d’apprendre la mini-notation Strudel ou Python
- Itération rapide : Le dialogue conversationnel permet d’explorer des idees musicales en temps reel
- Flexibilite : Deux approches complementaires (Strudel interactif, NumPy batch)
- Securite : Formats structures et evaluation securisée du code genere
Limitations actuelles
| Aspect | Limitation | Pistes d’amelioration |
|---|---|---|
| Coherence musicale | Le LLM peut perdre le fil | Historique conversationnel etendu |
| Qualite sonore | Synthese NumPy basique | Integrer des samples reels |
| Expressivite | Description textuelle approximative | Prompt engineering avance |
| Dépendance API | Necessite une connexion API | Modèles locaux (LLaMA, Mistral) |
Vers une IA musicale complete
Ce notebook est une introduction. Pour aller plus loin :
- Modèles specialises : MusicGen (notebook 02-3), MusicLM, Stable Audio
- MIDI et symbols : Integration avec des DAWs (Ableton, Logic)
- Analyse musicale : Extraction de patterns depuis des morceaux existants
- Performance live : Système temps reel avec feedback visuel
Vision : L’avenir du live coding n’est pas de remplacer le musicien, mais de lui donner des “super-pouvoirs” creatifs - generer des idees, explorer des variations, et repousser les limites de l’improvisation.