Plainsong

A keyboard-first music editor that writes plain-text .track files — composed as single notes on a pitch×time sheet, played back at runtime by game-audio. Built for game leitmotifs, and it can sing.

▶ Open in your browser · Windows · Linux · Trackyard · Discord · Source

The browser version runs with no install — the piano, recording, playback, file open/save (as downloads, or drag a .track onto the window), and WAV export (whole song and per-layer stems) all work. Share a song as a link: …/plainsong-web/#url=<a .track URL> opens it directly, and File → Open Link loads a .track from any URL (on the web it also rewrites the address bar to that shareable link). A shared track's @imports are fetched too — relative ones resolve against the link, so a song and its instrument packs can live side by side on any web host. MP3 export and microphone recording are desktop-only.

Who this is for. This tutorial skips the obvious (there is a Play button) and focuses on the features you would otherwise never find: the isomorphic piano keyboard, record-driven composition, singing voices with phonetic lyrics, reusable leitmotifs, custom tunings, and the source-only cross-file features.

The whole document is a text file. Every visual edit reserializes it; the Source view lets you edit the text directly and an AI can edit either side. Nothing is hidden in a binary blob.

The Sheet

There is one canvas for everything. Each note is a horizontal line: Y = pitch (rounded to semitones), length = duration, thickness = volume (quiet notes are thinner; toggle vol lane in the sheet header for a strip at the bottom with a draggable bar per note), color = instrument (hashed to a hue), and a sloped line means glide. The left pitch gutter is a mini piano keyboard — black keys shaded, every C labelled (C4, C5…). The ruler numbers each bar (centered) and alternate bars are faintly shaded for easier reading.

Placing notes

ActionResult
Ctrl + click empty gridPlaces a note at the snapped position, and auditions it
Ctrl + drag on empty gridPlaces a note and sets its length live as you drag (draws it as a stroke)
Drag on empty gridRubber-band multiselect — selects every note the box touches
Click empty gridClears the selection
Drag a note bodyMoves it — a plain click never nudges it (a small drag is ignored); dragging past the view edge auto-scrolls so you can move notes across the whole song
Drag a note's right edgeResizes its duration
Ctrl + drag a noteDuplicates the selection and drags the copy
Click overlapping notesCycles the selection through the stacked notes under the cursor
Shift + click a noteToggles it in/out of the selection
Drag a selected note's end handleOn a flat note: pulls a glide to the target pitch
Drag a curve pointBends the pitch curve — sideways moves it in time (0..1 of the note), up/down changes the pitch offset
Double-click a selected noteAdds a curve point where you click
Right-click a curve pointDeletes that point
The one collision to know: plain drag is the rubber-band select; Ctrl places notes (click or draw). If you meant to place and got a selection box, you forgot Ctrl.

Pitch curves. A note's pitch is not just one line — it follows a multi-point curve over the note's length, exactly like the wave editor. A flat note has none; drag its end handle to pull a glide, then double-click to add more points and drag each one (a small handle on the selected note) to shape the trajectory. This expresses complex bends and per-note vibrato. The sheet draws the same curve the engine plays. Motif instances keep a single glide value, so their notes show only the end handle.

Snap & stacking

The Snap combo in the toolbar quantizes placement: Off, 1, 1/2, 1/3 (triplet), 1/4 (default), 1/6 (triplet), 1/8. Hold Shift during any click or drag to override snap for free positioning — Shift also disables pitch rounding, so you can place microtonal pitches between the semitone lanes.

Notes on the same pitch that overlap in time are drawn in parallel sub-lanes so nothing hides behind anything else. Draw order is inactive → active → selected.

The toolbar's Quantize button snaps the selected notes' start and length onto the current snap grid after the fact; recorded notes already snap their start to it as you play. To grab every note of one instrument at once, right-click a sound in the Sounds panel → Select notes using this sound.

Keyboard reference

Everything is reachable from the keyboard. Shortcuts are suppressed while a text field is focused.

