Generation de Chansons Completes : YuE2

Module 02-Audio-Advanced — Generation de chansons (lyrics + style -> chanson complete)
Modeles YuE2-3B (AR-NAR Mixture-of-Transformers, 48 kHz stereo) ; comparaisons MusicGen et YuE-v1
Kernel Python 3 (python3)
GPU requis 24 GB VRAM en BF16 sous Linux (pic mesure ~11.2 GiB sur RTX 4090) — ce notebook documente le routage

Licence des poids : CC BY-NC 4.0. Les poids YuE2-3B sont diffuse sous licence Creative Commons Attribution-NonCommercial 4.0 : l’usage commercial est interdit. Le code d’inference releve des avis tiers du depot (THIRD_PARTY_NOTICES.md). Cette contrainte s’applique a toute utilisation des artefacts produits ici.

Objectifs pedagogiques :

  1. Distinguer text-to-song (paroles structurees + style -> chanson avec chant) du text-to-music (description instrumentale -> musique sans parole).
  2. Comprendre l’architecture YuE2 : un unique backbone AR-NAR Mixture-of-Transformers qui ecrit partition symbolique et tokens semantiques, puis un flow-matching acoustique et un VAE 48 kHz stereo.
  3. Executer les 4 etapes canoniques du pipeline : plan() -> generate_semantic() -> synthesize() -> decode().
  4. Pratiquer les trois usages marques de YuE2 : generation zero-shot, cover zero-shot (cot="melody"), edition agentique d’une partition ABC.
  5. Comparer honnetement, sur meme prompt et meme seed, YuE2-3B, MusicGen-medium et l’etat de l’art documente YuE-v1.
# Parametres Papermill - JAMAIS modifier ce commentaire

# Configuration notebook
notebook_mode = "batch"          # "batch" | "interactive"
seed = 42                        # meme seed pour toute la comparaison meme-prompt
cot_mode = "full"                # "full" (melodie+accords) | "melody" (covers) | "off"
cfg_scale = 1.2                  # guidance texte (valeur par defaut de la carte)
yue2_model_id = "m-a-p/YuE2-3B"
yue2_vae = "default"             # "default" (perceptuel) | "legacy" (musicalite benchmark)
yue2_wheel = "yue2_infer-0.1.5-py3-none-any.whl"
output_dir = "output/yue2-songs"
skip_widgets = True
# Parameters
BATCH_MODE = "true"

Les parametres Papermill configurent le modele (yue2_model_id), la graine (seed, fixee a 42 pour que la comparaison same-prompt same-seed de la Section 8 soit reproductible), le mode chain-of-thought (cot_mode) et la force de guidance (cfg_scale). Le output_dir concentre les artefacts generes (audio FLAC, partition ABC, tokens, latents) — ils ne sont pas commits dans le depot.

# Setup environnement et imports
import os
import sys
import time
import gc
import json
import shutil
import subprocess
from pathlib import Path

os.environ.setdefault("HF_HUB_DISABLE_SYMLINKS_WARNING", "1")

import platform

print("IMPORTS ET ENVIRONNEMENT")
print("=" * 45)
print(f"Python        {sys.version.split()[0]}")
print(f"Plateforme    {platform.system()} {platform.release()}")
print(f"Repertoire    {Path.cwd().name}")

# Repertoires de sortie (artefacts non commites)
Path(output_dir).mkdir(parents=True, exist_ok=True)
print(f"Sorties       {output_dir}/ pret")
IMPORTS ET ENVIRONNEMENT
=============================================
Python        3.10.12
Plateforme    Linux 6.18.33.2-microsoft-standard-WSL2
Repertoire    02-Advanced
Sorties       output/yue2-songs/ pret

L’environnement Python est pret et le repertoire d’artefacts est cree. Les sorties (audio, ABC, tokens) vivent sous output/yue2-songs/ et sont exclues du depot (regenerables par re-execution).

# Chargement robuste de la configuration .env
try:
    from dotenv import load_dotenv
    trouv = load_dotenv()
    print(f".env charge : {'oui' if trouv else 'aucun fichier .env (defauts utilises)'}")
except ImportError:
    print(".env ignore : python-dotenv absent de ce kernel")

cuda_dispo = False
try:
    import torch
    cuda_dispo = torch.cuda.is_available()
    if cuda_dispo:
        props = torch.cuda.get_device_properties(0)
        vram_gb = props.total_memory / 1024**3
        print(f"GPU           {props.name} — {vram_gb:.1f} GB VRAM")
    else:
        print("GPU           absent (CPU seul)")
except ImportError:
    print("torch         absent de ce kernel")
.env charge : aucun fichier .env (defauts utilises)
GPU           NVIDIA GeForce RTX 3090 — 24.0 GB VRAM

Dependances GPU et prerequis YuE2

YuE2-3B s’execute via le wheel d’inference officiel yue2_infer distribue dans le depot HuggingFace du modele (pas sur PyPI). La carte du modele exige :

Prerequis Exigence Pourquoi
GPU 24 GB VRAM, calcul BF16 backbone 3.59 B + VAE 48 kHz en FP32 + encodeur de texte (pic mesure ~11.2 GiB sur RTX 4090, marge de surete 24 GB)
OS Linux noyau d’inference compile pour Linux (CUDA graphs, FlashAttention)
Python 3.10+ wheel py3-none-any mais dependances GPU Linux
RAM ~24 GB host chargement des poids avant placement GPU

