Getting Started¶
Tutorial¶
This illustrates the core concepts of bitrhythm.
Samples (Tone.Sampler)
Dials (use cellx internally)
Observers (cellx)
See https://tonejs.github.io/ for more notes.
For an understanding of the global variables see the concepts and code walkthrough section.
- patterns and track_no
- isHit, delta
- samples
- dials
- Tone
- cellx
- window and any thing included with the script tag is available here
mem
is short for memory. All instruments and effects are saved here so that they can be accessed everywhere.
Step to create the basic song.
Click on
Add Sample URL
to add the following URLs/Kick01.wav
/Snare19.wav
/Closedhat01.wav
/MiscSynthStab04.wav
Click on
Add Dial
Enter the following into the window
scene1 = [
cellx("p 1000 1000 1000 1000"),
cellx("p 00x0 00x0 00x0 00x0"),
cellx("p 0000 x000 0000 x000"),
]
scene2 = [
cellx("p 1011 1001 1000 1000"),
cellx("p 00x0 00x0 00x0 00x0"),
cellx("p 0000 x000 0000 x000"),
]
patterns = scene1
var once = function () {
Tone.Master.volume.value = -30
mem.master = new Tone.Channel({channelCount: 2, volume: -10}).chain( Tone.Destination);
mem.volume_guard = guard([-20,-10]);
Sample("k", 0);
Sample("h", 1);
Sample("sn", 2);
handlers["ex"] = function (val) {
if (val > 0.5) {
mem["start_snare"] = true;
}
}
dials[0]["cell"].onChange(function (e) {
var val = parseFloat(e["data"].value);
handlers["ex"](val);
})
}
if (bars <= 3 ) {
transition = once;
} else {
transition = tweak;
}
if (isHit) {
if (track_no == 1) {
p(0);
}
if (track_no == 2) {
p(1);
}
if (track_no == 3) {
if (mem["start_snare"]) {
p(2);
};
}
}
Now try changing the code.
patterns = scene2
Increase the dial to see the addition of the snare. This is how you can use observers to trigger unrelated changes. I call them sideevents, as the logic is similar to sidechain, which typically observers the volume.
Comment and Uncomment lines in the if (isHit)
block. To mute and unmute sections.
Note: mem["k0_channel"].solo = true;
is not working.
Visuals
Change the once function to this and click + Execute Once
Code is taken from butterchurn. Try changing presets to get different visuals.
var tweak = function() {
initWinamp("_Aderrasi - Wanderer in Curved Space - mash0000 - faclempt kibitzing meshuggana schmaltz (Geiss color mix)");
render_loop = function () {
window.visualizer.render();
}
}
Tweaking
First click + Number
. This is useful to check if the knob function is actually working. And click + Execute Once
var tweak = function () {
mem.k1 = knob({ramp : [0.09,1.8, 0.4, 2, 1.5, 1, 0.5, 3, 5, 8, 2], "number": numbers[0]["v"] });
always = function () {
mem["k0_filter"].frequency.value = mem.k1.move() * 1000;
}
}
As you can sere numbers and dials will be available as a global array.
There is no way to remove them so be careful about the order in which you add them.
The following code will always be executed as its at the top level. As you can see this code implies that the first dial is connected to the master volume. Use guards to avoid going deaf as someundefineds editing can created bad frequency numbers.
Tone.Master.volume.value = volume_guard((1 - dials[0]["cell"]()) * -30);
Making a basic loop¶
Kick + Filter
Snare + Filter
Snare + Filter + Delay
High Hat
Lead + Filter
Dub Stab + Filter + Reverb
Tip: In Tone.js you can’t call connect one after another, you need to use chain.
TODO: Add glide to Lead to make it more 303 sounding
Master is connected with Surround and Volume Limiter. Use Gates and Limiters to avoid going deaf.
Tone.MultiInstrument gave lots of glitches, so custom voices are written in the voice function
Channels provide
Mute
Solo
More improvements for the Stab
- Chorus or Phaser
- Compressor
- Decay in envelope
- Separate filters
- EQ
- Sends for more delay
- LFO for filters
Freeverb does not work and also needs Mono to function properly
var reverb = new Tone.Freeverb().toDestination();
var reverb_mono = new Tone.Mono().connect(reverb);
reverb.dampening = 100;
reverb.roomSize = 0.9;
Full Code¶
volume_guard1 = guard([-20,15])
Tone.Master.volume.value = volume_guard1(Math.round(dials[0]["cell"]() * 30) -20);
// mem["stab_channel"].volume.value = volume_guard2(-2);
// mem["stab_filter"].frequency.value = Math.round(dials[1]["cell"]() * 10000);
scene1 = [
cellx("p 1000 1000 1000 1000"),
cellx("p 00x0 00x0 00x0 00x0"),
cellx("p 0x00 0000 0000 x000"),
cellx("p 0000 x000 0000 x000"),
cellx("p xx0x c0x0 x0x0 x0xx"),
cellx("p x000 x0x0 0000 x0x0"),
]
patterns = scene1
function NoiseSynth (name) {
name = name || "wf";
mem[name + "_stereo"] = new Tone.StereoWidener({width: 1});
mem[name] = new Tone.Noise("pink").start();
mem[name + "_filter"] = new Tone.Filter(400, 'lowpass', -96);
mem[name + "_channel"] = new Tone.Channel({channelCount: 2, volume: -10, pan: -0.8}).chain(mem[name + "_filter"], mem[name + "_stereo"], mem.master);
mem[name].connect(mem[name + "_channel"])
}
function Stab(name) {
name = name || "stab";
mem[name + "_filter"] = new Tone.Filter(5250, 'lowpass', -96);
mem[name + "_hfilter"] = new Tone.Filter(80, 'highpass', -96);
mem[name + "_reverb"] = new Tone.Reverb(0.1);
mem[name + "_delay"] = new Tone.FeedbackDelay("4n", 0.4);
// mem[name + "_pdelay"] = new Tone.PingPongDelay("2n", 0.1);
mem[name + "_stereo"] = new Tone.StereoWidener({width: 0.25});
mem[name + "_channel"] = new Tone.Channel({channelCount: 2, volume: -2}).chain(mem[name + "_filter"] , mem[name + "_delay"], mem[name + "_reverb"], mem[name + "_hfilter"] ,mem[name + "_stereo"], mem.master)
function voice(no, type) {
mem[name + "_synth" + no] = new Tone.MonoSynth({
oscillator: {
type: type
}
})
mem[name + "_synth" + no].connect(mem[name + "_channel"]);
}
voice(1, "sawtooth")
voice(2, "sawtooth")
voice(3, "sawtooth")
voice(4, "pwm")
voice(5, "pwm")
voice(6, "pwm")
}
function s(vel, notes, duration) {
vel = vel || 1.0;
duration = duration || "2n";
notes = notes || ["E2", "B2", "G2"];
mem["stab_synth1"].triggerAttackRelease(notes[0], duration, undefined, vel);
mem["stab_synth2"].triggerAttackRelease(notes[1], duration, undefined, vel);
mem["stab_synth3"].triggerAttackRelease(notes[2], duration, undefined, vel);
mem["stab_synth4"].triggerAttackRelease(notes[0], duration, undefined, vel);
mem["stab_synth5"].triggerAttackRelease(notes[1], duration, undefined, vel);
mem["stab_synth6"].triggerAttackRelease(notes[2], duration, undefined, vel);
}
function once() {
var pr;
const s = ( p ) => {
pr = p;
var img;
let x = 100;
let y = 100;
p.setup = function() {
var x = p.createCanvas(700, 410);
x.canvas.style.position = "absolute";
p.frameRate(30);
img = p.loadImage('/test.png');
};
p.draw = function() {
var e = getRandomInt(2);
p.clear();
if (e == 0) {
// p.fill(123);
// p.rect(x,y,50,50);
} else {
// p.image(img, 0, 0);
}
};
};
let myp5 = new p5(s, document.getElementById('canvas-container'));
var visualizer = initWinamp("_Aderrasi - Wanderer in Curved Space - mash0000 - faclempt kibitzing meshuggana schmaltz (Geiss color mix)");
render_loop = function () {
visualizer.render();
}
Tone.Master.volume.value = -30;
mem.master = new Tone.Channel({channelCount: 2, volume: -10}).chain(Tone.Destination);
// NoiseSynth();
Stab();
Sample("k", 0, 3000, 3);
Sample("h", 1, 7000, -15);
Sample("sn",2, 6000, -15);
Sample("c", 3, 620, 2);
Sample("l", 4, 420, -15);
handlers["1"] = function (val) {
if (val > 0.5) {
mem["start_snare"] = true;
} else {
mem["start_snare"] = false;
}
mem["stab_filter"].Q.value = Math.round(val * 5);
}
dials[1]["cell"].onChange(function (e) {
var val = parseFloat(e["data"].value);
handlers["1"](val);
})
}
function tweak () {
mem.k1 = knob({ramp : [0.525, 0.8, 0.4, 1, 0.25, 0.75, 1, 0.25, 0.1], "number": dials[2]["cell"] });
always = function () {
mem["stab_filter"].frequency.value = mem.k1.move() * 10000;
}
}
if (bars <= 3 ) {
transition = once;
} else {
transition = tweak;
}
if (isHit) {
if (track_no == 1) {
if (bars > 0 ) {
p(0);
}
}
if (track_no == 2) {
if (bars > 8 ) {
p(1);
}
}
if (track_no == 3) {
if (bars > 4 ) {
// s();
}
if (bars == 6) {
transition();
}
}
if (track_no == 4) {
if (mem["start_snare"]) {
p(2);
}
}
if (track_no == 5) {
if (bars > 12) {
p(3)
}
}
if (track_no == 6) {
// p(4, "C3")
}
}
if (count == (mem["k_last"] + 3)) {
pn("h");
}
Demo Song 1 // Techno¶
Postprocessed using Reaper with EQ and Surround effects to add some sparkle. Video is recorded with the help of Blackhole and Kap and rendered by Reaper.
Samples taken from Deep Techno and Dub Techno collections from splice. Sadly I can’t distribute the song itself as I would also have to distribute the samples with it.
Code for the Demo Song. The visualisation was disabled in the Demo as it was causing a huge lag while recording on both windows and mac.
Note: Could could be outdated due to latest changes to the API.
volume_guard1 = guard([-20,15])
volume_guard2 = guard([-20,15])
Tone.Master.volume.value = volume_guard1(Math.round(dials[0]["cell"]() * 30) -20);
//mem["stab_channel"].volume.value = volume_guard2(-2);
//mem["stab_filter"].frequency.value = Math.round(dials[1]["cell"]() * 10000);
//mem["l_filter"].frequency.value = Math.round(dials[1]["cell"]() * 1000);
scene1 = [
cellx("p 1000 1000 1000 1000"),
cellx("p x000 0000 0000 0000"),
cellx("p 0x00 0000 0x00 x000"),
cellx("p 0000 x000 0000 x000"),
cellx("p x000 00x0 0x00 x0xx"),
cellx("p 00x0 00x0 0000 00x0"),
cellx("p 00x0 00x0 00x0 00x0"),
]
patterns = scene1
always();
function Sample(name, no, filter, volume) {
name = name
filter = filter || 10000
volume = volume || 0
mem[name + "_filter"] = new Tone.Filter(filter, 'lowpass', -96);
mem[name + "_channel"] = new Tone.Channel({channelCount: 2, volume: volume}).chain(mem[name + "_filter"], mem.master)
samples[no].connect(mem[name + "_channel"]);
}
function p(s, note, len) {
note = note || "C3"
len = len || "16n"
samples[s].triggerAttackRelease(note, len, undefined);
}
function once () {
var vis = initWinamp("Cope - The Neverending Explosion of Red Liquid Fire");
render_loop = function () {
vis.render();
}
Tone.Master.volume.value = -30
mem.master = new Tone.Channel({channelCount: 2, volume: -10}).chain(Tone.Destination);
Sample("k", 0, 20000, 5);
Sample("h", 1, 20000, -5);
Sample("sn",2, 6000, -3);
Sample("c", 3, 1200, -10);
Sample("stab", 4, 420, 10);
Sample("l", 5, 20000, 8);
Sample("o", 6, 20000, -8);
handlers["1"] = function (val) {
if (val > 0.5) {
mem["start_snare"] = true;
}
}
dials[1]["cell"].onChange(function (e) {
var val = parseFloat(e["data"].value);
handlers["1"](val);
})
}
function tweak () {
mem.k1 = knob({initial : 0.42, ramp : [0.42, 0.525, 0.8, 0.4, 1, 0.65, 0.75, 1, 0.8], "number": dials[2]["cell"] });
always = function () {
mem["stab_filter"].frequency.value = mem.k1.move() * 1000;
}
}
function sampleTest () {
Sample("l", 4, 10000, -5);
}
if (bars <= 3 ) {
transition = once;
} else {
transition = tweak;
}
if (isHit) {
if (track_no == 1) {
if (bars > 4 ) {
p(0);
}
}
if (track_no == 2) {
if (bars > 8 ) {
p(1, "C3", "1n");
}
}
if (track_no == 3) {
if (bars > 0 ) {
p(4, "C3", "1n");
}
if (bars == 15) {
transition();
}
}
if (track_no == 4) {
if( mem["start_snare"]) {
p(2);
}
}
if (track_no == 5) {
}
if (track_no == 6) {
if (bars > 12) {
p(5, "F2", "16n")
}
}
if (track_no == 7) {
if (bars > 48) {
p(6, "C3", "48n")
}
}
}
Demo Song 2 // UK Hip Hop¶
volume_guard1 = guard([-20,15])
Tone.Master.volume.value = volume_guard1(Math.round(dials[0]["cell"]() * 30) -20);
intro = [
cellx("p x000 0000 0000 0000 "),
]
main = [
cellx("p x[2;^C2;+0.01]000 0000 x[0.1]0x[1]0 00x[1]0 "),
cellx("p 0000 x000 0000 x000 "),
cellx("p x0x0 x0x0 x0x0 x0x0 "),
cellx("p x000 0000 0000 0000 "),
cellx("p 0000 0000 0000 0000"),
cellx("p 0000 0000 0000 0000"),
]
bass = [
cellx("p x[2;^C2;+0.01]000 0000 x[0.1]0x[1]0 00x[1]0 "),
cellx("p 0000 x000 0000 x000 "),
cellx("p x0x0 x0x0 x0x0 x0x0 "),
cellx("p x000 0000 0000 0000 "),
cellx("p x[^B2]x.00 x[^B2]x.x[^B2]x. x[^B2]x.x[^B2]x.x[^B2]x.0 "),
cellx("p 00x[^D3]0 000x[^A2] 00x[^A2]x[^A2] 0x[^A2]0x[^A2] "),
]
end = [
cellx("p x[2;^C2;+0.01]000 0000 x[0.1]0x[1]0 00x[1]0 "),
cellx("p 0000 x000 0000 x000 "),
cellx("p x10x0 x0x0 x0x0 x0x0 "),
]
solo = [
cellx("p 0000 0000 0000 0000"),
cellx("p 0000 0000 0000 0000"),
cellx("p 0000 0000 0000 0000"),
]
fin = solo;
endgame = [
cellx("p x[2;^C2;+0.01]000 0000 x[0.1]0x[1]0 00x[1]0 "),
cellx("p 0000 x000 0000 x000 "),
cellx("p x0x0 x0x0 x0x0 x0x0 "),
cellx("p x000 0000 0000 0000 "),
cellx("p x[^B2]x.00 x[^B2]x.x[^B2]x. x[^B2]x.x[^B2]x.x[^B2]x.0 "),
]
empty = cellx("p 0000 0000 0000 0000")
function once () {
const setup = ( instance ) => {
var intro = []
var main = []
var bass = main
var end = []
var solo = end
var fin = end
var count = 0;
var old_scene = "";
let x = 100;
let y = 100;
instance.setup = function() {
var x = instance.createCanvas(1280, 410);
x.canvas.style.position = "absolute";
instance.frameRate(0.25);
intro.push(instance.loadImage('/closed/intro/1.png'));
main.push(instance.loadImage('/closed/main/1.png'));
main.push(instance.loadImage('/closed/main/2.png'));
main.push(instance.loadImage('/closed/main/3.png'));
main.push(instance.loadImage('/closed/main/4.png'));
main.push(instance.loadImage('/closed/main/5.png'));
main.push(instance.loadImage('/closed/main/6.png'));
main.push(instance.loadImage('/closed/main/7.png'));
end.push(instance.loadImage('/closed/end/1.png'));
};
instance.draw = function() {
if (old_scene != current_scene) {
old_scene = current_scene;
count = 0;
}
instance.clear();
var c = eval(`${current_scene}`)
instance.image(c[count], 0, 0);
count += 1
count = count % c.length;
};
};
let myp5 = new p5(setup, document.getElementById('canvas-container'));
Tone.Master.volume.value = -30
dials[2]["cell"](1)
mem["master_filter"] = new Tone.Filter(10000, 'lowpass', -96);
mem["master_stereo"] = new Tone.StereoWidener({width: 0.50});
dials[2]["cell"].onChange(function (e) {
var val = parseFloat(e["data"].value);
var cutoff_guard = guard([10, 20000]);
mem["master_filter"].frequency.value = cutoff_guard(val * 10000);
})
dials[3]["cell"](0)
dials[3]["cell"].onChange(function (e) {
var val = parseFloat(e["data"].value);
var volume_guard1 = guard([0,1])
audio.master.in.gain.value = volume_guard1(val / 10);
})
mem.master = new Tone.Channel({channelCount: 2, volume: -10}).chain( mem["master_filter"], mem["master_stereo"], Tone.Destination);
Sample("k", 0, null, -5);
Sample("sn1",1, null, 0);
Sample("h",2, 5000, -35);
Sample("m",3, null, -15);
Sample("f",4, null, -15);
name = "s"
filter = 2000
volume = -15
mem[name + "_filter"] = new Tone.Filter(filter, 'lowpass', -96);
mem[name + "_delay"] = new Tone.FeedbackDelay("4n", 0.4);
mem[name + "_channel"] = new Tone.Channel({channelCount: 2, volume: volume, pan: -0.25}).chain(mem[name + "_filter"], mem[name + "_delay"], mem.master)
samples[5].connect(mem[name + "_channel"]);
hit_map[name] = 5;
Sample("manager",6, null, -5);
Sample("order",7, null, -15);
Sample("cr",8, null, -15);
Sample("piano",9, 400, -5);
Sample("sitar",10, 5000, -15);
const audio = Audio();
const three = ThreeOhUnit(audio, "sawtooth", {
"cutoff": 78,
"resonance": 15,
"envMod": 4000,
"decay": 0.5
}
)
audio.master.in.gain.value = 0;
mem["three"] = three
// cutoff [30, 700], 400
// resonance: [1, 30], 15
// envMod: [0, 8000], 4000
// decay: [0.1, 0.9], 0.5
dials[1]["cell"](0.07825)
dials[1]["cell"].onChange(function (e) {
var val = parseFloat(e["data"].value);
cutoff_guard = guard([30, 700]);
three.params.cutoff.value = cutoff_guard(val * 1000);
})
mem.k1 = knob({ramp : [0.525, 0.8, 0.4, 0.25, 0.75, 0.25], step: 0.005, "number": dials[1]["cell"] });
always = function () {
let cutoff_guard = guard([30, 900]);
three.params.cutoff.value = cutoff_guard(mem.k1.move() * 1000);
}
}
function tweak() {
var snare_count = cellx(0);
snare_count.onChange(function (e) {
var val = parseInt(e["data"].value);
if (val == 22) {
current_scene = "fin";
p1(8, null, null, 12.25);
}
})
Mousetrap.bind(['f2'], function(e) {
snare_count(snare_count() + 1)
if (snare_count() <= 20) {
setTimeout(function () {
p(0, null, null, "24n", null );
setTimeout(function () {
p(8, null, null, "2n" );
p(10, 0.5, "A2", 1);
}, delta * 2)
}, delta * 1.5)
}
return false;
})
}
transition = once;
if (bars <= 2) {
current_scene = "intro";
} else {
// mem["three"].step("off");
if (bars == 3) {
eval_guard ? cue("Start intro") : "";
current_scene = "intro";
}
if (bars == 5) {
eval_guard ? cue("Start drums") : "";
current_scene = "main";
}
if (bars == 12) {
eval_guard ? cue("Add bass"): "";
current_scene = "bass";
}
if (bars == 32) {
eval_guard ?cue("Reverse "): "";
current_scene = "bass";
}
if (bars == 42) {
eval_guard ? cue("End "): ""
current_scene = "end";
}
if (bars == 48) {
eval_guard ? cue("Solo "): ""
current_scene = "solo";
}
transition = tweak;
}
patterns = eval(`${current_scene}`);
always();
if (track_no == 1) {
if (count == 0) {
cue("Sample")
p1(7, null, null, 12.25);
}
}
if (isHit) {
if (current_scene == "intro") {
if (track_no == 1) {
p(3, null, null, "1n");
}
} else if (current_scene == "fin") {
} else if (current_scene == "solo") {
} else if (current_scene == "end") {
if (track_no == 1) {
p(0, meta[tick]["volume"], meta[tick]["pitch"], "24n", meta[tick]["delay"] );
}
if (track_no == 2) {
p(1);
}
if (track_no == 3) {
p(2)
}
} else if (current_scene == "main") {
if (track_no == 1) {
p(0, meta[tick]["volume"], meta[tick]["pitch"], "24n", meta[tick]["delay"] );
}
if (track_no == 2) {
p(1);
}
if (track_no == 3) {
p(2)
}
if (track_no == 4) {
p(3, null, null, "1n");
}
if (track_no == 5) {
if (meta[tick]["pitch"]) {
// mem["three"].step({"glide": true, "accent": false, "note": meta[tick]["pitch"]});
} else {
// mem["three"].step("off");
}
}
if (track_no == 6) {
p(5, null, meta[tick]["pitch"], "1n");
}
} else if (current_scene == "bass") {
if (track_no == 1) {
p(0, meta[tick]["volume"], meta[tick]["pitch"], "24n", meta[tick]["delay"] );
}
if (track_no == 2) {
p(1);
}
if (track_no == 3) {
p(2)
}
if (track_no == 4) {
p(3, null, null, "1n");
}
if (track_no == 5) {
if (meta[tick]["pitch"]) {
mem["three"].step({"glide": true, "accent": false, "note": meta[tick]["pitch"]});
} else {
mem["three"].step("off");
}
}
if (track_no == 6) {
// p(5, 0.15, meta[tick]["pitch"], 1);
// p(9, 1, meta[tick]["pitch"], 1);
}
}
}