API Documentation

Complete reference for ChiptuneSynth — 4-track chiptune synthesizer for the Web Audio API

Quick Start

// 1. Include the library
<script src="chiptune-synth.js"></script>

// 2. Create and initialize
const synth = new ChiptuneSynth();
await synth.init();

// 3. Play a note
synth.playNoteByName('C', 4, 0, 0.5);

// 4. Or play a sound effect
synth.playPreset('coin');

// 5. Load an instrument
synth.loadInstrument('violin', 0);
synth.playNoteByName('A', 4, 0, 1.0);
The AudioContext requires a user gesture (click/tap) to start. Always call init() inside a click handler or after user interaction.

Constructor & Initialization

new ChiptuneSynth()

Creates a new synthesizer instance with 4 tracks (Lead, Bass, Drums, FX), default envelopes, and empty vibrato settings.

const synth = new ChiptuneSynth();

synth.init() async

Initializes the Web Audio API context, creates master gain, analyser, per-track gain nodes, and noise buffer. Must be called once before any playback.

await synth.init();

synth.dispose()

Stops all notes and closes the AudioContext. Call when you're done with the synth.

Playback Methods

synth.playNote(frequency, trackIndex, duration, startTime) → noteId

Plays a note at the given frequency (Hz) on the specified track.

ParameterTypeDefaultDescription
frequencynumberFrequency in Hz (e.g. 440 for A4)
trackIndexnumber0Track index: 0=Lead, 1=Bass, 2=Drums, 3=FX
durationnumber10Duration in seconds. Values ≥10 create sustained notes
startTimenumbernowAudioContext time to start (for scheduling)
const noteId = synth.playNote(440, 0, 0.5);  // A4, Lead, 0.5s
Use duration = 10 or higher for sustained notes that you control manually with stopNote(). This is how the keyboard and MIDI work.

synth.playNoteByName(note, octave, trackIndex, duration) → noteId

Plays a note by its musical name.

ParameterTypeDefaultDescription
notestringNote name: C, C#, D, D#, E, F, F#, G, G#, A, A#, B (also Db, Eb, Gb, Ab, Bb)
octavenumber4Octave (1-7)
trackIndexnumber0Target track
durationnumber10Duration in seconds
synth.playNoteByName('C', 4, 0, 0.5);  // Middle C on Lead
synth.playNoteByName('Eb', 3, 1, 1.0); // Eb3 on Bass

synth.stopNote(noteId)

Stops a specific note with its release envelope. Use the noteId returned by playNote() or playNoteByName().

synth.stopAllNotes()

Immediately stops all active notes across all tracks.

Presets & Instruments

synth.playPreset(name)

Loads and plays a built-in SFX preset in one call. Perfect for game sound effects.

synth.playPreset('coin');
synth.playPreset('explosion');
synth.playPreset('jump');

Available SFX Presets (10)

NameDescriptionUse Case
laserDescending zap beamShooting, beams
coinRising bright dingCollecting items
jumpQuick ascending sweepJumping, bouncing
explosionNoise burst decayDestruction, death
powerupRising sweep fanfarePower-ups, upgrades
hitSharp impact noiseDamage, collision
blipShort UI clickMenu selection, UI
bassDeep triangle thumpBass drops, impacts
shootQuick zap shotRapid fire weapons
1upAscending fanfareExtra lives, bonuses

synth.loadPreset(name)

Loads a preset's configuration (waveform, envelope, effects) onto a track without playing it. Useful for setting up a track before triggering notes manually.

synth.loadInstrument(name, trackIndex) → boolean

Loads a full instrument preset onto the specified track, configuring waveform, envelope, vibrato, and modulation.

synth.loadInstrument('violin', 0);
synth.playNoteByName('A', 4, 0, 2.0);

Available Instruments (12)

NameTypeDescription
pianotriangleBright percussive tone with quick decay
violinsawtoothWarm bowed string with vibrato
cellosawtoothDeep bowed string, low register
flutesineSoft breathy tone with vibrato
organsquareSustained pipe organ tone
brasssawtoothBold horn/trumpet sound
harmonicasquareReedy PWM tone with tremolo
synthLeadsquareClassic chiptune lead
synthPadsawtoothWide unison pad with filter
synthBasssawtoothPunchy synth bass
marimbasinePercussive mallet tone
electricGuitarsquareOverdriven guitar emulation

Track Configuration

synth.updateTrack(trackIndex, settings)

