New
Apps 1,232Makers 1,230This week 1,232
Promote your app−30%
0views in
the last 30 days

Robot PU Voice

Text-to-speech extension for the Robot PU and BBC micro:bit V2.

Built with Devin

by robotgyms · from USUnited States · added Oct 29, 2026
no ratings0 comments

Robot PU Voice

Text-to-speech for Robot PU (and any BBC micro:bit V2) built on a rewritten, hardened port of SAM — Software Automatic Mouth, the legendary speech synthesizer from the Commodore C64 (1982).

Robot PU singing into a microphone
Runs on any micro:bit V2 — sings loudest on Robot PU.

This is not the stock engine: the bundled SAM carries fixes for long-standing bugs — endless scans, buffer overruns, reads before index 0, silently dropped notes — plus a cancellable renderer and a real-time streaming pipeline. Details in Fixes to the 1982 SAM engine.

This extension is a rewrite of pxt-billy with a different architecture, built for robot hardware:

  • Works on any micro:bit V2 — no robot required, but tuned for Robot PU, whose amplifier and louder speaker give the voice much more presence.
  • Non-blocking speech — say queues the utterance and returns immediately, so the robot can keep walking while it talks.
  • Automatic power-down — the audio pipeline is put to sleep as soon as speech finishes. pxt-billy leaves the mixer running forever, so PWM on pin 0 keeps switching into the Robot PU amplifier: wasted battery and a warm speaker. Robot PU Voice disconnects it when idle.
  • Digits are spoken — say("level 3") says "level three". Plain digits are silent in the original reciter rules.
  • Stop / status / events — stop speaking, is speaking, wait until speech finished, and an on speech finished event.
  • Sing mode exposed — pxt-billy compiled a sing shim but never surfaced a block for it.
  • Phoneme preview — phonemes for converts English text to the SAM phoneme string without speaking, useful for tuning pronunciation.
  • Fixed mouth/throat parameter order — pxt-billy swaps them between its block and its shim, so its own presets are not the documented values.
  • Patched SAM engine — see Fixes to the 1982 SAM engine.

micro:bit V2 only (the CODAL audio pipeline is required; V1 is explicitly unsupported).

About Robot PU

Robot PU is a playful, programmable robot built on the micro:bit. It walks, dances, kicks, and balances on its IMU — and with its built-in amplifier and speaker on pin 0, the SAM voice comes out loud and clear instead of whisper-quiet through the micro:bit's tiny on-board speaker.

Hear it live:

Get the Robot PU kit on Amazon → — then add the pxt-robotpu motion extension so it can move while it sings. More at The Story of PU.

Add to a MakeCode project

You need a micro:bit V2 — and for the full voice, a Robot PU kit.

Speaking

All speech blocks are queued: they return immediately and a background fiber renders and plays each utterance in order, so the robot keeps running other code while it talks. Queued utterances play back-to-back without a gap — the audio pipeline only powers down once the queue fully drains.

Say something while the robot keeps running other code:

robotpuVoice.setVoice(VoicePreset.RobotPU)
robotpuVoice.say("Hello, I am Robot P U")

Speak and wait until done:

robotpuVoice.sayAndWait("Obstacle detected")

Direct phoneme input for precise pronunciation:

robotpuVoice.pronounce("AY4 AEM AH KUMPYUW3TER")

Phoneme strings accept phoneme letters, spaces, pause signs (. , ? -), / and * prefixes, #nnn pitch markers, and stress digits 1–8; anything else (like !) is stripped so a stray character can't silence the whole phrase.

Voice presets

set voice picks one of eight SAM personalities: Robot PU (signature), SAM (the original C64 voice), elf, little robot, stuffy guy, little old lady, extra terrestrial, dalek.

