Following my ongoing work on a theory of rhythms and a corresponding physical instrument using lasers, here is a version of the same idea implemented into an Arduino: a generative sequencer. The idea is to generate rhythms, and perhaps melodies, from one rhythm seed, then use mutated copies of it to create something more interesting, all this in realtime using knobs and buttons.
This is not ‘yet another step sequencer’, but really a generative music device, that builds a real time MIDI loop based on an underlying theory described in a previous post.
This is work in progress, and is shown ‘as is’ for the sake of getting feedback.
Approach
The approach is to generate a « seed » of rhythm that is then copied a few times into « instances », each instance being distorted in its own way. The controls for the human player (or programmer) are therefore all about adjusting the deformations (transforms actually) to apply to each instance, and of course defining the seed.
Eventually each instance is wired to a MIDI note, channel etc. to remote control a synthesizer or a drum machine or any MIDI setup, to generate actual sounds.
Clik here to view.

Principle: one seed, many transformed instances
The maths
Given a seed of rhythm of lengh length composed of pulses, each of duration m, then:
for each instance k of the seed, each pulse i, pulse(k, i) happen at time t = phase(k) + i . m . stretch(k), t < length where phase(k) and stretch(k) are the phase and stretch settings for the instance k.
Realtime control of the sequencer is therefore all about setting the phase and stretch values for each instance, once the pulse number and the pulse duration of the seed have been globally set.
Inversely, for a given instance k, at time t, we have a pulse if:
there exists an i, such as t = phase(k) + i * m * stretch(k) i.e. i = (t - phase(k))/(m * stretch(k))
In other words, if
(t - phase(k))/(m * stretch(k)) is integer (i.e. numerator % denominator == 0)
Thinking in MIDI ticks (24 per quarters), in 4/4, for 1 bar, length = 4 * 24, phase is in [-24 .. 24] and stretch is in [4:1 .. 1:4] and m in [12 .. 48] by steps of 12 ticks.
The implementation is the very simple: for each instance of the seed, and given its phase and stretch settings, whenever the modulo condition above is true, then we emit its MIDI note, with the set velocity on the set MIDI channel.
As usual, the pace of the loop is primarily based on the value from the Tempo potentiometer.
Clik here to view.

Overview of the circuit, with the switches and the knobs
Adding some swing
Clik here to view.

