THE PROBLEM
Bring a small music language to life.
QBasic’s PLAY command describes music using compact text commands. QBPlay interprets a useful subset of that language in a native macOS application: enter a tune, hear it, and export the rendered audio as a WAV file.
From characters to samples
The project separates lexical analysis, interpretation, musical events, synthesis, and playback. The lexer identifies commands; the interpreter applies tempo, octave, duration, and articulation state. Musical events carry timing and pitch into a renderer that generates the sample data.
CommandsInterpreter
Music eventsSynthesizer
Audio samples
AVAudioEngine handles playback, while Apple Accelerate supports waveform generation. The same rendered result can be used for listening and export, keeping those paths connected to one synthesis implementation.
Designing how a note sounds
The renderer starts from a sine wave and shapes it toward a square-like waveform. Short attack and release envelopes reduce clicks at note boundaries. Legato notes preserve oscillator phase across connected notes; normal and staccato articulation leave different amounts of silence within a note’s duration.
This makes the project more than a text parser: musical timing must become a precise number of samples, and transitions between events affect the result that a listener hears.
Compatibility with a deliberate boundary
The supported syntax includes named and numbered notes, accidentals, rests, dotted durations, tempo, octave shifts, and articulation. QBasic’s MB and MF commands are accepted for compatibility but do not change playback. The app produces a single monophonic stream; it does not emulate QBasic or support every historical PLAY feature.
Evidence and next steps
The repository provides a screenshot, sample audio, source code, and build instructions. Its source separates parsing from rendering, and the synthesis implementation explicitly handles envelopes and phase continuity.
Automated parser and renderer tests would strengthen the next iteration: malformed commands, error positions, note durations, rest silence, sample counts, and finite output values. A further practical question is how to bound memory use for long input, because the renderer assembles the complete output in memory.
Listen to the repository’s audio example ↗