Here’s a song I wrote in Javascript using Tone.js. This was a weekend project for getting to know Tone. Because all sounds are generated using the Web Audio API, it’s best to listen on a modern and zippy browser.
Chromium browsers, such as Brave or Google Chrome, are a good choice.
// Songs.js
// https://github.com/ckmahoney/music-theory/blob/master/src/Songs.js
function play8BitMusic(freq) {
Tone.Transport.bpm.value = 132;
Tone.Transport.start();
let phrase = 0;
const events = [];
for (let i = 0; i < 64 * 4; i++)
events.push(i);
const play = (synth, note, dur, t, vel) =>
synth.triggerAttackRelease(note, dur, t, vel);
const kickCompressor = new Tone.Compressor({threshold: -36, ratio: 15});
const kick = new Tone.MetalSynth({
frequency : 1,
envelope : {
attack : 0.001,
decay : 0.2,
release : 0.2
},
harmonicity : 1.04 ,
modulationIndex : 3,
resonance : 80,
octaves : 4
}).chain(kickCompressor, Tone.Master);
const hh = new Tone.MetalSynth({
frequency : 600 ,
envelope : {
attack : 0.005 ,
decay : 0.02,
sustain : 0
},
harmonicity : 5.1 ,
modulationIndex : 32 ,
resonance : 4000 ,
octaves : 1.5
});
const snare = new Tone.NoiseSynth({
noise : {
type : 'white'
},
envelope : {
attack : 0.005 ,
decay : 0.2,
sustain : 0
}
}).toMaster();
const tomCompressor = new Tone.Compressor({threshold: -24, ratio: 2.4});
const tom = new Tone.MembraneSynth({
pitchDecay : 0.1,
octaves : 10 ,
oscillator : {
type : 'sine'
},
envelope : {
attack : 0.001 ,
decay : 0.4 ,
sustain : 0.01 ,
release : 1.4 ,
attackCurve : 'exponential'
}
}).chain(tomCompressor, Tone.Master);
const padFilter = new Tone.AutoFilter({
frequency : 0.06 ,
type : 'sine' ,
depth : 1,
baseFrequency : 1200 ,
octaves : 3.6 ,
filter : {
type : 'highpass' ,
rolloff : -24 ,
Q : 4
}
});
const clapDist = new Tone.Chebyshev({order: 3, oversample: '4x'});
clapDist.wet.value = .1;
clapDist.wet.rampTo(.4, 80);
const clap = new Tone.NoiseSynth({
frequency : 200,
envelope: {
attack: 0,
decay: 0.09,
sustain: 0.9,
release: 0.001
},
harmonicity : 2 ,
modulationIndex : 32 ,
resonance : 600 ,
octaves : 2.5
}).chain(clapDist, Tone.Master);
clap.volume.value = -18;
const padVerb = new Tone.Freeverb({roomSize: 0.95, dampening: 12000});
padFilter.start();
padFilter.wet.value = 0.3;
padFilter.wet.rampTo(1, 13);
pad = new Tone.PolySynth(6, Tone.Synth, {
envelope: {
attack: 5
},
oscillator : {
type : "square"
}
}).chain(padFilter, padVerb, Tone.Master);
pad.volume.value = -12;
pad.set("detune", -1200);
pad.triggerAttack(["F6", "A6", "D6"], '1m', .1);
const leadDelay = new Tone.FeedbackDelay({delayTime: "4n", feedback: 0.65});
const lead = new Tone.FMSynth({
harmonicity : 5,
modulationIndex : 1/8,
detune : 30,
oscillator : {
type : 'sine'
},
envelope : {
attack : 0,
decay : '16n',
sustain : 0.2,
release : 0.5
},
modulation : {
type : 'square'
},
modulationEnvelope : {
attack : 0.1 ,
decay : 0 ,
sustain : 1 ,
release : 0.5
}
}).chain(leadDelay, Tone.Master);
const leadDegrees = [
[0, 3, 2, null],
[5, 3, -5],
[0, 5, 2],
[2, 3, 7]
];
const leadNotes = leadDegrees.map((phrase) =>
Notes.degreeToPitches(phrase, ['d', 'minor'], 5));
const bar = (tick) => tick % 16;
const beat = (tick) => tick % 4;
new Tone.Sequence(function(time, tick) {
if (phrase == 1) {
hh.toMaster();
}
if (tick === 0) {
console.log("Phrase: ", phrase)
let seq = new Tone.Sequence(function (t, note) {
console.log("playing note ", note);
play(lead, note, '8n.', '+6n');
}, leadNotes[phrase], '4m');
seq.loop = false;
seq.start();
// make the lead sound cooler as the song progresses.
lead.detune.value = phrase * 15;
}
if ((tick + 4) % 8 === 0) {
play(clap, '8n');
}
if (tick === 64 * 4 - 1) {
phrase++;
}
if (tick % 4 === 2) {
play(hh, '16n');
}
if (beat(tick) === 0) {
play(kick, '8n');
phrase > 0 && play(tom, 'A1', '8n.');
}
if ([10, 14, 30, 31].includes(tick)) {
let velMod = tick / 100;
play(snare, '8n', time, .5 + velMod);
}
}, events, '16n').start('+0.2');
}
Have fun.