Sur une machine Windows + 8 GB VRAM (RTX 3070), la generation ne peut pas s’executer : le verdict est RECOVERABLE-MACHINE — le notebook est concu pour s’executer entierement sur une machine cible (eGPU RTX 3090 24 GB sous Linux), et les cellules ci-dessous documentent honnetement l’etat local au lieu de le masquer.

Section 1 : Text-to-Song vs Text-to-Music

La distinction structurante de la serie Audio :

  • Text-to-music (MusicGen, 02-3) : une description instrumentale produit de la musique sans parole. L’entree est libre (“lo-fi hip hop avec un piano pluie”).
  • Text-to-song (YuE2, ce notebook) : des paroles structurees (versets, refreins, balises de genre) produisent une chanson avec chant, melodie et arrangement. L’entree contraint la forme : chaque bloc de paroles correspond a un segment chante.

Le text-to-song est le probleme le plus complet : il cumule la comprehension du langage (paroles), la composition (melodie, harmonie, structure) et la synthese audio pleine bande (voix + instruments, 48 kHz stereo). YuE2 l’attaque en decomposant le probleme — partition symbolique d’abord, rendu acoustique ensuite — la ou une generation purement acoustique devrait tout apprendre d’un bloc.

Section 2 : Architectures — de YuE-v1 a YuE2

YuE-v1 (2025) : deux stages autoregressifs separes

Stage Modele Role
1 AR 7B paroles + accord -> tokens semantiques (structure musicale)
2 NAR 7B tokens semantiques -> tokens acoustiques (rendu)

Deux modeles de 7 B enchaines : 14 B de poids a charger, et une frontiere etanche entre le symbolique et l’acoustique.

YuE2 (2026) : un backbone unique AR-NAR Mixture-of-Transformers

Composant Role
Backbone AR-NAR MoT 3.59 B ecrit la partition ABC ET les tokens semantiques dans un meme passe autoregressif puis non-autoregressif
Modele de plan (chain-of-thought) cot="full" planifie melodie + accords avant la generation ; cot="melody" planifie la melodie seule (covers) ; cot="off" saute le plan
Flow-matching acoustique tokens semantiques -> latents acoustiques
VAE 48 kHz stereo latents -> audio ; deux variantes : YuE2-Vae (qualite perceptive, defaut) et YuE2-Vae-legacy (musicalite superieure sur benchmarks)

Le gain structurel : la partition reste un artefact de premiere classe. Ou YuE-v1 produisait d’abord des tokens opaques, YuE2 ecrit une vraie partition ABC editable — c’est ce qui rend possibles le cover zero-shot et l’edition agentique des Sections 6-7.

Le schema ci-dessous rend le pipeline YuE2 et ses 4 etapes canoniques — c’est la decomposition que la Section 5 execute pas a pas sur le meme prompt :

