Modules / Sound

synthaudioplugin

Music as notes: a song as one file, played by Tone.js instruments in the page.

Putting it in a scene

A song is a project, one json file of the loader catalogue. The scene lists its projects in world.synthaudio, and may name a field of a row that chooses the project, so every seat hears the one the game is in. The plugin has no row of its own and no server half: a page's actors play it directly, a key down attacking a note and the key up releasing it.

"plugins": ["loader", "scenemanager", "player", "synthaudio"],
"world": {
  "loader": { "assets": {
    "town": { "type": "json", "url": "/games/mygame/assets/town.song.json" },
    "battle": { "type": "json", "url": "/games/mygame/assets/battle.song.json" }
  } },
  "synthaudio": {
    "projects": ["town", "battle"],
    "music": { "kind": "game", "path": "song", "fade": 1.5 },
    "volume": { "level": "volume", "muted": "muted" }
  }
}

A project, here with one synth track and one drum track:

{
  "bpm": 112,
  "length": 32,
  "master": {
    "compressor": { "threshold": -18, "ratio": 3, "attack": 0.01, "release": 0.2 },
    "limiter": -3,
    "echo": { "time": "8n", "feedback": 0.3 },
    "room": { "decay": 2 }
  },
  "tracks": {
    "lead": {
      "instrument": {
        "is": "synth",
        "params": { "wave": "triangle", "attack": 0.02, "decay": 0.2, "sustain": 0.5, "release": 0.4, "echo": 0.2, "room": 0.1, "gain": 0.7, "pan": -0.2 },
        "presets": {},
        "loaded": null
      },
      "patterns": { "a": { "length": 16, "notes": [[0, "C4", 4, 0.9], [4, "E4", 4, 0.8], [8, "G4", 8, 0.8]] } },
      "clips": [[0, "a", 2]]
    },
    "drums": {
      "instrument": {
        "is": "drumkit",
        "params": {
          "pads": {
            "kick": { "voice": "membrane", "pitch": "C1", "decay": 0.3, "gain": 1 },
            "hat": { "voice": "noise", "decay": 0.05, "gain": 0.4 }
          },
          "echo": 0, "room": 0, "gain": 0.9, "pan": 0
        },
        "presets": {},
        "loaded": null
      },
      "patterns": { "beat": { "length": 4, "notes": [[0, "kick", 1, 1], [2, "hat", 1, 0.6]] } },
      "clips": [[0, "beat", 8]]
    }
  }
}

On a machine without Web Audio, as in Node, the plugin has no context and plays nothing, but it still checks every project it is handed.

Properties

Read from world.synthaudio.

.projects : string[]

The loader json entries that are projects, by name. play and the music field accept only these names.

.music : { kind, path, fade? }

Chooses the project from the game. path is fields joined by dots, read off the first row of kind; the project it names plays, and none plays while it names none or there is no such row. When the field changes, the music changes at the next bar line of the project playing, a bar being 16 sixteenths. With fade the music instead fades out over fade seconds, changes, and fades back in over as many seconds. The whole output fades, including notes the keys play. play and stop still change the music until the field next changes.

.volume : { level, muted }

The names of the store keys the volume is read from: level holds the volume, and muted silences everything while true. Without it, or in a scene without a store, the volume is 1.

The project file

Time is counted in sixteenth notes, whole numbers, 16 to a bar. The song loops over length sixteenths.

KeyMeaning
bpmBeats a minute, above 0. A sixteenth lasts 60 / bpm / 4 seconds.
lengthThe song's length in sixteenths, a whole number from 1.
masterWhat every track goes out through, below.
tracksTracks by name, in the order they are listed. A track is { instrument, patterns, clips }.

master

KeyMeaning
compressor{ threshold, ratio, attack, release }: threshold in dB, ratio, and attack and release in seconds.
limiterThe limiter's threshold in dB, after the compressor.
echo{ time, feedback }: a delay time long, a Tone time such as "8n", that feeds back feedback of itself.
room{ decay }: a reverb decay seconds long.