KeysAction
Space · Shift+Space · Ctrl+SpacePlay / Pause · Stop · Stop and return to the very start
Home / EndMove the playhead to the start / end of the song
Ctrl+Z · Ctrl+Shift+Z / Ctrl+YUndo / Redo
Ctrl+C · Ctrl+VCopy · Paste (at the playhead, keeping relative offsets)
Ctrl+DDuplicate selection (offset +0.5 beat)
Ctrl+A · Ctrl+Shift+ASelect all notes (active layers) · select all notes in the current layer
Delete / BackspaceDelete selection
EscExit motif/instance editing, else clear the selection
/ With a single note selected: jump to the previous / next note in that layer. Otherwise: seek one beat back / forward (Shift: one bar) — also while playing
Tab / Shift+TabWhile editing a lyric: jump to the next / previous note, keeping the lyric field focused (fast lyric writing)
/ Raise / lower the volume of the selected notes (Shift: coarse steps)
Alt+/ · Alt+/Nudge the selection — in time by a snap step · in pitch by a semitone
Ctrl+ / Ctrl+Transpose the selection by an octave
Ctrl+LSet the loop region to span the selection
Ctrl+RToggle record
F1 / Ctrl+HShow / hide the shortcut list
PageUp / PageDownFocus previous / next layer (wraps)
Ctrl++ · Ctrl+- · Ctrl+0Zoom in · out · reset (both axes together)
Ctrl+wheel · Shift+wheelZoom · swap scroll axis
Ctrl+N/O/S · Ctrl+Shift+S/ONew · Open · Save · Save As · Reload
Ctrl+T · Ctrl+WNew document tab · Close tab (asks before discarding the last unsaved one)
Ctrl+PageUp / PageDownPrevious / next document tab
Ctrl+Q · Ctrl+Shift+QQuit · Force-quit

Real zoom. Ctrl+wheel scales both pixels-per-beat and semitone height, so notes get physically bigger — not just horizontally stretched, and it anchors on the point under the cursor (Ctrl++/ anchor on the view center). Ctrl+0 resets height and re-fits the song horizontally.

Piano mode

Piano (always on) plays the active sound from the computer keyboard. The layout is isomorphic and uses physical key positions, so it is independent of your keyboard layout. Two rules:

Up one QWERTY row = +1 semitone. Right one key = +3 semitones.

The F key is the base pitch (0). The grid below shows the semitone offset of every key before the octave shift:

1−11
2−8
3−5
4−2
5+1
6+4
7+7
8+10
9+13
0+16
Q−10
W−7
E−4
R−1
T+2
Y+5
U+8
I+11
O+14
P+17
A−9
S−6
D−3
F0
G+3
H+6
J+9
K+12
L+15
;+18
Z−8
X−5
C−2
V+1
B+4
N+7
M+10
,+13
.+16
/+19

The toolbar shows Oct +N + to shift the whole keyboard by octaves.

Record-driven composition

Rec is off by default — the piano just plays, so you can try out sounds and hunt for the next note freely. Turn Rec on in the toolbar and every key you press also writes a note at the insert line. This is the fast way to compose:

Singing voices phoneme-synth

Any sound can become a voice that sings its notes' lyrics. In the sound designer, pick the Voice preset: none (synth), male, female, child, robot, or ghost. A voice sound ignores the harmonics/envelope below it — it is synthesized by phoneme-synth instead.

Four voice knobs fine-tune the timbre, each seeded from the preset with a reset:

KnobRangeWhat it does
Formant0.5 – 2.0Shifts the vocal tract size (smaller = more childlike/chipmunk)
Tilt0.0 – 1.0Spectral tilt — brightness vs. darkness of the voice
Breath0.0 – 1.0Breathiness mixed into the tone
Vibrato0.0 – 1.0Pitch vibrato depth

Lyrics & phonemes

Each note carries a Lyric (in the note inspector), written in phonetic symbols. New notes on a voice sound default their lyric to la so they always sing something. The lyric is drawn right on the note in the sheet.

Fast lyric entry. With Rec off, select a note and just start typing — the text jumps straight into that note's lyric field, no clicking. Then Tab / Shift+Tab moves to the next / previous note with the field still focused, so you can type a whole line syllable by syllable. (With Rec on, letters record/play notes instead — turn Rec off to write lyrics.)

