scmJS docs

Music

trigscript/music plays music written as patterns: a note's name per step, held with -, rests with .. Everything is made when the script is built. Each note an instrument plays becomes a sound once, and a song becomes a table of which sound starts on which frame.

import { music } from "trigscript/music";

const lead = music.presets.chip();
const bass = music.presets.bass();
const drums = music.drums();

const song = music.song({ bpm: 120 },
  music.track(lead, "C5 - E5 - G5 - - . | A5 - G5 - E5 - - ."),
  music.track(bass, "C3 - - - - - - - | F3 - - - - - - -"),
  music.track(drums, "kh h sh h kh h sh h | kh h sh h kh kh sh h"),
);

program(async () => {
  start(music.loop(song));                // the song again and again, beside the program
  playWav(lead.note("C6", 0.1), 0);       // an instrument's note on its own, as a sound effect
});

Patterns. A step is a word, a quarter of a beat unless the song says otherwise. A note's name starts a note (C4, F#5, Bb2); notes joined by + sound together (C4+E4+G4); - holds what sounds a step longer; . is a rest; | is only for the eye. @ after a step's notes says how loud (C4@0.5). A drum kit's step is its sounds' letters: kh is the kick and the hat together.

Instruments. music.synth makes notes from waves, music.fmSynth by FM, and music.strings plucks them; music.sampler plays a recording of one note at every pitch, from a sound file the script imports. music.presets has instruments to start from: supersaw, pad, bass, acid, lead, chip, keys, bell and pluck. music.drums() is a kit made from nothing, a letter a drum: k kick, s snare, c clap, h closed hat, o open hat, t tom, r rim and x crash. music.kit makes one from clips of your own.

Longer songs are written in sections. Parts are named once, and each section says what they play in it; a pattern shorter than its section plays again until the section is full. Chords are written by name, with a template that says how their notes are played:

import { music } from "trigscript/music";

const parts = {
  keys: music.part(music.presets.keys()),
  bass: music.part(music.presets.bass()),
  drums: music.part(music.drums()),
};
const verse = music.section(4, {
  keys: music.chords("Am F C G", "1+2+3 - . 1+2+3 . . 1+2+3 -", { octave: 4 }),
  bass: music.chords("Am F C G", "1 - . 1 . 1 3 -", { octave: 2 }),
  drums: music.beat({ k: [0, 3, 4], s: [2, 6], h: [0, 1, 2, 3, 4, 5, 6, 7] }, 8),
}, { name: "verse" });
const song = music.arrange({ bpm: 120, steps: 2 }, parts, verse, verse.with({ keys: null }));

program(async () => {
  await music.play(song);
  print("The song is over.");
});

Music that follows the game. music.stems cuts a song of sections into a sound for each bar of each layer, and music.conduct plays them as a Band says: which section, which layers, and a stinger on the next beat.

import { music, Band } from "trigscript/music";

const parts = { keys: music.part(music.presets.keys()), drums: music.part(music.drums()) };
const calm = music.section(2, { keys: music.chords("Am F", "1+2+3 - - -", { octave: 4 }) }, { name: "calm" });
const fight = music.section(2, {
  keys: music.chords("Am F", "1+2+3 . 1+2+3 .", { octave: 4 }),
  drums: music.beat({ k: [0, 4], s: [2, 6], h: [0, 1, 2, 3, 4, 5, 6, 7] }, 8),
}, { name: "fight" });
const cut = music.stems(music.arrange({ bpm: 120, steps: 2 }, parts, calm, fight), {
  layers: { drums: ["drums"], keys: ["keys"] },
});

program(async () => {
  const band = new Band();
  band.loop = true;                                 // a section plays again until told otherwise
  start(music.conduct(cut, band));
  await until(bring(P8, units.AnyUnit, locations.Base, ">=", 1));
  band.go(cut.section("fight"));                    // from the next bar
});

What the game does with it. A bar lasts a whole number of frames (42 ms each at Fastest), so the tempo is the nearest that fits, and song.bpm says which. A sound starts within 21 ms of its frame. The game plays 32 sounds at once, and a song that would need more is refused. Notes that overlap add up with no limiter, so keep volumes down. music.render(song) mixes a song into one sound when the script is built instead: every note exactly in time and only one sound playing, but nothing can change while it plays. A program plays to its own player; a program of every player plays to each.

Name What it is
synth(options), fmSynth(options), strings(options), sampler(clip, root?) Instruments. instrument.note(name, seconds) is one note as a sound.
presets.supersaw() … presets.pluck() Instruments to start from, each taking the options of what it is made with.
drums(options?), kit(hits) Drum kits: one made from nothing, or a clip a letter.
track(instrument, pattern, options?), song(options, ...tracks) A pattern for an instrument, and tracks played together. Options: pan, volume, fx, reverb; bpm, steps, beats, swing.
part(instrument, options?), section(bars, parts, options?), arrange(options, parts, ...sections) A song in sections. section.with({ … }) is a section with some parts changed, null resting one.
chord(symbol, octave?), chords(progression, template, options?), over(notes, template) Chords by name, and a template that plays them.
beat(hits, steps?) A drum bar from the steps each letter plays on.
transpose(p, semitones), stretch(p, by), repeat(p, n) Patterns changed.
midi(notes, parts, options?) A song from a MIDI file the script imports.
riser(seconds), impact(), pump(beat) A rise into a drop, a boom to land on, and a pumping effect.
render(song, options?) The song mixed into one sound when the script is built.
play(song), loop(song) Play a song note by note, once or again and again.
stems(song, options), new Band(), conduct(stems, band) Music that follows the game: band.go, queue, mute, unmute, sting, stop.