set voice speed/pitch/mouth/throat tunes the voice directly (each value 0–255):

  • speed — how quickly the voice talks (0 = slow, 255 = fast)
  • pitch — how high or low the voice sounds
  • mouth — how tight-lipped or overtly enunciating it sounds
  • throat — how relaxed or tense the tone of voice is
robotpuVoice.configureVoice(150, 48, 150, 180)
robotpuVoice.say("I am a robot")

phonemes for converts English text into the SAM phoneme string without speaking it — useful for tuning pronunciation or preparing a pronounce/sing phonemes string:

serial.writeLine(robotpuVoice.toPhonemes("robot"))

See the original SAM manual for the phoneme alphabet and stress marks.

Singing

The sing blocks work like say: each call queues one item and returns, and the singer performs the whole sequence in the background, gaplessly. This is the recommended way to write songs.

sing chants English text on a flat pitch (SAM sing mode):

robotpuVoice.sing("daisy daisy")

sing note sings one musical note — choose the note, the syllable in SAM phonemes (/HAE sounds like "ha" — the "h" sound is spelled /H), and a hold count that stretches the vowel (bigger = longer):

robotpuVoice.singNote(SingNote.G4, "/HAE", 1)
robotpuVoice.singNote(SingNote.G4, "PIY", 1)
robotpuVoice.singNote(SingNote.A4, "BERTH", 4)
robotpuVoice.singNote(SingNote.G4, "DEY", 4)
robotpuVoice.singNote(SingNote.C5, "TUW", 4)
robotpuVoice.singNote(SingNote.B4, "YUW", 8)

Rests are queued just like notes — each plays in sequence with the speech around it — and there is one for every timing style:

  • rest … beats (singRest) — silence measured in hold units, so a rest matches a note of the same hold at the voice's own pace (rests keep their length when set singing tempo changes the speed):

    robotpuVoice.singNote(SingNote.C4, "DOW", 4)
    robotpuVoice.singRest(4)
    robotpuVoice.singNote(SingNote.G4, "SOH", 8)
    
  • rest in the sing note note dropdown — same thing inline: sing note rest syllable "" hold 4.

  • rest (ms) — silence in milliseconds, handy between spoken phrases.

  • sung rest for … — a playable for music.play timed in music.beat durations (see below).

sing phonemes takes a raw string with #nnn pitch markers, like MicroPython's speech.sing() — each marker sets the pitch for the phonemes after it, and repeated vowels hold the note. A whole phrase fits in one queued item:

robotpuVoice.singPhonemes("#115DOWWWWWW #103REYYYYYY #94MIYYYYYY")

set singing tempo % scales sung note lengths as a percent of the voice's normal speed — 100 is unchanged, 200 is twice as fast, 50 half — without touching the talking speed. 0 sings at the voice's own pace.

Singing through music.play

For beat-true timing or code that must run in step with each note, build a playable and pass it to the music play block — just like a tone or melody:

music.play(robotpuVoice.singNotePlayable(SingNote.C4, "DOW", music.beat(BeatFraction.Whole)), music.PlaybackMode.UntilDone)
music.play(robotpuVoice.singRestPlayable(music.beat(BeatFraction.Half)), music.PlaybackMode.UntilDone)
music.play(robotpuVoice.singNotePlayable(SingNote.G4, "SOH", music.beat(BeatFraction.Double)), music.PlaybackMode.UntilDone)