Each track goes out through its gain and pan, and is sent to the echo and the room by its echo and room. The tracks, the echo and the room then go through the compressor and the limiter, a fade, and the volume.

Patterns and clips

A track's patterns are phrases by name, { length, notes }, and its clips place them in the song.

PartMeaning
lengthThe pattern's length in sixteenths, a whole number from 1.
a note[sixteenth, note, long, velocity]: the sixteenth inside the pattern the note starts on, which must be inside its length; the note, as a name Tone reads ("C4", "F#3") or, on a drumkit track, the name of a pad; how many sixteenths it lasts, from 1; and its velocity, 0 to 1.
a clip[sixteenth, pattern, repeats]: the pattern played repeats times, from 1, one after another from that sixteenth of the song. A note past the song's end is not played.

Instruments

An instrument is { is, params, presets, loaded }. is names what it is and fixes the params it has. params holds every value it plays with, always all of them, and is all a project plays from. presets is its own bank by name, each preset holding exactly the keys of params. loaded is the name of the preset last loaded, or null; it is a label for whoever edits the project and nothing plays from it. The track's instrument is built again only when its is or its params other than gain, pan, echo and room change, so a changed knob is heard at once and the other tracks keep ringing.

isWhat it is and its params
synthA polyphonic synth. wave, and attack, decay, sustain, release.
monosynthOne voice through a lowpass filter. The same as synth, and cutoff, resonance. The filter is held still at cutoff.
fmsynthA polyphonic FM synth. The same as synth, and harmonicity, modulation.
pluckA plucked string. brightness, resonance, release.
drumkitA voice for each pad. pads. A note names the pad it hits.
samplerRecorded notes played through a lowpass filter. samples, attack, release, cutoff, resonance.

Every instrument also has echo, room, gain and pan.

ParamMeaning
waveThe oscillator's shape: sine, triangle, square or sawtooth.
attack, decay, releaseThe envelope's times, in seconds.
sustainThe envelope's held level, 0 to 1 of full.
cutoff, resonanceA lowpass filter's corner in Hz, and its Q. On pluck, resonance runs 0 to 1.
harmonicity, modulationAn FM synth's modulator frequency over its carrier, and its modulation index.
brightnessA plucked string's dampening, in Hz.
echo, roomHow much of the track is sent to the master's echo and room, 0 to 1.
gain, panThe track's level, 0 to 1 and up, and its pan, −1 left to 1 right.
padsA drumkit's pads by name. A pad is { voice, decay, gain } with more keys by its voice: membrane and metal add pitch, a note; noise adds nothing; sample adds sample, a path. A pad dies away over its decay seconds and plays at its gain.
samplesA sampler's recorded notes, note name to path. A note between two recorded ones is played by the nearest, pitched to it.

A sample path is relative to the project's own file in the catalogue. A sample that is not there plays nothing, and nothing says so. A track waiting on its samples plays those that have loaded and is built again as each loads.

Methods

On the page

Through plugins.synthaudio, from an actor or another plugin.

play( name ) : Promise<void>

Plays a project of projects now, from its start, looped over its length, in place of what plays.

stop( )

Stops the music and lets go of every note.

playing( ) : string | undefined

The name of the project heard. It is the empty string for a project auditioned, and undefined when none plays.

audition( project, base, from? )

Plays a project handed as it is, as a studio holds it, looped, from from sixteenths into it, or its start without. base is the catalogue entry its sample paths are relative to. The project is checked first, and an invalid one is rejected.

use( project, base )

Sets the project the keys play and builds it now without playing it. A project played or auditioned is used as well.

attack( track, note, vel? )

Starts a held note now on a track of the project used. vel is 0 to 1, default 1. On a drumkit the note is a pad's name. It fails if no project is in use or the project has no such track.

release( track, note )

Ends a note started with attack.

at( ) : number | null