8th note swing
The EMU SP-1200, early Linn machines, Roland 909, Akai MPC and many other machines are famous for their swing quantize, a feature that delays every other note by a certain amount in order to create a groovy feeling (see Swung Note).
Different machines express the swing factor in different ways, we will stick to the MPC format, expressed in percent from 50% (no swing, play straight) to 75% (hard shuffle).
For a swing for 8th notes, this swing factor represents the ratio of the period of the first 8th note over the period of the second 8th note, in percent.
In the Arduino code of our generative sequencer, we chose to do a fixed swing for 8th notes only.
A big constraint is that we are limited to a resolution of 24 ticks per quarter note, which is not a lot! By contrast, MPC have a 96 ppq resolution. Because a hard swing of 70% translates into hardly 2 MIDI ticks at 24 ppq, doing the swing on top of the ticks will not be accurate at all!
The only solution is to vary the internal tempo before and after each 8th note. The drawback (or advantage) is that the MIDI clock being sent will also move, reflecting the same swing. Since the Swing knob value is actually between 0 and 25 (to be read from50% to 75%), the tempo before (t-) and the tempo after (t+), are given by:
t+/- = (50 +/- swing) * t / 50 where t is the base loop period without swing
Video Demo
Here is a video demo. There are only 3 instances, selected by the switches 1, 2 and 3; the first switch selects the GLOBAL settings: step duration (quarter, 8th etc.), swing factor, tempo. Each instance can be set its Phase, Stretch, MIDI note, velocity and MIDI channel. Here I have preset the MIDI channels, instance 1 on channel 1 (the microKorg) and instances 2 and 3 on channel 2 (the MPC with a drum kit).
The goal is to build a simple beat by only adjusting the parameters.
(Arduino) Interactice Generative Sequencer from cyrille martraire on Vimeo.
The code
You can download the Arduino project here: generativesequencer1; below is the same source code for convenience. The code includes the knob pagination described in a previous post.
Please note that some parts of the code are not used any more, such as the big constant arrays, and some comments are not up to date (e-g no prime number anymore).
All analog inputs are wired to simple knobs. Digital inputs 8, 9, 10 , 11 are the four buttons used to switch pages. Digital output 12 is the activity LED (showing when the knob is active within the knob pagination). MIDI out is on the Tx pin.
/*
* Generative rhythm sequencer, more details at: http://cyrille.martraire.com
*
* Knob mapping according to a grid 2^n . prime^m, against the MIDI 24 ticks/quarter.
*
* Knob pagination to multiplex the knobs several times with LED to show activity.
*
* Memory-less event handling thanks to maths relationships.
*
* MIDI note on output on every 16 channel and MIDI clock according to tempo.
*
*
* Creative Commons License Cyrille Martraire cyrille.martraire.com
*/
// DEBUG int debug = false;
//---------- USER INPUT AND PAGINATION ----------- #define PAGE_NB 4 #define KNOB_NB 6 #define FIRST_PAGE_BUTTON 8
#define PROTECTED -1 #define ACTIVE 1
#define SYNC_LED 12
// the permanent storage of every value for every page, used by the actual music code int pageValues[PAGE_NB][KNOB_NB];
// last read knob values int knobsValues[KNOB_NB]; // knobs state (protected, enable...) int knobsStates[KNOB_NB]; // current (temp) value just read int value = 0; // the current page id of values being edited int currentPage = 0; // signals the page change boolean pageChange = false; //temp variable to detect when the knob's value matches the stored value boolean inSync = false;
// ---------- KNOBS CALIBRATION AND MAPPING --------- // rhythmic scale, to select globally int scale2[] = {1, 2, 3, 6, 6, 12, 12, 24, 24, 48, 48, 96, 192, 384, 768}; int scale3[] = {1, 2, 3, 6, 9, 12, 18, 24, 36, 48, 72, 96, 144, 384, 768}; int scale5[] = {1, 2, 3, 6, 12, 15, 24, 24, 30, 48, 60, 96, 120, 384, 768}; int scale7[] = {1, 2, 3, 6, 12, 21, 24, 24, 42, 48, 84, 96, 168, 384, 768}; int scale9[] = {1, 2, 3, 6, 12, 12, 24, 24, 27, 48, 54, 96, 108, 384, 768}; int scale11[] = {1, 2, 3, 6, 12, 12, 24, 24, 33, 48, 66, 96, 132, 384, 768}; //int scale13[] = {1, 2, 3, 6, 12, 24, 12, 24, 39, 48, 78, 96, 156, 384, 768};
int maxValue = 890; int scaleLen = 15; int *scale = scale3; int step = 60; int center = 30; int coeff = 10;
//---------- GENERATIVE MUSIC CODE --------- unsigned int cursor = 0; int bars = 1; int length = bars * 4 * 24;
// INPUTS int PHASE = 0; int STRETCH = 1; //int DIRECTION = 2; int NOTE = 2; int DURATION = 3; int VELOCITY = 4; int CHANNEL = 5;
// GLOBAL KNOBS (seed and global settings) int seedDuration = 24; int seedTimes = 8; int instanceNb = 4; int swing = 0;//0..25% (on top of 50%) // int loopPeriod = 125/6;//120BPM int actualPeriod = loopPeriod;
//instance i int phase = 0; int stretch = 1; int note = 48; int duration = 24; int velocity = 100; int channel = 1;
void setup(){ if(debug){ Serial.begin(19200); //debug } else { Serial.begin(31250); } pinMode(13, OUTPUT); setupKnobMapping(); setupPagination(); }
void setupKnobMapping(){ step = maxValue / scaleLen; if (step * scaleLen < maxValue) { step++; } center = step / 2; // for phase only coeff = step / 8; // +/-3 ticks, for phase only } void loop () { midiClock(); //TODO partition inputs reading every other cycle if required by CPU load poolInputWithPagination(); poolGlobalSettings(); // parameters for each instance (pages 1 to 3) for(int index = 1; index < instanceNb; index++){ processSeeInstance(pageValues[index]); } cursor++; cursor = cursor % length; delay(actualPeriod); }
void poolGlobalSettings(){ // global parameters seedDuration = mapC(pageValues[0][0], maxValue, 1, 4) * 12; seedTimes = mapC(pageValues[0][1], maxValue, 1, 16); instanceNb = 4;//mapC(pageValues[0][2], maxValue, 1, PAGE_NB); // = mapC(pageValues[0][3], maxValue, 1, PAGE_NB); swing = mapC(pageValues[0][4], maxValue, 0, 25); loopPeriod = mapC(pageValues[0][5], maxValue, 63, 2);// 12.5 ms - 62.5 if (cursor % 24 <= 12){ actualPeriod = (50 + swing) * loopPeriod / 50; } else { actualPeriod = (50 - swing) * loopPeriod / 50; } //TODO prime number selection and scale switch }
// custom map function, with min value always 0, and out max cannot be exceeded long mapC(long x, long in_max, long out_min, long out_max) { if (x > in_max) { return out_max; } return x * (out_max - out_min) / in_max + out_min; }
void processSeeInstance(int * params){ phase = mapC(params[PHASE], maxValue, 0, 24); stretch = mapC(params[STRETCH], maxValue, 0, 4); stretch = pow(2, stretch);// 4:1 to 1:4, in fourth note = mapC(params[NOTE], maxValue, 36, 48); //duration = mapC(params[DURATION], maxValue, 6, 96); velocity = mapC(params[VELOCITY], maxValue, 0, 127); channel = mapC(params[CHANNEL], maxValue, 0, 4); if(isPulse(phase, stretch)) { noteOn(channel, note, velocity); } }
// for each instance, and for the given cursor, is there a pulse? boolean isPulse(byte phase, byte stretch){ int num = cursor - phase; int denum = seedDuration * stretch / 4; return num % denum == 0; }
// Sends a MIDI tick (expected to be 24 ticks per quarter) void midiClock(){ Serial.print(0xF8, BYTE); }
// plays a MIDI note for one MIDI channel. Does not check that // channel is less than 15, or that data values are less than 127: void noteOn(char channel, char noteNb, char velo) { midiOut(0x90 | channel, noteNb, velo); }
// plays a MIDI message Status, Data1, Data2, no check void midiOut(char cmd, char data1, char data2) { Serial.print(cmd, BYTE); Serial.print(data1, BYTE); Serial.print(data2, BYTE); }
//********************************************
void setupPagination(){ pinMode(SYNC_LED, OUTPUT); for(int i=0; i < KNOB_NB; i++){ knobsValues[i] = analogRead(i); knobsStates[i] = ACTIVE; } }
// read knobs and digital switches and handle pagination void poolInputWithPagination(){ // read page selection buttons for(int i = FIRST_PAGE_BUTTON;i < FIRST_PAGE_BUTTON + PAGE_NB; i++){ value = digitalRead(i); if(value == LOW){ pageChange = true; currentPage = i - FIRST_PAGE_BUTTON; } } // if page has changed then protect knobs (unfrequent) if(pageChange){ pageChange = false; digitalWrite(SYNC_LED, LOW); for(int i=0; i < KNOB_NB; i++){ knobsStates[i] = PROTECTED; } } // read knobs values, show sync with the LED, enable knob when it matches the stored value for(int i = 0;i < KNOB_NB; i++){ value = analogRead(i); inSync = abs(value - pageValues[currentPage][i]) < 20; // enable knob when it matches the stored value if(inSync){ knobsStates[i] = ACTIVE; } // if knob is moving, show if it's active or not if(abs(value - knobsValues[i]) > 5){ // if knob is active, blink LED if(knobsStates[i] == ACTIVE){ digitalWrite(SYNC_LED, HIGH); } else { digitalWrite(SYNC_LED, LOW); } } knobsValues[i] = value; // if enabled then miror the real time knob value if(knobsStates[i] == ACTIVE){ pageValues[currentPage][i] = value; } } }
void printAll(){ Serial.println(""); Serial.print("page "); Serial.print(currentPage); //Serial.println(""); //printArray(knobsValues, 6); //Serial.println(""); //printArray(knobsStates, 6); for(int i = 0; i < 4; i++){ Serial.println(""); printArray(pageValues[i], 6); } }
void printArray(int *array, int len){ for(int i = 0;i< len;i++){ Serial.print(" "); Serial.print(array[i]); } }