├── .gitattributes ├── README.md ├── Schematic.png ├── Sequencer.ino ├── Sequencer.sch ├── Sequencer_LEDPCB.zip └── Sequencer_MainPCB.zip /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DIY Arudiono based Sequencer 2 | Files to the project I showcased on YouTube here 3 | If you have any questions, please watch the video first: https://youtu.be/4mnz6_HvwVs 4 | -------------------------------------------------------------------------------- /Schematic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datulab/diyArduinoSequencer/5f3d0c1a6dcf5dc53dc78ec0f0343d1c8d2f23a5/Schematic.png -------------------------------------------------------------------------------- /Sequencer.ino: -------------------------------------------------------------------------------- 1 | // --------------------------------------------------------------------------------------------------------------------------- 2 | // Code written by David Wieland aka Datulab Tech. Feel free to use this in your own projects and improve upon it. If you make 3 | // something cool, please let me know. This code has been written specifically for the Arduino nano and RGBW Neopixel LEDs. 4 | // If you want to use a differetn configuration, make sure to update the code. 5 | // C++ is not my main programming language, so please excuse if the code could be a bit nicer in some areas. 6 | // If you have any questions, make sure to check out the video on my YouTube channel about this. 7 | // If there still is anything, write me a message to info@datulab.tech 8 | // --------------------------------------------------------------------------------------------------------------------------- 9 | 10 | #include 11 | #include 12 | 13 | //Initializing the LEDs 14 | Adafruit_NeoPixel strip = Adafruit_NeoPixel(16, 13, NEO_GRBW + NEO_KHZ800); 15 | 16 | //Settings 17 | #define whiteBrightness 15 18 | #define voicePin A2 19 | #define bpmPin A1 20 | #define patternPin A0 21 | #define syncPin A7 22 | #define numPatterns 5 //number of patterns including one random 23 | #define patternChangeDuration 1000 //how long pattern change is displayed 24 | #define minBpm 20 //sets minimum bpm 25 | #define bpmScale 3 //ratio bpm above min is scaled with, at 3 bpm scales from 20 - 360 26 | #define trigDuration 10 //time in ms that trigger is high 27 | 28 | //Switch matrix variables 29 | byte inputs[] = {6, 7, 8, 9, 10}; 30 | const byte inCount = sizeof(inputs) / sizeof(inputs[0]); 31 | byte outputs[] = {2, 3, 4, 5}; 32 | const byte outCount = sizeof(outputs) / sizeof(outputs[0]); 33 | 34 | const byte outputPins[] = {17, 18, 19, 20}; 35 | 36 | //State variables 37 | bool lengthSelect; 38 | bool trig; 39 | bool sync; 40 | int bpm = 120; 41 | byte voice = 0; 42 | byte pattern = 0; 43 | bool beat[4 * numPatterns][16]; //[4 voices numPatterns (minus random pattern) patterns][16 beats] true for hit false for no hit 44 | byte lengths[4 * (numPatterns - 1)]; //length 1-15 of the voices 45 | byte gateLength = 14; //length of the gate output relative to the beat length (0-15) 46 | 47 | //Temporary variables 48 | byte pos; 49 | byte saveSlot; 50 | bool stateChanged; 51 | uint32_t currentColor; 52 | bool isActive[16]; 53 | int patternPot = 1025; 54 | int previousPatternPot; 55 | int voicePot; 56 | int bpmPot; 57 | bool gateChange; 58 | bool patternChange; 59 | bool syncHigh; 60 | 61 | //Timing variables 62 | unsigned long currentTime; //saves current time 63 | unsigned long previousBeat; //time of last beat change 64 | unsigned long previousPatternTime; //time of patternChange engage 65 | long beatDuration; 66 | long gateDuration; 67 | byte currentBeat[numPatterns]; 68 | 69 | //Colors 70 | byte voiceColor[4][4] = {{30, 10, 0, 0}, 71 | {50, 0, 2, 0}, 72 | {5, 0, 30, 0}, 73 | {0, 25, 10, 0}}; 74 | 75 | 76 | // --------------------------------------------------------------------------------------------------------------------------- 77 | // -------------------------------------------- Initial Setup ---------------------------------------------------------------- 78 | // --------------------------------------------------------------------------------------------------------------------------- 79 | void setup() { 80 | 81 | for(int i=0; i 0){ 127 | beat[voi + 4 * pat][7 - i] = true; 128 | beat1 = beat1 - (1 << (7 - i)); 129 | }else{ 130 | beat[voi + 4 * pat][7 - i] = false; 131 | } 132 | } 133 | 134 | int beat2 = EEPROM.read(voiceStart + 1); 135 | for(int i = 0; i < 8; i++){ 136 | if(beat2 / (1 << (7 - i)) > 0){ 137 | beat[voi + 4 * pat][15 - i] = true; 138 | beat2 = beat2 - (1 << (7 - i)); 139 | }else{ 140 | beat[voi + 4 * pat][15 - i] = false; 141 | } 142 | } 143 | 144 | lengths[voi + 4 * pat] = EEPROM.read(voiceStart + 2); //reading the length value 145 | } 146 | } 147 | } 148 | 149 | 150 | // --------------------------------------------------------------------------------------------------------------------------- 151 | // -------------------------------------------- Reading the switches --------------------------------------------------------- 152 | // --------------------------------------------------------------------------------------------------------------------------- 153 | 154 | 155 | void readSwitches(){ //Reading the switches and updating the beat, lengths, lengthSelect, gate, and sync variables with the current switch states 156 | saveSlot = voice + 4 * pattern; 157 | 158 | for (int i = 0; i < 4; i++){ //iterates through the 4 outputs 159 | digitalWrite(outputs[i],LOW); 160 | delayMicroseconds(5); 161 | 162 | for(int j = 0; j < 5; j++){ //iterates throught the 5 inputs 163 | bool state; //state of current switch 164 | if(digitalRead(inputs[j])){ 165 | state = false; 166 | }else{ 167 | state = true; 168 | } 169 | pos = 4 * j + i; //current switch number 170 | 171 | if(j < 4 && voice != 4){ //if on one of the beat switches 172 | if(lengthSelect && state){ //if in length selecting mode 173 | lengths[saveSlot] = pos; 174 | stateChanged = true; 175 | } //otherwise if switch is newly engaged 176 | else if(state && !isActive[pos]){ 177 | beat[saveSlot][pos] = !beat[saveSlot][pos]; 178 | isActive[pos] = true; 179 | stateChanged = true; 180 | } //resetting isActive if switch newly disengaged 181 | else if(!state && isActive[pos]){ 182 | isActive[pos] = false; 183 | } 184 | } 185 | 186 | else if (j == 4){ //if on one of the settings switches 187 | switch(i){ 188 | case 0: 189 | lengthSelect = state; 190 | case 1: 191 | trig = state; 192 | case 2: 193 | sync = state; 194 | } 195 | } 196 | } 197 | digitalWrite(outputs[i],HIGH); 198 | delayMicroseconds(5); 199 | } 200 | } 201 | 202 | 203 | // --------------------------------------------------------------------------------------------------------------------------- 204 | // -------------------------------------------- Reading the Pots ------------------------------------------------------------- 205 | // --------------------------------------------------------------------------------------------------------------------------- 206 | 207 | void readPots(){ //Reads pots and updates variables bpm, voice, pattern, and gate length with the current pot positions 208 | patternPot = analogRead(patternPin); //reads pots 209 | bpmPot = analogRead(bpmPin); 210 | voicePot = analogRead(voicePin); 211 | 212 | if(patternPot < previousPatternPot - 5 || patternPot > previousPatternPot + 5){ //if pattern pot has been changed 213 | if(previousPatternPot != 1025){ 214 | patternChange = true; //patternChange will switch LEDs to display pattern or gate length 215 | } 216 | previousPatternTime = millis(); 217 | if(lengthSelect){ //if in lenghth selecting mode 218 | gateLength = patternPot / 48; //sets gateLength to a 0-15 number according to pattern pot 219 | gateChange = true; 220 | } 221 | else{ //if in normal operating mode 222 | pattern = patternPot * numPatterns / 1024; //updates the pattern with pos of pattern pot 223 | } 224 | previousPatternPot = patternPot; 225 | } 226 | 227 | beatDuration = 60000 / (bpm * 4); //updates beatDuration with current bpm value 228 | gateDuration = beatDuration * gateLength / 16; //updates gateDuration with current beatDuration and gateLength 229 | bpm = minBpm + bpmPot / bpmScale; 230 | voice = voicePot * 5 / 1024; 231 | } 232 | 233 | 234 | // --------------------------------------------------------------------------------------------------------------------------- 235 | // -------------------------------------------- Updating the LEDs ------------------------------------------------------------ 236 | // --------------------------------------------------------------------------------------------------------------------------- 237 | 238 | void displayLEDs(){ 239 | if(lengthSelect && voice != 4){ //if length switch is active 240 | if(patternChange){ //gate length selection mode, represents relative gate length with red leds 241 | for(int i = 0; i < 16; i++){ 242 | if(i <= gateLength){ 243 | strip.setPixelColor(i, 50, 0, 0, 0); 244 | }else{ 245 | strip.setPixelColor(i, 0, 0, 0, 0); 246 | } 247 | } 248 | } 249 | else if(voice != 4){ //length selection mode, turns all active beats white and others off 250 | for(int i = 0; i < 16; i++){ 251 | if(i <= lengths[saveSlot]){ 252 | strip.setPixelColor(i, 0, 0, 0, 15); 253 | }else{ 254 | strip.setPixelColor(i, 0, 0, 0, 0); 255 | } 256 | } 257 | } 258 | } 259 | 260 | else if(patternChange){ //if pattern has been changed recently, pattern nr is displayed with led 261 | for(int i = 0; i < 16; i++){ 262 | if(i == pattern){ 263 | strip.setPixelColor(i, 0, 0, 0, 15); 264 | }else{ 265 | strip.setPixelColor(i, 0, 0, 0, 0); 266 | } 267 | } 268 | } 269 | 270 | else{ //normal operating mode, shows active beats in color of voice, turns others off 271 | if(voice != 4){ //if in voice editing mode 272 | for(int i = 0; i < 16; i++){ 273 | if(beat[saveSlot][i]){ //displays active beats for current voice 274 | strip.setPixelColor(i, voiceColor[voice][0], voiceColor[voice][1], voiceColor[voice][2], voiceColor[voice][3]); 275 | } 276 | else{ 277 | strip.setPixelColor(i, 0, 0, 0, 0); 278 | } 279 | } 280 | } 281 | else{ //if in performance mode, all voices are displayed with voice 1 as highest priority 282 | for(int i = 0; i < 16; i++){ 283 | strip.setPixelColor(i, 0, 0, 0, 0); 284 | } 285 | for(int j = 0; j < 4; j++){ 286 | for(int i = 0; i < 16; i++){ 287 | if(beat[3 - j + 4 * pattern][i]){ 288 | strip.setPixelColor(i, voiceColor[3 - j][0], voiceColor[3 - j][1], voiceColor[3 - j][2], voiceColor[3 - j][3]); 289 | } 290 | } 291 | } 292 | } 293 | 294 | for(int i = 0; i < 16; i++){ //highlights current beat 295 | if(currentBeat[voice] == i){ 296 | strip.setPixelColor(i, 0, 0, 0, 25); 297 | } 298 | } 299 | } 300 | strip.show(); 301 | } 302 | 303 | 304 | // --------------------------------------------------------------------------------------------------------------------------- 305 | // -------------------------------------------- Generates random beat -------------------------------------------------------- 306 | // --------------------------------------------------------------------------------------------------------------------------- 307 | 308 | void randomBeat(){ //creates a random beat for all 4 voices with voice 1 the most beats and voice 4 the fewest 309 | for(int i = 0; i < 4; i++){ 310 | for(int j = 0; j < 16; j++){ 311 | if(random(10) > 4 + i){ 312 | beat[16 + i][j] = true; 313 | }else{ 314 | beat[16 + i][j] = false; 315 | } 316 | } 317 | } 318 | } 319 | 320 | 321 | // --------------------------------------------------------------------------------------------------------------------------- 322 | // -------------------------------------------- Updates the beat ------------------------------------------------------------- 323 | // --------------------------------------------------------------------------------------------------------------------------- 324 | 325 | void advanceBeat(){ //updates currentBeat, if it reached end for a voice it rolls over to first beat 326 | if(pattern != 4){ 327 | for(int i = 0; i < 5; i++){ 328 | if(i < 4){ 329 | if(currentBeat[i] < lengths[i + 4 * pattern]){ 330 | currentBeat[i]++; 331 | }else{ 332 | currentBeat[i] = 0; 333 | } 334 | }else{ 335 | if(currentBeat[i] < lengths[0 + 4 * pattern]){ 336 | currentBeat[i]++; 337 | }else{ 338 | currentBeat[i] = 0; 339 | } 340 | } 341 | } 342 | } 343 | else{ //if on random pattern, new pattern is generated when beat rolls over to first position 344 | if(currentBeat[0] < 15){ 345 | currentBeat[0]++; 346 | }else{ 347 | currentBeat[0] = 0; 348 | randomBeat(); 349 | } 350 | for(int i = 0; i < 4; i++){ 351 | currentBeat[i + 1] = currentBeat[0]; 352 | } 353 | } 354 | } 355 | 356 | 357 | // --------------------------------------------------------------------------------------------------------------------------- 358 | // -------------------------------------------- Updates the outputs ---------------------------------------------------------- 359 | // --------------------------------------------------------------------------------------------------------------------------- 360 | 361 | void output(){ 362 | if(!trig && previousBeat + gateDuration > currentTime){ //if in gate mode and during active gate time 363 | for(int i = 0; i < 4; i++){ 364 | digitalWrite(outputPins[i],beat[saveSlot][currentBeat[i]]); 365 | } 366 | } 367 | else if(trig & previousBeat + trigDuration > currentTime){ //if in trig mode and during acrive trig time 368 | for(int i = 0; i < 4; i++){ 369 | digitalWrite(outputPins[i],beat[saveSlot][currentBeat[i]]); 370 | } 371 | } 372 | else{ //if not in active time, outputs are low 373 | for(int i = 0; i < 4; i++){ 374 | digitalWrite(outputPins[i],LOW); 375 | } 376 | } 377 | } 378 | 379 | 380 | // --------------------------------------------------------------------------------------------------------------------------- 381 | // -------------------------------------------- Checks time ------------------------------------------------------------------ 382 | // --------------------------------------------------------------------------------------------------------------------------- 383 | 384 | void checkTiming(){ //checks if enough time has passed to advance to the next beat 385 | 386 | currentTime = millis(); //gets current time in ms 387 | 388 | if(currentTime - previousBeat >= beatDuration){ //if it is time for new beat 389 | previousBeat = currentTime; 390 | if(!sync){ 391 | advanceBeat(); 392 | } 393 | } 394 | 395 | if(patternChange && currentTime - previousPatternTime >= patternChangeDuration){ //if enough time has passed since pattern pot was last moved, returning to normal LED display 396 | patternChange = false; 397 | 398 | if(gateChange){ //if gate length has changed, causes EEPROM write 399 | stateChanged = true; 400 | gateChange = false; 401 | } 402 | } 403 | } 404 | 405 | 406 | // --------------------------------------------------------------------------------------------------------------------------- 407 | // -------------------------------------------- Checks Sync Input ------------------------------------------------------------ 408 | // --------------------------------------------------------------------------------------------------------------------------- 409 | 410 | void checkSync(){ //advances the beat on every rising flank of the sync input 411 | if(analogRead(syncPin) > 600){ 412 | if(!syncHigh){ 413 | syncHigh = true; 414 | advanceBeat(); 415 | } 416 | } 417 | else{ 418 | syncHigh = false; 419 | } 420 | } 421 | 422 | 423 | // --------------------------------------------------------------------------------------------------------------------------- 424 | // -------------------------------------------- Writes to EEPROM ------------------------------------------------------------- 425 | // --------------------------------------------------------------------------------------------------------------------------- 426 | 427 | //Writes current beat patterns to EEPROM 428 | //beat patterns are converted to 2byte per voice 429 | //order is 1byte gate length, then repeated for each voice of each pattern 2byte pattern, 1byte length 430 | //as writes are rated to 100'000, location is moved by increment of 64byte every time to use EEPROM uniformally 431 | //to find newest entry, first byte is incremented (rolls over at 255), so looking for highest number will get newest 432 | 433 | void writeEEPROM(){ 434 | stateChanged = false; 435 | 436 | int value = 0; //overcomplicated way of finding the next save location 437 | int previousValue = 0; 438 | int startAddress = 64; 439 | 440 | for(int i = 0; i < 16; i++){ //finds newest starting address by comparing first byte of all locations 441 | value = EEPROM.read(i*64); 442 | if(value == previousValue + 1 || previousValue == 255 || (i == 0 && value != 0)){ 443 | previousValue = value; 444 | if(i < 15){ 445 | startAddress = (i + 1) * 64; 446 | }else{ 447 | startAddress = 0; 448 | } 449 | } 450 | } 451 | 452 | int index = 0; 453 | if(previousValue != 255){ 454 | index = previousValue + 1; 455 | } 456 | 457 | EEPROM.update(startAddress,index); //writes counter to first byte 458 | EEPROM.update(startAddress+1,gateLength); //writes gateLength to second byte 459 | 460 | for(int pat = 0; pat < 4; pat++){ //the 2 nested for loops iterate through the patterns and voices 461 | for(int voi = 0; voi < 4; voi++){ 462 | int beat1 = 0; //the 16 bool values for the beats get converted to 2 integers 463 | for(int i = 0; i < 8; i++){ 464 | if(beat[voi + 4 * pat][i]){ 465 | beat1 = beat1 + (1 << i); 466 | } 467 | } 468 | int beat2 = 0; 469 | for(int i = 0; i < 8; i++){ 470 | if(beat[voi + 4 * pat][8+i]){ 471 | beat2 = beat2 + (1 << i); 472 | } 473 | } 474 | 475 | int voiceStart = startAddress + 2 + 3 * (voi + 4 * pat); //writing the two integers of the beats and the length to EEPROM 476 | EEPROM.update(voiceStart, beat1); 477 | EEPROM.update(voiceStart + 1, beat2); 478 | EEPROM.update(voiceStart + 2, lengths[voi + 4 * pat]); 479 | } 480 | } 481 | } 482 | 483 | 484 | // --------------------------------------------------------------------------------------------------------------------------- 485 | // -------------------------------------------- Main Loop -------------------------------------------------------------------- 486 | // --------------------------------------------------------------------------------------------------------------------------- 487 | 488 | void loop() { 489 | readPots(); 490 | readSwitches(); 491 | if(stateChanged){ 492 | writeEEPROM(); 493 | } 494 | displayLEDs(); 495 | checkTiming(); 496 | if(sync){ 497 | checkSync(); 498 | } 499 | output(); 500 | } 501 | -------------------------------------------------------------------------------- /Sequencer_LEDPCB.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datulab/diyArduinoSequencer/5f3d0c1a6dcf5dc53dc78ec0f0343d1c8d2f23a5/Sequencer_LEDPCB.zip -------------------------------------------------------------------------------- /Sequencer_MainPCB.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datulab/diyArduinoSequencer/5f3d0c1a6dcf5dc53dc78ec0f0343d1c8d2f23a5/Sequencer_MainPCB.zip --------------------------------------------------------------------------------