├── README.md └── arpeggiator.ino /README.md: -------------------------------------------------------------------------------- 1 | arduino-arpeggiator 2 | =================== 3 | 4 | Arpeggiator based on Arduino and the SparkFun midi shield. It will run free 5 | or sync to midi clock if it detects a start message. 6 | 7 | Button D2 = Toggle hold-mode on/off (I might replace hold-mode on/off 8 | with an octave button, since I will probably always use it in hold mode.) 9 | 10 | Button D3 = Cycle through arpeggio modes 11 | 12 | Button D4 = Stop arpeggio 13 | 14 | 15 | Pot A1 = Tempo (With midi sync, controls the beat divisions) 16 | 17 | Pot A2 = Velocity 18 | 19 | 20 | Arpeggio modes: 21 | * Up 22 | * Down 23 | * Bounce (e.g., 1-2-3-2) 24 | * UpDown (e.g., 1-2-3-3-2-1) 25 | * OneThree (e.g., 1-3-2-4-3-5-4) 26 | * OneThreeEven (e.g., 1-3-2-4-3-5) 27 | 28 | 29 | Depends on http://sourceforge.net/projects/arduinomidilib/ 30 | -------------------------------------------------------------------------------- /arpeggiator.ino: -------------------------------------------------------------------------------- 1 | // todo: add bypass mode? 2 | 3 | #include 4 | 5 | 6 | #define STAT1 7 7 | #define STAT2 6 8 | #define BUTTON1 2 9 | #define BUTTON2 3 10 | #define BUTTON3 4 11 | 12 | const int CHANNEL = 1; 13 | const int UP = 0; 14 | const int DOWN = 1; 15 | const int BOUNCE = 2; 16 | const int UPDOWN = 3; 17 | const int ONETHREE = 4; 18 | const int ONETHREEEVEN = 5; 19 | const int MODES = 6; 20 | 21 | byte notes[10]; 22 | unsigned long tempo; 23 | unsigned long lastTime; 24 | unsigned long blinkTime; 25 | unsigned long tick; 26 | unsigned long buttonOneHeldTime; 27 | unsigned long buttonTwoHeldTime; 28 | unsigned long buttonThreeHeldTime; 29 | unsigned long debounceTime; 30 | int playBeat; 31 | int notesHeld; 32 | int mode; 33 | int clockTick; 34 | boolean blinkOn; 35 | boolean hold; 36 | boolean buttonOneDown; 37 | boolean buttonTwoDown; 38 | boolean buttonThreeDown; 39 | boolean bypass; 40 | boolean midiThruOn; 41 | boolean arpUp; 42 | boolean clockSync; 43 | 44 | void setup() { 45 | blinkTime = lastTime = millis(); 46 | buttonOneHeldTime = buttonTwoHeldTime = buttonThreeHeldTime = 0; 47 | notesHeld = 0; 48 | playBeat=0; 49 | blinkOn = false; 50 | hold=true; 51 | arpUp = true; // used to determine which way arp is going when in updown mode 52 | buttonOneDown = buttonTwoDown = buttonThreeDown = false; 53 | mode=0; 54 | bypass = midiThruOn = false; 55 | tempo = 400; 56 | debounceTime = 50; 57 | clockSync = false; 58 | clockTick = 1; 59 | 60 | pinMode(STAT1,OUTPUT); 61 | pinMode(STAT2,OUTPUT); 62 | pinMode(BUTTON1,INPUT); 63 | pinMode(BUTTON2,INPUT); 64 | pinMode(BUTTON3,INPUT); 65 | 66 | digitalWrite(BUTTON1,HIGH); 67 | digitalWrite(BUTTON2,HIGH); 68 | digitalWrite(BUTTON3,HIGH); 69 | 70 | // Initiate MIDI communications, listen to all channels 71 | MIDI.begin(MIDI_CHANNEL_OMNI); 72 | 73 | MIDI.setHandleNoteOn(HandleNoteOn); 74 | MIDI.setHandleControlChange (HandleControlChange); 75 | MIDI.setHandleClock (HandleClock); 76 | MIDI.setHandleStart (HandleStart); 77 | MIDI.setHandleStop (HandleStop); 78 | MIDI.turnThruOff(); 79 | 80 | digitalWrite(STAT1,HIGH); 81 | digitalWrite(STAT2,HIGH); 82 | 83 | } 84 | 85 | 86 | // This function will be automatically called when a NoteOn is received. 87 | // see documentation here: 88 | // http://arduinomidilib.sourceforge.net/ 89 | 90 | void HandleNoteOn(byte channel, byte pitch, byte velocity) { 91 | 92 | 93 | if (velocity == 0) // note released 94 | notesHeld--; 95 | else { 96 | // If it's in hold mode and you are not holding any notes down, 97 | // it continues to play the previous arpeggio. Once you press 98 | // a new note, it resets the arpeggio and starts a new one. 99 | if (notesHeld==0 && hold) 100 | resetNotes(); 101 | 102 | notesHeld++; 103 | } 104 | 105 | 106 | // Turn on an LED when any notes are held and off when all are released. 107 | if (notesHeld > 0) 108 | digitalWrite(STAT2,LOW); // stupid midi shield has high/low backwards for the LEDs 109 | else 110 | digitalWrite(STAT2,HIGH); // stupid midi shield has high/low backwards for the LEDs 111 | 112 | 113 | 114 | // find the right place to insert the note in the notes array 115 | for (int i = 0; i < sizeof(notes); i++) { 116 | 117 | if (velocity == 0) { // note released 118 | if (!hold && notes[i] >= pitch) { 119 | 120 | // shift all notes in the array beyond or equal to the 121 | // note in question, thereby removing it and keeping 122 | // the array compact. 123 | if (i < sizeof(notes)) 124 | notes[i] = notes[i+1]; 125 | } 126 | } 127 | else { 128 | 129 | if (notes[i] == pitch) 130 | return; // already in arpeggio 131 | else if (notes[i] != '\0' && notes[i] < pitch) 132 | continue; // ignore the notes below it 133 | else { 134 | // once we reach the first note in the arpeggio that's higher 135 | // than the new one, scoot the rest of the arpeggio array over 136 | // to the right 137 | for (int j = sizeof(notes); j > i; j--) 138 | notes[j] = notes[j-1]; 139 | 140 | // and insert the note 141 | notes[i] = pitch; 142 | return; 143 | } 144 | } 145 | } 146 | } 147 | 148 | // just pass midi CC through 149 | void HandleControlChange (byte channel, byte number, byte value) { 150 | MIDI.sendControlChange(number,value,channel); 151 | } 152 | 153 | // This is a midi clock sync "start" message. In this case, switch to clock 154 | // sync mode and trigger the first note. 155 | void HandleStart () { 156 | clockSync = true; 157 | clockTick = 1; 158 | playBeat = 0; 159 | cli(); 160 | tick = millis(); 161 | sei(); 162 | 163 | handleTick(tick); 164 | } 165 | 166 | // Turn clock sync off when "stop" is received. 167 | void HandleStop () { 168 | clockSync = false; 169 | } 170 | 171 | // on each tick of the midi clock, determine whether or note to play 172 | // a note 173 | void HandleClock () { 174 | 175 | cli(); 176 | tick = millis(); 177 | sei(); 178 | 179 | // keep a counter of every clock tick we receive. if the count 180 | // corresponds with the subdivision selected with the tempo knob, 181 | // play the note and reset the clock. 182 | // 183 | // clock is 1 based instead of 0 for ease of calculation 184 | 185 | if (clockTick >= (int)map(analogRead(1),0,1023,1,4)*6 + 1) { 186 | handleTick(tick); 187 | clockTick = 1; 188 | } 189 | 190 | 191 | clockTick++; 192 | } 193 | 194 | void loop() { 195 | 196 | MIDI.read(); 197 | 198 | 199 | cli(); 200 | tick = millis(); 201 | sei(); 202 | 203 | handleButtonOne(); 204 | handleButtonTwo(); 205 | handleButtonThree(); 206 | 207 | // if not in clock synch mode, we just read the tempo from the 208 | // tempo knob. 209 | if (!clockSync) { 210 | // There's no need to be precise about it here. This simple 211 | // calculation is done quickly and gives a very wide range. 212 | tempo = 6000 / ((127-analogRead(1)/8) + 2); 213 | handleTick(tick); 214 | } 215 | 216 | } 217 | 218 | void handleTick(unsigned long tick) { 219 | 220 | // leave the LED long enough to be brightish but not so long 221 | // that it ends up being solid instead of blinking 222 | if (blinkOn && tick - blinkTime > 10) { 223 | blinkOn=false; 224 | digitalWrite(STAT1,HIGH); // stupid midi shield has high/low backwards for the LEDs 225 | } 226 | if (clockSync || tick - lastTime > tempo) { 227 | blinkTime = lastTime = tick; 228 | digitalWrite(STAT1,LOW); // stupid midi shield has high/low backwards for the LEDs 229 | blinkOn = true; 230 | 231 | 232 | if ((hold || notesHeld > 0) && notes[0] != '\0') { 233 | 234 | 235 | // stop the previous note 236 | // MIDI.sendNoteOff(notes[playBeat],0,CHANNEL); 237 | 238 | // fixes a bug where a random note would sometimes get played when switching chords 239 | if (notes[playBeat] == '\0') 240 | playBeat = 0; 241 | 242 | // play the current note 243 | MIDI.sendNoteOn(notes[playBeat],velocity(),CHANNEL); 244 | 245 | // decide what the next note is based on the mode. 246 | if (mode == UP) 247 | up(); 248 | else if (mode == DOWN) 249 | down(); 250 | else if (mode == BOUNCE) 251 | bounce(); 252 | else if (mode == UPDOWN) 253 | upDown(); 254 | else if (mode == ONETHREE) 255 | oneThree(); 256 | else if (mode == ONETHREEEVEN) 257 | oneThreeEven(); 258 | 259 | } 260 | } 261 | } 262 | 263 | 264 | int velocity() { 265 | int velocity = 127 - analogRead(0)/8; 266 | 267 | // don't let it totally zero out. 268 | if (velocity == 0) 269 | velocity++; 270 | 271 | return velocity; 272 | 273 | } 274 | 275 | void up() { 276 | playBeat++; 277 | if (notes[playBeat] == '\0') 278 | playBeat=0; 279 | } 280 | 281 | void down() { 282 | if (playBeat == 0) { 283 | playBeat = sizeof(notes)-1; 284 | while (notes[playBeat] == '\0') { 285 | playBeat--; 286 | } 287 | } 288 | else 289 | playBeat--; 290 | } 291 | 292 | 293 | void bounce() { 294 | if (sizeof(notes) == 1) 295 | playBeat=0; 296 | else 297 | if (arpUp) { 298 | if (notes[playBeat+1] == '\0') { 299 | arpUp = false; 300 | playBeat--; 301 | } 302 | else 303 | playBeat++; 304 | } 305 | else { 306 | if (playBeat == 0) { 307 | arpUp = true; 308 | playBeat++; 309 | } 310 | else 311 | playBeat--; 312 | } 313 | } 314 | 315 | 316 | void upDown() { 317 | if (sizeof(notes) == 1) 318 | playBeat=0; 319 | else 320 | if (arpUp) { 321 | if (notes[playBeat+1] == '\0') { 322 | arpUp = false; 323 | } 324 | else 325 | playBeat++; 326 | } 327 | else { 328 | if (playBeat == 0) { 329 | arpUp = true; 330 | } 331 | else 332 | playBeat--; 333 | } 334 | } 335 | 336 | void oneThree() { 337 | if (arpUp) 338 | playBeat += 2; 339 | else 340 | playBeat--; 341 | 342 | arpUp = !arpUp; 343 | 344 | if (notes[playBeat] == '\0') { 345 | playBeat = 0; 346 | arpUp = true; 347 | } 348 | } 349 | 350 | void oneThreeEven() { 351 | 352 | if (notes[playBeat+1] == '\0') { 353 | playBeat = 0; 354 | arpUp = true; 355 | return; 356 | } 357 | 358 | 359 | if (arpUp) 360 | playBeat += 2; 361 | else 362 | playBeat--; 363 | 364 | arpUp = !arpUp; 365 | 366 | 367 | } 368 | 369 | // empties out the arpeggio. used when switching modes, when in hold mode and 370 | // a new arpeggio is started, or when the reset button is pushed. 371 | void resetNotes() { 372 | for (int i = 0; i < sizeof(notes); i++) 373 | notes[i] = '\0'; 374 | } 375 | 376 | 377 | // Basic code to read the buttons. 378 | char button(char button_num) 379 | { 380 | return (!(digitalRead(button_num))); 381 | } 382 | 383 | 384 | // Button one is the the Hold button. 385 | void handleButtonOne() { 386 | boolean buttonOnePressed = button(BUTTON1); 387 | if (buttonOnePressed) { 388 | if (buttonOneHeldTime == 0) 389 | buttonOneHeldTime = tick; 390 | 391 | if (!buttonOneDown && (tick - buttonOneHeldTime > debounceTime)) { 392 | buttonOneDown = true; 393 | hold = !hold; 394 | resetNotes(); 395 | } 396 | } 397 | else { 398 | buttonOneDown = false; 399 | buttonOneHeldTime = 0; 400 | } 401 | } 402 | 403 | // Button two is the mode button. 404 | void handleButtonTwo() { 405 | boolean buttonTwoPressed = button(BUTTON2); 406 | if (buttonTwoPressed) { 407 | if (buttonTwoHeldTime == 0) 408 | buttonTwoHeldTime = tick; 409 | 410 | if (!buttonTwoDown && (tick - buttonTwoHeldTime > debounceTime)) { 411 | buttonTwoDown = true; 412 | playBeat=0; 413 | mode++; 414 | if (mode == MODES) { 415 | mode=0; 416 | } 417 | arpUp = true; 418 | 419 | } 420 | } 421 | else { 422 | buttonTwoDown = false; 423 | buttonTwoHeldTime = 0; 424 | } 425 | } 426 | 427 | // button three is the reset button 428 | void handleButtonThree() { 429 | boolean buttonThreePressed = button(BUTTON3); 430 | if (buttonThreePressed) { 431 | if (buttonThreeHeldTime == 0) 432 | buttonThreeHeldTime = tick; 433 | 434 | if (!buttonThreeDown && (tick - buttonThreeHeldTime > debounceTime)) { 435 | 436 | buttonThreeDown = true; 437 | resetNotes(); 438 | } 439 | } 440 | else { 441 | buttonThreeDown = false; 442 | buttonThreeHeldTime = 0; 443 | } 444 | } 445 | 446 | 447 | 448 | 449 | 450 | 451 | 452 | 453 | 454 | 455 | 456 | 457 | 458 | 459 | 460 | --------------------------------------------------------------------------------