Tutorial 13. Sample chopping
Chopping a breakbeat |
The great power of loading a buffer of audio into memory comes in the infinite possibilities of how that buffer is played back. We don't need to play it front to back at the same speed it was recorded at.
In this example we'll be implementing a great litmus test of any sampler: the chopping of a breakbeat. We'll be loading up an amen break sample and creating a rudimentary system that will automatically chop it for us.
The sample we'll be working with is one bar of an amen break. I have converted it to a MEAP array using the online converter script.
Because we know this sample contains one measure of a drum beat, we can do some simple math to chop it into 8 equal sections, each corresponding to one eighth note in musical terms.
Looking at the amen.h file we can see that it is 44908 samples long. Dividing that by eight tells us that each eighth note will last ~5613 samples.
We can then find the starting index of each eighth note by multiplying 5613 by the numbers 0 through 7.
Now if we start playing the sample at each one of these numbers, we will effectively start the sample at a specific eighth note.
These chops are tied to the touch pads in updateTouch, which would allow us to finger-drum them.
We want to create an automatic chopper though.
We'll set up a metronome to tick once per eighth note. We can convert samples to ms by dividing the number of ms by the sampling rate. 5613/32768 gives us ~171ms per eighth note.
Now we can create an array of the order we play the samples in, and play it back like a melody!
{0, 1, 2, 3, 4, 5, 6, 7} will play through the break in the correct order, but we could type any number 0-7 in each of those indices to re-order the break.
{0, 0, 0, 0, 0, 0, 0, 0} would play the first kick drum 8 times in a row.
{7, 6, 5, 4, 3, 2, 1, 0} would play the chops in reverse order.
In updateControl we move through this array like a melody.
When we get to the end of the array, we randomize it!
We will always keep the first, second and last eight notes playing their normal chops to keep some structure, but everything else will be shuffled around.
The third, fourth, fifth and sixth eighth notes will play a random chop.
Finally, one of those eighth notes will be chosed to repeat the chop that came before it instead.
This sequence is recalculated every time it comes to an end, so we now have an infinitely changing breakbeat!
FULL CODE BELOW
/*
Automatically chops an amen break
*/
#define CONTROL_RATE 128 // Hz, powers of 2 are most reliable
#include <Meap.h> // MEAP library, includes all dependent libraries, including all Mozzi modules
Meap meap; // creates MEAP object to handle inputs and other MEAP library functions
MIDI_CREATE_INSTANCE(HardwareSerial, Serial1, MIDI); // defines MIDI in/out ports
// ---------- YOUR GLOBAL VARIABLES BELOW ----------
#include "amen.h" //44908 samples
// one eighth of the file is 5613 samples (171ms) num_samples/sr
mSample<amen_NUM_CELLS, AUDIO_RATE> sample(amen_DATA);
float default_freq;
int chop_points[8] = { 0, 5613, 11226, 16839, 22452, 28065, 33678, 39291 };
EventDelay metro;
int chop_order[8] = { 0, 1, 2, 3, 4, 5, 6, 7 };
int chop_length = 171;
int chop_index = 0;
void setup() {
Serial.begin(115200); // begins Serial communication with computer
Serial1.begin(31250, SERIAL_8N1, 43, 44); // sets up MIDI: baud rate, serial mode, rx pin, tx pin
startMozzi(CONTROL_RATE); // starts Mozzi engine with control rate defined above
meap.begin(); // sets up MEAP object
// ---------- YOUR SETUP CODE BELOW ----------
default_freq = (float)amen_SAMPLERATE / (float)amen_NUM_CELLS;
sample.setFreq(default_freq);
}
void loop() {
audioHook(); // handles Mozzi audio generation behind the scenes
}
/** Called automatically at rate specified by CONTROL_RATE macro, most of your mode should live in here
*/
void updateControl() {
meap.readInputs();
// ---------- YOUR updateControl CODE BELOW ----------
if (metro.ready()) {
metro.start(chop_length);
sample.start(chop_points[chop_order[chop_index]]);
chop_index = (chop_index + 1);
if(chop_index == 8){
chop_index = 0;
chop_order[2] = meap.irand(0, 7);
chop_order[3] = meap.irand(0, 7);
chop_order[4] = meap.irand(0, 7);
chop_order[5] = meap.irand(0, 7);
int repeat_index = meap.irand(2, 5);
chop_order[repeat_index] = chop_order[repeat_index - 1];
}
}
}
/** Called automatically at rate specified by AUDIO_RATE macro, for calculating samples sent to DAC, too much code in here can disrupt your output
*/
AudioOutput_t updateAudio() {
int64_t out_sample = sample.next();
return StereoOutput::fromNBit(8, (out_sample * meap.volume_val) >> 12, (out_sample * meap.volume_val) >> 12);
}
/**
* Runs whenever a touch pad is pressed or released
*
* int number: the number (0-7) of the pad that was pressed
* bool pressed: true indicates pad was pressed, false indicates it was released
*/
void updateTouch(int number, bool pressed) {
if (pressed) { // Any pad pressed
sample.start(chop_points[number]);
} else { // Any pad released
}
switch (number) {
case 0:
if (pressed) { // Pad 0 pressed
Serial.println("t0 pressed ");
} else { // Pad 0 released
Serial.println("t0 released");
}
break;
case 1:
if (pressed) { // Pad 1 pressed
Serial.println("t1 pressed");
} else { // Pad 1 released
Serial.println("t1 released");
}
break;
case 2:
if (pressed) { // Pad 2 pressed
Serial.println("t2 pressed");
} else { // Pad 2 released
Serial.println("t2 released");
}
break;
case 3:
if (pressed) { // Pad 3 pressed
Serial.println("t3 pressed");
} else { // Pad 3 released
Serial.println("t3 released");
}
break;
case 4:
if (pressed) { // Pad 4 pressed
Serial.println("t4 pressed");
} else { // Pad 4 released
Serial.println("t4 released");
}
break;
case 5:
if (pressed) { // Pad 5 pressed
Serial.println("t5 pressed");
} else { // Pad 5 released
Serial.println("t5 released");
}
break;
case 6:
if (pressed) { // Pad 6 pressed
Serial.println("t6 pressed");
} else { // Pad 6 released
Serial.println("t6 released");
}
break;
case 7:
if (pressed) { // Pad 7 pressed
Serial.println("t7 pressed");
} else { // Pad 7 released
Serial.println("t7 released");
}
break;
}
}
/**
* Runs whenever a DIP switch is toggled
*
* int number: the number (0-7) of the switch that was toggled
* bool up: true indicated switch was toggled up, false indicates switch was toggled
*/
void updateDip(int number, bool up) {
if (up) { // Any DIP toggled up
} else { //Any DIP toggled down
}
switch (number) {
case 0:
if (up) { // DIP 0 up
Serial.println("d0 up");
} else { // DIP 0 down
Serial.println("d0 down");
}
break;
case 1:
if (up) { // DIP 1 up
Serial.println("d1 up");
} else { // DIP 1 down
Serial.println("d1 down");
}
break;
case 2:
if (up) { // DIP 2 up
Serial.println("d2 up");
} else { // DIP 2 down
Serial.println("d2 down");
}
break;
case 3:
if (up) { // DIP 3 up
Serial.println("d3 up");
} else { // DIP 3 down
Serial.println("d3 down");
}
break;
case 4:
if (up) { // DIP 4 up
Serial.println("d4 up");
} else { // DIP 4 down
Serial.println("d4 down");
}
break;
case 5:
if (up) { // DIP 5 up
Serial.println("d5 up");
} else { // DIP 5 down
Serial.println("d5 down");
}
break;
case 6:
if (up) { // DIP 6 up
Serial.println("d6 up");
} else { // DIP 6 down
Serial.println("d6 down");
}
break;
case 7:
if (up) { // DIP 7 up
Serial.println("d7 up");
} else { // DIP 7 down
Serial.println("d7 down");
}
break;
}
}