/**
*
Sequences Overlaid
*by Benito
*Pressing (most) keys starts a short sequence.
*- Capital letters are an octave above.
*- Mouse X-axis duration of that sequence.
*Playing several sequences at once brings in the bass.
*Pressing [enter] switches the percussion.
*Clicking/dragging plays dings:
*- Mouse Y-axis selects which ding.
*- Mouse X-axis controls pan.
*/
import net.beadsproject.beads.core.*;
import net.beadsproject.beads.data.*;
import net.beadsproject.beads.ugens.*;
import net.beadsproject.beads.events.*;
AudioContext ac;
Compressor comp;
Envelope clicker, basslev, bassfreq, bd, bdfreq;
float bdfreq1 = 70;
float bdfreq2 = 40;
float targetbassfreq = 110 * 2f/3;
ArrayList cue;
int lastding = -1;
Ding[] dings;
ArrayList[] playing;
int drumstatus = 1;
float[] thescale = {1, 9f/8, 6f/5, 5f/4, 45f/32, 7f/4, 5f/3, 7f/4};
int[] sequence = {11, 13, 14, 12, 8, 9, 12, 13, 4, 7, 5, 6, 3, 7, 3};
void setup() {
size(400, 400);
cue = new ArrayList();
dings = new Ding[12];
playing = new ArrayList[3];
playing[0] = new ArrayList();
playing[1] = new ArrayList();
playing[2] = new ArrayList();
ac = new AudioContext(256);
clicker = new Envelope(ac, 0);
bd = new Envelope(ac, 0);
bdfreq = new Envelope(ac, bdfreq2);
BiquadFilter bdfilt = new BiquadFilter(ac, 1, BiquadFilter.BP_PEAK);
bdfilt.setFrequency(bdfreq).setQ(30);
bdfilt.addInput(bd);
Gain bdgain = new Gain(ac, 1, 20);
bdgain.addInput(bdfilt);
basslev = new Envelope(ac, 0);
bassfreq = new Envelope(ac, 110 * 2f / 3);
WavePlayer basstone = new WavePlayer(ac, bassfreq, Buffer.SQUARE);
OnePoleFilter bassopf = new OnePoleFilter(ac, 150);
bassopf.addInput(basstone);
Gain bassgain1 = new Gain(ac, 1, basslev);
bassgain1.addInput(bassopf);
RandomPWM basspwm = new RandomPWM(ac, RandomPWM.RAMPED_NOISE, 4000, 10000, 1);
Function basswarble = new Function(basspwm) {
public float calculate() {
return x[0] * .25 + .75;
}
};
Gain bassgain2 = new Gain(ac, 1, basswarble);
bassgain2.addInput(bassgain1);
comp = new Compressor(ac, 2);
comp.setRatio(2);
comp.addInput(clicker);
Gain mainGain = new Gain(ac, 2, .2);
mainGain.addInput(comp);
mainGain.addInput(bassgain2);
mainGain.addInput(bd);
mainGain.addInput(bdgain);
ac.out.addInput(mainGain);
Bead manager = new Bead() {
int cuenum = 0;
int clicknum = 0;
public void messageReceived(Bead mess) {
// if drum status is right, make a click
if(drumstatus > 0) {
float velocity = .4;
switch(clicknum % 12) {
case 1:
case 3:
case 6:
case 8:
case 10:
velocity = .1;
break;
}
clicker.addSegment(velocity, .1);
clicker.addSegment(-.3 * velocity, .2);
clicker.addSegment(.2 * velocity, .23);
clicker.addSegment(-.5 * velocity, .28);
clicker.addSegment(.25 * velocity, .31);
clicker.addSegment(-.05 * velocity, .4);
clicker.addSegment(0, .7);
}
// bass drum if appropriate
if(drumstatus > 1) {
switch(clicknum % 24) {
case 8:
case 10:
case 12:
case 15:
case 19:
case 21:
bdhit();
break;
}
}
// now to deal with cued sequences
//see how many sequences are playing
int numseqplaying = playing[0].size() + playing[1].size() + playing[2].size();
// if nothing's playing, make sure next sequence is on the beat
if(numseqplaying == 0) {
cuenum = 0;
}
// if a key's been cued, put the appropraite sequence in the appropriate playlist
// we'll cap the number of consecutively playing sequences at 10?
if(clicknum % 3 == cuenum && numseqplaying <= 20) {
if(cue.size() > 0) {
int thenum = (Integer)cue.get(0);
float theduration = (Float)cue.get(1);
cue.remove(0);
cue.remove(0);
int transpose = 0;
float pan = ((float)(thenum * 17) % 43) / 21 - 1;
if(thenum >= 'A' && thenum <= 'Z') {
transpose += thescale.length;
thenum += 'a' - 'A';
}
int seqlen = (thenum % 7) + 2;
int seqstart = thenum % sequence.length;
ArrayList theseq = new ArrayList();
theseq.add(theduration);
theseq.add(pan);
for(int i = 0; i < seqlen; i++) {
theseq.add(sequence[(seqstart + i) % sequence.length] + transpose);
}
playing[cuenum].add(theseq);
cuenum = (cuenum + 2) % 3;
}
}
ArrayList playlist = playing[clicknum % 3];
for(int i = 0; i < playlist.size(); i++) {
ArrayList currseq = (ArrayList)playlist.get(i);
float thisdur = (Float)currseq.get(0);
float thispan = (Float)currseq.get(1);
int thisnote = (Integer)currseq.get(2);
playNote(thisnote, thisdur * 500, thispan);
if(currseq.size() <= 3) {
playlist.remove(i);
} else {
currseq.remove(2);
}
}
// adjust the bass loudness depending on number playing
float currlev = basslev.getValue();
float targetlev = numseqplaying - 3;
if(numseqplaying < 4) {
targetlev = 0;
} else {
targetlev = (1 + 3 * targetlev / (targetlev + 4)) * .1;
}
if(targetlev > currlev) {
basslev.clear();
basslev.addSegment(targetlev, 750);
basslev.addSegment(0, 6000);
}
//check the dings - remove done dings
for(int i = 0; i < dings.length; i++) {
Ding ding = dings[i];
if(ding != null) {
if(ding.env.getValue() == 0) {
dings[i] = null;
}
}
}
clicknum++;
}
};
Clock clock = new Clock(ac, 200);
clock.setTicksPerBeat(1);
clock.addMessageListener(manager);
ac.out.addDependent(clock);
ac.start();
}
void playNote(int note, float duration, float pan) {
float freq = 108f * pow(2, note / thescale.length) * thescale[note % thescale.length] + 2;
WavePlayer saw = new WavePlayer(ac, freq, Buffer.SINE);
Envelope env = new Envelope(ac, 0);
Gain g = new Gain(ac, 1, env);
g.addInput(saw);
Panner p = new Panner(ac, pan);
p.addInput(g);
env.addSegment(.8, 15);
env.addSegment(.4, 40);
env.addSegment(0, max(duration - 70, 30));
env.addSegment(0, 30, new KillTrigger(p));
comp.addInput(p);
}
void bdhit() {
bd.clear();
bd.addSegment(.8, 4);
bd.addSegment(0, 6);
bdfreq.clear();
bdfreq.addSegment(bdfreq1, 4);
bdfreq.addSegment(bdfreq2, 40);
}
void doDing(int dingnum) {
float pan = (float)mouseX / width * 2 - 1;
if(dings[dingnum] != null && dings[dingnum].g.isDeleted() == false) {
dings[dingnum].ding(pan);
} else {
float freq = 442f * 3f/2 * thescale[(dingnum + 5) % thescale.length] * pow(2, (dingnum + 5) / thescale.length) - 3;
dings[dingnum] = new Ding(freq, pan);
}
}
int generateDingNum() {
return (((int)((float)mouseY / height * dings.length * .999)) * 7) % dings.length;
}
class Ding {
Envelope env;
Gain g;
Panner p;
Envelope panenv;
public Ding(float freq, float pan) {
println(freq);
env = new Envelope(ac, .0001);
WavePlayer tone = new WavePlayer(ac, freq, Buffer.SINE);
g = new Gain(ac, 1, env);
g.addInput(tone);
panenv = new Envelope(ac, pan);
p = new Panner(ac, panenv);
p.addInput(g);
comp.addInput(p);
ding(pan);
}
public void ding(float pan) {
env.clear();
env.addSegment(.3, 10);
env.addSegment(0, 10000, .1, null);
env.addSegment(0, 100, new KillTrigger(p));
panenv.clear();
panenv.addSegment(pan, 50);
}
}
void draw() {
background(0);
// this is all you!
}
void keyTyped() {
if(key == ENTER) {
drumstatus = (drumstatus + 1) % 3;
} else {
if(cue.size() < 10) {
cue.add((int)key);
cue.add((float)mouseX / width);
}
bassfreq.clear();
bassfreq.addSegment(targetbassfreq, 300);
int bassnote = (key % 17 + 4);
targetbassfreq = 55 * 2f/3 * thescale[bassnote % thescale.length] * pow(2, bassnote / thescale.length);
bassfreq.addSegment(targetbassfreq, 500);
}
}
void keyPressed() {
// don't let hitting shut down the sketch
if(key == ESC) key = 0;
}
void mousePressed() {
int dingnum = generateDingNum();
doDing(dingnum);
lastding = dingnum;
}
void mouseDragged() {
int dingnum = generateDingNum();
if(dingnum != lastding) {
doDing(dingnum);
lastding = dingnum;
}
}