Expand description
Tracker-style pattern sequencer for game music.
A Song is a set of lanes that advance one row at a time at a fixed
tempo. Each lane is written in a compact text notation and plays on one
synthesis channel. SongPlayer implements Sequencer, so a song
plays sample-accurately on any host that drives a
SequencedProcessor,
including the cpal engine.
§Notation
A lane is a whitespace-separated list of cells, one per row. Line breaks are whitespace too, so long patterns can be laid out a few bars per line.
| Cell | Meaning |
|---|---|
C4, F#3, Bb2 | Note on (C4 is middle C, MIDI 60). Releases the lane’s previous note. |
C4^047 | Chip arpeggio: cycle the notes at the given hex semitone offsets from the root (2-4 digits) on the channel’s arpeggiator. Needs the arp feature. |
E2+B2+E3 | Chord: start 2-4 notes together, one per voice of the channel. Releases the lane’s previous note(s). |
E2^07c! | Interval chord: hold the notes at the given hex semitone offsets from the root (2-4 digits) together, like an arpeggio that does not cycle. |
. | Hold: nothing changes. |
- | Note off: release the lane’s note. |
k s h o c x | Kick, snare, closed hi-hat, open hi-hat, crash, clap. |
lt mt ht | Low, mid, high tom. |
k+c, s+h | Several drum hits on the same row. |
@2, C4@2 | Switch the lane’s channel to patch 2 of the song (then play the note). |
| | Bar line; ignored, for readability. |
Each lane is monophonic like a sound-chip channel: a new note releases the
lane’s previous one. Chords are the exception: a chord cell starts all its
notes at once, and the lane’s next note, chord or - releases all of them.
A chord takes one of the channel’s four voices per note, so it holds at
most four; lanes that share its channel steal the oldest voices. @n
works on chord cells too (E2+B2+E3@1). Drum hits ring out on the drum
machine independently of the lane.
use synthie::params::ChannelNo;
use synthie::pattern::{Song, Tempo};
// A power-chord riff (root, fifth, octave) from a single lane.
let riff = Song::new(Tempo::bpm(140.0, 2))
.lane(ChannelNo(0), "E2+B2+E3 . G2+D3+G3 . | A2^07c! . . -")?;use synthie::params::ChannelNo;
use synthie::pattern::{Song, Tempo};
// One row per eighth note at 152 bpm.
let song = Song::new(Tempo::bpm(152.0, 2))
.lane(ChannelNo(0), "E4 . G4 . A4 . B4 . | E5 . . . - . . .")?
.lane(ChannelNo(1), "E2 E2 E2 E2 G2 A2 . . | E2 . . . - . . .")?
.lane(ChannelNo(3), "k+c h s h k k s h | k h s h k k s o")?;
assert_eq!(song.len(), 16);§Timing
Rows last 60 / rows_per_minute seconds. The player carries the
fractional frames of each row over to the next, so the tempo does not
drift however the row length divides the sample rate. Lanes shorter than
the song hold their last state until it loops.
Structs§
- Cell
- One row of a lane.
- Drum
Hits - A set of drum hits that fire on the same row.
- Lane
- One track of a song: the channel it plays on and its rows.
- Parse
Error - A token in a lane that could not be parsed, with its location.
- Song
- A piece of music: lanes played in lockstep at a fixed tempo.
- Song
Player - Plays a
Songas aSequencer. - Tempo
- How fast a song advances.
Enums§
- Action
- What a cell does to its lane.
- Parse
Error Kind - Why a token could not be parsed.
Constants§
- MAX_
ARP_ NOTES - Most notes an arpeggio cell can cycle through (the arpeggiator’s limit).
- MAX_
CHORD_ NOTES - Most notes a chord cell can hold: one per voice of its channel.
Functions§
- parse_
note - Parse a note name such as
C4,F#3,Bb2orC-1(C4 is MIDI 60).