/ Arcade_Synth_Controller / Arpy.h
Arpy.h
1 // SPDX-FileCopyrightText: 2022 Tod Kurt for Adafruit Industries 2 // 3 // SPDX-License-Identifier: MIT 4 5 /** 6 * Arpy - a class to do arpeggios for you 7 * 12 Jan 2022 - Tod Kurt @todbot 8 * See https://github.com/todbot/mozzi_experiments/ for examples 9 */ 10 class Arpy 11 { 12 public: 13 14 Arpy(): enabled(false), arp_id(0), arp_pos(0), root_note(0), note_played(0) 15 { 16 tr_steps = 1; tr_dist = 12; tr_pos = 0; // FIXME make these defaults 17 } 18 19 // turn Arpy on or off 20 void on() { enabled = true; } 21 void off() { 22 enabled = false; 23 if( noteOffHandler) { noteOffHandler( note_played ); } 24 } 25 // let you know what Arpy thinks about doing its job 26 bool isOn() { return enabled; } 27 28 // pick which arpeggio to play 29 void setArpId(uint8_t arp_id) { arp_id = arp_id % arp_count; } 30 31 // which arpeggio is currently set 32 uint8_t getArpId() { return arp_id; } 33 34 // go to next arpeggio, a little convenience function 35 void nextArpId() { arp_id = (arp_id+1) % arp_count; } 36 37 // return acount of how many different arpeggios Arpy knows 38 uint8_t getArpCount() { return arp_count; } 39 40 // set the root note of the arpeggio 41 void setRootNote( uint8_t note ) { root_note = note; } 42 uint8_t getRootNote() { return root_note; } 43 44 // set the octave offset for root_note (root_note += (octave_offset*12) 45 void setOctaveOffset( uint8_t offset ) { octave_offset = offset; } 46 uint8_t getOctaveOffset() { return octave_offset; } 47 48 // set BPM of arpeggio 49 void setBPM( float bpm ) { 50 bpm = bpm; 51 per_beat_millis = 1000 * 60 / bpm; 52 note_duration = gate_percentage * per_beat_millis; 53 } 54 55 // set note duration as a percentage of BPM 56 void setGateTime( float percentage ) { 57 if( percentage < 0 || percentage > 1 ) { return; } 58 gate_percentage = percentage; 59 } 60 float getGateTime() { return gate_percentage; } // percentage 0.0 - 1.0 61 62 // set the function to call when note on happens 63 void setNoteOnHandler(void (*anoteOnHandler)(uint8_t)) { 64 noteOnHandler = anoteOnHandler; 65 } 66 67 // set the function to call when note off happens 68 void setNoteOffHandler(void (*anoteOffHandler)(uint8_t)) { 69 noteOffHandler = anoteOffHandler; 70 } 71 72 // number of "octaves" (if transpose distance=12) 73 void setTransposeSteps(uint8_t steps) { if( steps > 0) { tr_steps = steps; } } 74 75 // the distance in semitones between steps, often 12 for an octave 76 void setTransposeDistance(uint8_t dist) { tr_dist = dist; } 77 78 // call update as fast as possible, will trigger noteOn and noteOff functions 79 void update() { update(-1); } // -1 means "root note is updated immediately when changed" 80 81 // call update as fast as possible, will trigger noteOn and noteOff function 82 // "root_note_new" is new root note to use at top of arp "measure" 83 // negative notes are invalid. "root_note_new == -1" is used as internal signaling 84 void update(int root_note_new) 85 { 86 if( !enabled ) { return; } 87 88 uint32_t now = millis(); 89 90 if( now - last_beat_millis > per_beat_millis ) { 91 last_beat_millis = now; 92 int8_t tr_amount = tr_dist * tr_pos; // tr_pos may be zero 93 94 // only make musical changes at start of a new measure // FIXME allow immediate 95 if( arp_pos == 0 ) { 96 if( root_note_new >= 0 ) { root_note = root_note_new; } 97 tr_pos = (tr_pos + 1) % tr_steps; 98 } 99 note_played = root_note + arps[arp_id][arp_pos] + tr_amount; 100 note_played = constrain(note_played + (12 * octave_offset), 0,127); 101 if( noteOnHandler) { noteOnHandler( note_played ); } 102 arp_pos = (arp_pos+1) % arp_len; 103 } 104 105 if( now - last_beat_millis > note_duration ) { 106 if( note_played != 0 ) { // we have a note to turn off! 107 if( noteOffHandler) { noteOffHandler( note_played ); } 108 note_played = 0; // say we've turned off the note 109 } 110 } 111 } 112 113 private: 114 bool enabled; // is arp playing or not 115 float bpm; // our arps per minute 116 uint8_t arp_id; // which arp we using 117 uint8_t arp_pos; // where in current arp are we 118 uint8_t tr_steps; // if transposing, how many times, 1= normal 119 int8_t tr_dist; // like an octave 120 uint8_t tr_pos; // which tranposed we're on (0..tr_steps) 121 uint8_t root_note; // 122 uint8_t octave_offset; // offset for root_note 123 uint16_t per_beat_millis; // = 1000 * 60 / bpm; 124 125 uint8_t note_played; // the note we have played, so we can unplay it later 126 uint16_t note_duration; 127 float gate_percentage; // percentage of per_beat_millis to play a note, 0.0-1.0 128 129 uint32_t last_beat_millis; 130 131 void (*noteOnHandler)(uint8_t) = nullptr; 132 void (*noteOffHandler)(uint8_t) = nullptr; 133 134 static const int arp_len = 4; // number of notes in an arp 135 static const uint8_t arp_count= 8; // how many arps in "arps" 136 int8_t arps[arp_count][arp_len] = { 137 {0, 4, 7, 12}, // major 138 {0, 3, 7, 10}, // minor 7th 139 {0, 3, 6, 3}, // Diminished 140 {0, 5, 7, 12}, // Suspended 4th 141 {0, 12, 0, -12}, // octaves 142 {0, 12, 24, -12}, // octaves 2 143 {0, -12, -12, 0}, // octaves 3 (bass) 144 {0, 0, 0, 0}, // root 145 }; 146 147 // FIXME: how to programmatically set arps? 148 149 };