flowchart LR
    A["paroles + style"] --> P["plan()
    partition ABC planifiee
    (melodie, accords)"]
    P --> G["generate_semantic(plan)
    tokens semantiques AR-NAR"]
    G --> S["synthesize(semantic)
    flow-matching -> latents"]
    S --> D["decode(latents)
    VAE 48 kHz stereo"]
    D --> O["chanson.flac
    + artefacts ABC"]

Les trois usages marques se branchent sur ce meme pipeline : le cover remplace le plan par une melodie transcrite (cot='melody'), l’edition agentique edite l’ABC renvoye par plan() puis regenere par pipe(abc=...).

Section 3 : Installation du wheel d’inference

Le code d’inference YuE2 ne passe pas par PyPI : le wheel yue2_infer-0.1.5 est publie dans le depot du modele. L’installation canonique (carte du modele) :

python -m pip install huggingface-hub==0.36.2
hf download m-a-p/YuE2-3B yue2_infer-0.1.5-py3-none-any.whl --local-dir .
python -m pip install ./yue2_infer-0.1.5-py3-none-any.whl

puis :

from yue2 import YuE2Pipeline
pipe = YuE2Pipeline.from_pretrained("m-a-p/YuE2-3B", device="cuda")

La cellule suivante verifie ces prerequis sur la machine courante et en deduit un verdict d’executabilite honnete (regle F : un environnement manquant se repare ou se route, il ne se contourne pas).

# Verification des prerequis et verdict d'executabilite
print("VERIFICATION DES PREREQUIS YUE2")
print("=" * 45)

yue2_importable = False
try:
    import yue2  # noqa: F401
    from yue2 import YuE2Pipeline
    yue2_importable = True
    print("wheel yue2        present")
except ImportError:
    print("wheel yue2        ABSENT (installation Section 3 non faite dans ce kernel)")

os_ok = platform.system() == "Linux"
print(f"OS Linux requis   {'oui' if os_ok else 'NON (' + platform.system() + ')'}")

vram_ok = False
if cuda_dispo:
    try:
        import torch
        vram_ok = torch.cuda.get_device_properties(0).total_memory >= 23 * 1024**3
    except Exception:
        vram_ok = False
    print(f"VRAM >= 24 GB     {'oui' if vram_ok else 'NON'}")
else:
    print("VRAM >= 24 GB     NON (pas de CUDA)")

YUE2_DISPONIBLE = yue2_importable and os_ok and vram_ok

print()
print("VERDICT D'EXECUTABILITE")
print("-" * 45)
if YUE2_DISPONIBLE:
    print("EXECUTION LOCALE REELLE : tous les prerequis sont reunis.")
    print("Les cellules de generation produisent des artefacts reels.")
else:
    manquants = []
    if not yue2_importable:
        manquants.append("wheel yue2_infer")
    if not os_ok:
        manquants.append("OS Linux")
    if not vram_ok:
        manquants.append("GPU 24 GB BF16")
    print("RECOVERABLE-MACHINE : la generation YuE2 ne peut pas s'executer ici.")
    print(f"Manquants : {', '.join(manquants)}")
    print("Routage : eGPU RTX 3090 24 GB sous Linux (machine GenAI du cluster).")
    print("Les cellules suivantes documentent l'appel canonique et le routage ;")
    print("les artefacts reels proviennent de la machine cible (verdict par axe")
    print("en conclusion). Comparaison same-prompt same-seed : Section 8.")
VERIFICATION DES PREREQUIS YUE2
=============================================
wheel yue2        present
OS Linux requis   oui
VRAM >= 24 GB     oui

VERDICT D'EXECUTABILITE
---------------------------------------------
EXECUTION LOCALE REELLE : tous les prerequis sont reunis.
Les cellules de generation produisent des artefacts reels.

Interpretation : verdict d’executabilite

Le verdict est calcule, pas declare : chaque prerequis (wheel, OS, VRAM) est teste independamment, et le verdict agreg suit. Sur la machine d’authoring (Windows, 8 GB), le verdict est RECOVERABLE-MACHINE — la meme cellule passe en execution locale reelle sur la machine cible sans modification du notebook. C’est la traduction pratique de la regle F : on ne remplace pas l’outil par un simulacre, on documente la machine qui peut l’executer reellement.

# Chargement du pipeline YuE2 et affichage des 4 etapes canoniques
pipe = None
if YUE2_DISPONIBLE:
    from yue2 import YuE2Pipeline
    t0 = time.time()
    kw = {"device": "cuda"}
    if yue2_vae == "legacy":
        kw["vae"] = "m-a-p/YuE2-Vae-legacy"
    pipe = YuE2Pipeline.from_pretrained(yue2_model_id, **kw)
    print(f"Pipeline charge en {time.time() - t0:.1f} s")
else:
    print("PIPELINE NON CHARGE ICI (verdict precedent : RECOVERABLE-MACHINE)")
    print()
    print("Appel canonique execute sur la machine cible :")
    print()
    print("  from yue2 import YuE2Pipeline")
    print("  pipe = YuE2Pipeline.from_pretrained('m-a-p/YuE2-3B', device='cuda')")
    print("  # variante benchmark : vae='m-a-p/YuE2-Vae-legacy'")
    print()
    print("Liberation en fin de session : pipe.close() (VRAM du VAE FP32).")

print()
print("4 ETAPES CANONIQUES DU PIPELINE (decomposition interne du pipe.__call__)")
print("-" * 45)
for i, (nom, role) in enumerate([
    ("plan()", "paroles+style -> partition ABC planifiee (melodie, accords si cot=full)"),
    ("generate_semantic(plan)", "partition -> tokens semantiques (backbone AR-NAR MoT)"),
    ("synthesize(semantic)", "tokens semantiques -> latents acoustiques (flow-matching)"),
    ("decode(latents)", "latents -> audio 48 kHz stereo (VAE)"),
], start=1):
    print(f"  {i}. {nom:26s} {role}")
Pipeline charge en 55.6 s

4 ETAPES CANONIQUES DU PIPELINE (decomposition interne du pipe.__call__)
---------------------------------------------
  1. plan()                     paroles+style -> partition ABC planifiee (melodie, accords si cot=full)
  2. generate_semantic(plan)    partition -> tokens semantiques (backbone AR-NAR MoT)
  3. synthesize(semantic)       tokens semantiques -> latents acoustiques (flow-matching)
  4. decode(latents)            latents -> audio 48 kHz stereo (VAE)

Interpretation : pipeline et decomposition canonique

L’appel de convenance pipe(style=..., lyrics=..., cot=..., seed=...) enchaine les 4 etapes d’un bloc. La Section 5 execute d’abord la forme decomposee — pour rendre visible ce que chaque etape produit (la partition ABC planifiee en particulier) — puis la forme convenance pour la comparaison. Le choix du VAE (default vs legacy) est un compromis explicite : qualite perceptive contre musicalite mesuree sur benchmarks.

Section 4 : Preparation des paroles et du style

YuE2 attend deux entrees textuelles distinctes :

  • lyrics : paroles structurees par balises de section ([verse], [chorus]…). Chaque bloc correspond a un segment chante ; la structure contraint la forme de la chanson.
  • style : description du style — genre, ambiance, instrumentation, langue du chant. C’est l’equivalent text-to-music du prompt, mais il conditionne le rendu des paroles.

Les paroles ci-dessous sont originales (ecrites pour ce cours) : aucun probleme de droits, et le meme couplet paroles+style servira a toutes les generations du notebook — c’est la base de la comparaison same-prompt same-seed de la Section 8.

# Preparation des paroles et du style (originaux, meme prompt partout)
lyrics_demo = """[verse]
Les neurones veillent encore cette nuit
Chaque couche apprend un peu mieux
Le gradient descend vers la verite

[chorus]
Genere, genere la chanson
Des mots a la melodie
Genere, genere la chanson
La machine a du genie

[verse]
Un token, puis un autre, et le refrain
Se dessine dans le bruit du train"""

style_demo = "inspiring french pop, female vocal, warm analog synth, 110 bpm, key of A minor"

print("PAROLES ET STYLE (meme prompt pour toutes les generations)")
print("=" * 45)
print(lyrics_demo)
print()
print(f"style : {style_demo}")
print()
print(f"graine : {seed} (fixee pour toute la session)")
n_blocs = lyrics_demo.count("[")
print(f"blocs de paroles : {n_blocs} -> {n_blocs} segments chantes attendus")
PAROLES ET STYLE (meme prompt pour toutes les generations)
=============================================
[verse]
Les neurones veillent encore cette nuit
Chaque couche apprend un peu mieux
Le gradient descend vers la verite

[chorus]
Genere, genere la chanson
Des mots a la melodie
Genere, genere la chanson
La machine a du genie

[verse]
Un token, puis un autre, et le refrain
Se dessine dans le bruit du train

style : inspiring french pop, female vocal, warm analog synth, 110 bpm, key of A minor

graine : 42 (fixee pour toute la session)
blocs de paroles : 3 -> 3 segments chantes attendus

Interpretation : formater l’entree

Deux Pieges classiques du formatage :

  1. Balises absentes : des paroles sans [verse]/[chorus] laissent le modele decider seul du decoupage — resultat moins controle.
  2. Style contradictoire : un style qui exige une langue que les paroles n’ont pas produit des generations hybrides ; la langue du chant se declare dans le style.

La graine fixe (seed=42) rend la generation reproductible : meme prompt + meme graine = meme artefact, ce qui rend la comparaison de la Section 8 une comparaison de modeles, pas une comparaison de tirages.

Exercice 1 : Formatage de paroles multi-blocs

Objectif : ecrire une fonction structurer_paroles(couples_refrain) qui prend une liste de couples (texte, type de bloc) et renvoie les paroles balisees au format YuE2.

Etapes : 1. Parcourir la liste des (texte, type) ; 2. Entourer chaque texte de sa balise [type] ; 3. Joindre les blocs par une ligne vide.

Indice : "[" + typ + "]\n" + texte puis "\n\n".join(blocs).

# Exercice 1 : Formatage de paroles multi-blocs
def structurer_paroles(couples):
    # TODO etudiant : renvoyer les paroles balisees [verse]/[chorus]...
    pass


# Auto-verification attendue :
# structurer_paroles([("la nuit tombe", "verse"), ("refrain", "chorus")])
# -> "[verse]\nla nuit tombe\n\n[chorus]\nrefrain"
print("Exercice a completer : structurer_paroles")
Exercice a completer : structurer_paroles

Section 5 : Generation zero-shot, etape par etape (cellule-type a)

On execute le pipeline en forme decomposee sur le prompt de reference : chaque etape produit un artefact observable — la partition ABC planifiee par plan(), les tokens semantiques, les latents, l’audio final. cot="full" laisse le modele planifier melodie et accords avant de generer.

Sur la machine cible (24 GB, Linux), chaque etape affiche sa duree et son artefact ; en mode degrade, la cellule affiche l’appel canonique exact et le routage.

# Generation zero-shot en 4 etapes canoniques (cellule-type a)
chanson_zero_shot = None
if YUE2_DISPONIBLE and pipe is not None:
    t0 = time.time()

    # Etape 1 : plan (partition ABC planifiee, sauvegardee via l'API documentee)
    plan = pipe.plan(style=style_demo, lyrics=lyrics_demo, cot=cot_mode)
    print(f"[1/4] plan() en {time.time() - t0:.1f} s")
    plan.save(str(Path(output_dir) / "plan_zero_shot"))
    print(f"      plan sauvegarde : {output_dir}/plan_zero_shot")

    # Etape 2 : tokens semantiques (AR-NAR)
    t1 = time.time()
    semantic = pipe.generate_semantic(plan)
    print(f"[2/4] generate_semantic() en {time.time() - t1:.1f} s")

    # Etape 3 : latents acoustiques (flow-matching)
    t1 = time.time()
    latents = pipe.synthesize(semantic)
    print(f"[3/4] synthesize() en {time.time() - t1:.1f} s")

    # Etape 4 : audio 48 kHz stereo (VAE)
    t1 = time.time()
    chanson_zero_shot = pipe.decode(latents)
    print(f"[4/4] decode() en {time.time() - t1:.1f} s")

    # Persistance : le chemin decompose rend l'audio brut (ndarray), pas l'objet
    # SongResult du chemin one-shot -> on ecrit avec soundfile, la primitive
    # utilisee par SongResult.save (FLAC PCM_24, 48 kHz).
    import soundfile as sf
    sortie = Path(output_dir) / "zero_shot.flac"
    sf.write(sortie, chanson_zero_shot, 48000, subtype="PCM_24")
    print(f"chanson ecrite : {sortie} ({sortie.stat().st_size / 1024:.0f} Ko)")

    # Artefacts du plan (partition score.abc incluse — entree de la Section 7)
    plan.save(str(Path(output_dir) / "zero_shot"))
    print(f"artefacts (partition ABC incluse) : {output_dir}/zero_shot/")
    print(f"duree totale : {time.time() - t0:.1f} s")
else:
    print("GENERATION ZERO-SHOT — ROUTEE (RECOVERABLE-MACHINE)")
    print("-" * 45)
    print("Appel canonique (machine cible, 24 GB BF16 Linux) :")
    print()
    print("  plan = pipe.plan(style=style_demo, lyrics=lyrics_demo, cot='full')")
    print("  semantic = pipe.generate_semantic(plan)")
    print("  latents = pipe.synthesize(semantic)")
    print("  song = pipe.decode(latents)")
    print("  song.save('zero_shot.flac')")
    print("  song.save_artifacts('output/yue2-songs/zero_shot')")
    print()
    print("Ordre de grandeur (RTX 4090, carte du modele) : ~71 s pour une chanson")
    print("de 3.6 min, pic VRAM ~11.2 GiB. Artefacts attendus : zero_shot.flac,")
    print("Artefacts attendus : zero_shot.flac + dossier zero_shot/ (score.abc, tokens, latents).")
[1/4] plan() en 71.3 s
      plan sauvegarde : output/yue2-songs/plan_zero_shot
[2/4] generate_semantic() en 27.1 s
[3/4] synthesize() en 16.0 s
[4/4] decode() en 9.0 s
chanson ecrite : output/yue2-songs/zero_shot.flac (19741 Ko)
artefacts (partition ABC incluse) : output/yue2-songs/zero_shot/
duree totale : 125.1 s

Interpretation : ce que chaque etape apporte

Etape Artefact Ce qu’il revele
plan() partition ABC le modele a compose : structure, melodie, grille d’accords — lisible et editable
generate_semantic() tokens semantiques la traduction symbolique -> representation dense du rendu attendu
synthesize() latents le contenu acoustique en espace latent (flow-matching)
decode() WAV/FLAC 48 kHz stereo le rendu final, voix et instruments

Le point pedagogique : la partition de l’etape 1 est un artefact de premiere classe. Ce n’est pas un sous-produit — c’est l’interface d’entree des Sections 6 (cover) et 7 (edition agentique). Une architecture qui produit du symbolique editable ouvre des usages qu’une generation purement acoustique ne peut pas offrir.

Section 6 : Cover zero-shot depuis une melodie transcrite (cellule-type b)

Le cover zero-shot : on fournit au modele une melodie source (partition ABC sans symboles d’accords) et un style cible — le modele reharmonise et arrange la melodie dans le style demande, avec les paroles fournies. Le mode chain-of-thought adapte est cot="melody" : le plan melodique est impose (la source), seul l’habillage est genere.

La voie canonique de la famille YuE2 : transcrire l’enregistrement source avec SheetSage2 (audio -> ABC, notebook 04-16 de cette serie) puis couvrir. Ici, pour isoler la demonstration, la melodie source est une ABC ecrite a la main — un air simple en La mineur.

# Cover zero-shot : melodie ABC imposee + style cible (cellule-type b)
melodie_source = 'X:1\nT:Air simple pour cover\nM:4/4\nL:1/8\nK:Am\nA2 c2 e2 a2 | g2 e2 c2 A2 | B2 d2 f2 b2 | a6 z2 |\nA2 c2 e2 a2 | g2 e2 c2 e2 | d2 c2 B2 G2 | A6 z2 |'

print("MELODIE SOURCE (ABC, sans accords — imposee au plan)")
print("=" * 45)
print(melodie_source)
Path(output_dir, "melodie_source.abc").write_text(melodie_source, encoding="utf-8")

chanson_cover = None
if YUE2_DISPONIBLE and pipe is not None:
    t0 = time.time()
    chanson_cover = pipe(
        style="energetic rock cover, electric guitar, drums, " + style_demo.split(",")[0],
        lyrics=lyrics_demo,
        cot="melody",
        abc=melodie_source,
        seed=seed,
        cfg_scale=cfg_scale,
    )
    sortie = Path(output_dir) / "cover_rock.flac"
    chanson_cover.save(sortie)
    print(f"cover ecrit : {sortie} en {time.time() - t0:.1f} s")
else:
    print()
    print("COVER — ROUTE (RECOVERABLE-MACHINE)")
    print("-" * 45)
    print("Appel canonique :")
    print()
    print("  cover = pipe(style=..., lyrics=lyrics_demo, cot='melody',")
    print("                      abc=melodie_source, seed=seed, cfg_scale=cfg_scale)")
    print()
    print("Diff avec la Section 5 : le plan melodique n'est pas genere, il est")
    print("IMPOSE par l'ABC fournie. Seuls harmonisation/arrangement/rendu sont generes.")
    print("En production, la melodie provient de SheetSage2 (transcription audio->ABC).")
MELODIE SOURCE (ABC, sans accords — imposee au plan)
=============================================
X:1
T:Air simple pour cover
M:4/4
L:1/8
K:Am
A2 c2 e2 a2 | g2 e2 c2 A2 | B2 d2 f2 b2 | a6 z2 |
A2 c2 e2 a2 | g2 e2 c2 e2 | d2 c2 B2 G2 | A6 z2 |
cover ecrit : output/yue2-songs/cover_rock.flac en 51.0 s

Interpretation : cover = plan impose

La difference avec la Section 5 tient en une ligne : abc=melodie_source remplace le plan melodique genere. Le modele garde la main sur tout sauf la melodie — harmonisation, arrangement, timbres, chant. C’est le pattern des applications de cover (theme de jeu reharmonise, standard de jazz reinterprete), et c’est aussi la brique qui se combine avec SheetSage2 (notebook 04-16) pour boucler : n’importe quel enregistrement -> melodie ABC -> cover dans le style voulu.

Section 7 : Edition agentique d’une partition (cellule-type c)

L’edition agentique exploite le fait que la generation produit une partition ABC sur disque (artefact score.abc de la Section 5). Le flux documente par la carte :

  1. la generation de la Section 5 sauvegarde ses artefacts dont score.abc ;
  2. on edite la partition — reharmoniser un accord, developper un solo, changer la structure ;
  3. on regenere par le pipe de haut niveau avec abc=<partition editee> — le plan impose est la partition editee, comme en mode cover.

La boucle symbolique <-> audio est le claim central de YuE2 : la partition n’est pas une curiosite, c’est le plan de travail sur lequel un agent (ou un humain) itere avant de payer le cout de la synthese.

# Edition agentique : reharmoniser UN accord de la partition produite (cellule-type c)
chanson_editee = None
score_abc_path = Path(output_dir) / "zero_shot" / "score.abc"
if YUE2_DISPONIBLE and pipe is not None and score_abc_path.exists():
    abc_originale = score_abc_path.read_text(encoding="utf-8")
    print("PARTITION ORIGINALE (extrait, artefact de la Section 5) :")
    print(abc_originale[:200])
    print()

    # Edition symbolique : etendre le PREMIER ACCORD de la grille en accord de septieme.
    # Le planneur emet des accords inline quotes ("Fm", "Bbm", ...) dans le corps de la
    # partition ; les quotes de l'en-tete (name="Vocal Melody") ne sont PAS des accords.
    # On cherche donc apres la ligne de tonalite K: un token quote court en A-G.
    corps = abc_originale[abc_originale.find("K:"):]
    accord = remplacement = "(aucun)"
    abc_editee = abc_originale
    pos = corps.find('"')
    while pos != -1:
        fin_q = corps.find('"', pos + 1)
        token = corps[pos + 1:fin_q] if fin_q != -1 else ""
        if token and token[0] in "ABCDEFG" and 2 <= len(token) <= 6:
            accord = '"' + token + '"'
            remplacement = '"' + token + '7"'
            abc_editee = abc_originale.replace(accord, remplacement, 1)
            break
        pos = corps.find('"', fin_q + 1) if fin_q != -1 else -1
    modifie = abc_editee != abc_originale
    print(f"reharmonisation appliquee : {'oui (' + accord[1:-1] + ' -> ' + remplacement[1:-1] + ')' if modifie else 'cible non trouvee dans la grille'}")
    Path(output_dir, "score_edite.abc").write_text(abc_editee, encoding="utf-8")

    if modifie:
        # Re-entree documentee : pipe(abc=...) impose la partition editee
        t0 = time.time()
        chanson_editee = pipe(
            style=style_demo, lyrics=lyrics_demo, cot="full",
            abc=abc_editee, seed=seed, cfg_scale=cfg_scale,
        )
        sortie = Path(output_dir) / "edition_agentique.flac"
        chanson_editee.save(sortie)
        print(f"regeneration depuis la partition editee : {time.time() - t0:.1f} s -> {sortie}")
else:
    print("EDITION AGENTIQUE — ROUTEE (RECOVERABLE-MACHINE)")
    print("-" * 45)
    print("Flux canonique (machine cible, API documentee de la carte) :")
    print()
    print("  song = pipe(style=..., lyrics=..., cot='full', seed=...)   # Section 5")
    print("  song.save_artifacts('output/yue2-songs/zero_shot')        # produit score.abc")
    print("  abc = Path('.../zero_shot/score.abc').read_text(...)      # partition editable")
    print("  abc_edite = abc.replace('|Am |', '|Am7 |', 1)             # edition symbolique")
    print("  edited = pipe(style=..., lyrics=..., cot='full',")
    print("                     abc=abc_edite, seed=...)               # re-entree par abc=")
    print()
    print("Point cle : l'edition coute une ligne de texte, la regeneration")
    print("reutilise le pipeline deja charge — iteration agentique abordable (pas de")
    print("rechargement de poids entre les tours).")
PARTITION ORIGINALE (extrait, artefact de la Section 5) :
X:1
T:
M:4/4
L:1/32
Q:1/4=108
V: Vocal clef=treble name="Vocal Melody" snm="Vocal"
V: Ins clef=treble name="Ins Melody" snm="Inst."
K:Fm
% intro
V: Vocal
z24z4"Fm"z4|"Fm"z32|"Fm"z32|"Bbm"z32|
V: Ins
Z

reharmonisation appliquee : oui (Fm -> Fm7)
regeneration depuis la partition editee : 57.3 s -> output/yue2-songs/edition_agentique.flac

Interpretation : la partition comme plan de travail

La partition editee re-entre par pipe(abc=...) — la meme porte que le cover de la Section 6, mais avec une partition issue du modele lui-meme plutot que d’une source externe. Un agent peut donc iterer — reharmoniser, developper un solo, reviser les paroles, changer le style — en ne payant que la regeneration. C’est la difference entre un modele boite noire (prompt -> audio, tout ou rien) et un modele inspectable (prompt -> artefact editable -> audio).

Section 8 : Comparaison meme prompt, meme seed

Acceptance de l’issue : comparer honnetement YuE2-3B, MusicGen-medium (audiocraft en local, pattern du notebook 02-3) et YuE-v1 (etat documente de l’ancien 02-7), sur le meme prompt et la meme graine quand c’est possible.

Modele Type Executable ici ? Ce qu’on compare honnetement
YuE2-3B text-to-song machine cible 24 GB generation reelle (artefacts routes)
MusicGen-medium text-to-music audiocraft si present dans le kernel rendu instrumental du meme theme — la comparaison est bornee par nature : MusicGen ne chante pas, et l’API audiocraft n’expose pas de graine
YuE-v1 text-to-song 2 stages non (14 B, flash-attn) architecture et resultats rapportes (papier YuE-v1), jamais re-copies comme mesures propres

La comparaison meme-seed n’a de sens qu’entre systemes qui acceptent une graine et le meme format de prompt ; entre familles differentes (text-to-song vs text-to-music), on compare la reponse au meme theme, en declarant la difference de nature.

# Comparaison : MusicGen via audiocraft (pattern 02-3) + tableau honnete
musicgen_reponse = None
try:
    from audiocraft.models import MusicGen
    mg = MusicGen.get_pretrained("facebook/musicgen-medium")
    mg.set_generation_params(duration=15)
    musicgen_actif = True
    print("MusicGen-medium charge (audiocraft, pattern du notebook 02-3)")
except Exception as e:
    musicgen_actif = False
    print(f"MusicGen indisponible dans ce kernel ({type(e).__name__})")
    print("Pattern 02-3 : pip install audiocraft — colonne documentee, pas de simulacre.")

if musicgen_actif:
    # Meme theme, formate text-to-music (MusicGen ne chante pas ; pas de graine API)
    prompt_instrumental = ("inspiring pop instrumental, warm analog synth, 110 bpm, A minor, "
                           "female-vocal-like lead melody")
    t0 = time.time()
    wav = mg.generate([prompt_instrumental])
    musicgen_reponse = wav[0, 0].cpu().numpy()
    print(f"rendu instrumental du meme theme : {musicgen_reponse.size / mg.sample_rate:.1f} s "
          f"en {time.time() - t0:.1f} s (graine non fixable via l'API audiocraft — declare)")
    del mg
    gc.collect()

print()
print("TABLEAU COMPARATIF")
print("-" * 45)
lignes = [
    ("Entree", "paroles balisees + style", "description instrumentale", "paroles + accords"),
    ("Sortie", "chanson chantee 48 kHz stereo", "musique instrumentale", "chanson chantee"),
    ("Chemin", "plan ABC -> semantique -> latents -> VAE", "codec audio AR direct", "2 stages AR 7B separes"),
    ("Editable", "oui (partition ABC 1re classe)", "non (audio direct)", "partiellement (tokens)"),
    ("Poids", "3.59 B", "~2.3 B (medium)", "14 B (2 x 7B)"),
    ("Exec Ici", "routee 24 GB", "audiocraft local si installe", "non executable"),
]
entetes = ("Critere", "YuE2-3B", "MusicGen-medium", "YuE-v1")
largeurs = [max(len(str(l[i])) for l in [entetes] + lignes) for i in range(4)]
print("  ".join(h.ljust(largeurs[i]) for i, h in enumerate(entetes)))
for l in lignes:
    print("  ".join(str(c).ljust(largeurs[i]) for i, c in enumerate(l)))
print()
print("Claim SongBench (RAPORTE, non reproduit ici) : YuE2 6.9632 vs Suno v5 6.8721")
print("sur WildSongBench (192 prompts, best-of-8) — chiffre de la carte modele, a")
print("consommer comme claim editeur, pas comme mesure de ce notebook.")
MusicGen indisponible dans ce kernel (ModuleNotFoundError)
Pattern 02-3 : pip install audiocraft — colonne documentee, pas de simulacre.

TABLEAU COMPARATIF
---------------------------------------------
Critere   YuE2-3B                                   MusicGen-medium               YuE-v1                
Entree    paroles balisees + style                  description instrumentale     paroles + accords     
Sortie    chanson chantee 48 kHz stereo             musique instrumentale         chanson chantee       
Chemin    plan ABC -> semantique -> latents -> VAE  codec audio AR direct         2 stages AR 7B separes
Editable  oui (partition ABC 1re classe)            non (audio direct)            partiellement (tokens)
Poids     3.59 B                                    ~2.3 B (medium)               14 B (2 x 7B)         
Exec Ici  routee 24 GB                              audiocraft local si installe  non executable        

Claim SongBench (RAPORTE, non reproduit ici) : YuE2 6.9632 vs Suno v5 6.8721
sur WildSongBench (192 prompts, best-of-8) — chiffre de la carte modele, a
consommer comme claim editeur, pas comme mesure de ce notebook.

Interpretation : comparer sans tricher

Trois regles d’honnetete appliquees :

  1. Meme nature seulement : YuE2 et YuE-v1 sont comparables (text-to-song) ; MusicGen repond au meme theme mais sa sortie est de nature differente — la comparaison le declare au lieu de l’occulter.
  2. Mesure vs marketing : le score SongBench est un claim rapporte (carte du modele), jamais presente comme une mesure de cette session — reproduire le mesure, pas le slogan.
  3. Ce qui n’a pas tourne est dit : YuE-v1 n’est pas executable ici ; sa colonne decrit l’architecture documentee, pas une execution.

Exercice 2 : Grille d’evaluation comparative

Objectif : construire grille_evaluation(audio_a, audio_b) qui, etant donnes deux fichiers audio, remplit une grille qualitative (duree, debit de paroles entendues, presence d’un refrain distinct, largeur stereo percue).

Etapes : 1. Charger chaque audio (duree via ses metadonnees) ; 2. Noter chaque critere sur 0-2 avec une regle explicite (ex. refrain distinct = 2 si un segment se repete avec paroles identiques) ; 3. Rendre la grille sous forme de dictionnaire.

Indice : se focaliser sur des criteres observables sans modele annexes — la grille doit rester applicable a l’oreille.

# Exercice 2 : Grille d'evaluation comparative
def grille_evaluation(audio_a, audio_b):
    # TODO etudiant : renvoyer la grille qualitative des deux audios...
    pass


print("Exercice a completer : grille_evaluation")
Exercice a completer : grille_evaluation

Exercice 3 : Choisir son mode chain-of-thought

Objectif : ecrire choisir_cot(objectif) qui renvoie le mode adapt parmi "full", "melody", "off" selon l’objectif : creation libre, cover d’une melodie existante, iteration rapide sur le rendu.

Etapes : 1. Documenter la table de decision (objectif -> mode) en commentaire ; 2. Renvoyer le mode ; 3. Gerer le cas objectif inconnu en renvoyant "full" (defaut sur).

Indice : cot="off" saute le plan — utile quand on fournit deja une partition (edition agentique de la Section 7), cout moindre mais zero planification.

# Exercice 3 : Choisir son mode chain-of-thought
def choisir_cot(objectif):
    # TODO etudiant : table de decision objectif -> "full" | "melody" | "off"...
    pass


print("Exercice a completer : choisir_cot")
Exercice a completer : choisir_cot
# Statistiques de session
print("STATISTIQUES DE SESSION")
print("=" * 45)
print(f"Date                {time.strftime('%Y-%m-%d %H:%M:%S')}")
print(f"Kernel              python3 ({sys.version.split()[0]})")
print(f"Modele              {yue2_model_id} (AR-NAR MoT 3.59 B, 48 kHz stereo)")
print(f"Graine              {seed} (same-prompt same-seed, Section 8)")
print(f"CoT                 {cot_mode} | cfg_scale {cfg_scale} | VAE {yue2_vae}")
if cuda_dispo:
    import torch
    print(f"Device              {torch.cuda.get_device_properties(0).name}")
else:
    print("Device              CPU (generation routee RECOVERABLE-MACHINE)")
print(f"Verdict exec        {'REEL' if YUE2_DISPONIBLE else 'RECOVERABLE-MACHINE (eGPU 3090 24 GB Linux)'}")
artefacts = sorted(p.name for p in Path(output_dir).glob('*')) if Path(output_dir).exists() else []
print(f"Artefacts           {len(artefacts)} fichier(s) dans {output_dir}/")
for a in artefacts:
    print(f"  - {a}")
print(f"Licence poids       CC BY-NC 4.0 (usage commercial interdit)")

if YUE2_DISPONIBLE and pipe is not None:
    pipe.close()
    print("pipeline libere (pipe.close())")
STATISTIQUES DE SESSION
=============================================
Date                2026-09-13 04:22:58
Kernel              python3 (3.10.12)
Modele              m-a-p/YuE2-3B (AR-NAR MoT 3.59 B, 48 kHz stereo)
Graine              42 (same-prompt same-seed, Section 8)
CoT                 full | cfg_scale 1.2 | VAE default
Device              NVIDIA GeForce RTX 3090
Verdict exec        REEL
Artefacts           7 fichier(s) dans output/yue2-songs/
  - cover_rock.flac
  - edition_agentique.flac
  - melodie_source.abc
  - plan_zero_shot
  - score_edite.abc
  - zero_shot
  - zero_shot.flac
Licence poids       CC BY-NC 4.0 (usage commercial interdit)
pipeline libere (pipe.close())

Conclusion : le symbolique comme interface

Ce notebook a suivi la generation de chansons de YuE-v1 (deux stages sepres, 14 B) a YuE2-3B (backbone AR-NAR unique + flow-matching + VAE 48 kHz stereo), en executant les 4 etapes canoniques plan -> generate_semantic -> synthesize -> decode et les trois usages marques : generation zero-shot, cover zero-shot (cot='melody'), edition agentique d’une partition ABC.

Le fil rouge : la partition ABC est devenue une interface — planifiee (Section 5), imposee (Section 6), editee (Section 7). C’est la these de la famille YuE2 : les satellites MERT2 (comprehension, notebook 04-15) et SheetSage2 (transcription, notebook 04-16) ferment la boucle audio -> symbolique -> audio autour du generateur.

Verdicts par axe

Axe Verdict
Vrais poids YuE2-3B charges SOTA-OK sur machine cible 24 GB / RECOVERABLE-MACHINE documente ici (wheel + Linux + VRAM testes un a un)
Execution des generations RECOVERABLE-MACHINE — eGPU RTX 3090 24 GB sous Linux (machine GenAI du cluster)
Comparaison MusicGen service local quand actif, sinon colonne documentee — jamais de simulacre
Comparaison YuE-v1 INTRINSIC local (14 B + flash-attn hors de portee de cette machine) — resultats rapportes, identifies comme tels
Claim SongBench vs Suno v5 rapporte, non reproduit — declare comme claim editeur
Licence poids CC BY-NC 4.0, rappelee en tete, en setup et en stats de session

Pour aller plus loin

  • Notebook 04-15 (MERT2) : mesurer les embeddings de vos generations — la comprehension musicale de la meme famille.
  • Notebook 04-16 (SheetSage2) : transcrire un enregistrement en ABC pour nourrir un cover zero-shot.
  • Site officiel YuE2 (map-yue2.github.io) : le rapport technique et les demos.
Retour au sommet