The sixteenth the project heard is at, a whole number inside its length, or null when none plays. A project asked for is heard from the moment it starts, so until the bar line it waits for, these report the project before it.

position( ) : number | null

The same as at, in sixteenths and not rounded.

project( ) : Project | undefined

The project heard.

held( ) : { track, note }[]

Every note a key holds now: attacked and not yet released.

quiet( tracks )

Takes the named tracks, in every project, out of the song from now until it is handed others, as a mixer's mute and solo do. The keys still play them.

close( )

Stops every note and the music, removes the page's click and key listeners, and closes the context.

Reading and checking a project

From #plugins/synthaudio/synthaudio.sim.ts. Nothing here touches Web Audio, so it runs anywhere.

check( v )

Throws, naming where, on anything a project may not hold. A file comes from outside the program, so every project is checked before anything plays it.

flatten( project ) : Played[]

Every note the song plays, in time order, as { at, track, note, len, vel }, with each clip's pattern repeated and notes past the song's end left out.

samples( project ) : string[]

Every sample path the project names, each once.

sixteenth( bpm ) : number

The seconds one sixteenth lasts at bpm.

midi( name ) : number

A note name as a number of semitones, counted as MIDI counts them: C-1 is 0 and C4 is 60. Sharps and flats are accepted. named( n ) gives the name back, spelling black keys as sharps.

Edits

From the same file. Each changes the project it is handed and checks what it is handed, throwing, naming it, on anything that would leave the project unplayable. A studio's undo is a copy of the project made before the edit.

EditWhat it does
setSong( p, bpm, length )Sets the tempo and the length in sixteenths.
addTrack( p, name, is, presets )Adds a track last, of instrument is with the bank presets. Its params are a copy of the first preset, which is loaded. It has no patterns and no clips. The name must be new and not empty, and the bank must not be empty.
removeTrack( p, name )Takes a track away.
renameTrack( p, name, to )Renames a track, keeping its place in the order.
changeInstrument( p, name, is, presets )Gives a track another instrument with a new bank, the first preset loaded. Its notes stay. Pad names and note names mean different things, so a change between a drumkit and anything else takes its patterns and clips away.
load( p, name, preset )Copies a preset of the track's bank over its params and marks it loaded. On a drumkit, notes that hit a pad the preset has not got are removed.
keep( p, name, preset )Copies the track's params into its bank as preset, over one of that name, and marks it loaded.
set( p, name, path, value )Sets one param. path runs inside the params: ['cutoff'], or ['pads', 'kick', 'decay']. Only a value of the kind already there is taken.
setPad( p, name, pad, value )Adds or replaces a pad of a drumkit track. With undefined it takes the pad away, and every note that hits it.
addPattern( p, name, pattern, length )Adds an empty pattern of length sixteenths to a track.
removePattern( p, name, pattern )Takes a pattern away, with every clip that plays it.
renamePattern( p, name, pattern, to )Renames a pattern and the clips that play it.
resizePattern( p, name, pattern, length )Changes a pattern's length. A note that would start past it is removed.
addNote( p, name, pattern, n )Adds a note and returns its index among the pattern's notes. A note at the same sixteenth with the same note name is replaced.
setNote( p, name, pattern, k, n )Replaces note number k of a pattern.
removeNote( p, name, pattern, k )Removes note number k.
step( p, name, pattern, at, note, vel )Toggles a one-sixteenth hit of note at sixteenth at: removes it if there is one, adds it at velocity vel if not. Returns whether it is hit now.
addClip( p, name, c )Adds a clip to a track and returns its index among the track's clips.
setClip( p, name, k, c )Replaces clip number k.
removeClip( p, name, k )Removes clip number k.

Properties of the module

context : Tone.Context | undefined

The client's own Tone context over its own Web Audio context, never Tone's global one. It is undefined where there is no Web Audio, as in Node. It resumes on the first pointer press or key press on the page.

In the editor

A studio edits a project with the edits above, auditions it with audition, and mutes tracks with quiet.

In the guide