Updates one or more properties of a track. Changes apply in real-time to currently playing notes.

synth.updateTrack(0, {
  type: 'sawtooth',
  volume: 0.4,
  unisonVoices: 4,
  unisonDetune: 20
});

Track Properties

PropertyTypeDefaultDescription
typestring'square'Waveform: 'square', 'triangle', 'sawtooth', 'sine', 'noise'
volumenumber0.3Track volume (0-1)
dutyCyclenumber0.5Pulse width for square waves (0-1)
detunenumber0Fine tuning in cents (1200 = 1 octave)
octaveOffsetnumber0Octave shift (±)
semitoneOffsetnumber0Semitone shift (±)
pitchEnvnumber0Pitch envelope depth in semitones
glidenumber0Portamento time in seconds

You can also directly set properties on the track objects:

synth.tracks[0].type = 'sawtooth';
synth.tracks[0].volume = 0.4;
synth.updateLiveNotes(0); // Apply to active notes

Envelopes (ADSR)

synth.updateEnvelope(trackIndex, settings)

Sets the amplitude envelope for a track. Controls how notes fade in, sustain, and fade out.

synth.updateEnvelope(0, {
  attack:  0.1,   // Fade-in time (seconds)
  decay:   0.2,   // Decay to sustain level
  sustain: 0.6,   // Held level (0-1)
  release: 0.5    // Fade-out after note stops
});
PropertyTypeRangeDescription
attacknumber0-5sTime to reach full volume
decaynumber0-5sTime to fall from peak to sustain level
sustainnumber0-1Volume level while note is held (0 = off after decay)
releasenumber0-10sFade-out time after note stops

Default Envelopes

TrackAttackDecaySustainRelease
0 - Lead0.010.100.70.20
1 - Bass0.010.200.80.15
2 - Drums0.0010.100.00.05
3 - FX0.0050.300.00.20

Vibrato

synth.updateVibrato(trackIndex, settings)

Configures pitch modulation (vibrato) for a track.

synth.updateVibrato(0, {
  rate:  5,   // Speed in Hz
  depth: 12   // Amount in cents
});
PropertyTypeDefaultDescription
ratenumber0LFO speed in Hz (0 = off)
depthnumber0Pitch deviation in cents (0 = off)

Filter

Each track has an optional filter for shaping the frequency content of the sound.

synth.tracks[0].filterEnabled = true;
synth.tracks[0].filterType = 'lowpass';
synth.tracks[0].filterCutoff = 2000;
synth.tracks[0].filterQ = 5;
synth.updateLiveNotes(0);
PropertyTypeDefaultDescription
filterEnabledbooleanfalseEnable/disable filter
filterTypestring'lowpass''lowpass', 'highpass', 'bandpass', 'notch'
filterCutoffnumber20000Cutoff frequency in Hz
filterQnumber0.1Resonance / Q factor
filterKeyTracknumber0Key tracking 0-100 (cutoff follows pitch)
filterEnvAmountnumber0Filter envelope depth (semitones)
filterEnvAttacknumber0.01Filter envelope attack (seconds)
filterEnvReleasenumber0.2Filter envelope release (seconds)

Unison & Detune

Stack multiple detuned oscillators for a thick, wide sound. Essential for pads and leads.

synth.tracks[0].unisonVoices = 8;
synth.tracks[0].unisonDetune = 25;  // cents between voices
synth.tracks[0].unisonSpread = 80;  // stereo spread %
PropertyTypeDefaultDescription
unisonVoicesnumber1Number of oscillators (1-16)
unisonDetunenumber0Detune spread in cents
unisonSpreadnumber0Stereo panning spread (0-100%)

LFOs (Tremolo & Filter Modulation)

Tremolo (Amplitude LFO)

synth.tracks[0].tremoloRate = 6;    // Hz
synth.tracks[0].tremoloDepth = 50;   // 0-100

Filter LFO

synth.tracks[0].filterEnabled = true;
synth.tracks[0].filterCutoff = 1000;
synth.tracks[0].lfoFilterRate = 3;     // Hz
synth.tracks[0].lfoFilterDepth = 2000;  // Hz range
PropertyTypeDescription
tremoloRatenumberTremolo speed in Hz (0 = off)
tremoloDepthnumberTremolo amount (0-100)
lfoFilterRatenumberFilter LFO speed in Hz (0 = off)
lfoFilterDepthnumberFilter LFO range in Hz
ChiptuneSynth has 3 independent LFOs per track: Vibrato (pitch), Tremolo (volume), and Filter modulation.

