MOONDAW

Avec un LLM

Le FM-1 et votre LLM

Un morceau du FM-1 est un fichier texte, et un modèle de langage le lit et l'écrit. Cette page dit ce que ça change en pratique : ce qu'on peut lui demander d'écrire, comment vérifier ce qu'il a écrit avant de l'envoyer à la machine, et comment il peut prendre la machine en main pour juger son travail lui-même.

Tous les outils de cette page sont dans le dépôt du firmware et s'exécutent depuis sa racine. Ils demandent python3, et ceux qui parlent à la machine python-rtmidi.

Écrire des morceaux

Ce qu'on demande à un modèle, c'est la frappe, pas la musique : une basse sous une grille qui existe déjà, une section doublée puis transposée, des paroles épelées en phonèmes, les mêmes quatre mesures avec un autre groove. Le morceau reste le vôtre — le modèle tape.

Ce qu'un .trk est

Du texte UTF-8, une instruction par ligne. version 3 d'abord, et obligatoire ; puis l'en-tête, les instruments, les blocs de steps, et l'arrangement. Un bloc est une ligne pat suivie de ses lignes de step ; une phrase est une ligne arr suivie d'un bloc par piste. Hors ces deux cas, l'ordre des lignes ne compte pas.

version 3
bpm 96
ticks 6

pat t0 00 len 16
00 C-2 i09 fVOL6A
04 D-2 i09
08 C-2 i09
0C D-2 i09

pat t1 00 len 16
00 C-3 i00 fGAT03
06 C-3 i00 fGAT03
0A G-2 i00 fGAT03

arr 00 steps auto
name INTRO
t0 00
t1 00

