Complete reference for ChiptuneSynth — 4-track chiptune synthesizer for the Web Audio API
// 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); init() inside a click handler or after user interaction.Creates a new synthesizer instance with 4 tracks (Lead, Bass, Drums, FX), default envelopes, and empty vibrato settings.
const synth = new ChiptuneSynth(); 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(); Stops all notes and closes the AudioContext. Call when you're done with the synth.
Plays a note at the given frequency (Hz) on the specified track.
| Parameter | Type | Default | Description |
|---|---|---|---|
| frequency | number | — | Frequency in Hz (e.g. 440 for A4) |
| trackIndex | number | 0 | Track index: 0=Lead, 1=Bass, 2=Drums, 3=FX |
| duration | number | 10 | Duration in seconds. Values ≥10 create sustained notes |
| startTime | number | now | AudioContext time to start (for scheduling) |
const noteId = synth.playNote(440, 0, 0.5); // A4, Lead, 0.5s duration = 10 or higher for sustained notes that you control manually with stopNote(). This is how the keyboard and MIDI work.Plays a note by its musical name.
| Parameter | Type | Default | Description |
|---|---|---|---|
| note | string | — | Note name: C, C#, D, D#, E, F, F#, G, G#, A, A#, B (also Db, Eb, Gb, Ab, Bb) |
| octave | number | 4 | Octave (1-7) |
| trackIndex | number | 0 | Target track |
| duration | number | 10 | Duration in seconds |
synth.playNoteByName('C', 4, 0, 0.5); // Middle C on Lead
synth.playNoteByName('Eb', 3, 1, 1.0); // Eb3 on Bass Stops a specific note with its release envelope. Use the noteId returned by playNote() or playNoteByName().
Immediately stops all active notes across all tracks.
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'); | Name | Description | Use Case |
|---|---|---|
| laser | Descending zap beam | Shooting, beams |
| coin | Rising bright ding | Collecting items |
| jump | Quick ascending sweep | Jumping, bouncing |
| explosion | Noise burst decay | Destruction, death |
| powerup | Rising sweep fanfare | Power-ups, upgrades |
| hit | Sharp impact noise | Damage, collision |
| blip | Short UI click | Menu selection, UI |
| bass | Deep triangle thump | Bass drops, impacts |
| shoot | Quick zap shot | Rapid fire weapons |
| 1up | Ascending fanfare | Extra lives, bonuses |
Loads a preset's configuration (waveform, envelope, effects) onto a track without playing it. Useful for setting up a track before triggering notes manually.
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); | Name | Type | Description |
|---|---|---|
| piano | triangle | Bright percussive tone with quick decay |
| violin | sawtooth | Warm bowed string with vibrato |
| cello | sawtooth | Deep bowed string, low register |
| flute | sine | Soft breathy tone with vibrato |
| organ | square | Sustained pipe organ tone |
| brass | sawtooth | Bold horn/trumpet sound |
| harmonica | square | Reedy PWM tone with tremolo |
| synthLead | square | Classic chiptune lead |
| synthPad | sawtooth | Wide unison pad with filter |
| synthBass | sawtooth | Punchy synth bass |
| marimba | sine | Percussive mallet tone |
| electricGuitar | square | Overdriven guitar emulation |
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
}); | Property | Type | Default | Description |
|---|---|---|---|
| type | string | 'square' | Waveform: 'square', 'triangle', 'sawtooth', 'sine', 'noise' |
| volume | number | 0.3 | Track volume (0-1) |
| dutyCycle | number | 0.5 | Pulse width for square waves (0-1) |
| detune | number | 0 | Fine tuning in cents (1200 = 1 octave) |
| octaveOffset | number | 0 | Octave shift (±) |
| semitoneOffset | number | 0 | Semitone shift (±) |
| pitchEnv | number | 0 | Pitch envelope depth in semitones |
| glide | number | 0 | Portamento 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 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
}); | Property | Type | Range | Description |
|---|---|---|---|
| attack | number | 0-5s | Time to reach full volume |
| decay | number | 0-5s | Time to fall from peak to sustain level |
| sustain | number | 0-1 | Volume level while note is held (0 = off after decay) |
| release | number | 0-10s | Fade-out time after note stops |
| Track | Attack | Decay | Sustain | Release |
|---|---|---|---|---|
| 0 - Lead | 0.01 | 0.10 | 0.7 | 0.20 |
| 1 - Bass | 0.01 | 0.20 | 0.8 | 0.15 |
| 2 - Drums | 0.001 | 0.10 | 0.0 | 0.05 |
| 3 - FX | 0.005 | 0.30 | 0.0 | 0.20 |
Configures pitch modulation (vibrato) for a track.
synth.updateVibrato(0, {
rate: 5, // Speed in Hz
depth: 12 // Amount in cents
}); | Property | Type | Default | Description |
|---|---|---|---|
| rate | number | 0 | LFO speed in Hz (0 = off) |
| depth | number | 0 | Pitch deviation in cents (0 = off) |
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); | Property | Type | Default | Description |
|---|---|---|---|
| filterEnabled | boolean | false | Enable/disable filter |
| filterType | string | 'lowpass' | 'lowpass', 'highpass', 'bandpass', 'notch' |
| filterCutoff | number | 20000 | Cutoff frequency in Hz |
| filterQ | number | 0.1 | Resonance / Q factor |
| filterKeyTrack | number | 0 | Key tracking 0-100 (cutoff follows pitch) |
| filterEnvAmount | number | 0 | Filter envelope depth (semitones) |
| filterEnvAttack | number | 0.01 | Filter envelope attack (seconds) |
| filterEnvRelease | number | 0.2 | Filter envelope release (seconds) |
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 % | Property | Type | Default | Description |
|---|---|---|---|
| unisonVoices | number | 1 | Number of oscillators (1-16) |
| unisonDetune | number | 0 | Detune spread in cents |
| unisonSpread | number | 0 | Stereo panning spread (0-100%) |
synth.tracks[0].tremoloRate = 6; // Hz
synth.tracks[0].tremoloDepth = 50; // 0-100 synth.tracks[0].filterEnabled = true;
synth.tracks[0].filterCutoff = 1000;
synth.tracks[0].lfoFilterRate = 3; // Hz
synth.tracks[0].lfoFilterDepth = 2000; // Hz range | Property | Type | Description |
|---|---|---|
| tremoloRate | number | Tremolo speed in Hz (0 = off) |
| tremoloDepth | number | Tremolo amount (0-100) |
| lfoFilterRate | number | Filter LFO speed in Hz (0 = off) |
| lfoFilterDepth | number | Filter LFO range in Hz |
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 */ }
}); | Property | Type | Default | Description |
|---|---|---|---|
| track | number | 0 | Target track for MIDI notes |
| channel | number | 0 | MIDI channel filter (1-16, 0 = all) |
| onConnect | function | — | Called when a MIDI device connects |
| onDisconnect | function | — | Called when a MIDI device disconnects |
| onNoteOn | function | — | Called on note on (note, velocity, channel) |
| onNoteOff | function | — | Called on note off (note, channel) |
| onCC | function | — | Called on control change (cc, value, channel) |
| CC | Name | Effect |
|---|---|---|
| 1 | Mod Wheel | Controls vibrato depth |
| 7 | Volume | Controls track volume |
| 64 | Sustain Pedal | Sustains held notes |
| 120 | All Sound Off | Stops all notes |
| 123 | All Notes Off | Stops all notes |
Disables MIDI input and stops all held MIDI notes.
Changes which track receives MIDI input.
Sets the MIDI channel filter (0 = all channels).
Returns whether MIDI is currently active.
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);
} Returns frequency-domain (FFT) data for spectrum visualizations. Values range from 0-255.
Sets the master output volume (0-1).
Returns the current master volume.
Resets all tracks, envelopes, and vibrato to their default values.
Converts a note name and octave to frequency in Hz.
ChiptuneSynth.noteToFrequency('A', 4); // → 440
ChiptuneSynth.noteToFrequency('C', 4); // → 261.63 Converts a MIDI note number (0-127) to frequency in Hz.
Converts a frequency to the nearest MIDI note number.
Returns an array of all available SFX preset names.
ChiptuneSynth.getPresetNames();
// → ['laser','coin','jump','explosion','powerup','hit','blip','bass','shoot','1up'] Returns an array of all available instrument preset names.
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' You can craft your own instruments by combining track settings, envelopes, vibrato, and filters. Here's how to build sounds from scratch.
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); 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! 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); 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);
} 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); | Track | Index | Waveform | Volume |
|---|---|---|---|
| Lead | 0 | square | 0.30 |
| Bass | 1 | triangle | 0.40 |
| Drums | 2 | noise | 0.50 |
| FX | 3 | sawtooth | 0.25 |
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.