The phonetic alphabet:

Vowels

aeiou@äöü

Voiced

mnlrjq (ng)

Noise

sc (sh) fx (ach) zw (v)

Plosives

ptkbdg

A lyric can hold more than one syllable: spaces separate words and ' separates syllables within a wordhal'lo du sings a two-syllable word and then another word, all sharing the note's duration. A leading ' ties the note to the previous note on the same sound: the run is sung as one legato phrase (syllables flow into each other), drawn as a small tie arc between the notes in the sheet. Without it every note starts a fresh phrase.

Any note (sung or not) also carries an articulation in its inspector — separate or legato. Legato ties it to the previous note on the same sound: a slur for instruments, one flowing word when sung. This is the structured equivalent of the leading '.

Because lyrics live on ordinary notes, a sung line inherits everything else notes have: glide, volume ramps, layer effects, motif reuse. Auditions sing too — placing or pitch-dragging a voice note sings its lyric instead of falling back to the synth.

Subtitles

A subtitle links a timestamp to a line of text, shown as an overlay synced to the playhead — for karaoke lyrics, spoken lines, or cutscene captions. Unlike note lyrics, subtitles are standalone: a subtitle is just (beat → text), independent of any note.

Edit them in the Subtitles section of the left library panel. + at playhead adds an entry at the current position; each row has a to seek there, its beat, its text, and to delete. During playback — or while you scrub the insert line — the active subtitle (the last one whose beat has passed) is drawn at the bottom of the sheet. An entry with empty text acts as an end marker: it clears the overlay from that beat on.

Subtitles are saved in the .track file as a # Subtitles section (one <beat> <text> line each), which round-trips through the format, so any tool that reads the file can pick them up. Export .srt writes a timed SubRip file (timing derived from the song tempo) for videos and cutscenes; in-game display synced to the music is planned.

Leitmotifs