MIDI Support

synth.enableMIDI(options) async→ inputNames[]

Enables MIDI input from connected controllers. Returns an array of detected MIDI input device names.

const inputs = await synth.enableMIDI({
  track: 0,
  channel: 0,              // 0 = all channels
  onConnect: (name) => console.log('Connected:', name),
  onDisconnect: (name) => console.log('Disconnected:', name),
  onNoteOn: (note, vel, ch) => { /* MIDI note 0-127 */ },
  onNoteOff: (note, ch) => { /* ... */ },
  onCC: (cc, value, ch) => { /* CC 0-127 */ }
});

Options

PropertyTypeDefaultDescription
tracknumber0Target track for MIDI notes
channelnumber0MIDI channel filter (1-16, 0 = all)
onConnectfunctionCalled when a MIDI device connects
onDisconnectfunctionCalled when a MIDI device disconnects
onNoteOnfunctionCalled on note on (note, velocity, channel)
onNoteOfffunctionCalled on note off (note, channel)
onCCfunctionCalled on control change (cc, value, channel)

Supported MIDI CC

CCNameEffect
1Mod WheelControls vibrato depth
7VolumeControls track volume
64Sustain PedalSustains held notes
120All Sound OffStops all notes
123All Notes OffStops all notes

synth.disableMIDI()

Disables MIDI input and stops all held MIDI notes.

synth.setMIDITrack(trackIndex)

Changes which track receives MIDI input.

synth.setMIDIChannel(ch)

Sets the MIDI channel filter (0 = all channels).

synth.isMIDIEnabled() → boolean

Returns whether MIDI is currently active.

Visualizer Data

synth.getWaveformData() → Uint8Array

Returns time-domain waveform data for drawing oscilloscope-style visualizations. Values range from 0-255 (128 = center).

function draw() {
  const data = synth.getWaveformData();
  // Draw on canvas...
  ctx.beginPath();
  data.forEach((v, i) => {
    const x = (i / data.length) * width;
    const y = (v / 255) * height;
    i === 0 ? ctx.moveTo(x, y) : ctx.lineTo(x, y);
  });
  ctx.stroke();
  requestAnimationFrame(draw);
}

synth.getFrequencyData() → Uint8Array

Returns frequency-domain (FFT) data for spectrum visualizations. Values range from 0-255.

synth.setMasterVolume(value)

Sets the master output volume (0-1).

synth.getMasterVolume() → number

Returns the current master volume.

synth.resetToDefaults()

Resets all tracks, envelopes, and vibrato to their default values.

Static Methods

ChiptuneSynth.noteToFrequency(note, octave) static→ number

Converts a note name and octave to frequency in Hz.

ChiptuneSynth.noteToFrequency('A', 4);  // → 440
ChiptuneSynth.noteToFrequency('C', 4);  // → 261.63

ChiptuneSynth.midiToFrequency(midi) static→ number

Converts a MIDI note number (0-127) to frequency in Hz.

ChiptuneSynth.frequencyToMidi(freq) static→ number

Converts a frequency to the nearest MIDI note number.

ChiptuneSynth.getPresetNames() static→ string[]

Returns an array of all available SFX preset names.

ChiptuneSynth.getPresetNames();
// → ['laser','coin','jump','explosion','powerup','hit','blip','bass','shoot','1up']

ChiptuneSynth.getInstrumentNames() static→ string[]

Returns an array of all available instrument preset names.

ChiptuneSynth.getInstruments() static→ object

Returns an object keyed by instrument name, each value containing the full definition (track settings, envelope, vibrato, filter, etc.).

const instruments = ChiptuneSynth.getInstruments();
// { piano: { name, label, icon, type, volume, ... }, violin: { ... }, ... }

// Access a specific instrument definition
const piano = instruments.piano;
console.log(piano.type);  // 'triangle'
console.log(piano.label); // 'Piano'

Creating Custom Instruments

You can craft your own instruments by combining track settings, envelopes, vibrato, and filters. Here's how to build sounds from scratch.

Basic Custom Sound

Configure a track manually, then play notes on it:

// Create a retro pluck bass
synth.updateTrack(1, {
  type: 'sawtooth',
  volume: 0.5,
  octaveOffset: -1
});
synth.updateEnvelope(1, {
  attack: 0.005,
  decay: 0.3,
  sustain: 0.0,
  release: 0.1
});
synth.playNoteByName('E', 2, 1, 0.4);