Ce morceau joue : il ne définit aucun instrument, et i09 comme i00 sont deux des dix sons que la machine donne d'office. Une ligne de step porte jusqu'à quatre notes jointes par +, l'instrument, jusqu'à trois effets et une syllabe chantée ('MA). Les octaves sont celles du MIDI : C-4 est le do du milieu, A-4 le la 440. Le reste de la grammaire — les instruments et leurs couches, les zones de samples, les sections, les phrases qui en rejouent une autre — est dans le skill.

Ce que le modèle doit savoir

  • Tout le morceau, jamais un fragment. Le FM-1 refuse un patch : chaque fichier envoyé est un morceau entier, et ce qu'il ne dit pas n'est pas « inchangé », il est supprimé. Donnez au modèle le fichier que la machine a sauvé, pas un extrait.
  • Une ligne que le lecteur ne reconnaît pas est ignorée en silence. Un mot-clé mal orthographié n'est pas une erreur : c'est une ligne qui disparaît. C'est la chose la plus coûteuse de ce format, et la raison de la section suivante.
  • Ce que cette machine ne porte pas tombe à l'entrée : drive, amp et tone d'un instrument, les lignes preview et ref, le moteur PLUCK, les pistes au-delà de la huitième. Un modèle à qui il faut l'une de ces choses devrait le dire plutôt qu'écrire un fichier qui revient autrement.
  • Un instrument qu'aucun step ne joue n'est pas enregistré. L'écrivain ne garde que les instruments qu'un step nomme : un son défini et jamais joué revient absent, sans que rien le signale.

Ce que la machine tient : huit pistes, seize blocs par piste, 128 steps par bloc et 6 000 pour tout le morceau, quatre notes et trois effets par step, 64 instruments, 128 phrases. Les dix premiers instruments ont déjà un son — BASS, SUB, LEAD, KEYS, HARP, PAD, ORGAN, BELL, VOICE, KIT.

Vérifier le fichier

Le lecteur et l'écrivain du firmware se compilent sur l'ordinateur et n'ont pas besoin de la machine. trkdump dit ce que le FM-1 enregistrerait, à partir de votre fichier :

cc -std=c11 -O2 tests/trkdump.c core/song.c core/trk.c -o build/trkdump
build/trkdump song.trk

La boucle à tenir : écrire le fichier, le passer par trkdump, lire ce qui revient. Le but n'est pas un diff vide — la machine réécrit tout en entier, défauts compris. Le but est l'écart : une ligne absente est un mot-clé que le lecteur a ignoré, une valeur changée est une valeur ramenée dans ses bornes, un champ parti est un champ que cette machine ne porte pas.

Du morceau de seize lignes ci-dessus, par exemple, il rend les deux presets écrits en entier, les huit lignes de mixage et les réglages que le fichier laissait de côté :

meter 4 4
tracks 8
reverb 64 64
delay 18 60

inst 00 engine PULSE timbre 1 adsr 0 34 170 14 filter 150 60 84 name BASS
inst 09 engine DRUM timbre 0 adsr 0 0 255 20 level 6 name KIT

trk 0 vol 100 pan 0
...

tests/trkcheck.c se compile de la même façon et va plus loin : il charge le fichier, dit ce qui n'est pas porté, réécrit, recharge, et exige que les deux écritures soient identiques.

Envoyer

# un morceau vers la machine
python3 tools/fm1_send.py song.trk
# ce qu'elle porte
python3 tools/fm1_send.py --list
# un morceau qui revient
python3 tools/fm1_send.py --get NAME -o song.trk

La machine doit être sur HOME. Le morceau est réduit par le même lecteur et le même écrivain avant de partir : ce qui arrive est exactement ce que trkdump montrait. Rien n'est remplacé sur la machine sans --replace.

Le même outil ramène un morceau de la machine, et c'est par là qu'une séance commence quand le morceau existe déjà : --get, puis le fichier au modèle.

Les samples ne voyagent pas dans un morceau : une ligne smp est un nom, et la banque se remplit à part, avec tools/fm1_samples.py — le manuel le dit.

Piloter la machine

C'est la moitié qu'on ne devine pas. tools/fm1_ctl.py est une télécommande pour un FM-1 allumé : elle lit ce qu'il fait, appuie sur ses boutons, tourne ses encodeurs, joue ses touches, écrit dans le morceau en mémoire, allume ses LED et photographie son écran. La machine n'a aucun mode à mettre — ça marche pendant que quelqu'un joue dessus.

Ce que ça rend possible, et que rien d'autre ne permet ici : un modèle juge son propre travail. Il fait quelque chose, prend l'écran, regarde. Tout passe par où passerait un pouce : ARP est l'UNDO de la machine, et rien n'atteint la flash avant save.

Lire ce qu'elle fait

python3 tools/fm1_ctl.py status

Une ligne de clé=valeur, faite pour être lue telle quelle.

screen=l'écran où elle est : HOME, SONG, PHRASE, PATTERN, INSTRUMENT, MIXER, PROJECT, CANVAS, SETTINGS, HELP, SYSTEM
song= slot= dirty=le morceau ouvert, sa place en flash, et s'il reste des modifications non enregistrées
playing= phrase= step=ce qui joue, et où en est exactement la tête de lecture
bpm= phrases= tracks=la forme du morceau
cpu= max=la charge maintenant, et le pire bloc depuis le dernier --reset
batt= free=la pile, et les chunks qui restent dans le pool de steps

Les autres lectures servent quand quelque chose va mal : --profile (le pire bloc étage par étage, en µs), --boot (pourquoi la machine a démarré la dernière fois), --trail (où en était le run précédent quand il s'est arrêté), --crash (les registres du dernier plantage).

Voir l'écran

python3 tools/fm1_ctl.py screen -o shot.png

Le panneau en image, 240 × 240, lu bande par bande. C'est la commande qui rend un modèle capable de vérifier ce qu'il vient de faire sans demander à personne de lui décrire l'écran.

Appuyer, tourner, jouer

python3 tools/fm1_ctl.py press SEL
python3 tools/fm1_ctl.py press HOME SEQ        # plusieurs ensemble
python3 tools/fm1_ctl.py turn K2 -3            # trois crans à gauche
python3 tools/fm1_ctl.py key C-4 --hold 20     # une touche, tenue

Les boutons portent les noms imprimés sur la façade :

FX  SEL  ENV  LFO  EDIT  GLO
HOME  SAVE  ARP  SEQ  PLAY  REC
OCT-  OCT+

Les encodeurs sont SELECT ALGO PRESETS K1 K2 K3 K4, et un tour est un entier signé, en décimal. Les touches vont de F-3 à G-5 ; OCT− et OCT+ ne les déplacent pas, ils déplacent ce qu'elles jouent.

Les noms de la façade ne sont pas ceux des écrans : ARP est UNDO et ALGO tourné à droite REDO, SEQ FX ENV choisissent la colonne sur PATTERN, GLO ouvre le morceau et FX la table de mixage. Les gestes de chaque écran sont dans le manuel.

Écrire dans le morceau en mémoire

python3 tools/fm1_ctl.py open NAME
python3 tools/fm1_ctl.py step 1 03 10 'C-4+E-4+G-4 i03 fVOL40'
python3 tools/fm1_ctl.py step 1 03 10 --    # vide le step
python3 tools/fm1_ctl.py cell 00 1 03       # la piste 1 joue le bloc 03 dans la phrase 00
python3 tools/fm1_ctl.py length 1 03 32
python3 tools/fm1_ctl.py loop 00
python3 tools/fm1_ctl.py save

Les pistes se comptent de 1 à 8 ici, comme elles sont écrites sur les écrans, et non t0–t7 comme dans le fichier : step 1 ... est la piste que le .trk appelle t0, et step 0 ... sort sur « tracks are 1 to 8 ».

Un step s'écrit exactement comme une ligne de .trk l'écrit, moins le numéro de rang, et en un seul argument. Phrases, blocs, rangs, instruments et valeurs d'effet sont en hexadécimal, comme sur les écrans ; les steps de length et les crans d'un encodeur sont en décimal. open et new refusent d'abandonner des modifications non enregistrées sans --force, et un morceau entier passe par fm1_send.py, puis open.

Les LED

python3 tools/fm1_ctl.py led FX lit
python3 tools/fm1_ctl.py led C-4 breath A-4 mid
python3 tools/fm1_ctl.py led all app          # rendues au firmware

N'importe quel bouton, n'importe quelle touche, par son nom. Les niveaux sont off glow mid lit breath, les pourcentages 50 62 75 87 100, ou app pour rendre cette LED au firmware ; all les nomme toutes.

Dessiner sur l'écran

La machine sait montrer un canvas dont l'ordinateur est le propriétaire : le script dessine, la machine affiche, et aucun firmware n'est compilé pour ça.

python3 tools/fm1_ctl.py canvas clear black
python3 tools/fm1_ctl.py canvas fill 20 40 200 60 amber
python3 tools/fm1_ctl.py canvas text 12 80 'HELLO' --font 20 --colour cyan
python3 tools/fm1_ctl.py canvas line 0 120 239 120 grey
python3 tools/fm1_ctl.py canvas image picture.png
python3 tools/fm1_ctl.py canvas events

L'écran est 240 × 240. Une couleur est #RRGGBB ou un mot du thème — black white grey green cyan amber red. Le y d'un texte est sa ligne de base, et les fontes sont 20, 14, 12 et mono. HOME quitte le canvas et rend l'écran au firmware.

canvas events est l'autre moitié : il rapporte les boutons appuyés et relâchés, les touches, et de combien chaque encodeur a tourné depuis le dernier regard. Dessiner et relire en boucle, c'est une interface entière qui tourne sur le panneau pendant que le programme vit sur l'ordinateur. Au-delà de quelques commandes, on écrit un script contre tools/fm1_proto.py :

from fm1_proto import Canvas

c = Canvas()
c.keep_glo()             # GLO devient le « retour » du script

def frame(ev):           # au départ, puis à chaque geste
    c.clear("black")
    c.text(12, 30, f"K2 {ev.turns.get('K2', 0)}", "cyan")
    if "SEL" in ev.pressed:
        c.fill(0, 100, 240, 40, "amber")
    c.show()

c.run(frame)

tools/protos/ en contient une douzaine, chacune un écran essayé sur le panneau avant d'être construit dans le firmware.

--udp parle au simulateur sur l'ordinateur plutôt qu'à la machine, pour fm1_ctl.py comme pour un script : c'est le bon endroit pour essayer quelque chose.

Mettre son modèle dans la boucle

Tout ce qu'un modèle a besoin de savoir de cette machine est écrit pour lui, dans le dépôt du firmware, sous skill/moondaw-fm1/ :

  • SKILL.md — ce qu'est la machine, les deux choses qui se font depuis un ordinateur, et les habitudes qui comptent quand on modifie le morceau de quelqu'un.
  • references/format.md — la grammaire du .trk, ses limites, et ce que la machine laisse tomber à l'entrée.
  • references/step-fx.md — les 28 effets de step, chacun avec sa portée et la page que la machine affiche.
  • references/engines.md — les moteurs, les dix kits de batterie et leur map General MIDI, l'alphabet phonétique de la voix VOX.
  • references/control.md — le pilotage : la ligne de status, les boutons, l'écran, le canvas et ses gestes.

C'est du Markdown, écrit pour un modèle et pas pour un lecteur. Un modèle qui lit les fichiers du clone le trouve là ; sinon, les cinq fichiers se collent dans une conversation. Ils sont sur GitHub avec le reste du firmware, et ce qui s'y trouve de généré sort des sources de la machine : le skill ne peut pas prétendre un effet que le firmware n'a pas.

Il existe un skill voisin, moondaw, pour les autres MOONDAW — celui du navigateur, du Mac, d'iOS, de la console. Il ne décrit pas cette machine : le suivre ici produit des fichiers que le FM-1 refuse, ou qu'il vide en silence.

Le reste est une conversation ordinaire. Vous posez les steps au pouce, vous décrivez ce qui manque, et le fichier revient avec. Si le modèle a la machine au bout du câble, il peut aussi aller voir si c'était bien ça.