This module provides a lightweight, interrupt-driven music player that uses PWM to generate tones based on a compact ASCII music string format similar to that used by Microsoft Extended BASIC's PLAY function.
Useful for creating simple tunes and sound effects from an attached piezo speaker.
Music sequences are defined as strings with the following commands:
C D E F G A B– Play corresponding note in current octaveRor,– Rest (silence)!– Click (full-volume pulse)
#after note – Sharp (e.g.,C#)-after note – Flat (e.g.,D-)
.after note – Dotted note (adds 50% duration):after note – Triplet note (2/3 duration)
- Number after note or command – Sets duration (for notes) or value (for commands)
Example:C4= quarter note C,L8= default length = eighth note
O<number>– Set octave (1–8); e.g.,O4<– Decrease octave by 1>– Increase octave by 1S<number>– Transpose by semitones -96 to 96 (cumulative)
[<number>- Start of loop segment. If no number, loops once]- End of loop segment. No nested loops.- Transposition and octave changes "stack" within loops
T<number>– Set tempo in BPM (beats per minute); e.g.,T120L<number>– Set default note length (1= whole,2= half,4= quarter, etc.)M<number>– Set gap (silence) after each note as fraction of note length
(0= no gap,1= 1/8 gap, ...,7= 7/8 gap)
V<number>– Set volume level (0–7); affects subsequent notes
Note: Whitespace is ignored. Commands are case-insensitive.
"T120 O5 L4 V4 C D E F G A B >C < <B A G F E D C"
Call once at startup:
setup(pin, timerid)pin: GPIO pin number for PWM outputtimerid: ID of hardware timer to use for interrupts
play(seq): Starts playing the given music string. Returns immediately; playback occurs in the background via timer interrupt.stop(): Stops playback immediately and disables PWM & interrupt.isplaying(): Returns1if music is playing,0if not.
- Uses a periodic hardware timer for note start/stop
- Handler implemented in Viper mode for speed
- Interrupts are automatically disabled when not needed
- Uses a global array
buf_playto store parsed note data - Buffer grows automatically as needed
- PWM frequency is set per-note (up to ~8 kHz based on internal frequency table)
- Duty cycle encodes volume (3-bit value embedded in upper bits of frequency word)
[freq | (duty << 13)]– Frequency (13 bits) + volume (3 bits)[duration | (gap << 13)]– Note duration (13 bits) + gap timing (3 bits)
Used for buffer and state management:
BUF_POS,BUF_TOP,PLAYING,COUNTDOWN,NOTE_OFF,DATA_START
machine.PWM,machine.Pin,machine.Timer, andarraymodules
- Max note frequency limited by PWM and data format (up to B8 ≈ 7902 Hz)
- Only one sequence can play at a time
- Monophonic output only (no polyphony)
Use this to convert RTTTL ringtones into the module's native format.
Example RTTTL:
PacMan:d=16,o=6,b=140:b5,b,f#,d#,8b,8d#,c,c7,g,f,8c7,8e,b5,b,f#,d#,8b,8d#,32d#,32e,f,32f,32f#,g,32g,32g#,a,8b
Translates to:
T140L16O6<B>BF#D#B8D#8C>C<GF>C8<E8<B>BF#D#B8D#8D#32E32FF32F#32GG32G#32AB8
See source code for the translator implementation.