Skip to content

About

Your Bambu A1 sings a different song every print. Plus the first full decode of the M1006 music gcode grammar and Bambu's per-motor loudness tables.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

2 Commits

Folders and files

Repository files navigation

A1 Jukebox

Your Bambu A1 sings a different song every print. Not a firmware mod — the jingle is plain gcode in the machine start/end blocks, so every print can carry its own tune.

It also contains, as far as we can tell, the first full write-up of the M1006 music grammar: note encoding, duration units, the three-voice layout, and Bambu's own per-motor loudness tables. OpenBambuAPI has one example line and "allows playing music with the stepper coils"; the rest of this was decoded from Bambu's own MIDI→gcode converter bundle and verified against the stock jingles in a real sliced file.

python3 jukebox.py list                 # the songbook, and what it has played
python3 jukebox.py preview all          # hear every song as a WAV, before printing
python3 jukebox.py concert              # a gcode that ONLY sings — send it like a print
python3 jukebox.py splice job.gcode     # swap a fresh song into a sliced file
python3 jukebox.py restore job.gcode    # put Bambu's stock jingles back

⚠️ The printer ignores every M1006 unless Settings → Print options → Sound is ON. If nothing plays, check that first.

Nothing here touches firmware. The worst case is an ugly song.


The M1006 grammar

M17                       ; energize the motors — they ARE the speaker
M400 S1                   ; settle
M1006 S1                  ; begin sequence
M1006 L70 M70 N99         ; per-motor master volume (X, Y, Z)
M1006 A<n> B<d> L<v>  C<n> D<d> M<v>  E<n> F<d> N<v>
...                       ; one line per timeline slice
M1006 W                   ; wait for playback to finish
M18                       ; release the motors

Three motors, so three simultaneous voices — a hard ceiling, confirmed by Bambu's own wiki.

voice motor note duration volume
1 X A B L
2 Y C D M
3 Z E F N
  • note = MIDI − 23, clamped at ≥ 0. 0 is silence. Legal 1..72 = MIDI 24..95 = C1..B6. So 37 is middle C.
  • duration is in hundredths of a second — B20 is 200 ms.
  • volume = floor(master / 100 × CURVE[motor][note − 1]), and is omitted when it works out to 0. The three 72-entry response tables are in volume_curves.json.
  • A note longer than 2.00 s gets re-struck in 2.00 s chunks.

Where songs have to live: the loudness map

Stepper motors are nothing like flat. Straight out of the response curves:

  • C3–F#3 is a dead zone on all three motors. Never put anything there.
  • Y is the strongest overall — ≥90 across F#1..B6, dead only at F2 and D#3–F3. Give it the lead melody.
  • X is loud at C2 and again F#4–D5, dead B2–F#3. Good for a counter-melody up high.
  • Z is loud C1–C2 and again from D5 up. Bass down low, or sparkle up top.

That is exactly why Bambu's own tune sits at C4–C5 over a quiet D4 pedal.

The notch that will catch you: X craters just above D5. It reads 99 at D5 and 99 again from F#5 up, but in between — D#5 = 75, E5 = 55, F5 = 75. A line that walks up past D5 falls into a half-volume hole for three semitones. Go up an octave instead: X is a flat 99 across C6–G6.

songbook.py's lint() checks all of this for you — out-of-range notes, notes sitting in a motor's dead spot, and voices whose lengths don't add up.


Writing a song

Songs are readable, not gcode. Each one has a start phrase and a matching end phrase, so a single song bookends a print — a question when it begins and an answer when it finishes.

dict(
    name="Bell Tower",
    idea="Slow, wide, plagal — the amen cadence. Big spaces between the "
         "voices so the motors ring instead of chatter.",
    tempo=100,
    start=dict(Y="C4/2 F4/2",  X="r/1 C5/3",  Z="F2/2 C2/2"),
    end=dict(  Y="F4/2 C4/2",  X="A4/2 E4/2", Z="F2/2 C2/2"),
),

PITCH/BEATS, r for a rest, fractional beats fine. Then:

python3 jukebox.py preview "Bell Tower"   # renders a WAV so you can hear it
python3 jukebox.py show "Bell Tower"      # the gcode it compiles to

The nine songs in songbook.py are originals, written against the loudness map above. previews/AUDITION_REEL.wav is all of them back to back.

A caveat worth stating plainly: the pitches, rhythm and per-motor loudness are exact, straight off Bambu's tables. The timbre in the WAV previews is a synth guess at what a stepper sounds like. Use them to check the notes, not the tone.


A different song on every print

M1006 blocks live in the machine start/end gcode, so the natural place to swap one in is after slicing, before uploading. In OrcaSlicer:

Print Settings → Others → Post-processing Scripts

python3 /path/to/a1-jukebox/orca_hook.py

Orca waits for the script, so the song is chosen for the print you just started — not by a timer, and not popped off a queue built in advance. The picker takes the least-recently-played song and never repeats inside the last three, so consecutive prints never sound the same.

ledger.json is the memory: what exists, what played and when, on which file.

It fails safe. Any error and the gcode is left exactly as sliced — a print must never break because the music didn't work. The hook is stdlib-only for the same reason, since Orca's flatpak sandbox has no site-packages.

splice is careful about what it touches: on a 221,937-line gcode, a diff shows only the two jingle blocks changed. Sliced .3mf files work too — every Metadata/plate_*.gcode inside is patched and the .md5 sidecar refreshed.


jukebox.py concert — make it sing with nothing on the bed

Generates a gcode that only sings: no heating, no homing, no extrusion, no motion. Send it like a print and the printer plays the whole songbook standing still. It carries M73 progress lines the way Bambu's own converter does, so the job reports a real duration instead of finishing the instant it starts.

Verified inert before it ever goes to a machine — zero heat, home, motion or extrude commands in the file.

python3 jukebox.py concert          # writes concert.gcode

examples/concert.gcode is a pre-built one: all nine songs, 71 seconds.


Files

m1006.py the instrument: encode() notation→gcode, decode()/describe() gcode→notes, render_wav()
volume_curves.json Bambu's three per-motor response tables, extracted exactly
songbook.py the songs, in readable notation, plus lint()
jukebox.py pick / preview / splice / restore / concert, and the ledger
orca_hook.py the post-processing hook — a song chosen when you hit Print
previews/ a WAV of every song, and the audition reel

Python 3, standard library only. No dependencies.

Credits

Built by Iris with Ivy, August 2026, on an A1 with an AMS Lite. The idea of publishing it was Ivy's mum's.

MIT — see LICENSE.

About

Your Bambu A1 sings a different song every print. Plus the first full decode of the M1006 music gcode grammar and Bambu's per-motor loudness tables.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages