149 lines
5.1 KiB
C++
149 lines
5.1 KiB
C++
// SPDX-FileCopyrightText: 2022 Tod Kurt for Adafruit Industries
|
|
//
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
/**
|
|
* Arpy - a class to do arpeggios for you
|
|
* 12 Jan 2022 - Tod Kurt @todbot
|
|
* See https://github.com/todbot/mozzi_experiments/ for examples
|
|
*/
|
|
class Arpy
|
|
{
|
|
public:
|
|
|
|
Arpy(): enabled(false), arp_id(0), arp_pos(0), root_note(0), note_played(0)
|
|
{
|
|
tr_steps = 1; tr_dist = 12; tr_pos = 0; // FIXME make these defaults
|
|
}
|
|
|
|
// turn Arpy on or off
|
|
void on() { enabled = true; }
|
|
void off() {
|
|
enabled = false;
|
|
if( noteOffHandler) { noteOffHandler( note_played ); }
|
|
}
|
|
// let you know what Arpy thinks about doing its job
|
|
bool isOn() { return enabled; }
|
|
|
|
// pick which arpeggio to play
|
|
void setArpId(uint8_t arp_id) { arp_id = arp_id % arp_count; }
|
|
|
|
// which arpeggio is currently set
|
|
uint8_t getArpId() { return arp_id; }
|
|
|
|
// go to next arpeggio, a little convenience function
|
|
void nextArpId() { arp_id = (arp_id+1) % arp_count; }
|
|
|
|
// return acount of how many different arpeggios Arpy knows
|
|
uint8_t getArpCount() { return arp_count; }
|
|
|
|
// set the root note of the arpeggio
|
|
void setRootNote( uint8_t note ) { root_note = note; }
|
|
uint8_t getRootNote() { return root_note; }
|
|
|
|
// set the octave offset for root_note (root_note += (octave_offset*12)
|
|
void setOctaveOffset( uint8_t offset ) { octave_offset = offset; }
|
|
uint8_t getOctaveOffset() { return octave_offset; }
|
|
|
|
// set BPM of arpeggio
|
|
void setBPM( float bpm ) {
|
|
bpm = bpm;
|
|
per_beat_millis = 1000 * 60 / bpm;
|
|
note_duration = gate_percentage * per_beat_millis;
|
|
}
|
|
|
|
// set note duration as a percentage of BPM
|
|
void setGateTime( float percentage ) {
|
|
if( percentage < 0 || percentage > 1 ) { return; }
|
|
gate_percentage = percentage;
|
|
}
|
|
float getGateTime() { return gate_percentage; } // percentage 0.0 - 1.0
|
|
|
|
// set the function to call when note on happens
|
|
void setNoteOnHandler(void (*anoteOnHandler)(uint8_t)) {
|
|
noteOnHandler = anoteOnHandler;
|
|
}
|
|
|
|
// set the function to call when note off happens
|
|
void setNoteOffHandler(void (*anoteOffHandler)(uint8_t)) {
|
|
noteOffHandler = anoteOffHandler;
|
|
}
|
|
|
|
// number of "octaves" (if transpose distance=12)
|
|
void setTransposeSteps(uint8_t steps) { if( steps > 0) { tr_steps = steps; } }
|
|
|
|
// the distance in semitones between steps, often 12 for an octave
|
|
void setTransposeDistance(uint8_t dist) { tr_dist = dist; }
|
|
|
|
// call update as fast as possible, will trigger noteOn and noteOff functions
|
|
void update() { update(-1); } // -1 means "root note is updated immediately when changed"
|
|
|
|
// call update as fast as possible, will trigger noteOn and noteOff function
|
|
// "root_note_new" is new root note to use at top of arp "measure"
|
|
// negative notes are invalid. "root_note_new == -1" is used as internal signaling
|
|
void update(int root_note_new)
|
|
{
|
|
if( !enabled ) { return; }
|
|
|
|
uint32_t now = millis();
|
|
|
|
if( now - last_beat_millis > per_beat_millis ) {
|
|
last_beat_millis = now;
|
|
int8_t tr_amount = tr_dist * tr_pos; // tr_pos may be zero
|
|
|
|
// only make musical changes at start of a new measure // FIXME allow immediate
|
|
if( arp_pos == 0 ) {
|
|
if( root_note_new >= 0 ) { root_note = root_note_new; }
|
|
tr_pos = (tr_pos + 1) % tr_steps;
|
|
}
|
|
note_played = root_note + arps[arp_id][arp_pos] + tr_amount;
|
|
note_played = constrain(note_played + (12 * octave_offset), 0,127);
|
|
if( noteOnHandler) { noteOnHandler( note_played ); }
|
|
arp_pos = (arp_pos+1) % arp_len;
|
|
}
|
|
|
|
if( now - last_beat_millis > note_duration ) {
|
|
if( note_played != 0 ) { // we have a note to turn off!
|
|
if( noteOffHandler) { noteOffHandler( note_played ); }
|
|
note_played = 0; // say we've turned off the note
|
|
}
|
|
}
|
|
}
|
|
|
|
private:
|
|
bool enabled; // is arp playing or not
|
|
float bpm; // our arps per minute
|
|
uint8_t arp_id; // which arp we using
|
|
uint8_t arp_pos; // where in current arp are we
|
|
uint8_t tr_steps; // if transposing, how many times, 1= normal
|
|
int8_t tr_dist; // like an octave
|
|
uint8_t tr_pos; // which tranposed we're on (0..tr_steps)
|
|
uint8_t root_note; //
|
|
uint8_t octave_offset; // offset for root_note
|
|
uint16_t per_beat_millis; // = 1000 * 60 / bpm;
|
|
|
|
uint8_t note_played; // the note we have played, so we can unplay it later
|
|
uint16_t note_duration;
|
|
float gate_percentage; // percentage of per_beat_millis to play a note, 0.0-1.0
|
|
|
|
uint32_t last_beat_millis;
|
|
|
|
void (*noteOnHandler)(uint8_t) = nullptr;
|
|
void (*noteOffHandler)(uint8_t) = nullptr;
|
|
|
|
static const int arp_len = 4; // number of notes in an arp
|
|
static const uint8_t arp_count= 8; // how many arps in "arps"
|
|
int8_t arps[arp_count][arp_len] = {
|
|
{0, 4, 7, 12}, // major
|
|
{0, 3, 7, 10}, // minor 7th
|
|
{0, 3, 6, 3}, // Diminished
|
|
{0, 5, 7, 12}, // Suspended 4th
|
|
{0, 12, 0, -12}, // octaves
|
|
{0, 12, 24, -12}, // octaves 2
|
|
{0, -12, -12, 0}, // octaves 3 (bass)
|
|
{0, 0, 0, 0}, // root
|
|
};
|
|
|
|
// FIXME: how to programmatically set arps?
|
|
|
|
};
|