Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save zeffii/7521180 to your computer and use it in GitHub Desktop.
Save zeffii/7521180 to your computer and use it in GitHub Desktop.
<<< "assignment 3 - Multipart 32 Step Sequencer" >>>;
/*
Assignment 3: Make Techno
Please save this file to the directory that contains the folder called audio,
and play it from that location. If you can't get it to work, please ask for help
in the forums.
*/
// think of this as the master fader
Gain master => dac;
0.7 => master.gain;
// initialize an oscillator/voice
// this whitenoise will appear on the kickdrum during the first 32 kicks
Noise noise_hat => Pan2 noise_location => master;
0.0 => noise_hat.gain;
// subbass
SinOsc sin_bass => master;
0.3 => sin_bass.gain;
42 => sin_bass.freq;
// these arrays will eventually hold sample data for several different sounds
SndBuf samples[4];
SndBuf2 pads[2]; // these are stereo
SndBuf clicks[5];
// me.dir() returns a string representation of the path in which the current
// chuck file is located. Adding me.dir() to the substring "/audio"/ is
// is called 'concatenation'
me.dir() + "/audio/" => string path;
// the string array of sample names to load.
[ "kick_01.wav",
"snare_03.wav",
"snare_01.wav",
"hihat_03.wav"
] @=> string sample_names[];
// the string array of sample names to load.
[ "click_01.wav",
"click_02.wav",
"click_03.wav",
"click_04.wav",
"click_05.wav"
] @=> string click_names[];
// i'll use this as a pad.
path + "stereo_fx_03.wav" => string pad_name;
// stores how many waveforms im going to store the samples array
sample_names.cap() => int num_waveforms;
// this single loop will do a few things, namely:
// - .read each sample into samples[]
// - set their .pos to the end of the sample
// - chuck all samples[] to master
for (0 =>int i; i<num_waveforms; i++){
// concatenate the audio directory path name with the sample name, and
// .read from it.
path + sample_names[i] => samples[i].read;
// use the sample length (ie: .samples()) to set the playhead to the end
// this avoids triggering every sound in an audible way on the first beat
samples[i].samples() => samples[i].pos;
samples[i] => master;
}
// this loads the pad sample twice, so i can easily play two versions of it
// side by side. Unlike the previous loop, these samples will first be chucked
// to a Pan2 device. So let's make a Pan2 array first
Pan2 pad_pans[2];
for(0 => int i; i< pads.cap(); i++){
pad_name => pads[i].read;
pads[i].samples() => pads[i].pos;
pads[i] => pad_pans[i] => dac;
}
// this loads the percussion (second layer samples) - it's identical
// in many ways to the for loop that fills the samples[] array, forgive me
// if i don't comment it heavily
click_names.cap() => int num_clickwaveforms;
Pan2 click_pans[num_clickwaveforms];
// fill clicks with .samples(), set playhead (pos), add pan then send to dac
for (0 =>int i; i<num_clickwaveforms; i++){
path + click_names[i] => clicks[i].read;
clicks[i].samples() => clicks[i].pos;
clicks[i] => click_pans[i] => dac;
}
// setup the multipart step sequencer, each 1 means trigger.
[
[1,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0, 1,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0], // kick
[0,0,0,0,1,0,0,0,0,0,0,0,1,0,0,0, 0,0,0,0,1,0,0,0,0,0,0,0,1,0,0,1], // snare
[0,0,1,0,0,0,1,0,0,0,1,0,0,0,1,0, 0,0,1,0,0,1,1,0,0,0,1,0,0,0,1,0], // hat1
[1,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0, 1,0,0,0,1,0,0,0,1,0,0,1,1,0,0,0] // hat2
] @=> int multi_parts[][];
multi_parts.cap() => int parts;
multi_parts[0].cap() => int steps;
<<< "parts:", parts, " ", "steps: ", steps >>>;
// all dorian notes
// these notes will be played side by side for 8 kicks each.
//[c4, g4], [d4, g4], [d4, a4], [e4, a4]
[[60, 67], [62, 67], [62, 69], [64, 69]
] @=> int dorians[][];
[
[0,0,1,0, 0,0,1,0, 0,0,1,0, 0,0,1,0, 0,0,1,0, 0,0,1,0, 0,0,1,0, 0,0,1,0],
[1,1,0,1, 1,1,0,1, 1,1,0,0, 1,1,0,0, 1,1,0,1, 1,1,0,0, 1,1,0,1, 1,1,0,1]
] @=> int percs[][];
// I don't like straight techno, shuffle is used to get a swing effect,
// this simulates the swing parameter of drum machines
int shuffle;
float tick_duration;
121 => float long_tick;
long_tick * .74 => float short_tick;
// let's start with some faux vinyl release.
3 => int into_sample;
samples[into_sample].samples()-13000 => samples[into_sample].pos;
-.4 => samples[into_sample].rate; // reverse
2.15::second => now;
0.0 => sin_bass.gain;
1.0 => float bend; // used for the ride [hihat] pitchbend
0 => int chords; // placeholder counter for which two notes to play together.
0 => int beat;
0 => int tick;
// this will play the above 32 steps, 8 times.
while(beat<8){
// initialize the two pad notes
if (beat % 1 == 0){
<<< "print that thing" >>>;
0.9 => pad_pans[0].pan;
-0.9 => pad_pans[1].pan;
0 => pads[0].pos;
10 => pads[1].pos; // slight offset
0.6 => pads[0].gain;
0.6 => pads[1].gain;
// this is where functions will be very welcome in the following weeks
// yes, at the moment this is a lot of code to do trivial stuff, but you
// must understand how this works before knowing why functions are so
// amazing.
Std.mtof(dorians[chords][0]) / 440.0 => float note_rate1;
Std.mtof(dorians[chords][1]) / 440.0 => float note_rate2;
1.4 => float transpose;
note_rate1 * transpose => pads[0].rate;
note_rate2 * transpose => pads[1].rate;
// increment chord counter, if we are below 3
if (chords <3) 1+=> chords;
// this must mean we are at 3 and must reset for the next iteration,
// this avoids triggering a note combo that doesn't exist
else 0 => chords;
}
for(0 => int i; i<steps; i++){
// every second step (i) in steps will set shuffle to 0,
// staring from step 0, this will output 0101010101010101....
i % 2 => shuffle;
for(0 => int j; j<parts; j++){
multi_parts[j][i] => int trigger;
if (trigger==1){
// cool! trigger is 1, set position to 0 (or some offset)
0 => samples[j].pos;
// kick
if (j==0){
// this doesn't start the sample from 0, but uses an offset
// to drop some portion of the attack
2446 => samples[j].pos;
1.74 => samples[j].rate;
2.2 => samples[j].gain;
}
// snare
if (j==1){
Math.random2f(.32,.3) => samples[j].gain;
150 => samples[j].pos;
.78 => samples[j].rate;
}
// 3rd sample is hihat (index 2)
if (j==2){
Math.random2f(.1,.2)+0.2 => samples[j].gain;
4.3 => samples[j].rate;
1200 => samples[j].pos;
if (beat >3) {
0.0 => samples[j].gain;
}
}
// 4th sample is also a long hihat (index 3)
if (j==3){
(Math.random2f(2.2, 2.4) - 3.3) => samples[j].gain;
Math.random2f(3.22, 3.24) - 1.1 => samples[j].rate;
13900 => samples[j].pos;
if (beat >2) {
// transition, pitch up each time
2.13 * bend => samples[j].rate;
1.02 *=> bend;
12900 => samples[j].pos;
}
if (beat >3) {
2.3 => samples[j].rate;
2800 => samples[j].pos;
0.3 => samples[j].gain;
}
else{
// piggyback this offbeat trigger to make noise OSC audible
noise_hat.gain(0.011);
}
}
}
}
// percussion layer 2
// similar to the previous for loop, so i'll ease of on the comments
for(0 => int j; j<2; j++){
// don't do anything inside this block, unless
// it is from the 4th beat onward
if (beat < 4) break;
percs[j][i] => int trigger;
if (trigger==1){
if (j==0){
0.2 => clicks[2].gain;
360 => clicks[2].pos;
.7 => clicks[2].rate;
}
if (j==1){
// pick random sample from 0,1,3,4
[0,1,3,4] @=> int choices[];
Math.random2(0, choices.cap()) => int choice;
0.4 => clicks[choice].gain;
Math.random2f(-.3, .3) => click_pans[choice].pan;
Math.random2(60, 190) => clicks[choice].pos;
Math.random2f(.2, .7) => clicks[choice].rate;
}
}
}
if (shuffle==0) long_tick => tick_duration;
else short_tick => tick_duration;
tick_duration::ms => now;
tick++;
noise_hat.gain(0.0); // this is the same as writing 0.0 => ..
}
beat++;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment