⌈⌋ branch:  Bitrhythm


Artifact Content

Artifact cd65abe2e76ab39d4059b5491f7db8920720f73b143d457cd51c7dfab7cb6c90:

  • File source/demo.md — part of check-in [a03a381331] at 2021-11-16 10:11:35 on branch trunk — Cleanup of Docs (user: dev size: 22715)

Getting Started

Tutorial

This illustrates the core concepts of bitrhythm.

  1. Samples (Tone.Sampler)
  2. Dials (use cellx internally)
  3. 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.

  1. Click on Add Sample URL to add the following URLs

    • /Kick01.wav
    • /Snare19.wav
    • /Closedhat01.wav
    • /MiscSynthStab04.wav
  2. Click on Add Dial

  3. 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);

        }
    } 


}