A motif is a named, reusable group of notes — the core feature for game music. Compose it once, drop transposable instances wherever it recurs, and vary it at runtime in the game. Each motif has its own color (hashed from its name), so every occurrence of the same leitmotif is recognisable at a glance across the whole piece.

  1. The Motifs library shows each motif as a small pitch×time thumbnail. + Motif creates one and drops you into edit mode: only that motif is shown (its coordinates are its own, not the song's), and you place its notes in the sheet like normal notes.
  2. With a motif selected, Space auditions it on its own with the active instrument — even before it is placed anywhere. Esc, clicking the motif again, or clicking a layer tab leaves edit mode.
  3. Place on layer drops an instance at the playhead. Instances render as a slim colored handle above their notes (the ♪ name label shows on hover or when selected) — drag the handle to reposition and transpose, double-click to edit that instance's overrides. The instance inspector's sound picker has a button to jump straight to that sound in the inspector.
  4. The instance inspector sets its sound, start, transpose, and tuning. If the instance's sound is a voice, empty motif-note lyrics fill with la — so a leitmotif can sing.

Imported motifs are read-only. A motif brought in from another file (via @import) can't be edited here — its inspector shows an Open in original file button that opens the source as a document tab so you can edit it at the source.

Per-instance overrides & trim. Double-click an instance's ♪ chip (or hit Edit notes in sheet in its inspector) to enter instance editing: drag this placement's notes and the changes are stored as overrides on the instance — the motif itself stays untouched, other placements keep playing the original. Delete skips selected notes — they drop out of this placement only (a skip override; untoggle it in the inspector to bring a note back). The inspector lists every override field by field (× removes one) and sets Trim (from/count) to keep only a contiguous range of the motif's notes; trimmed and skipped notes render faded while editing.

Custom tunings

Beyond 12-TET, define your own scales. In the Tunings library: + Tuning creates one; the inspector sets:

Assign a tuning per layer with the Tun combo on each layer card, and set that layer's Root frequency in Hz — the nearest note name is shown next to it, and plays it as a reference tone. Motif instances can override tuning too. Deleting or renaming a tuning cascades to everything that referenced it.

Switching a tuning does not move notes. A note stores a scale-degree number, not an absolute pitch; the tuning turns that number into a frequency. Switching to a tuning with the same number of steps per octave keeps each note on its degree (a real key/mode change). Switching to one with a different step count makes the same numbers land on different octaves, so the music reflows — plainsong warns you and offers to either keep the numbers or remap each note to the new scale degree closest in frequency.

The left pitch gutter follows the current layer's tuning: a piano keyboard for 12-TET, and for a custom tuning a scale ruler — one line per scale degree, a bold line labelled at each period (octave) boundary, the root marked, and the degree index shown when there is room. So the ruler always matches the scale you are composing in.

Layers & effects

Layers are mix groups, not composition containers — you compose in the sheet with the active sound, and which layer a note belongs to is just an attribute. New documents start with one layer; the concept only surfaces when you need it.

Each layer card (above the grid) has: name (click to focus, Shift/Ctrl+click to toggle active for editing), M mute, S solo, Vol, Pan, Tun, Root. Inactive layers render faded and non-interactive.

The layer inspector adds the runtime semantics that the game switches:

Move a note to another layer with the Layer combo at the top of the note inspector.

Playback, mixing & audition

Adaptive music

Game music reacts: combat layers fade in, the mix ducks in menus, a fanfare lands on the beat. In the .track format all of that is data — and Plainsong lets you rehearse it live, without starting your game.

On the game side the same file plays through game-audio: load the .track, call set_parameter from your game loop, done — what you rehearsed in the sandbox is what ships.

Sound designer

Selecting a sound opens a full synth editor (non-voice sounds):

Source view & cross-file playback

Toggle Source to open a live text editor beside the sheet. Every visual edit reserializes it; every text edit reparses live (errors show in red and block playback). It is the only place to reach the source-only features:

Files open as document tabs in the top bar (Ctrl+PageUp/PageDown to cycle). Drop a .track file to open it; drop an audio file (WAV/MP3/OGG/FLAC) to add it as a sampler sound you can place like any other; drop a .mid to import it (below). External changes auto-reload unless you have unsaved edits.

Sharing parts. A .track can hold any mix of sounds, waves, motifs and tunings, and @import (a local path or a URL) pulls another file's definitions into yours — an imported instrument makes no sound until you use it, so sharing a part is just sharing a link. To publish a clean single-purpose pack, File → Export pack writes a .track containing only the sounds, only the waves, only the motifs, or only the tunings of the current song.

Open vs. Import. Two ways to bring in a URL, for two intentions — both open a new document, differing only in what it contains. File → Open Link fetches the file itself and opens it — for looking at or editing someone's song. File → Import Link opens a new, empty song whose only content is @import <url>: the linked file's sounds, motifs and tunings are available to compose with, but you write your own piece and the original stays untouched (and updates with it). Share a song with Open, a sound pack with Import. In the browser the address bar carries both — #url=<track-url> opens, #import=<track-url> starts a fresh song that imports the pack.

Song metadata. With nothing selected, the inspector shows the song's title, author, description and tags. They are saved as a # Meta section in the .track, so shared songs and packs stay identifiable in file listings and future song browsers.

MIDI import

Drop a .mid / .midi file onto the window — or use Import MIDI in the toolbar — to convert a Standard MIDI File into a new song in its own tab. The conversion (from game-audio) carries over:

The first tempo wins — later tempo changes are dropped with a warning in the status bar. The imported song pulls its instruments from the shared bank via @import sounds/all.track, so provide that bank next to where you save (or remap the sounds) for playback; until then the sheet still shows every note, colored by instrument. Save as .track like any song.

Audio export

Export Audio in the toolbar renders the whole song to a .wav or .mp3 file — pick the extension in the save dialog. Export Stems writes one WAV per audible layer instead, for external mixing. Everything is synthesized offline exactly as it plays, so the export is shareable and usable anywhere, not just in a game running game-audio. The .track file stays the editable source; the audio file is the baked result. Both work in the browser too (as downloads, WAV only).

Export Stems renders one .wav per audible layer into a folder you pick — for mixing the parts separately in a DAW.


Plainsong is free software. Source: gitlab.com/porky11/plainsong. The runtime is game-audio.