Sounds editor — keyboard reference

The Sounds tab is a tracker-style pattern editor. Click any cell in the 32×5 pattern grid to place the cursor, then use the keymap below to write notes, instruments, volumes, and stops. The cursor's sub-column (Note / Inst / Vol) determines which keys are active.

Notes — FT2 two-octave keymap

Two rows on the keyboard map to two consecutive octaves, anchored on the editor's current octave (default 4). The lower row (Z–M plus ", L .") covers C through D# of the next octave; the upper row (Q–U plus 2-7) covers C through B of the next octave; I–P plus 9 0 reach the third octave's C through E.

KeyNoteOctave offset
ZC+0
SC#+0
XD+0
DD#+0
CE+0
VF+0
GF#+0
BG+0
HG#+0
NA+0
JA#+0
MB+0
,C+1
LC#+1
.D+1
QC+1
2C#+1
WD+1
3D#+1
EE+1
RF+1
5F#+1
TG+1
6G#+1
YA+1
7A#+1
UB+1
IC+2
9C#+2
OD+2
0D#+2
PE+2

Pressing a note key writes the note at the cursor, advances the cursor down one row, and live-previews the note via the cell's instrument.

Stop / clear

KeyEffect
=Note-off — triggers ADSR release; voice rings through release phase, then silences.
-Note-cut — immediate silence, no release ramp.
Backspace / DeleteClear the active sub-column at the cursor (Note / Inst / Vol).

Octave shift

KeyEffect
<Lower the editor's base octave (clamped to 0).
>Raise the editor's base octave (clamped to 8).

The current octave is shown in the transport bar at the bottom of the Sounds tab. Default is 4 (so Z = C4).

Hex digits — Inst and Vol sub-columns

When the cursor is on the Inst or Vol sub-column, 0–9 and A–F write hex values.

Sub-columnBehavior
Inst (2 hex digits)Shift-register: each keypress shifts the existing value left and appends the new digit. Type 0 then 5 to enter 05. Cursor does NOT auto-advance, so you can build a 2-digit instrument id without losing the row.
Vol (1 hex digit)Replace: each keypress overwrites the current value. Cursor does NOT auto-advance.

Navigation

KeyEffect
/ Move cursor up / down one row.
/ Move cursor left / right one sub-column (wraps across channel boundaries).
TabMove cursor right (same as →).
Shift+TabMove cursor left (same as ←).
HomeJump to row 0, channel 0, Note sub-column.
EndJump to last row of the current channel + sub-column.
PageUp / PageDownJump 8 rows up / down.

Undo / redo

KeyEffect
Ctrl+Z / ⌘+ZUndo.
Ctrl+Shift+Z / ⌘+Shift+ZRedo.
Ctrl+YRedo (alternate).

Undo snapshots the entire SOND data on each meaningful change. Typing bursts are debounced with a 250 ms window so a sequence of note keypresses collapses into one undo step. The undo stack is capped at 50 snapshots; older snapshots drop FIFO.

Transport buttons

Below the pattern grid the transport bar shows the current pattern, row, and BPM. Buttons:

ButtonEffect
▶ PlayStart the host engine on the active pattern (loops; same as cart-Forth's play-pattern).
■ StopHalt the engine and all SFX slots (panic button). Equivalent to stop-music followed by -1 stop-sfx.
⊙ FollowToggle whether the cursor tracks the playhead row while the engine is running.

Music and SFX buses

As of P5.2 the host tracker engine has two parallel buses: a music slot that loops your background track, and four SFX slots that overlay one-shot or looping sound effects on top. Each slot reads patterns from the same SOND pool — the distinction is entirely about which cart-Forth word you call, not how you author the pattern.

Music bus: ( pattern -- ) play-music starts a looping pattern; ( -- ) stop-music halts it. play-pattern is an alias retained from pre-P5.2 — both names produce identical bytecode.

SFX bus: ( pattern slot -- ) play-sfx fires a one-shot pattern on slot 0–3 (or -1 to auto-allocate the first idle slot, evicting the oldest if all four are busy). loop-sfx ( pattern slot -- ) is the looping variant. ( slot -- ) stop-sfx stops one slot; -1 stop-sfx stops all SFX without touching music.

When two slots write to the same voice on the same tick, the engine dispatches in fixed order — music first, then SFX 0, 1, 2, 3 — and the last write wins. Authors who want non-conflicting layering can author SFX patterns to use voices the music doesn't touch (typically the sawtooth + noise channels).

play-once (pre-P5.2 single-pattern player) is retained as a shorthand: it's equivalent to passing your pattern number followed by -1 to play-sfx. For example, 2 play-once and 2 -1 play-sfx compile to equivalent behavior — both fire pattern 2 on an auto-allocated SFX slot. The semantic shift is intentional — pre-P5.2 it cut music; post-P5.2 it overlays.

On GameTank carts the same music and SFX also play in native ROM builds (cart-build --emit gtr) — the ROM embeds a 5-voice ACP mixer plus a tracker sequencer, and tempo stays locked to 60 Hz wall-time even when a frame runs over budget.

Dynamic tempo

A cart can change the music slot's tempo at any time by calling ( frames-per-row -- ) set-bpm. The new tempo (clamped to 2..=64) takes effect at the next row boundary. SFX slots are never affected.

Call set-bpm every frame to drive smooth tempo ramps — useful for Space-Invaders-style march acceleration or any rhythm-based gameplay that responds to game state.

\ Halve the music tempo from frame ~60 onward.
variable frame
: update
    frame @ 1 + frame !
    frame @ 60 = if 4 set-bpm then
;

set-bpm is a no-op when no music is playing — it does not pre-stage a tempo for a future play-music; that call always re-reads the cart's SOND header tempo.