15. Audio : musique, effets sonores et parole
Sections
Réglages de lecture
Aucune voix pour cette langue dans ce navigateur.
Plus de voix
Code et termes techniques en anglais
Désactivé : la voix de la page lit tout, sans pause entre deux voix.
Aucune voix pour cette langue dans ce navigateur.
Plus de voix
D’autres voix : Microsoft Edge propose des voix naturelles en ligne, et Windows (Paramètres › Heure et langue › Voix), macOS et Android en installent d’autres.
Code : les workflows au format API dans audio/workflows/, les exécutions de cette leçon dans audio/runs/, le pipeline pipeline.py, les scripts de mesure analyze.py, measure.py et wer.py, le validateur de workflows validate.py, et les tests, tous exécutés par audio/check.sh et le workflow comfyui-audio.yml.
Les premières leçons généraient des images. Le cœur de ComfyUI génère aussi du son : de la musique avec paroles, des effets sonores, et la piste audio d’une vidéo. Le graphe est du même genre, avec un chargeur, un encodeur de texte, un latent vide, un échantillonneur et un décodeur VAE ; ce qui change, c’est ce que représente un latent et ce que l’encodeur de texte fait d’un prompt. Cette leçon lit cela dans le code source de la v0.36.0, fait tourner deux familles de modèles sur une RTX 5080, mesure ce qui en est sorti face à ce qui était demandé, puis regarde ce que le cœur ne fait pas, la parole, et ce qu’il faut pour l’ajouter.
Chaque extrait ci-dessous a été généré pour cette leçon et normalisé à −16 LUFS par le pipeline de la leçon. Sa légende donne le modèle, la seed, le workflow et la licence. Le cours a mesuré les extraits avec des scripts ; il ne les a pas jugés à l’oreille, et ni les mesures ni ce texte n’affirment qu’ils sonnent bien.
Le type AUDIO
Section intitulée « Le type AUDIO »Les images circulent entre les nœuds sous forme de tenseurs ; le son aussi. Une valeur AUDIO est un dictionnaire Python à deux clés : waveform, un tenseur de flottants de forme [batch, channels, samples], et sample_rate, un entier. En termes C#, c’est le WaveFormat de NAudio plus un float[] par canal ; en Java, un AudioFormat plus les échantillons d’un AudioInputStream, déjà décodés en flottants.
LoadAudio décode un fichier avec PyAV, les bindings FFmpeg dont ComfyUI a besoin, et lit donc tout ce que lit FFmpeg, y compris la piste audio d’une vidéo. Les échantillons entiers deviennent des flottants entre −1 et 1, et le nœud ajoute la dimension de batch (nodes_audio.py, lignes 333 à 400) :
waveform, sample_rate = load(audio_path)audio = {"waveform": waveform.unsqueeze(0), "sample_rate": sample_rate}Sa liste de fichiers est le dossier d’entrée filtré par type de contenu, audio et vidéo (ligne 364), et comme pour LoadImage, sa clé de cache est le SHA-256 du fichier (lignes 384 à 390) : remplacez le fichier, et l’exécution suivante le recharge.
Deux nœuds terminent un graphe :
PreviewAudioécrit un fichier FLAC dans le dossier temporaire, sous un nom aléatoireComfyUI_temp_, pour que le navigateur puisse le lire (_ui.py, lignes 417 à 430).SaveAudioAdvancedécrit dans le dossier de sortie, en FLAC, MP3 ou Opus (nodes_audio.py, lignes 251 à 296).SaveAudio,SaveAudioMP3etSaveAudioOpusexistent toujours, marqués comme dépréciés.
SaveAudioAdvanced a un dynamic combo : l’entrée quality n’existe que lorsque format vaut mp3 ou opus. Au format API, une telle sous-entrée s’écrit avec un point, "format.quality". Envoyé sans elle, un workflow avec "format": "mp3" revient du serveur CPU de la CI avec Required input is missing: quality, et avec "format.quality": "nope", avec Value not in list: format.quality: 'nope' not in ['V0', '128k', '320k']. Les fichiers enregistrés portent aussi le workflow : ffprobe montre une balise prompt dans les fichiers FLAC et Opus, le même JSON que la leçon 3 trouvait dans les fichiers PNG.
La catégorie audio du cœur compte 15 nœuds, et ce sont des nœuds de plomberie : LoadAudio, RecordAudio, PreviewAudio, les quatre nœuds d’enregistrement, EmptyAudio, TrimAudioDuration, SplitAudioChannels, JoinAudioChannels, AudioConcat, AudioMerge, AudioAdjustVolume et AudioEqualizer3Band. Le workflow de la CI, 15-cpu-empty-audio.api.json, en utilise trois pour enregistrer deux secondes de silence sans modèle.
Latents : un VAE audio compresse le temps
Section intitulée « Latents : un VAE audio compresse le temps »Le VAE de la leçon 2 transforme une image 1024 × 1024 en un latent 128 × 128 à 4 ou 16 canaux. Un VAE audio fait la même chose le long du temps : une trame latente représente un nombre fixe d’échantillons, et le modèle de diffusion travaille sur la courte suite de trames, jamais sur les 48 000 échantillons de chaque seconde. Le cœur détecte quel VAE contient un fichier à partir des noms de ses tenseurs, dans sd.py, et les chiffres diffèrent selon le modèle :
| VAE | Fréquence d’échantillonnage | Échantillons par trame latente | Trames latentes par seconde | Canaux |
|---|---|---|---|---|
| Stable Audio Open 1.0 | 44.1 kHz | 2 048 | 21.5 | 64 |
| ACE-Step 1.5 | 48 kHz | 1 920 | 25 | 64 |
| Stable Audio 3 | 44.1 kHz | 4 096 | 10.8 | 256 |
Les nœuds de latent vide transforment des secondes en trames. EmptyLatentAudio, écrit pour Stable Audio Open 1.0, calcule round((seconds * 44100 / 2048) / 2) * 2 trames de 64 canaux (nodes_audio.py, lignes 31 à 34) ; EmptyAceStep1.5LatentAudio calcule round(seconds * 48000 / 1920) (nodes_ace.py, lignes 103 à 107). Stable Audio 3 n’a pas de nœud à lui : son template utilise EmptyLatentAudio, et l’échantillonneur corrige la forme. Quand le latent est entièrement nul, fix_empty_latent_channels le répète jusqu’au nombre de canaux du modèle et remet sa longueur à l’échelle selon le rapport des deux tailles de trame, ici 2 048 / 4 096 (sample.py, lignes 45 à 69, appelé par KSampler à la ligne 1574 de nodes.py). Un latent qui n’est pas vide, issu de VAEEncodeAudio, n’est pas modifié.
Les fichiers de cette leçon concordent avec ce calcul, à une exception près. Des exécutions ACE-Step de 6, 10, 20, 30 et 40 secondes ont donné exactement 288 000, 480 000, 960 000, 1 440 000 et 1 920 000 échantillons. Des exécutions Stable Audio 3 de 3 et 10 secondes ont donné 131 072 et 442 368 échantillons, soit 32 et 108 trames de 4 096 ; une exécution de 4 secondes a donné 180 224 échantillons, 44 trames, là où la formule en donne 43. Le décodeur de Stable Audio 3 complète son entrée jusqu’à un multiple d’une taille de bloc (vae_sa3.py, lignes 300 à 304), ce qui expliquerait la trame en trop : à vérifier.
VAEDecodeAudio a une étape de plus qui compte quand on mesure le volume sonore : il divise la forme d’onde par cinq fois son écart type chaque fois que ce produit dépasse 1 (nodes_audio.py, lignes 108 à 110). Les sorties fortes sont atténuées ; les faibles restent telles quelles. C’est une sécurité contre l’écrêtage, pas une norme de volume : les extraits musicaux bruts de cette page mesuraient entre −11.4 et −21.2 LUFS, et une ambiance de pièce −39.9 LUFS, d’où la normalisation avec FFmpeg dans le pipeline à la fin de cette leçon.
De la musique avec ACE-Step 1.5
Section intitulée « De la musique avec ACE-Step 1.5 »Quel ACE-Step
Section intitulée « Quel ACE-Step »Le cœur v0.36.0 prend en charge deux générations d’ACE-Step, un modèle musical d’ACE Studio et StepFun. La version 1.0 a TextEncodeAceStepAudio et EmptyAceStepLatentAudio ; la version 1.5 a TextEncodeAceStepAudio1.5, EmptyAceStep1.5LatentAudio et un ReferenceTimbreAudio expérimental pour les reprises (nodes_ace.py). Le template livré avec le cœur, Text to Audio (ACE-Step 1.5), charge quatre fichiers de Comfy-Org/ace_step_1.5_ComfyUI_files : le modèle de diffusion turbo, deux encodeurs de texte et le VAE. Le cours utilise les mêmes fichiers, sauf le second encodeur de texte : le modèle de langage 1.7B au lieu du 4B, ce qui économise 4.7 GB de téléchargement et de mémoire.
| Fichier | Rôle | Taille | SHA-256 |
|---|---|---|---|
acestep_v1.5_turbo.safetensors |
transformer de diffusion, 8 étapes | 4.79 GB | 3f6e0797…e422d7 |
qwen_0.6b_ace15.safetensors |
embeddings du texte et des paroles | 1.19 GB | fd4590c8…aa7a7 |
qwen_1.7b_ace15.safetensors |
modèle de langage qui planifie l’audio | 3.71 GB | ed63e924…ebd710 |
ace_1.5_vae.safetensors |
VAE audio, 48 kHz stéréo | 0.34 GB | 6de92e3a…cb2bb |
Le dépôt amont, ACE-Step/Ace-Step1.5, est sous licence MIT, et sa fiche dit « You can strictly use the generated music for commercial purposes » ; les fichiers réempaquetés portent Apache 2.0 sur Hugging Face. Aucune des deux licences ne restreint la publication des sorties ni l’endroit où elles sont utilisées, et la fiche indique que les données d’entraînement sont sous licence, libres de droits et synthétiques.
Tags, paroles et métadonnées passent par un modèle de langage
Section intitulée « Tags, paroles et métadonnées passent par un modèle de langage »TextEncodeAceStepAudio1.5 a les entrées qu’on attend d’un prompt musical, tags, lyrics, bpm, duration, timesignature, language et keyscale, et d’autres plus inattendues : seed, generate_audio_codes, cfg_scale, temperature, top_p, top_k et min_p (nodes_ace.py, lignes 31 à 61). Ce sont les réglages d’un modèle de langage, et le tokenizer montre ce qui se passe (ace15.py, lignes 208 à 274) :
-
La tonalité, le tempo, la signature rythmique et la durée deviennent du texte. Ils sont écrits en YAML dans un bloc
<think>d’un prompt de chat pour le modèle de langage, et sous forme de liste# Metaspour le modèle d’embeddings :lm_template = "<|im_start|>system\n# Instruction\nGenerate audio semantic tokens based on the given conditions:\n\n<|im_end|>\n<|im_start|>user\n# Caption\n{}\n\n# Lyric\n{}\n<|im_end|>\n<|im_start|>assistant\n{}\n\n<|im_end|>\n" -
Le modèle de langage génère ensuite des codes audio, cinq par seconde de musique :
tokens_duration = duration * 5, utilisé à la fois comme nombre minimal et maximal de tokens (lignes 226 à 230). La boucle d’échantillonnage est celle de n’importe quel modèle de chat : logits restreints à la plage des tokens audio, guidance sans classifieur entre le prompt et un prompt négatif au bloc<think>vide, température, top-k, top-p, et untorch.Generatorinitialisé avec laseeddu nœud (lignes 30 à 140). -
Le modèle 0.6B produit les embeddings de la description et, séparément, des paroles (lignes 323 à 339).
Dans le modèle de diffusion, un détokeniseur développe chaque code audio en cinq trames, de 5 à 25 trames par seconde, la cadence du latent du VAE, et ces trames sont les « indices » autour desquels le transformer débruite (ace_step15.py, lignes 1077 à 1112). Avec generate_audio_codes désactivé, aucun plan n’est généré, et le modèle part à la place d’un latent de silence intégré (model_base.py, lignes 2516 à 2553).
flowchart LR T[tags, paroles] --> E[Qwen3 0.6B : embeddings] M[tonalité, BPM, durée, en texte] --> L[Modèle de langage 1.7B : 5 codes audio par seconde] T --> L S[seed] --> L L --> D[Détokeniseur : 25 trames par seconde] E --> X[Transformer de diffusion, 8 étapes] D --> X N[Latent vide : 25 trames par seconde] --> X X --> V[Décodeur VAE : 48 kHz stéréo]Cela compte pour ce que vous pouvez attendre. Rien dans le graphe n’impose une tonalité ou un tempo : C major et 90 sont des mots dans un prompt, que le modèle de langage a été entraîné à suivre. Les mesures ci-dessous vérifient à quel point il y parvient.
Le graphe est celui du template, au format API : 15-ace-step.api.json. ModelSamplingAuraFlow règle le décalage du flow matching à 3, et KSampler exécute 8 étapes d’euler à CFG 1, parce que le modèle turbo a été distillé pour se passer de guidance. Le conditionnement négatif est le positif mis à zéro par ConditioningZeroOut. La seed est réglée deux fois, sur l’encodeur de texte pour les codes audio et sur KSampler pour le bruit ; les exécutions de cette leçon leur donnent toujours la même valeur.
L’exécuter, et combien de temps cela prend
Section intitulée « L’exécuter, et combien de temps cela prend »Les 21 exécutions sont dans runs/ace-step.json, chacune étant une liste de modifications --set node.input=value du workflow, et pipeline.py batch les a mises en file l’une après l’autre sur un ComfyUI démarré avec son propre dossier de base. Sur la RTX 5080, avec 12.3 GB de VRAM libres au début du lot, le journal du serveur indique :
- La première exécution a pris 38.8 s, surtout du chargement : les deux encodeurs de texte ont été préparés sur 4 672 MB, le modèle de diffusion sur 4 564 MB, et le VAE chargé sur 321.7 MB, avec le chargement dynamique en VRAM de ComfyUI.
- Un extrait de 20 secondes a ensuite pris 4.3 à 4.5 s, un extrait de 10 secondes 3.2 s, et un extrait de 40 secondes 8.5 s.
- Le même extrait de 20 secondes sans codes audio a pris 1.9 s : la planification du modèle de langage représente plus de la moitié du temps.
Seed, durée, tags, paroles
Section intitulée « Seed, durée, tags, paroles »La boucle dont partent les mesures suivantes demande solo acoustic guitar loop, fingerpicked, warm, dry studio recording, instrumental, les paroles [Instrumental], C major, 90 BPM, 4/4, 20 secondes, seed 1.
Généré par ComfyUI v0.36.0 : ACE-Step 1.5 turbo avec Qwen3 0.6B et 1.7B, seed 1, 8 étapes, euler, simple, CFG 1, décalage 3, exécution loop-c-seed1 de runs/ace-step.json, workflow 15-ace-step.api.json. Licence MIT. Normalisé à −16 LUFS, MP3 à 96 kb/s.
measure.py lit chaque fichier FLAC enregistré et affiche son format, une empreinte de ses échantillons et son pic. Pour les exécutions ACE-Step, il compare aussi la tonalité et le tempo demandés avec ce qu’entend analyze.py. La tonalité est l’estimation de Krumhansl-Kessler : le chroma moyen de l’extrait, calculé avec chroma_cqt de librosa, corrélé avec les 24 profils de tonalités majeures et mineures ; r est la meilleure corrélation. Le tempo est le beat_track de librosa. Un extrait synthétique C-F-G-C à 120 BPM, que construisent les tests, ressort en C major avec r = 0.97 et 117.5 BPM.
$ python measure.py runs/ace-step.json G:/learn-lab/comfyui/audio/runs/aceloop-c-seed1: 48000 Hz, 2 ch, 20.00 s, samples 3a0f4dc422dd21cc, peak 1.00 | asked C major, 90 BPM; heard C major (r=0.66), 92.3 BPM | key yes, tempo ratio 1.03loop-c-seed1-again: 48000 Hz, 2 ch, 20.00 s, samples 3a0f4dc422dd21cc, peak 1.00 | asked C major, 90 BPM; heard C major (r=0.66), 92.3 BPM | key yes, tempo ratio 1.03loop-c-seed2: 48000 Hz, 2 ch, 20.00 s, samples 06a7e133106ce1ce, peak 0.90 | asked C major, 90 BPM; heard G major (r=0.49), 161.5 BPM | key no, tempo ratio 1.79loop-c-seed3: 48000 Hz, 2 ch, 20.00 s, samples b13fe842c3eef028, peak 1.00 | asked C major, 90 BPM; heard C major (r=0.42), 60.1 BPM | key yes, tempo ratio 0.67loop-c-10s: 48000 Hz, 2 ch, 10.00 s, samples 388373d59bbef315, peak 0.77 | asked C major, 90 BPM; heard G major (r=0.67), 95.7 BPM | key no, tempo ratio 1.06loop-c-40s: 48000 Hz, 2 ch, 40.00 s, samples 1ab6165dd90985c5, peak 1.00 | asked C major, 90 BPM; heard A minor (r=0.41), 89.1 BPM | key relative, tempo ratio 0.99loop-c-no-codes: 48000 Hz, 2 ch, 20.00 s, samples ef78ea83a483aef3, peak 1.00 | asked C major, 90 BPM; heard C major (r=0.69), 89.1 BPM | key yes, tempo ratio 0.99loop-tags-rock: 48000 Hz, 2 ch, 20.00 s, samples bc707dd2c2b04877, peak 1.00 | asked C major, 90 BPM; heard C major (r=0.66), 89.1 BPM | key yes, tempo ratio 0.99Ce que montrent ces lignes :
- Même seed, mêmes échantillons, mais pas pour la raison qu’on croit.
loop-c-seed1-againa la même empreinte, mais le journal du serveur indique qu’elle s’est exécutée en 0.06 s : toutes les entrées des nœuds étaient identiques sauf le préfixe du nom de fichier, donc ComfyUI a réutilisé son cache et seul le nœud d’enregistrement a tourné. Le vrai test est un serveur neuf. Arrêté, redémarré et soumis aux deux mêmes exécutions, il a produit les mêmes empreintes d’échantillons,3a0f4dc422dd21ccpour la boucle etf892c13bf2b0f920pour le jingle plus bas. C’était sur un seul GPU avec un seul pilote ; d’un GPU à l’autre, les réserves de la leçon 2 s’appliquent : à vérifier. - Une autre seed, un autre morceau. La seed 2 a été entendue en G major, la dominante de C, et la seed 3 en C major avec une corrélation plus faible. La seed pilote l’échantillonnage des codes audio par le modèle de langage et le bruit de diffusion, elle change donc plus que la texture.
- La durée change aussi le morceau. 10 secondes de la seed 1 ne sont pas la première moitié de 20 secondes de la seed 1 : le modèle de langage écrit 50 codes au lieu de 100, et la tonalité est sortie en G major. À 40 secondes, l’estimation a été A minor, le relatif mineur de C, avec un r faible.
- Sans codes audio, le résultat était encore C major à 89 BPM, la plus proche de la demande des trois boucles, en moins de la moitié du temps. Sur un seul extrait, cela ne dit rien de général.
- D’autres tags, même tonalité et même tempo.
loop-tags-rocka gardé la tonalité, le tempo et la seed et a changé les tags endistorted electric guitar riff, heavy rock, drums, instrumental. La tonalité et le tempo ont été tenus ; les scripts ne peuvent pas dire si l’extrait sonne rock.
Généré par ComfyUI v0.36.0 : le même workflow et le même prompt, seed 2, exécution loop-c-seed2. Entendu en G major à 161.5 BPM. Licence MIT. Normalisé à −16 LUFS, MP3 à 96 kb/s.
Généré par ComfyUI v0.36.0 : le même workflow et le même prompt, seed 1, generate_audio_codes à false, exécution loop-c-no-codes. Licence MIT. Normalisé à −16 LUFS, MP3 à 96 kb/s.
Généré par ComfyUI v0.36.0 : le même workflow, seed 1, C major, 90 BPM, tags distorted electric guitar riff, heavy rock, drums, instrumental, exécution loop-tags-rock. Licence MIT. Normalisé à −16 LUFS, MP3 à 96 kb/s.
Tonalité et tempo, huit demandes
Section intitulée « Tonalité et tempo, huit demandes »Le balayage garde les tags de la boucle et la seed 1, et change la tonalité et le tempo :
| Demandé | Tonalité entendue | r | Tempo entendu | Tonalité | Rapport de tempo |
|---|---|---|---|---|---|
| C major, 90 BPM | C major | 0.66 | 92.3 | oui | 1.03 |
| C major, 70 BPM | G major | 0.72 | 143.6 | non | 2.05 |
| E minor, 100 BPM | E minor | 0.65 | 66.3 | oui | 0.66 |
| A major, 140 BPM | A major | 0.86 | 143.6 | oui | 1.03 |
| F# minor, 80 BPM | F# minor | 0.49 | 80.7 | oui | 1.01 |
| Bb major, 120 BPM | F# major | 0.43 | 117.5 | non | 0.98 |
| D minor, 60 BPM | D minor | 0.74 | 117.5 | oui | 1.96 |
| G major, 160 BPM | G major | 0.59 | 83.4 | oui | 0.52 |
| Eb major, 95 BPM | F# major | 0.50 | 95.7 | non | 1.01 |
Sur les 20 exécutions musicales distinctes de la leçon (la répétition servie par le cache mise à part), l’estimation de tonalité a correspondu à la demande 12 fois, a trouvé le relatif mineur une fois et a manqué 7 fois. L’estimation de tempo était à moins de 7 % de la demande 12 fois ; 7 fois, elle était proche du double, de la moitié ou des deux tiers, y compris pour la chanson plus bas, et une fois elle était sans rapport, 1.79 fois la demande pour la seed 2. Un détecteur de pulsation se cale souvent sur la moitié ou le double du tempo qu’un musicien compterait, donc un rapport de 2 ou 0.5 est aussi probablement l’erreur de l’estimateur que celle du modèle ; le cours n’a pas compté les temps à l’oreille, et les rapports proches de 2 restent à vérifier. Les deux échecs dans des tonalités à bémols, Bb et Eb major, tous deux entendus en F# major, sont le motif qui ressort, sur une seed chacun.
Les exemples Guitar Alchemist, et un échec
Section intitulée « Les exemples Guitar Alchemist, et un échec »Un jingle d’introduction de leçon : short cheerful intro jingle for an online guitar lesson, bright acoustic guitar strum, glockenspiel, handclaps, ends on a clear final chord, instrumental, G major, 110 BPM, 6 secondes. Les seeds 1 et 2 ont toutes deux été entendues en G major à 112.3 BPM, avec r = 0.87 et 0.51.
Généré par ComfyUI v0.36.0 : ACE-Step 1.5 turbo, seed 1, G major, 110 BPM, 6 s, exécution jingle-seed1, workflow 15-ace-step.api.json. Licence MIT. Normalisé à −16 LUFS, MP3 à 96 kb/s.
Une chanson avec des paroles originales sur la construction d’un accord, gentle folk song, acoustic guitar, soft female vocals, intimate, D major, 80 BPM, 30 secondes :
[verse]Find the root and then the thirdStack a fifth and hear the chord[chorus]One, four, five, and home againElle a été entendue en D major avec r = 0.87, et à 152 BPM, près du double de la demande. Whisper large-v3, le transcripteur de la section sur la parole plus bas, a entendu « Find the root and then the third Stack a fifth and hear the chord Four and five and home » : le couplet est intact, et le refrain a perdu « One » et « again », un taux d’erreur de 3 mots sur 20.
Généré par ComfyUI v0.36.0 : ACE-Step 1.5 turbo, seed 1, D major, 80 BPM, 30 s, paroles ci-dessus, exécution song-lyrics. La voix est celle du modèle ; aucune voix de chanteur réel n’a été demandée. Licence MIT. Normalisé à −16 LUFS, MP3 à 96 kb/s.
Un ii-V-I jazz, c’est là que cela a échoué. Les tags étaient jazz guitar trio playing a ii-V-I progression, Dm7 G7 Cmaj7, medium swing, clean archtop guitar, upright bass, brushes on snare, instrumental, C major, 120 BPM, 20 secondes. measure.py --chords étiquette chaque mesure avec le plus proche de 60 modèles d’accords (majeur, mineur, 7, maj7 et m7 sur 12 fondamentales), les mesures étant comptées à partir du détecteur de pulsation :
jazz-251-seed1: 48000 Hz, 2 ch, 20.00 s, samples c3e2218b5183858c, peak 1.00 | asked C major, 120 BPM; heard D major (r=0.36), 123.0 BPM | key no, tempo ratio 1.03 1.04 s G r=0.38 3.04 s C#maj7 r=0.57 5.04 s C#maj7 r=0.50 7.04 s Dm r=0.66 9.03 s Bbmaj7 r=0.40 11.05 s Ab r=0.59 13.05 s C#maj7 r=0.64 15.05 s Bm r=0.57jazz-251-seed2: 48000 Hz, 2 ch, 20.00 s, samples 3b84301e70e82c49, peak 1.00 | asked C major, 120 BPM; heard F minor (r=0.34), 60.1 BPM | key no, tempo ratio 0.50 0.98 s C r=0.37 4.97 s Dm7 r=0.29 8.99 s Fm7 r=0.60Aucune des deux seeds ne montre un cycle Dm7, G7, Cmaj7, et les deux corrélations de tonalité sont les plus faibles de la leçon, ce que donne une harmonie qui ne se pose pas dans une seule tonalité. Les étiquettes d’accords elles-mêmes sont une preuve faible : sur l’extrait synthétique C-F-G-C, dont les tests connaissent les accords, les mesures sont sorties décalées et étiquetées Cmaj7, C, Fmaj7, G7, donc prenez-les comme un indice. Ce qu’on peut dire, c’est qu’une grille d’accords écrite dans les tags n’a pas été suivie comme une grille, en deux essais sur deux. Les tags sont une description pour un modèle de langage, pas une partition : ACE-Step 1.5 prend une tonalité, un tempo et une signature rythmique, et rien dans ses entrées n’est un accord.
Généré par ComfyUI v0.36.0 : ACE-Step 1.5 turbo, seed 1, C major, 120 BPM, 20 s, exécution jazz-251-seed1. La grille demandée n’est pas celle qu’a trouvée l’analyse. Licence MIT. Normalisé à −16 LUFS, MP3 à 96 kb/s.
Effets sonores avec Stable Audio 3
Section intitulée « Effets sonores avec Stable Audio 3 »Le cœur a deux familles Stable Audio. Stable Audio Open 1.0 se conditionne sur un encodeur de texte T5 et sur deux nombres, seconds_start et seconds_total, réglés par ConditioningStableAudio. Stable Audio 3, dont Comfy-Org a réempaqueté les fichiers dans Comfy-Org/stable-audio-3, utilise un encodeur de texte T5Gemma et seulement seconds_total, que le modèle calcule à partir de la longueur du latent quand aucun nœud ne le règle : int(noise.shape[-1] / 10.7666), le nombre de trames divisé par le nombre de trames par seconde (model_base.py, lignes 877 à 906). Le cœur livre deux templates pour le modèle medium de 9.2 GB. Le cours utilise les deux petits modèles, l’un entraîné pour les effets sonores et l’autre pour la musique, de 2.27 GB chacun, avec le même encodeur de texte :
| Fichier | Taille | SHA-256 |
|---|---|---|
stable_audio_3_small_sfx.safetensors |
2.27 GB | ed9cf1b6…2e515 |
stable_audio_3_small_music.safetensors |
2.27 GB | da85866b…4b64 |
t5gemma_b_b_ul2.safetensors |
1.19 GB | 1e1eba25…150e |
Le fichier de l’encodeur de texte n’est pas celui de Google : google/t5gemma-b-b-ul2 publie un fichier d’une autre empreinte, et celui de Comfy-Org en est une conversion.
Le graphe, 15-stable-audio-sfx.api.json, est celui du template sans sa branche d’écriture du prompt : CheckpointLoaderSimple pour le modèle et son VAE, CLIPLoader de type stable_audio pour T5Gemma, deux CLIPTextEncode, EmptyLatentAudio, et KSampler avec 8 étapes de lcm à CFG 1, comme le template le règle pour ces modèles post-entraînés de façon adversariale.
Deux licences
Section intitulée « Deux licences »Les poids sont sous la Stability AI Community License, datée du 5 juillet 2024, le même fichier dans les trois dépôts Stable Audio 3, et l’encodeur de texte sous les Gemma Terms of Use, modifiées pour la dernière fois le 1er avril 2026. Ce qu’elles disent des sorties, mot pour mot :
- Stability, section IV.c.(iii) : « As between You and Stability AI, You own any outputs generated from the Models or Derivative Works to the extent permitted by applicable law. » Sa définition des Derivative Works se termine par « but do not include the output of any Model. »
- Stability, section III, pour l’usage commercial : « If at any time You or Your Affiliate(s), either individually or in aggregate, generate more than USD $1,000,000 in annual revenue (…), any licenses granted to You under this Agreement shall terminate as of such date. » L’usage commercial exige aussi de s’enregistrer auprès de Stability AI.
- Stability, section IV.a, seulement si vous distribuez les modèles ou un produit qui les utilise : une copie de l’accord, un fichier « Notice », et la mention « Powered by Stability AI » affichée. Les deux licences sont accordées « worldwide » (Stability, sections II et III) ; les conditions de Gemma n’ont pas de clause territoriale.
- Stability, section IV.b : les sorties ne peuvent pas servir « to create or improve any foundational generative AI model ».
- Gemma, section 3.3 : « Google claims no rights in Outputs you generate using Gemma. You and your users are solely responsible for Outputs and their subsequent uses. »
Publier les extraits de cette leçon sur un site public est permis par les deux. Le site ne distribue aucun poids, donc les obligations de notice et de « Powered by Stability AI » ne s’appliquent pas ; chaque extrait indique tout de même quel modèle l’a produit.
Un médiator, un grincement de frette, une pièce
Section intitulée « Un médiator, un grincement de frette, une pièce »runs/stable-audio.json contient sept exécutions. La première a pris 11.8 s avec le chargement, l’encodeur de texte préparé sur 537 MB, le modèle sur 875 MB et le VAE sur 406 MB ; les suivantes ont pris 0.5 à 0.9 s chacune. Le volume sonore brut, d’après la première passe de loudnorm de FFmpeg :
| Exécution | Prompt | Durée | Pic | Volume sonore |
|---|---|---|---|---|
pick-seed1 |
a single guitar pick plucking one steel acoustic guitar string, close microphone, dry | 4.09 s | 0.41 | −29.2 LUFS |
pick-seed2 |
le même, seed 2 | 4.09 s | 0.32 | −30.1 LUFS |
fret-squeak |
fingers sliding along wound acoustic guitar strings, fret squeak, close microphone | 2.97 s | 0.17 | −27.2 LUFS |
studio-room |
quiet recording studio room tone, soft air conditioning hum, distant muffled footsteps | 10.03 s | 0.14 | −39.9 LUFS |
Généré par ComfyUI v0.36.0 : Stable Audio 3 small SFX avec T5Gemma, seed 1, 8 étapes, lcm, simple, CFG 1, 4 s, exécution pick-seed1 de runs/stable-audio.json, workflow 15-stable-audio-sfx.api.json. Stability AI Community License et Gemma Terms of Use. Normalisé par le pipeline, à −17.7 LUFS (voir l’exercice 3), MP3 à 96 kb/s.
Généré par ComfyUI v0.36.0 : Stable Audio 3 small SFX, seed 1, 3 s, exécution fret-squeak. Stability AI Community License et Gemma Terms of Use. Normalisé à −16.9 LUFS, MP3 à 96 kb/s.
Généré par ComfyUI v0.36.0 : Stable Audio 3 small SFX, seed 1, 10 s, exécution studio-room. Stability AI Community License et Gemma Terms of Use. Remonté de −39.9 à −16.7 LUFS par le pipeline, donc son souffle est 23 dB plus fort que généré ; une ambiance de pièce est censée rester bien en dessous de la musique. MP3 à 96 kb/s.
Le modèle musical prend son tempo et sa tonalité dans le prompt, en texte. acoustic guitar loop, 100 BPM, C major, fingerpicked, 10 secondes, a donné C major (r = 0.90) à 99.4 BPM avec la seed 1, et C major (r = 0.93) à 136.0 BPM avec la seed 2. Le même prompt sur le modèle d’effets sonores a donné G major (r = 0.80) à 99.4 BPM.
Généré par ComfyUI v0.36.0 : Stable Audio 3 small music avec T5Gemma, seed 1, 10 s, exécution music-loop-100, workflow 15-stable-audio-sfx.api.json avec ckpt_name modifié. Stability AI Community License et Gemma Terms of Use. Normalisé à −16.4 LUFS, MP3 à 96 kb/s.
Du son pour une vidéo
Section intitulée « Du son pour une vidéo »Deux éléments du cœur v0.36.0 touchent au son des vidéos, et aucun n’a été exécuté pour cette leçon.
- LTX-2.x génère la vidéo et l’audio ensemble.
LTXVEmptyLatentAudiodimensionne un latent audio à partir d’un nombre d’images vidéo et d’une cadence,LTXVAudioVAEDecodele transforme enAUDIO, et la classe de modèleLTXAVdébruite les deux (nodes_lt_audio.py). Son VAE audio travaille sur un spectrogramme mel et se termine par un vocodeur (audio_vae.py). Le templateText to Video (LTX-2.5)du cœur demande un transformer de 21.5 GB et un encodeur de texte de 15.4 GB, tous deux quantifiés en int8, soit plus que ce que les 16 GB de VRAM de cette machine contiennent à la fois et plus que ce que le cours télécharge sans demander. La LTX-2.x Community License, datée du 11 août 2026, permet de publier les sorties (« Licensor claims no rights in the Output you generate using LTX-2.x », section 5), et exige une licence payante des entités dont le chiffre d’affaires annuel atteint 10 000 000 $. Si LTX-2.5 tient dans 16 GB avec du déchargement, et ce que vaut son son : à vérifier. - Le VAE de MMAudio est là, MMAudio non.
comfy/ldm/mmaudione contient qu’un VAE et un vocodeur BigVGAN, quesd.pycharge pour un modèle à 16 kHz (lignes 905 à 921), et le VAE audio de LTX lui emprunte sa classe de distribution gaussienne. Aucun nœud n’échantillonne un modèle vidéo-vers-audio avec lui. Quel modèle utilise ce VAE dans le cœur, s’il y en a un : à vérifier.
Sans l’un ni l’autre, le son d’une vidéo dans le cœur, ce sont les images de la leçon sur la vidéo plus un graphe audio séparé : générez un extrait de la durée de la vidéo avec ACE-Step ou Stable Audio, et assemblez les deux avec FFmpeg.
La parole : absente du cœur
Section intitulée « La parole : absente du cœur »Il n’y a pas de modèle local de synthèse vocale dans ComfyUI v0.36.0. Démarré avec --disable-api-nodes, le serveur liste 666 types de nœuds dans /object_info, et aucun de leurs noms ni de leurs noms d’affichage ne contient « speech », « TTS » ou « voice » ; les 15 nœuds de la catégorie audio sont la plomberie listée au début de cette leçon. Les modèles musicaux du cœur savent chanter des paroles, ce qui n’est pas la même chose.
Les nœuds de parole qui existent sont des nœuds API, qui envoient votre texte à un service payant et téléchargent le résultat : ElevenLabsTextToSpeech (nodes_elevenlabs.py, ligne 240), FishAudioTextToSpeech (nodes_fishaudio.py, ligne 185) et HeyGenTextToSpeechNode (nodes_heygen.py, ligne 696), avec des nœuds de clonage de voix à côté. Ils demandent un compte Comfy avec des crédits, et ce sont eux qui justifient l’existence de --disable-api-nodes. Pour de la parole locale, il faut un pack de nœuds personnalisés, et les règles de la leçon 11 s’appliquent.
Choisir et lire un pack
Section intitulée « Choisir et lire un pack »Le cours a choisi comfyui-kokoro, 66 étoiles GitHub, Apache 2.0, épinglé sur son dernier commit du 23 février 2026. Il enveloppe Kokoro-82M, un modèle de 82 millions de paramètres sous Apache 2.0, au moyen de kokoro-onnx, qui l’exécute avec ONNX Runtime. Il est assez petit pour être lu en entier : un __init__.py de deux lignes et un fichier Python de 291 lignes.
L’audit de la leçon 11, avant l’installation :
$ python code/comfyui/custom-nodes/audit/audit.py comfyui-kokoro --details 40== comfyui-kokoro: 16 files, 2 Python, 0 JavaScript, web folder: no autorun 2 file that runs without being asked __init__.py imported by ComfyUI at every start requirements.txt installed with pip by ComfyUI-Manager network 2 network call (urllib, requests, httpx, aiohttp client, socket) ComfyUIKokoro.py:97 with requests.get(url, stream=True, allow_redirects=True) as response: ComfyUIKokoro.py:122 r = requests.get(url)La lecture du code et des dépendances ajoute ce que les compteurs ne peuvent pas dire :
- À l’import, le fichier importe NumPy, PyTorch,
kokoro_onnx,requestsettqdm, et définit trois classes. Rien ne s’exécute. - À la première exécution, chaque nœud appelle
download_modeletdownload_voices(ComfyUIKokoro.py, lignes 96 à 137). Le modèle vient d’une release d’un fork,github.com/taylorchu/kokoro-onnx/releases/download/v0.2.0/kokoro.onnx, pas des releases de kokoro-onnx lui-même ; les 54 voix viennent dehexgrad/Kokoro-82Msur la branchemain, pas d’un commit épinglé. Aucun des deux téléchargements n’est vérifié par une empreinte, et les deux sont écrits dans le propre dossier du pack. Les voix sont lues avectorch.load(content, weights_only=True), le mode sûr de la leçon 11. - Le fichier du fork est le fichier officiel. Téléchargés des deux façons,
kokoro.onnxdu fork etkokoro-v1.0.onnxde la releasemodel-files-v1.0de kokoro-onnx ont les mêmes 325 532 387 octets et le même SHA-256,7d5df8ec…36a6c5. C’était vrai le 17 septembre 2026 ; rien n’empêche un fichier de release de changer. - Les dépendances ne sont pas épinglées :
kokoro-onnx,onnxruntime,numpy,requests,tqdm. Le 17 septembre 2026, elles se résolvaient en kokoro-onnx 0.6.1 (MIT), onnxruntime 1.30.0, phonemizer 3.4.0 et espeakng-loader 0.2.4. Ce dernier embarque une bibliothèque eSpeak NG précompilée et ses données, que phonemizer charge pour transformer le texte en phonèmes. eSpeak NG est sous GPL 3.0, ce qui compte si vous redistribuez une installation, pas pour l’audio qu’il produit. - kokoro-onnx lit les voix avec
np.load(voices_path), dont le comportement par défaut refuse les pickles, et ne fait aucun appel réseau ; ses seules URL sont dans des messages d’erreur. - Les voix sont des enregistrements de personnes. Le
VOICES.mdde Kokoro dit « Support for non-English languages may be absent or thin due to weak G2P and/or lack of training data », annonce moins de 11 heures de données d’entraînement en français, et attribue la voix françaiseff_siwisau jeu de données SIWIS, les enregistrements d’une vraie locutrice. La licence du modèle permet de publier ses sorties ; ce cours ne publie tout de même pas de parole synthétique dans une voix faite à partir des enregistrements d’une personne réelle, donc cette section donne des transcriptions et des mesures, pas des extraits.
L’installation, dans l’instance isolée seulement, avec les dépendances épinglées sur ce qui a été examiné, et les deux fichiers de modèle placés à la main pour que le pack ne télécharge jamais rien. Le cours a exécuté les commandes Windows, avec uv, dans l’environnement virtuel propre à l’instance :
git clone https://github.com/stavsap/comfyui-kokoro "$env:COMFY_BASE\custom_nodes\comfyui-kokoro"git -C "$env:COMFY_BASE\custom_nodes\comfyui-kokoro" checkout cfb59b5605b351e196182c836f0b48c1a784e52fuv pip install --python .\venv\Scripts\python.exe kokoro-onnx==0.6.1 onnxruntime==1.30.0 phonemizer==3.4.0 espeakng-loader==0.2.4 dlinfo==2.0.0 joblib==1.6.0 cloudpickle==3.1.2 flatbuffers==25.12.19 protobuf==7.36.1Copy-Item kokoro-v1.0.onnx "$env:COMFY_BASE\custom_nodes\comfyui-kokoro\kokoro_v1.onnx"Copy-Item voices-v1.0.bin "$env:COMFY_BASE\custom_nodes\comfyui-kokoro\voices_v1.bin"git clone https://github.com/stavsap/comfyui-kokoro "$COMFY_BASE/custom_nodes/comfyui-kokoro"git -C "$COMFY_BASE/custom_nodes/comfyui-kokoro" checkout cfb59b5605b351e196182c836f0b48c1a784e52fuv pip install --python ./venv/bin/python kokoro-onnx==0.6.1 onnxruntime==1.30.0 phonemizer==3.4.0 espeakng-loader==0.2.4 dlinfo==2.0.0 joblib==1.6.0 cloudpickle==3.1.2 flatbuffers==25.12.19 protobuf==7.36.1cp kokoro-v1.0.onnx "$COMFY_BASE/custom_nodes/comfyui-kokoro/kokoro_v1.onnx"cp voices-v1.0.bin "$COMFY_BASE/custom_nodes/comfyui-kokoro/voices_v1.bin"git clone https://github.com/stavsap/comfyui-kokoro "$COMFY_BASE/custom_nodes/comfyui-kokoro"git -C "$COMFY_BASE/custom_nodes/comfyui-kokoro" checkout cfb59b5605b351e196182c836f0b48c1a784e52fuv pip install --python ./venv/bin/python kokoro-onnx==0.6.1 onnxruntime==1.30.0 phonemizer==3.4.0 espeakng-loader==0.2.4 dlinfo==2.0.0 joblib==1.6.0 cloudpickle==3.1.2 flatbuffers==25.12.19 protobuf==7.36.1cp kokoro-v1.0.onnx "$COMFY_BASE/custom_nodes/comfyui-kokoro/kokoro_v1.onnx"cp voices-v1.0.bin "$COMFY_BASE/custom_nodes/comfyui-kokoro/voices_v1.bin"Les commandes Linux et macOS sont à vérifier, tout comme ces wheels exactes sur ces systèmes. Sous Windows, uv a installé 9 paquets, et ONNX Runtime a indiqué AzureExecutionProvider et CPUExecutionProvider : Kokoro tourne ici sur le CPU. Le pack s’est importé en 0.0 s au démarrage du serveur.
Trois langues, mesurées
Section intitulée « Trois langues, mesurées »Le graphe fait trois nœuds, 15-kokoro-tts.api.json : KokoroSpeaker choisit une voix, KokoroGenerator prend le texte, la vitesse et une langue, et SaveAudioAdvanced enregistre l’AUDIO qu’il renvoie. runs/kokoro.json fait dire trois phrases de ce cours en anglais avec af_heart, en français avec ff_siwis et en espagnol avec ef_dora. Chacune a pris 2.6 à 4.7 s sur le CPU, 6.9 s pour la première.
Pour mesurer l’intelligibilité sans juger à l’oreille, wer.py transcrit chaque extrait avec Whisper large-v3 via faster-whisper, en int8 sur le CPU, beam 5, température 0, et calcule le taux d’erreur sur les mots (WER) : la distance d’édition entre la phrase et la transcription, en mots, divisée par le nombre de mots de la phrase, après passage en minuscules et suppression de la ponctuation. Les erreurs de Whisper lui-même comptent aussi, donc un WER est un majorant des erreurs du modèle de parole.
| Extrait | WER | Erreurs / mots | Whisper a entendu |
|---|---|---|---|
| en 1 | 0.217 | 5 / 23 | Welcome to Lesson 15. A C major 7 chord stacks a major 3rd, a perfect 5th, and a major 7th on the root. |
| en 2 | 0.0 | 0 / 18 | The sampler removes noise from the latent in eight steps, then the VAE decodes it into a waveform. |
| en 3 | 0.0 | 0 / 21 | Save the workflow as JSON, queue it through the API, and download the file when the history says it is done. |
| fr 1 | 0.28 | 7 / 25 | Bienvenue dans la leçon 15. Un accord de N doufles majeure 7 en pile une tierce majeure, une quinte juste et une septième majeure sur la fondamentale. |
| fr 2 | 0.053 | 1 / 19 | L’échantillonneur retire le bruit du latent en huit étapes, puis le verre le décode en forme d’onde. |
| fr 3 | 0.167 | 4 / 24 | Enregistrez le nWorkflow frange son, mettez-le en fil par l’API et téléchargez le fichier quand l’historique indique qu’il est terminé. |
| es 1 | 0.08 | 2 / 25 | Bienvenidos a la lección 15. Un acorde de do mayor 7 apila una tercera mayor, una quinta justa y una séptima mayor sobre la fundamental. |
| es 2 | 0.0 | 0 / 21 | El muestreador quita el ruido del latente en ocho pasos y luego el VAE lo decodifica en una forma de onda. |
| es 3 | 0.0 | 0 / 25 | Guarda el flujo de trabajo en JSON, ponlo en la cola con la API y descarga el archivo cuando el historial diga que ha terminado. |
Les phrases étaient :
- en : « Welcome to lesson fifteen. A C major seven chord stacks a major third, a perfect fifth and a major seventh on the root. » / « The sampler removes noise from the latent in eight steps, then the VAE decodes it into a waveform. » / « Save the workflow as JSON, queue it through the API, and download the file when the history says it is done. »
- fr : « Bienvenue dans la leçon quinze. Un accord de do majeur sept empile une tierce majeure, une quinte juste et une septième majeure sur la fondamentale. » / « L’échantillonneur retire le bruit du latent en huit étapes, puis le VAE le décode en forme d’onde. » / « Enregistrez le workflow en JSON, mettez-le en file par l’API, et téléchargez le fichier quand l’historique indique qu’il est terminé. »
- es : « Bienvenidos a la lección quince. Un acorde de do mayor siete apila una tercera mayor, una quinta justa y una séptima mayor sobre la fundamental. » / « El muestreador quita el ruido del latente en ocho pasos, y luego el VAE lo decodifica en una forma de onda. » / « Guarda el flujo de trabajo en JSON, ponlo en la cola con la API y descarga el archivo cuando el historial diga que ha terminado. »
Lisez les erreurs, pas seulement les taux. Les 5 erreurs de la phrase anglaise 1 et les deux de la phrase espagnole 1 viennent de Whisper qui écrit les nombres en chiffres, « 15 » pour « fifteen » et « 3rd » pour « third » : la parole était juste. C’est en français que Kokoro a échoué. « do majeur » est devenu « N doufles majeure », « le VAE » est devenu « le verre », parce que les phonèmes français lisent le sigle comme un mot, et « le workflow en JSON » est devenu « le nWorkflow frange son ». Le journal du serveur montre pourquoi pour ce dernier : phonemizer a averti 1 utterances containing language switches et extra phones may appear in the "fr-fr" phoneset pour les deux phrases françaises contenant des mots anglais, qu’eSpeak NG a prononcés avec des phonèmes anglais au milieu de la parole française. « en file » entendu comme « en fil » est un homophone, le choix de Whisper. En ne comptant que les erreurs de parole, le français est à 5 mots sur 25, 1 sur 19 et 3 sur 24 ; l’anglais et l’espagnol à 0.
Automatiser : un pipeline par l’API
Section intitulée « Automatiser : un pipeline par l’API »Les clients de la leçon 4 suivaient un prompt par le WebSocket. Pour des tâches audio qui durent quelques secondes, pipeline.py interroge plutôt /history une fois par seconde, avec la seule bibliothèque standard de Python, et ajoute l’étape dont un site ou un jeu a besoin après la génération : un volume sonore cohérent et un petit fichier.
POST /promptavec un workflow, après les modifications--set node.input=value. Un workflow rejeté affiche lesnode_errorsdu serveur.GET /history/{prompt_id}jusqu’à ce que le statut soitsuccessouerror.GET /viewpour chaque fichier qu’un nœud d’enregistrement signale sousaudio.- FFmpeg et son filtre
loudnorm, en deux passes : la première mesure le volume intégré, le true peak et la plage de volume ; la seconde applique un gain linéaire calculé à partir de la mesure, vers −16 LUFS avec un plafond à −1.5 dBTP, et encode en Opus à 48 kHz.
−16 LUFS est le niveau qu’Apple demande aux podcasts, « around -16 dB LKFS, with a +/- 1 dB tolerance » ; Spotify normalise la musique à −14 dB LUFS. La cible est une option de la ligne de commande.
Une exécution réelle, 8 secondes de la boucle avec la seed 7, sur le serveur de la leçon :
$ python pipeline.py run http://127.0.0.1:8196 workflows/15-ace-step.api.json --set 5.seed=7 --set 8.seed=7 --set 5.duration=8.0 --set 7.seconds=8.0 --set 10.filename_prefix=l15/demo --out outqueuedsuccess after 24.2 snode 10: l15/demo_00001.flac -> out\demo_00001.flac -13.9 LUFS -> demo_00001.opus, -16.0 LUFS, 96455 bytesLes 24.2 s comprennent le chargement des modèles dans un serveur qui venait de démarrer. Le fichier FLAC fait 400 921 octets ; le fichier Opus, 96 455. Les extraits de cette page sont passés par la même fonction encode vers du MP3, que davantage de navigateurs lisent :
$ python pipeline.py encode loop-c-seed1_00001.flac 15-loop-c-seed1.mp3-18.5 LUFS -> -16.4 LUFS$ python pipeline.py encode pick-seed1_00001.flac 15-pick-seed1.mp3-29.2 LUFS -> -17.7 LUFS$ python pipeline.py encode studio-room_00001.flac 15-studio-room.mp3-39.9 LUFS -> -16.7 LUFSLes onze extraits de cette page pèsent 1.9 MB ensemble.
Le code est vérifié sans GPU. check.sh unit valide chaque workflow et chaque exécution par rapport aux définitions de nœuds enregistrées depuis le serveur de la leçon, object_info.audio.json, avec les mêmes règles que le serveur (types, liens, bornes, listes, dynamic combos), et exécute les tests : le pipeline face à un faux ComfyUI, la cible de volume avec FFmpeg, les estimations de tonalité et de tempo sur des extraits synthétiques, et le WER. check.sh server démarre ComfyUI v0.36.0 sur le CPU, fait passer le workflow silencieux par pipeline.py, et compare la transcription. La CI exécute le premier sous Ubuntu, Windows et macOS, et le second sous Ubuntu.
Points clés
Section intitulée « Points clés »AUDIO, c’estwaveform[batch, channels, samples]plussample_rate; le chargement passe par PyAV, l’enregistrement parSaveAudioAdvanced, dont laqualitys’écritformat.qualityau format API.- Un latent audio est une suite de trames : 25 par seconde pour ACE-Step 1.5, 10.8 pour Stable Audio 3. Les nœuds de latent vide transforment des secondes en trames, et
KSampleradapte un latent vide au modèle. - ACE-Step 1.5 transforme tags, paroles, tonalité, tempo et durée en texte pour un modèle de langage, qui écrit cinq codes audio par seconde ; la seed et la durée changent ce plan, et rien n’impose la tonalité ni le tempo.
- Mesurée sur 20 exécutions, la tonalité a correspondu 12 fois ; une grille d’accords écrite dans les tags n’a pas été suivie, deux fois. Mesurez avant de faire confiance à un prompt, et lisez les erreurs propres à l’estimateur, comme un tempo au double.
- La même seed sur la même machine a donné les mêmes échantillons après un redémarrage ; un prompt répété dans un serveur en marche ne fait que toucher le cache.
- Les petits modèles de Stable Audio 3 produisent effets sonores et courtes musiques en moins d’une seconde chacun sur un GPU de 16 GB ; leurs sorties vous appartiennent sous la Stability AI Community License, qui prend fin pour les entreprises au-delà de 1 000 000 USD de chiffre d’affaires.
- Le cœur n’a pas de modèle de parole local, seulement des nœuds API payants. Un pack de parole est un pack de nœuds personnalisés : auditez-le, épinglez ses dépendances, placez ses fichiers à la main, et mesurez-le ; Kokoro a été intelligible en anglais et en espagnol et a fait de vraies erreurs en français.
- Normalisez le volume sonore après la génération : le décodeur ne fait qu’empêcher l’écrêtage. Un
loudnormen deux passes atteint −16 LUFS sauf si le plafond de true peak interdit le gain.
Exercices
Section intitulée « Exercices »- Un graphe demande 10 secondes à
EmptyAceStep1.5LatentAudioet unedurationde 20 àTextEncodeAceStepAudio1.5. Combien y a-t-il de trames latentes et de codes audio, et que fait le modèle de diffusion de la différence ? Et avec 20 secondes de latent et unedurationde 10 ? - Mesurez cinq seeds de plus de la boucle en C major, de 4 à 8, et comptez combien sont entendues en C major. Écrivez le fichier d’exécutions, mettez-le en file avec
pipeline.py batch, et lisez la sortie demeasure.py. - L’extrait du médiator a atteint −17.7 LUFS, pas −16. Pourquoi, d’après les chiffres de l’extrait brut et la documentation de
loudnormde FFmpeg ? Que changeriez-vous pour atteindre −16, et qu’est-ce que cela coûterait ? - Kokoro a lu « le VAE » comme « le verre ». Réécrivez la phrase française pour que le sigle soit épelé, exécutez-la, et mesurez-la avec
wer.py. - Un workflow règle
"bpm": 400et"keyscale": "H major"surTextEncodeAceStepAudio1.5. Que signalevalidate.py, et que dirait le serveur ? - Modifiez le workflow ACE-Step pour enregistrer un MP3 à 320 kb/s par l’API. Quelles entrées du nœud 10 changent ?
Solution 1
Latent : round(10 * 48000 / 1920) = 250 trames. Codes : le tokenizer arrondit la durée vers le haut et demande au modèle de langage exactement 20 * 5 = 100 codes, que le détokeniseur transforme en 500 trames d’indices. prepare_condition garde ensuite les 250 premières trames d’indices, lm_hints[:, :src_latents.shape[1], :] (ace_step15.py, ligne 1104) : vous obtenez la première moitié d’un plan de 20 secondes, et le modèle de langage a passé du temps sur la seconde moitié pour rien.
Dans l’autre sens, 500 trames latentes et 50 codes : comme 50 * 5 est inférieur à 500, les codes sont complétés jusqu’à ceil(500 / 5) = 100 avec le token constant 35847 avant d’être développés (lignes 1096 à 1097), donc la seconde moitié du latent est guidée par un code de remplissage répété plutôt que par un plan. Le rendu sonore n’a pas été mesuré : à vérifier. Gardez les deux nombres égaux, comme le font les exécutions de la leçon.
Solution 2
[ {"name": "loop-c-seed4", "workflow": "../workflows/15-ace-step.api.json", "set": ["5.seed=4", "8.seed=4", "10.filename_prefix=l15/loop-c-seed4"]}]Répétez l’entrée pour les seeds 5 à 8. Les valeurs par défaut du workflow sont déjà les tags de la boucle, C major, 90 BPM et 20 secondes, donc les seeds et le préfixe sont les seuls changements. Puis :
python pipeline.py batch http://127.0.0.1:8188 runs/seeds.json --out out/seedspython measure.py runs/seeds.json out/seedsLe cours n’a exécuté que les seeds 1 à 3 : 2 sur 3 ont été entendues en C major. Avec 8 seeds, un décompte reste un petit échantillon ; donnez-le avec les valeurs de r, et écoutez celles en dessous de 0.5.
Solution 3
Le médiator brut est à −29.2 LUFS avec un pic d’échantillon de 0.41, environ −7.7 dBFS. Atteindre −16 LUFS demande +13.2 dB, ce qui mettrait le pic vers +5.5 dBFS, au-dessus du plafond de −1.5 dBTP. La documentation de loudnorm de FFmpeg dit qu’en mode linéaire, « the change in integrated loudness shouldn’t result in a true peak which exceeds the target TP », et que sinon « normalization mode will revert to dynamic » ; le mode dynamique limite alors les pics et atterrit sous la cible sur un extrait de 4 secondes dominé par une seule attaque. Pour atteindre −16, relevez le plafond (TP=0, en acceptant une attaque plus forte), ou compressez l’attaque avant de normaliser, ce qui change le son du médiator. Pour les effets courts, une cible de pic plutôt qu’une cible de volume est souvent la meilleure règle.
Solution 4
Écrivez les lettres comme le français les lit, par exemple « puis le vé-a-eu le décode en forme d’onde », ou évitez le sigle, « puis le décodeur le transforme en forme d’onde », et placez la phrase sur le nœud 2 :
python pipeline.py run http://127.0.0.1:8188 workflows/15-kokoro-tts.api.json --set "2.text=puis le vé-a-eu le décode en forme d'onde." --set 2.lang=\"French\" --out out/fr --bitrate ""python wer.py --model faster-whisper-large-v3 --lang fr --text "puis le VAE le décode en forme d'onde." out/fr/kokoro_00001.flacComparez le WER à la phrase de référence, pas au texte réécrit. Le cours n’a pas exécuté cette variante : à vérifier.
Solution 5
validate.py vérifie les entrées par rapport aux définitions enregistrées, comme le font les tests :
node 5 (TextEncodeAceStepAudio1.5) bpm: 400 is outside [10, 300]node 5 (TextEncodeAceStepAudio1.5) keyscale: 'H major' is not one of 34 optionsLe serveur rejette le prompt avec un 400 et une entrée node_errors pour le nœud 5, que pipeline.py affiche avant de sortir avec le code 2. « H » est le nom allemand de B (si), et la liste ne contient que les noms de notes anglais.
Solution 6
format devient mp3, et la sous-entrée dynamique format.quality apparaît, avec V0, 128k ou 320k :
python pipeline.py run http://127.0.0.1:8188 workflows/15-ace-step.api.json --set 10.format=\"mp3\" --set 10.format.quality=\"320k\" --bitrate ""--set ne coupe qu’au premier point, donc le nom de l’entrée est format.quality. Sans elle, le serveur répond Required input is missing: quality. --bitrate "" saute FFmpeg, puisque le fichier est déjà compressé.
- ComfyUI en v0.36.0, commit
ee71d5c:comfy_extras/nodes_audio.py,comfy_extras/nodes_ace.py,comfy/text_encoders/ace15.py,comfy/ldm/ace/ace_step15.py,comfy/model_base.py,comfy/sample.py,comfy/sd.py,comfy/ldm/audio/vae_sa3.py,comfy_extras/nodes_lt_audio.py,comfy_api/latest/_ui.py. - ACE-Step : dépôt ACE-Step 1.5, fiche du modèle, fichiers ComfyUI ; J. Gong et al., ACE-Step 1.5: Pushing the Boundaries of Open-Source Music Generation, 2026.
- Stability AI : Stable Audio 3 small SFX, Stable Audio 3 small music, Community License, fichiers ComfyUI ; Z. Evans et al., Stable Audio 3, 2026. Google : T5Gemma, Gemma Terms of Use.
- Lightricks : LTX-2.x Community License.
- Parole : comfyui-kokoro, Kokoro-82M, kokoro-onnx, ONNX Runtime, eSpeak NG, base de données SIWIS de synthèse vocale en français, faster-whisper.
- Mesure : librosa ; D. Temperley, « What’s Key for Key? The Krumhansl-Schmuckler Key-Finding Algorithm Reconsidered », Music Perception 17(1), 1999, DOI 10.2307/40285812, qui décrit les profils de Krumhansl-Kessler.
- Volume sonore et formats : FFmpeg
loudnorm, PyAV, Opus, UIT-R BS.1770, exigences audio d’Apple Podcasts, normalisation du volume sonore de Spotify.