The duration socket takes music.beat(BeatFraction.…) straight from the music category, so notes follow music.setTempo. Other playables: sung words (English on a flat pitch), sung phonemes (#nnn markers), spoken words, and sung rest for …:

music.play(robotpuVoice.singPlayable("daisy daisy"), music.PlaybackMode.UntilDone)
music.play(robotpuVoice.singPhonemesPlayable("#115DOWWWWWW #103REYYYYYY #94MIYYYYYY"), music.PlaybackMode.UntilDone)
music.play(robotpuVoice.sayPlayable("that is all"), music.PlaybackMode.UntilDone)

Playables render through the same queue and audio pipeline as the say/ sing blocks, so the sound is identical — spoken words really is say in a play socket. The difference is only the playback mode: in background behaves like say (enqueue and return), until done behaves like say and wait, and looping in background repeats the phrase until music.stopAllSounds().

Queued blocks vs music.play playables

sing note / sing / rest (queued) playables in music.play
Timing hold count / milliseconds music.beat fractions, follows setTempo
Sequencing whole program queues, plays in background until done waits per note
Note transitions gapless — pipeline stays awake between queued items pipeline may power down between notes
Program flow handler returns while the song plays until done blocks; in background and looping in background modes also work
Best for songs, talking while the robot moves syncing LEDs or motion to notes, looping a phrase, programs built around the music category

Speech control

  • stop speaking — clears the queue and abandons the utterance being rendered.
  • is speaking — true while speech is playing or queued.
  • wait until speech finished — blocks until the queue drains and the last samples play out.
  • on speech finished — event raised when everything has been spoken:
robotpuVoice.onSpeechFinished(function () {
    basic.showIcon(IconNames.Happy)
})

rest (ms) queues timed silence just like a note — it plays in sequence with the queued speech, so a singer can wait a beat and join back in on time:

robotpuVoice.sing("wait for it")
robotpuVoice.rest(500)
robotpuVoice.sing("done waiting")

The utterance queue holds 3 items at once; calls beyond that wait for room so queued speech is never dropped — handy for a singer that stays a few notes ahead of the voice. For very long phrases you can still pack them into sing phonemes strings and use wait until speech finished to take a breath between them.

Tutorials

  • Sing Happy Birthday — step-by-step: say the message, then sing the whole song note by note.
  • Sing and Talk with music.play — playable blocks inside the music play block: real beats, LED sync, and looping speech.
  • Vocal Performance — a three-song medley (Memory, Birds of a Feather, Can't Stop the Feeling) with a beat-synced body groove at each song's real BPM, head glances, turns to each side of the room, and a final bow (needs the pxt-robotpu extension).
  • Sing and Dance — Jackson — Robot PU walks, moonwalks, and side-steps a Dangerous-style groove while the song plays in the background (needs the pxt-robotpu extension).
  • Sing and Light Show — low-cost demo on a bare micro:bit: talk and sing while an LED light show runs at the same time. No robot required.
  • Minion Banana Quartet — four micro:bits sing the Banana song in parts, started in sync over radio.
  • Speak Chinese — say and pronounce Mandarin: 你好, 谢谢, 恭喜发财 — with a vowel/consonant cheat sheet and tone-mark tricks.
  • Sing a Long Song — "Can You Feel the Love Tonight" — multi-phrase singing with # pitch markers and vowel stretching.

Note to pitch number table

SAM pitch ≈ 30000 / frequency(Hz) — smaller numbers are higher notes:

SingNote Note Hz #nnn SingNote Note Hz #nnn
C3 C3 131 229 C4 middle C 262 115
CSharp3 C#3 139 216 CSharp4 C#4 277 108
D3 D3 147 204 D4 D4 294 102
DSharp3 D#3 156 193 DSharp4 D#4 311 96
E3 E3 165 182 E4 E4 330 91
F3 F3 175 172 F4 F4 349 86
FSharp3 F#3 185 162 FSharp4 F#4 370 81
G3 G3 196 153 G4 G4 392 77
GSharp3 G#3 208 144 GSharp4 G#4 415 72
A3 A3 220 136 A4 A4 440 68
ASharp3 A#3 233 129 ASharp4 A#4 466 64
B3 B3 247 122 B4 B4 494 61
C5 C5 523 57 CSharp5 C#5 554 54
D5 D5 587 51 DSharp5 D#5 622 48
E5 E5 659 46 F5 F5 698 43
FSharp5 F#5 740 41 G5 G5 784 38
GSharp5 G#5 831 36 A5 A5 880 34
ASharp5 A#5 932 32 B5 B5 988 30

C major scale (Do Re Mi Fa Sol La Ti Do): #115 #102 #91 #86 #77 #68 #61 #57

MicroPython songs use a slightly different table (#115 #103 #94 #88 #78 #70 #62 #58) — both work; the difference is under a quarter tone.

Audio output

The extension works on any micro:bit V2 — no robot required — and audio plays on the on-board speaker and pin 0 through the CODAL mixer. Fair warning: the built-in speaker is small, so the SAM voice stays quiet even at full volume. On Robot PU, pin 0 feeds a dedicated sound amplifier and a higher-power speaker, so the voice comes out much louder and clearer — that is the stage this extension is tuned for.

Use use on-board speaker / output audio to pin 0 to route it, and set speech volume for volume.

When the speech queue drains, the extension waits for the last samples to play out and then puts uBit.audio to sleep — PWM on pin 0 stops, so the amplifier idles instead of dissipating power. power down audio forces the same shutdown immediately.

Fixes to the 1982 SAM engine

The bundled sam/ sources are the s-macke C port with a pile of bug fixes. The original code has buffer overruns, unterminated scans, and reads before the start of arrays — on a microcontroller those hang or corrupt the render instead of just sounding wrong:

  • Endless scans — Parser1 scans for the 0x9b end marker, and SAMMain overwrote the 255 terminator Init() installs with 32, so a scan that reached index 255 never stopped. SetInput now guarantees the marker exists and the terminator survives.
  • Reads before index 0 — six rewrite rules peek at the previous phoneme with phonemeindex[pos-1]; on the first phoneme that reads before the array. All of them are guarded now.
  • Clobbered terminator — Insert() could write at index 255, the slot every downstream scan relies on; writes there are refused, and flags[index] lookups guard the 255 end marker.
  • Output chunk overrun — PrepareOutput fills 60-entry output arrays where slot 59 must carry the terminator; it now flushes a full chunk like the 254 marker does, and Render bounds its scan at 60.
  • Wrong phoneme lengths — the CH/J splits and the glottal-stop insert passed a stale register (mem59) as the new phoneme's length; they now use phonemeLengthTable.
  • Multi-character literals — the reciter indexed its rule tables with mem64 - 'AA' and compared against 'YY' — implementation- defined constants that happen to work on some compilers. Replaced with single characters plus a bounds check on the table index.
  • Unstoppable renders — Render had no way out mid-utterance, so stop speaking used to wait for every frame to finish; a flag the render loop checks now bails out mid-note.
  • Out-of-order samples — the renderer writes a few samples ahead of its position counter; output is collected into a fixed window keyed by sample index instead of assuming strict ordering.
  • Fragile input — one character the parser rejects (like !) used to fail the whole utterance, and a run of . pause signs truncated the render. Phoneme input is sanitized and pauses collapsed before the engine sees it, and the reciter output gets a guaranteed NUL bound before SetInput's strlen runs.
  • Dropped song notes — sing note used to repeat a syllable's last two letters as the hold, so "BAAT" became BAATATAT… where AT is not a phoneme and the whole note rendered as silence. Trailing consonants are peeled off so the vowel stretches: "BAAAAAAT".
  • RAM — the C64 pitch timetable is const so it lives in flash.

On top of that sits the concurrency layer the single-threaded original never needed: an IRQ-guarded utterance queue, a generation counter so a cancelled batch can't resurrect, and a lock around the reciter's file-static state so phonemes for can't corrupt a render in flight.

Supported targets

  • for PXT/microbit (micro:bit V2)

Acknowledgements

License

MIT

Description taken from the repository README.

Ratings

–
0 ratings
5 0
4 0
3 0
2 0
1 0

Rate this app

Sign in to rate

Comments 0

Sign in to comment

No comments yet.

Related apps

#microbit · #robotics · #education