Custom SFX (Preset-Style)

Replicate the preset pattern — configure a track, play a note, and let the envelope handle the rest:

// Custom "warp" SFX on FX track (3)
function playWarp() {
  synth.updateTrack(3, {
    type: 'square',
    volume: 0.3,
    dutyCycle: 0.25,
    pitchEnv: -24       // pitch drops 2 octaves
  });
  synth.updateEnvelope(3, {
    attack: 0.01,
    decay: 0.4,
    sustain: 0.0,
    release: 0.1
  });
  synth.playNote(880, 3, 0.5);
}
// Now call playWarp() anytime!

Rich Instrument with Effects

Combine multiple features for expressive instruments:

// Dreamy synth pad with filter sweep
function setupDreamPad(track) {
  synth.updateTrack(track, {
    type: 'square',
    volume: 0.25,
    dutyCycle: 0.3,
    unisonVoices: 6,
    unisonDetune: 18,
    unisonSpread: 70
  });
  synth.updateEnvelope(track, {
    attack: 0.8,
    decay: 0.5,
    sustain: 0.6,
    release: 2.0
  });
  synth.updateVibrato(track, {
    rate: 4,
    depth: 8
  });
  // Add filter with LFO modulation
  synth.tracks[track].filterEnabled = true;
  synth.tracks[track].filterType = 'lowpass';
  synth.tracks[track].filterCutoff = 1200;
  synth.tracks[track].filterQ = 3;
  synth.tracks[track].lfoFilterRate = 0.3;
  synth.tracks[track].lfoFilterDepth = 800;
}

setupDreamPad(0);
synth.playNoteByName('C', 4, 0, 3.0);

Multi-Track Layered Sound

Layer multiple tracks for complex sounds:

// Layered "epic hit" — noise + bass + lead
function playEpicHit() {
  // Layer 1: noise crash
  synth.updateTrack(2, { type: 'noise', volume: 0.4 });
  synth.updateEnvelope(2, { attack:0.001, decay:0.3, sustain:0, release:0.1 });
  synth.playNote(200, 2, 0.4);

  // Layer 2: sub bass thump
  synth.updateTrack(1, { type: 'sine', volume: 0.6, pitchEnv: 12 });
  synth.updateEnvelope(1, { attack:0.001, decay:0.25, sustain:0, release:0.1 });
  synth.playNote(60, 1, 0.3);

  // Layer 3: bright accent
  synth.updateTrack(0, { type: 'square', volume: 0.2 });
  synth.updateEnvelope(0, { attack:0.001, decay:0.1, sustain:0, release:0.2 });
  synth.playNote(440, 0, 0.15);
}

Reusable Instrument Object Pattern

Organize your custom instruments as config objects:

// Define instrument configs
const myInstruments = {
  retroLead: {
    track: { type: 'square', dutyCycle: 0.25, volume: 0.3 },
    env:   { attack: 0.02, decay: 0.15, sustain: 0.5, release: 0.3 },
    vib:   { rate: 5.5, depth: 10 }
  },
  fatBass: {
    track: { type: 'sawtooth', volume: 0.5, unisonVoices: 3, unisonDetune: 10 },
    env:   { attack: 0.01, decay: 0.2, sustain: 0.7, release: 0.1 },
    vib:   { rate: 0, depth: 0 }
  }
};

// Apply an instrument to a track
function applyInstrument(name, trackIndex) {
  const inst = myInstruments[name];
  synth.updateTrack(trackIndex, inst.track);
  synth.updateEnvelope(trackIndex, inst.env);
  synth.updateVibrato(trackIndex, inst.vib);
}

applyInstrument('retroLead', 0);
synth.playNoteByName('C', 5, 0, 0.5);
The Reusable Instrument Object pattern is the recommended approach for managing custom instruments in your game or app. It keeps your sound design clean and easy to tweak.
Discover 8BitForge — a creative app built with ChiptuneSynth featuring a visual instrument editor, 8 tracks, full mixer with EQ/compressor, 3 LFOs per track, and 50+ built-in instruments. Learn more

Default Values

Track Defaults

TrackIndexWaveformVolume
Lead0square0.30
Bass1triangle0.40
Drums2noise0.50
FX3sawtooth0.25

Architecture Note

Per-note gainNode handles only the ADSR envelope (normalized 0→1→sustain). Track volume is controlled by a separate _trackGains[] node. This architecture allows real-time parameter changes without interrupting the ADSR schedule of playing notes.