Components and supplies
Alphanumeric LCD, 16 x 2
SparkFun Analog/Digital MUX Breakout - CD74HC4067
Arduino UNO
Project description
Code
Arpeggino GitHub repository
This is the GitHub repository for the Arpeggino project. It includes the Arduino sketch, all code needed, schemas, and extra files. You can upload it to your Arduino board as-is or you can easily modify the schema to support your own board configuration. The code is written in C++, and you can easily find the places you need to modify the code to adjust it to your boards. A few examples: (1) Instead of having 8 keys, you can start with just a few (2) If you are using a board that has more I/O pins, you can omit the usage of the multiplexer easily (3) Remove the usage of the LCD screen if you don't have one (4) Program your own MIDI sequences and play them when a button gets clicked
Midier GitHub repository
Midier is the engine behind Arpeggino. It is a library written in C++ to play, record, loop and program MIDI notes, arpeggios and sequences on Arduino. It is comprehensively documented, and has plenty of plug-and-play examples available. You can use Midier outside Arpeggino, and integrate MIDI sequences and loops to your own projects easily.
Controlino GitHub repository
Controlino is the Arduino library that is used by Arpeggino for complex I/O controls that can be behind a multiplexer. It offers easy control of buttons and potentiometers, and supports both simple and complex clicking gestures such as: (1) Down (2) Up (3) Click (4) Double Click (Click-Click) (5) Long Click (Press) (6) Double Click and Press (Click-Press) It is fully documented and offers plenty of examples. You can use Controlino outside of Arpeggino to integrate complex click gestures in your projects, and control buttons and potentiometers behind a multiplexer.
Tutorial: Step Three - LCD - Part 3 - Sketch
c_cpp
1#include <Controlino.h> 2#include <LiquidCrystal.h> 3#include <Midier.h> 4 5#include <assert.h> 6 7namespace arpeggino 8{ 9 10namespace utils 11{ 12 13struct Timer 14{ 15 // control 16 17 void start() // start (or restart) 18 { 19 _millis = millis(); 20 } 21 22 void reset() // restart only if ticking 23 { 24 if (ticking()) 25 { 26 start(); 27 } 28 } 29 30 void stop() 31 { 32 _millis = -1; 33 } 34 35 // query 36 37 bool elapsed(unsigned ms) const // only if ticking 38 { 39 return ticking() && millis() - _millis >= ms; 40 } 41 42 bool ticking() const 43 { 44 return _millis != -1; 45 } 46 47private: 48 unsigned long _millis = -1; 49}; 50 51} // utils 52 53namespace state 54{ 55 56midier::Layers<8> layers; // the number of layers chosen will affect the global variable size 57midier::Sequencer sequencer(layers); 58 59} // state 60 61namespace io 62{ 63 64// here we declare all I/O controls with their corresponding pin numbers 65 66controlino::Selector Selector(/* s0 = */ 6, /* s1 = */ 5, /* s2 = */ 4, /* s3 = */ 3); 67controlino::Multiplexer Multiplexer(/* sig = */ 2, Selector); 68 69controlino::Potentiometer BPM(A0, /* min = */ 20, /* max = */ 230); // we limit the value of BPM to [20,230] 70 71// all configuration keys are behind the multiplexer 72controlino::Key Note(Multiplexer, 7); 73controlino::Key Mode(Multiplexer, 6); 74controlino::Key Octave(Multiplexer, 5); 75controlino::Key Perm(Multiplexer, 4); 76controlino::Key Steps(Multiplexer, 3); 77controlino::Key Rhythm(Multiplexer, 2); 78 79struct LCD : LiquidCrystal 80{ 81 LCD(uint8_t rs, uint8_t e, uint8_t d4, uint8_t d5, uint8_t d6, uint8_t d7) : LiquidCrystal(rs, e, d4, d5, d6, d7) 82 {} 83 84 template <typename T> 85 char print(const T & arg) 86 { 87 return LiquidCrystal::print(arg); 88 } 89 90 template <typename T> 91 char print(char col, char row, const T & arg) 92 { 93 setCursor(col, row); 94 return print(arg); 95 } 96 97 template <typename T> 98 char print(char col, char row, char max, const T & arg) 99 { 100 const auto written = print(col, row, arg); 101 102 for (unsigned i = 0; i < max - written; ++i) 103 { 104 write(' '); // make sure the non-used characters are clear 105 } 106 107 return written; 108 } 109}; 110 111LCD lcd(/* rs = */ 7, /* e = */ 8, /* d4 = */ 9, /* d5 = */ 10, /* d6 = */ 11, /* d7 = */ 12); 112 113} // io 114 115namespace configurer 116{ 117 118enum class Action 119{ 120 None, 121 122 Summary, 123 Focus, 124}; 125 126// a configurer is responsible for updating a single configuration 127// parameter according to changes of an I/O control 128 129struct Configurer 130{ 131 Action(*check)(); 132 void(*update)(); 133}; 134 135Configurer BPM = 136 { 137 .check = []() 138 { 139 if (io::BPM.check() == controlino::Potentiometer::Event::Changed) 140 { 141 return Action::Summary; 142 } 143 144 return Action::None; 145 }, 146 .update = []() 147 { 148 state::sequencer.bpm = io::BPM.read(); 149 }, 150 }; 151 152Configurer Note = 153 { 154 .check = []() 155 { 156 if (io::Note.check() == controlino::Key::Event::Down) 157 { 158 return Action::Summary; 159 } 160 161 return Action::None; 162 }, 163 .update = []() 164 { 165 auto & config = state::sequencer.config; // a shortcut 166 167 if (config.accidental() == midier::Accidental::Flat) 168 { 169 config.accidental(midier::Accidental::Natural); 170 } 171 else if (config.accidental() == midier::Accidental::Natural) 172 { 173 config.accidental(midier::Accidental::Sharp); 174 } 175 else if (config.accidental() == midier::Accidental::Sharp) 176 { 177 config.accidental(midier::Accidental::Flat); 178 179 if (config.note() == midier::Note::C) { config.note(midier::Note::D); } 180 else if (config.note() == midier::Note::D) { config.note(midier::Note::E); } 181 else if (config.note() == midier::Note::E) { config.note(midier::Note::F); } 182 else if (config.note() == midier::Note::F) { config.note(midier::Note::G); } 183 else if (config.note() == midier::Note::G) { config.note(midier::Note::A); } 184 else if (config.note() == midier::Note::A) { config.note(midier::Note::B); } 185 else if (config.note() == midier::Note::B) { config.note(midier::Note::C); } 186 } 187 }, 188 }; 189 190Configurer Mode = 191 { 192 .check = []() 193 { 194 if (io::Mode.check() == controlino::Key::Event::Down) 195 { 196 return Action::Focus; 197 } 198 199 return Action::None; 200 }, 201 .update = []() 202 { 203 const auto current = state::sequencer.config.mode(); 204 const auto next = (midier::Mode)(((unsigned)current + 1) % (unsigned)midier::Mode::Count); 205 206 state::sequencer.config.mode(next); 207 }, 208 }; 209 210Configurer Octave = 211 { 212 .check = []() 213 { 214 if (io::Octave.check() == controlino::Key::Event::Down) 215 { 216 return Action::Summary; 217 } 218 219 return Action::None; 220 }, 221 .update = []() 222 { 223 const auto current = state::sequencer.config.octave(); 224 const auto next = (current % 7) + 1; 225 226 state::sequencer.config.octave(next); 227 }, 228 }; 229 230Configurer Perm = 231 { 232 .check = []() 233 { 234 if (io::Perm.check() == controlino::Key::Event::Down) 235 { 236 return Action::Focus; 237 } 238 239 return Action::None; 240 }, 241 .update = []() 242 { 243 const auto current = state::sequencer.config.perm(); 244 const auto next = (current + 1) % midier::style::count(state::sequencer.config.steps()); 245 246 state::sequencer.config.perm(next); 247 }, 248 }; 249 250Configurer Steps = 251 { 252 .check = []() 253 { 254 if (io::Steps.check() == controlino::Key::Event::Down) 255 { 256 return Action::Focus; 257 } 258 259 return Action::None; 260 }, 261 .update = []() 262 { 263 auto & config = state::sequencer.config; // a shortcut 264 265 if (config.looped() == false) // we set to loop if currently not looping 266 { 267 config.looped(true); 268 } 269 else 270 { 271 unsigned steps = config.steps() + 1; 272 273 if (steps > 6) 274 { 275 steps = 3; 276 } 277 278 config.steps(steps); 279 config.perm(0); // reset the permutation 280 config.looped(false); // set as non looping 281 } 282 }, 283 }; 284 285Configurer Rhythm = 286 { 287 .check = []() 288 { 289 if (io::Rhythm.check() == controlino::Key::Event::Down) 290 { 291 return Action::Focus; 292 } 293 294 return Action::None; 295 }, 296 .update = []() 297 { 298 const auto current = state::sequencer.config.rhythm(); 299 const auto next = (midier::Rhythm)(((unsigned)current + 1) % (unsigned)midier::Rhythm::Count); 300 301 state::sequencer.config.rhythm(next); 302 } 303 }; 304 305} // configurer 306 307namespace viewer 308{ 309 310enum class What 311{ 312 Title, 313 Data, 314}; 315 316enum class How 317{ 318 Summary, 319 Focus, 320}; 321 322using Viewer = void(*)(What, How); 323 324struct : utils::Timer 325{ 326 // query 327 bool operator==(Viewer other) const { return _viewer == other; } 328 bool operator!=(Viewer other) const { return _viewer != other; } 329 330 // assignment 331 void operator=(Viewer other) { _viewer = other; } 332 333 // access 334 void print(What what, How how) { _viewer(what, how); } 335 336private: 337 Viewer _viewer = nullptr; 338} focused; 339 340void BPM(What what, How how) 341{ 342 assert(how == How::Summary); 343 344 if (what == What::Title) 345 { 346 io::lcd.print(13, 1, "bpm"); 347 } 348 349 if (what == What::Data) 350 { 351 io::lcd.print(9, 1, 3, state::sequencer.bpm); 352 } 353} 354 355void Note(What what, How how) 356{ 357 assert(how == How::Summary); 358 359 if (what == What::Data) 360 { 361 io::lcd.setCursor(0, 0); 362 363 const auto & config = state::sequencer.config; // a shortcut 364 365 if (config.note() == midier::Note::A) { io::lcd.print('A'); } 366 else if (config.note() == midier::Note::B) { io::lcd.print('B'); } 367 else if (config.note() == midier::Note::C) { io::lcd.print('C'); } 368 else if (config.note() == midier::Note::D) { io::lcd.print('D'); } 369 else if (config.note() == midier::Note::E) { io::lcd.print('E'); } 370 else if (config.note() == midier::Note::F) { io::lcd.print('F'); } 371 else if (config.note() == midier::Note::G) { io::lcd.print('G'); } 372 373 if (config.accidental() == midier::Accidental::Flat) { io::lcd.print('b'); } 374 else if (config.accidental() == midier::Accidental::Natural) { io::lcd.print(' '); } 375 else if (config.accidental() == midier::Accidental::Sharp) { io::lcd.print('#'); } 376 } 377} 378 379void Mode(What what, How how) 380{ 381 if (what == What::Data) 382 { 383 midier::mode::Name name; 384 midier::mode::name(state::sequencer.config.mode(), /* out */ name); 385 386 if (how == How::Summary) 387 { 388 name[3] = '\\0'; // trim the full name into a 3-letter shortcut 389 io::lcd.print(0, 1, name); 390 } 391 else if (how == How::Focus) 392 { 393 io::lcd.print(0, 1, sizeof(name), name); 394 } 395 } 396 else if (what == What::Title && how == How::Focus) 397 { 398 io::lcd.print(0, 0, "Mode: "); 399 } 400} 401 402void Octave(What what, How how) 403{ 404 assert(how == How::Summary); 405 406 if (what == What::Title) 407 { 408 io::lcd.print(3, 0, 'O'); 409 } 410 else if (what == What::Data) 411 { 412 io::lcd.print(4, 0, state::sequencer.config.octave()); 413 } 414} 415 416void Style(What what, How how) 417{ 418 if (how == How::Summary) 419 { 420 if (what == What::Title) 421 { 422 io::lcd.print(6, 0, 'S'); 423 } 424 else if (what == What::Data) 425 { 426 const auto & config = state::sequencer.config; // a shortcut 427 428 io::lcd.print(7, 0, config.steps()); 429 io::lcd.print(8, 0, config.looped() ? '+' : '-'); 430 io::lcd.print(9, 0, 3, config.perm() + 1); 431 } 432 } 433 else if (how == How::Focus) 434 { 435 if (what == What::Title) 436 { 437 io::lcd.print(0, 0, "Style: "); 438 } 439 else if (what == What::Data) 440 { 441 const auto & config = state::sequencer.config; // a shortcut 442 443 io::lcd.print(7, 0, config.steps()); 444 io::lcd.print(8, 0, config.looped() ? '+' : '-'); 445 io::lcd.print(9, 0, 3, config.perm() + 1); 446 447 midier::style::Description desc; 448 midier::style::description(config.steps(), config.perm(), /* out */ desc); 449 io::lcd.print(0, 1, 16, desc); // all columns in the LCD 450 451 if (config.looped()) 452 { 453 io::lcd.setCursor(strlen(desc) + 1, 1); 454 455 for (unsigned i = 0; i < 3; ++i) 456 { 457 io::lcd.print('.'); 458 } 459 } 460 } 461 } 462} 463 464void Rhythm(What what, How how) 465{ 466 if (how == How::Summary) 467 { 468 if (what == What::Title) 469 { 470 io::lcd.print(4, 1, 'R'); 471 } 472 else if (what == What::Data) 473 { 474 io::lcd.print(5, 1, 2, (unsigned)state::sequencer.config.rhythm() + 1); 475 } 476 } 477 else if (how == How::Focus) 478 { 479 if (what == What::Title) 480 { 481 io::lcd.print(0, 0, "Rhythm #"); 482 } 483 else if (what == What::Data) 484 { 485 io::lcd.print(8, 0, 2, (unsigned)state::sequencer.config.rhythm() + 1); 486 487 midier::rhythm::Description desc; 488 midier::rhythm::description(state::sequencer.config.rhythm(), /* out */ desc); 489 io::lcd.print(0, 1, desc); 490 } 491 } 492} 493 494} // viewer 495 496namespace component 497{ 498 499struct Component 500{ 501 configurer::Configurer configurer; 502 viewer::Viewer viewer; 503}; 504 505Component All[] = 506 { 507 { configurer::BPM, viewer::BPM }, 508 { configurer::Note, viewer::Note }, 509 { configurer::Mode, viewer::Mode }, 510 { configurer::Octave, viewer::Octave }, 511 { configurer::Perm, viewer::Style }, 512 { configurer::Steps, viewer::Style }, 513 { configurer::Rhythm, viewer::Rhythm }, 514 }; 515 516} // component 517 518namespace control 519{ 520 521namespace view 522{ 523 524void summary(viewer::Viewer viewer = nullptr) // 'nullptr' means all components 525{ 526 if (viewer::focused != nullptr) // some viewer is currently in focus 527 { 528 viewer::focused.stop(); // stop the timer 529 viewer::focused = nullptr; // mark as there's no viewer currently in focus 530 io::lcd.clear(); // clear the screen entirely 531 viewer = nullptr; // mark to print all titles and values 532 } 533 534 if (viewer == nullptr) 535 { 536 for (const auto & component : component::All) 537 { 538 component.viewer(viewer::What::Title, viewer::How::Summary); 539 component.viewer(viewer::What::Data, viewer::How::Summary); 540 } 541 } 542 else 543 { 544 viewer(viewer::What::Data, viewer::How::Summary); 545 } 546} 547 548void focus(viewer::Viewer viewer) 549{ 550 if (viewer::focused != viewer) // either in summary mode or another viewer is currently in focus 551 { 552 io::lcd.clear(); // clear the screen entirely 553 viewer::focused = viewer; // mark this viewer as the one being in focus 554 viewer::focused.print(viewer::What::Title, viewer::How::Focus); // print the title (only if just became the one in focus) 555 } 556 557 viewer::focused.print(viewer::What::Data, viewer::How::Focus); // print the data anyways 558 viewer::focused.start(); // start the timer or restart it if ticking already 559} 560 561} // view 562 563} // control 564 565namespace handle 566{ 567 568void focus() 569{ 570 if (viewer::focused.elapsed(3200)) 571 { 572 control::view::summary(); // go back to summary view 573 } 574} 575 576void components() 577{ 578 // components will update the configuration on I/O events 579 580 for (const auto & component : component::All) 581 { 582 const auto action = component.configurer.check(); 583 584 if (action == configurer::Action::None) 585 { 586 continue; // nothing to do 587 } 588 589 // update the configuration only if in summary mode or if this configurer is in focus 590 591 if ((action == configurer::Action::Summary && viewer::focused == nullptr) || 592 (action == configurer::Action::Focus && viewer::focused == component.viewer)) 593 { 594 component.configurer.update(); 595 } 596 597 if (action == configurer::Action::Summary) 598 { 599 control::view::summary(component.viewer); 600 } 601 else if (action == configurer::Action::Focus) 602 { 603 control::view::focus(component.viewer); 604 } 605 } 606} 607 608void keys() 609{ 610 // we extend `controlino::Key` so we could hold a Midier handle with every key 611 struct Key : controlino::Key 612 { 613 Key(char pin) : controlino::Key(io::Multiplexer, pin) // keys are behind the multiplexer 614 {} 615 616 midier::Sequencer::Handle h; 617 }; 618 619 static Key __keys[] = { 15, 14, 13, 12, 11, 10, 9, 8 }; // channel numbers of the multiplexer 620 621 for (auto i = 0; i < sizeof(__keys) / sizeof(Key); ++i) 622 { 623 auto & key = __keys[i]; 624 625 const auto event = key.check(); 626 627 if (event == Key::Event::None) 628 { 629 continue; // nothing has changed 630 } 631 632 if (event == Key::Event::Down) // a key was pressed 633 { 634 key.h = state::sequencer.start(i + 1); // start playing an arpeggio of the respective scale degree 635 } 636 else if (event == Key::Event::Up) // a key was released 637 { 638 state::sequencer.stop(key.h); // stop playing the arpeggio 639 } 640 } 641} 642 643void click() 644{ 645 // actually click Midier for it to play the MIDI notes 646 state::sequencer.click(midier::Sequencer::Run::Async); 647} 648 649} // handle 650 651extern "C" void setup() 652{ 653 // initialize the Arduino "Serial" module and set the baud rate 654 // to the same value you are using in your software. 655 // if connected physically using a MIDI 5-DIN connection, use 31250. 656 Serial.begin(9600); 657 658 // initialize the LCD 659 io::lcd.begin(16, 2); 660 661 // print the initial configuration 662 control::view::summary(); 663} 664 665extern "C" void loop() 666{ 667 handle::focus(); 668 handle::components(); 669 handle::keys(); 670 handle::click(); 671} 672 673} // arpeggino 674
Tutorial: Step Five - Layers - Sketch
c_cpp
1#include <Controlino.h> 2#include <LiquidCrystal.h> 3#include <Midier.h> 4 5#include <assert.h> 6 7namespace arpeggino 8{ 9 10namespace utils 11{ 12 13struct Timer 14{ 15 // control 16 17 void start() // start (or restart) 18 { 19 _millis = millis(); 20 } 21 22 void reset() // restart only if ticking 23 { 24 if (ticking()) 25 { 26 start(); 27 } 28 } 29 30 void stop() 31 { 32 _millis = -1; 33 } 34 35 // query 36 37 bool elapsed(unsigned ms) const // only if ticking 38 { 39 return ticking() && millis() - _millis >= ms; 40 } 41 42 bool ticking() const 43 { 44 return _millis != -1; 45 } 46 47private: 48 unsigned long _millis = -1; 49}; 50 51} // utils 52 53namespace state 54{ 55 56midier::Layers<8> layers; // the number of layers chosen will affect the global variable size 57midier::Sequencer sequencer(layers); 58 59struct : utils::Timer 60{ 61 midier::Layer * layer = nullptr; 62 unsigned char id; 63} layer; 64 65midier::Config * config = &sequencer.config; 66 67} // state 68 69namespace io 70{ 71 72// here we declare all I/O controls with their corresponding pin numbers 73 74controlino::Selector Selector(/* s0 = */ 6, /* s1 = */ 5, /* s2 = */ 4, /* s3 = */ 3); 75controlino::Multiplexer Multiplexer(/* sig = */ 2, Selector); 76 77controlino::Potentiometer BPM(A0, /* min = */ 20, /* max = */ 230); // we limit the value of BPM to [20,230] 78 79// all configuration keys are behind the multiplexer 80controlino::Key Note(Multiplexer, 7); 81controlino::Key Mode(Multiplexer, 6); 82controlino::Key Octave(Multiplexer, 5); 83controlino::Key Perm(Multiplexer, 4); 84controlino::Key Steps(Multiplexer, 3); 85controlino::Key Rhythm(Multiplexer, 2); 86 87// control buttons 88controlino::Button Layer(Multiplexer, 1); 89controlino::Button Record(Multiplexer, 0); 90 91struct LCD : LiquidCrystal 92{ 93 LCD(uint8_t rs, uint8_t e, uint8_t d4, uint8_t d5, uint8_t d6, uint8_t d7) : LiquidCrystal(rs, e, d4, d5, d6, d7) 94 {} 95 96 template <typename T> 97 char print(const T & arg) 98 { 99 return LiquidCrystal::print(arg); 100 } 101 102 template <typename T> 103 char print(char col, char row, const T & arg) 104 { 105 setCursor(col, row); 106 return print(arg); 107 } 108 109 template <typename T> 110 char print(char col, char row, char max, const T & arg) 111 { 112 const auto written = print(col, row, arg); 113 114 for (unsigned i = 0; i < max - written; ++i) 115 { 116 write(' '); // make sure the non-used characters are clear 117 } 118 119 return written; 120 } 121}; 122 123LCD lcd(/* rs = */ 7, /* e = */ 8, /* d4 = */ 9, /* d5 = */ 10, /* d6 = */ 11, /* d7 = */ 12); 124 125utils::Timer flashing; 126 127} // io 128 129namespace configurer 130{ 131 132enum class Action 133{ 134 None, 135 136 Summary, 137 Focus, 138}; 139 140// a configurer is responsible for updating a single configuration 141// parameter according to changes of an I/O control 142 143struct Configurer 144{ 145 Action(*check)(); 146 void(*update)(); 147}; 148 149Configurer BPM = 150 { 151 .check = []() 152 { 153 if (io::BPM.check() == controlino::Potentiometer::Event::Changed) 154 { 155 return Action::Summary; 156 } 157 158 return Action::None; 159 }, 160 .update = []() 161 { 162 state::sequencer.bpm = io::BPM.read(); 163 }, 164 }; 165 166Configurer Note = 167 { 168 .check = []() 169 { 170 if (io::Note.check() == controlino::Key::Event::Down) 171 { 172 return Action::Summary; 173 } 174 175 return Action::None; 176 }, 177 .update = []() 178 { 179 if (state::config->accidental() == midier::Accidental::Flat) 180 { 181 state::config->accidental(midier::Accidental::Natural); 182 } 183 else if (state::config->accidental() == midier::Accidental::Natural) 184 { 185 state::config->accidental(midier::Accidental::Sharp); 186 } 187 else if (state::config->accidental() == midier::Accidental::Sharp) 188 { 189 state::config->accidental(midier::Accidental::Flat); 190 191 if (state::config->note() == midier::Note::C) { state::config->note(midier::Note::D); } 192 else if (state::config->note() == midier::Note::D) { state::config->note(midier::Note::E); } 193 else if (state::config->note() == midier::Note::E) { state::config->note(midier::Note::F); } 194 else if (state::config->note() == midier::Note::F) { state::config->note(midier::Note::G); } 195 else if (state::config->note() == midier::Note::G) { state::config->note(midier::Note::A); } 196 else if (state::config->note() == midier::Note::A) { state::config->note(midier::Note::B); } 197 else if (state::config->note() == midier::Note::B) { state::config->note(midier::Note::C); } 198 } 199 }, 200 }; 201 202Configurer Mode = 203 { 204 .check = []() 205 { 206 if (io::Mode.check() == controlino::Key::Event::Down) 207 { 208 return Action::Focus; 209 } 210 211 return Action::None; 212 }, 213 .update = []() 214 { 215 const auto current = state::config->mode(); 216 const auto next = (midier::Mode)(((unsigned)current + 1) % (unsigned)midier::Mode::Count); 217 218 state::config->mode(next); 219 }, 220 }; 221 222Configurer Octave = 223 { 224 .check = []() 225 { 226 if (io::Octave.check() == controlino::Key::Event::Down) 227 { 228 return Action::Summary; 229 } 230 231 return Action::None; 232 }, 233 .update = []() 234 { 235 const auto current = state::config->octave(); 236 const auto next = (current % 7) + 1; 237 238 state::config->octave(next); 239 }, 240 }; 241 242Configurer Perm = 243 { 244 .check = []() 245 { 246 if (io::Perm.check() == controlino::Key::Event::Down) 247 { 248 return Action::Focus; 249 } 250 251 return Action::None; 252 }, 253 .update = []() 254 { 255 const auto current = state::config->perm(); 256 const auto next = (current + 1) % midier::style::count(state::config->steps()); 257 258 state::config->perm(next); 259 }, 260 }; 261 262Configurer Steps = 263 { 264 .check = []() 265 { 266 if (io::Steps.check() == controlino::Key::Event::Down) 267 { 268 return Action::Focus; 269 } 270 271 return Action::None; 272 }, 273 .update = []() 274 { 275 if (state::config->looped() == false) // we set to loop if currently not looping 276 { 277 state::config->looped(true); 278 } 279 else 280 { 281 unsigned steps = state::config->steps() + 1; 282 283 if (steps > 6) 284 { 285 steps = 3; 286 } 287 288 state::config->steps(steps); 289 state::config->perm(0); // reset the permutation 290 state::config->looped(false); // set as non looping 291 } 292 }, 293 }; 294 295Configurer Rhythm = 296 { 297 .check = []() 298 { 299 if (io::Rhythm.check() == controlino::Key::Event::Down) 300 { 301 return Action::Focus; 302 } 303 304 return Action::None; 305 }, 306 .update = []() 307 { 308 const auto current = state::config->rhythm(); 309 const auto next = (midier::Rhythm)(((unsigned)current + 1) % (unsigned)midier::Rhythm::Count); 310 311 state::config->rhythm(next); 312 } 313 }; 314 315} // configurer 316 317namespace viewer 318{ 319 320enum class What 321{ 322 Title, 323 Data, 324}; 325 326enum class How 327{ 328 Summary, 329 Focus, 330}; 331 332using Viewer = void(*)(What, How); 333 334struct : utils::Timer 335{ 336 // query 337 bool operator==(Viewer other) const { return _viewer == other; } 338 bool operator!=(Viewer other) const { return _viewer != other; } 339 340 // assignment 341 void operator=(Viewer other) { _viewer = other; } 342 343 // access 344 void print(What what, How how) { _viewer(what, how); } 345 346private: 347 Viewer _viewer = nullptr; 348} focused; 349 350void BPM(What what, How how) 351{ 352 assert(how == How::Summary); 353 354 if (what == What::Title) 355 { 356 io::lcd.print(13, 1, "bpm"); 357 } 358 359 if (what == What::Data) 360 { 361 io::lcd.print(9, 1, 3, state::sequencer.bpm); 362 } 363} 364 365void Note(What what, How how) 366{ 367 assert(how == How::Summary); 368 369 if (what == What::Data) 370 { 371 io::lcd.setCursor(0, 0); 372 373 if (state::config->note() == midier::Note::A) { io::lcd.print('A'); } 374 else if (state::config->note() == midier::Note::B) { io::lcd.print('B'); } 375 else if (state::config->note() == midier::Note::C) { io::lcd.print('C'); } 376 else if (state::config->note() == midier::Note::D) { io::lcd.print('D'); } 377 else if (state::config->note() == midier::Note::E) { io::lcd.print('E'); } 378 else if (state::config->note() == midier::Note::F) { io::lcd.print('F'); } 379 else if (state::config->note() == midier::Note::G) { io::lcd.print('G'); } 380 381 if (state::config->accidental() == midier::Accidental::Flat) { io::lcd.print('b'); } 382 else if (state::config->accidental() == midier::Accidental::Natural) { io::lcd.print(' '); } 383 else if (state::config->accidental() == midier::Accidental::Sharp) { io::lcd.print('#'); } 384 } 385} 386 387void Mode(What what, How how) 388{ 389 if (what == What::Data) 390 { 391 midier::mode::Name name; 392 midier::mode::name(state::config->mode(), /* out */ name); 393 394 if (how == How::Summary) 395 { 396 name[3] = '\\0'; // trim the full name into a 3-letter shortcut 397 io::lcd.print(0, 1, name); 398 } 399 else if (how == How::Focus) 400 { 401 io::lcd.print(0, 1, sizeof(name), name); 402 } 403 } 404 else if (what == What::Title && how == How::Focus) 405 { 406 io::lcd.print(0, 0, "Mode: "); 407 } 408} 409 410void Octave(What what, How how) 411{ 412 assert(how == How::Summary); 413 414 if (what == What::Title) 415 { 416 io::lcd.print(3, 0, 'O'); 417 } 418 else if (what == What::Data) 419 { 420 io::lcd.print(4, 0, state::config->octave()); 421 } 422} 423 424void Style(What what, How how) 425{ 426 if (how == How::Summary) 427 { 428 if (what == What::Title) 429 { 430 io::lcd.print(6, 0, 'S'); 431 } 432 else if (what == What::Data) 433 { 434 io::lcd.print(7, 0, state::config->steps()); 435 io::lcd.print(8, 0, state::config->looped() ? '+' : '-'); 436 io::lcd.print(9, 0, 3, state::config->perm() + 1); 437 } 438 } 439 else if (how == How::Focus) 440 { 441 if (what == What::Title) 442 { 443 io::lcd.print(0, 0, "Style: "); 444 } 445 else if (what == What::Data) 446 { 447 io::lcd.print(7, 0, state::config->steps()); 448 io::lcd.print(8, 0, state::config->looped() ? '+' : '-'); 449 io::lcd.print(9, 0, 3, state::config->perm() + 1); 450 451 midier::style::Description desc; 452 midier::style::description(state::config->steps(), state::config->perm(), /* out */ desc); 453 io::lcd.print(0, 1, 16, desc); // all columns in the LCD 454 455 if (state::config->looped()) 456 { 457 io::lcd.setCursor(strlen(desc) + 1, 1); 458 459 for (unsigned i = 0; i < 3; ++i) 460 { 461 io::lcd.print('.'); 462 } 463 } 464 } 465 } 466} 467 468void Rhythm(What what, How how) 469{ 470 if (how == How::Summary) 471 { 472 if (what == What::Title) 473 { 474 io::lcd.print(4, 1, 'R'); 475 } 476 else if (what == What::Data) 477 { 478 io::lcd.print(5, 1, 2, (unsigned)state::config->rhythm() + 1); 479 } 480 } 481 else if (how == How::Focus) 482 { 483 if (what == What::Title) 484 { 485 io::lcd.print(0, 0, "Rhythm #"); 486 } 487 else if (what == What::Data) 488 { 489 io::lcd.print(8, 0, 2, (unsigned)state::config->rhythm() + 1); 490 491 midier::rhythm::Description desc; 492 midier::rhythm::description(state::config->rhythm(), /* out */ desc); 493 io::lcd.print(0, 1, desc); 494 } 495 } 496} 497 498} // viewer 499 500namespace component 501{ 502 503struct Component 504{ 505 configurer::Configurer configurer; 506 viewer::Viewer viewer; 507}; 508 509Component All[] = 510 { 511 { configurer::BPM, viewer::BPM }, 512 { configurer::Note, viewer::Note }, 513 { configurer::Mode, viewer::Mode }, 514 { configurer::Octave, viewer::Octave }, 515 { configurer::Perm, viewer::Style }, 516 { configurer::Steps, viewer::Style }, 517 { configurer::Rhythm, viewer::Rhythm }, 518 }; 519 520} // component 521 522namespace control 523{ 524 525void flash() 526{ 527 if (io::flashing.ticking()) 528 { 529 return; // already flashing 530 } 531 532 digitalWrite(13, HIGH); 533 io::flashing.start(); 534} 535 536namespace view 537{ 538 539void summary(viewer::Viewer viewer = nullptr) // 'nullptr' means all components 540{ 541 if (viewer::focused != nullptr) // some viewer is currently in focus 542 { 543 viewer::focused.stop(); // stop the timer 544 viewer::focused = nullptr; // mark as there's no viewer currently in focus 545 io::lcd.clear(); // clear the screen entirely 546 viewer = nullptr; // mark to print all titles and values 547 } 548 549 if (viewer == nullptr) 550 { 551 for (const auto & component : component::All) 552 { 553 component.viewer(viewer::What::Title, viewer::How::Summary); 554 component.viewer(viewer::What::Data, viewer::How::Summary); 555 } 556 557 // layers and bars 558 559 io::lcd.setCursor(13, 0); 560 561 char written = 0; 562 563 if (state::layer.layer != nullptr) 564 { 565 written += io::lcd.print('L'); 566 written += io::lcd.print(state::layer.id); 567 } 568 569 while (written++ < 3) 570 { 571 io::lcd.write(' '); 572 } 573 } 574 else 575 { 576 viewer(viewer::What::Data, viewer::How::Summary); 577 } 578} 579 580void focus(viewer::Viewer viewer) 581{ 582 if (viewer::focused != viewer) // either in summary mode or another viewer is currently in focus 583 { 584 io::lcd.clear(); // clear the screen entirely 585 viewer::focused = viewer; // mark this viewer as the one being in focus 586 viewer::focused.print(viewer::What::Title, viewer::How::Focus); // print the title (only if just became the one in focus) 587 } 588 589 viewer::focused.print(viewer::What::Data, viewer::How::Focus); // print the data anyways 590 viewer::focused.start(); // start the timer or restart it if ticking already 591} 592 593void bar(midier::Sequencer::Bar bar) 594{ 595 io::lcd.setCursor(14, 0); 596 597 char written = 0; 598 599 if (bar != midier::Sequencer::Bar::None) 600 { 601 written = io::lcd.print((unsigned)bar); 602 } 603 604 while (written++ < 2) 605 { 606 io::lcd.write(' '); 607 } 608} 609 610} // view 611 612namespace config 613{ 614 615void layer(midier::Layer * layer, unsigned char id) // `nullptr` means go back to global 616{ 617 if (state::layer.layer == nullptr && layer == nullptr) 618 { 619 return; // nothing to do 620 } 621 622 // we allow setting the same layer for updating its config and the timer 623 624 state::layer.layer = layer; 625 state::layer.id = id; 626 627 if (layer == nullptr) 628 { 629 // increase the volume of all layers 630 state::sequencer.layers.eval([](midier::Layer & layer) 631 { 632 layer.velocity = midier::midi::Velocity::High; 633 }); 634 635 state::layer.stop(); // stop the timer 636 state::config = &state::sequencer.config; // point to global configuration 637 } 638 else 639 { 640 // lower the volume of all layers 641 state::sequencer.layers.eval([](midier::Layer & layer) 642 { 643 layer.velocity = midier::midi::Velocity::Low; 644 }); 645 646 // increase the volume of the selected layer 647 state::layer.layer->velocity = midier::midi::Velocity::High; 648 649 state::layer.start(); // start ticking 650 state::config = layer->config.view(); // point to this layer's configuration 651 } 652 653 control::view::summary(); 654} 655 656void global() 657{ 658 layer(nullptr, 0); 659} 660 661} // config 662 663} // control 664 665namespace handle 666{ 667 668void flashing() 669{ 670 if (io::flashing.elapsed(70)) 671 { 672 digitalWrite(13, LOW); 673 io::flashing.stop(); 674 } 675} 676 677void recording() 678{ 679 static bool __recording = false; 680 681 const auto recording = state::sequencer.recording(); // is recording at the moment? 682 683 if (__recording != recording) 684 { 685 digitalWrite(A1, recording ? HIGH : LOW); 686 __recording = recording; 687 } 688} 689 690void focus() 691{ 692 if (viewer::focused.elapsed(3200)) 693 { 694 state::layer.reset(); // restart the layer timer 695 696 control::view::summary(); // go back to summary view 697 } 698} 699 700void components() 701{ 702 // components will update the configuration on I/O events 703 704 for (const auto & component : component::All) 705 { 706 const auto action = component.configurer.check(); 707 708 if (action == configurer::Action::None) 709 { 710 continue; // nothing to do 711 } 712 713 const auto layered = (state::layer.layer != nullptr) && (component.viewer != viewer::BPM); // all configurers but BPM are per layer 714 715 if (layered) 716 { 717 state::layer.start(); // start ticking 718 } 719 720 // update the configuration only if in summary mode or if this configurer is in focus 721 722 if ((action == configurer::Action::Summary && viewer::focused == nullptr) || 723 (action == configurer::Action::Focus && viewer::focused == component.viewer)) 724 { 725 if (layered && state::layer.layer->config.outer()) 726 { 727 // the selected layer should now detach from the global configuration as 728 // it is being configured specifically. 729 state::layer.layer->config = state::sequencer.config; // deep copy the global configuration 730 731 // we also need to point to the configuration of this layer 732 state::config = state::layer.layer->config.view(); 733 } 734 735 component.configurer.update(); 736 } 737 738 if (action == configurer::Action::Summary) 739 { 740 control::view::summary(component.viewer); 741 } 742 else if (action == configurer::Action::Focus) 743 { 744 control::view::focus(component.viewer); 745 } 746 } 747} 748 749void keys() 750{ 751 // we extend `controlino::Key` so we could hold a Midier handle with every key 752 struct Key : controlino::Key 753 { 754 Key(char pin) : controlino::Key(io::Multiplexer, pin) // keys are behind the multiplexer 755 {} 756 757 midier::Sequencer::Handle h; 758 }; 759 760 static Key __keys[] = { 15, 14, 13, 12, 11, 10, 9, 8 }; // channel numbers of the multiplexer 761 762 for (auto i = 0; i < sizeof(__keys) / sizeof(Key); ++i) 763 { 764 auto & key = __keys[i]; 765 766 const auto event = key.check(); 767 768 if (event == Key::Event::None) 769 { 770 continue; // nothing has changed 771 } 772 773 if (event == Key::Event::Down) // a key was pressed 774 { 775 control::config::global(); // go back to global configutarion when playing new layers 776 777 key.h = state::sequencer.start(i + 1); // start playing an arpeggio of the respective scale degree 778 } 779 else if (event == Key::Event::Up) // a key was released 780 { 781 state::sequencer.stop(key.h); // stop playing the arpeggio 782 } 783 } 784} 785 786void record() 787{ 788 const auto event = io::Record.check(); 789 790 if (event == controlino::Button::Event::Click) 791 { 792 state::sequencer.record(); 793 } 794 else if (event == controlino::Button::Event::Press) 795 { 796 if (state::layer.layer == nullptr) 797 { 798 state::sequencer.revoke(); // revoke the last recorded layer as no layer is selected 799 } 800 else 801 { 802 state::layer.layer->revoke(); // revoke the selected layer 803 } 804 } 805 else if (event == controlino::Button::Event::ClickPress) 806 { 807 state::sequencer.wander(); 808 } 809 else 810 { 811 return; 812 } 813 814 control::config::global(); // go back to global configuration 815} 816 817void layer() 818{ 819 if (state::layer.elapsed(6000)) 820 { 821 control::config::global(); // go back to global configuration after 6 seconds 822 } 823 else 824 { 825 const auto event = io::Layer.check(); 826 827 if (event == controlino::Button::Event::Click) // iterate layers 828 { 829 if (viewer::focused != nullptr) 830 { 831 state::layer.reset(); // reset the layer timer only if there's one selected currently 832 833 control::view::summary(); // go back to summary view 834 } 835 else 836 { 837 static const auto __count = state::sequencer.layers.count(); 838 839 static unsigned char __index = 0; 840 841 if (state::layer.layer == nullptr || __index >= __count) 842 { 843 __index = 0; // search from the start again 844 } 845 846 midier::Layer * layer = nullptr; 847 848 while (__index < __count) 849 { 850 midier::Layer & prospect = state::sequencer.layers[__index++]; 851 852 if (prospect.running()) 853 { 854 layer = &prospect; 855 break; 856 } 857 } 858 859 if (layer == nullptr) 860 { 861 control::config::global(); 862 } 863 else 864 { 865 control::config::layer(layer, __index); 866 } 867 } 868 } 869 else if (event == controlino::Button::Event::Press) 870 { 871 if (state::layer.layer != nullptr) // a layer is selected 872 { 873 if (state::layer.layer->config.inner()) 874 { 875 // we make it point to the global configuration 876 state::layer.layer->config = state::config = &state::sequencer.config; 877 878 // reset the timer 879 state::layer.reset(); 880 881 // print the new (global) configuration 882 control::view::summary(); 883 } 884 } 885 else // no layer is selected 886 { 887 // making all previous dynamic layers static 888 889 state::sequencer.layers.eval([](midier::Layer & layer) 890 { 891 if (layer.config.outer()) 892 { 893 layer.config = state::sequencer.config; // make it static and copy the current global configuration 894 } 895 }); 896 } 897 } 898 else if (event == controlino::Button::Event::ClickPress) 899 { 900 // set all layers to be dynamically configured 901 902 state::sequencer.layers.eval([](midier::Layer & layer) 903 { 904 layer.config = &state::sequencer.config; 905 }); 906 907 control::config::global(); 908 } 909 } 910} 911 912void click() 913{ 914 // actually click Midier for it to play the MIDI notes 915 const auto bar = state::sequencer.click(midier::Sequencer::Run::Async); 916 917 if (bar != midier::Sequencer::Bar::Same) 918 { 919 control::flash(); 920 921 if (viewer::focused == nullptr && state::layer.layer == nullptr) 922 { 923 control::view::bar(bar); 924 } 925 } 926} 927 928} // handle 929 930extern "C" void setup() 931{ 932 // initialize the Arduino "Serial" module and set the baud rate 933 // to the same value you are using in your software. 934 // if connected physically using a MIDI 5-DIN connection, use 31250. 935 Serial.begin(9600); 936 937 // initialize the LEDs 938 pinMode(13, OUTPUT); 939 pinMode(A1, OUTPUT); 940 941 // initialize the LCD 942 io::lcd.begin(16, 2); 943 944 // print the initial configuration 945 control::view::summary(); 946} 947 948extern "C" void loop() 949{ 950 handle::flashing(); 951 handle::recording(); 952 handle::focus(); 953 handle::components(); 954 handle::keys(); 955 handle::record(); 956 handle::layer(); 957 handle::click(); 958} 959 960} // arpeggino 961
Tutorial: Step Three - LCD - Part 1 - Sketch
c_cpp
1#include <Controlino.h> 2#include <Midier.h> 3 4namespace arpeggino 5{ 6 7namespace state 8{ 9 10midier::Layers<8> layers; // the number of layers chosen will affect the global variable size 11midier::Sequencer sequencer(layers); 12 13} // state 14 15namespace io 16{ 17 18// here we declare all I/O controls with their corresponding pin numbers 19 20controlino::Selector Selector(/* s0 = */ 6, /* s1 = */ 5, /* s2 = */ 4, /* s3 = */ 3); 21controlino::Multiplexer Multiplexer(/* sig = */ 2, Selector); 22 23controlino::Potentiometer BPM(A0, /* min = */ 20, /* max = */ 230); // we limit the value of BPM to [20,230] 24 25// all configuration keys are behind the multiplexer 26controlino::Key Note(Multiplexer, 7); 27controlino::Key Mode(Multiplexer, 6); 28controlino::Key Octave(Multiplexer, 5); 29controlino::Key Perm(Multiplexer, 4); 30controlino::Key Steps(Multiplexer, 3); 31controlino::Key Rhythm(Multiplexer, 2); 32 33} // io 34 35namespace configurer 36{ 37 38// a configurer is a method that is responsible for updating a single 39// configuration parameter according to changes of an I/O control 40using Configurer = void(*)(); 41 42void BPM() 43{ 44 if (io::BPM.check() == controlino::Potentiometer::Event::Changed) 45 { 46 state::sequencer.bpm = io::BPM.read(); 47 } 48} 49 50void Note() 51{ 52 if (io::Note.check() != controlino::Key::Event::Down) 53 { 54 return; // nothing to do 55 } 56 57 // the key was just pressed 58 59 auto & config = state::sequencer.config; // a shortcut 60 61 if (config.accidental() == midier::Accidental::Flat) 62 { 63 config.accidental(midier::Accidental::Natural); 64 } 65 else if (config.accidental() == midier::Accidental::Natural) 66 { 67 config.accidental(midier::Accidental::Sharp); 68 } 69 else if (config.accidental() == midier::Accidental::Sharp) 70 { 71 config.accidental(midier::Accidental::Flat); 72 73 if (config.note() == midier::Note::C) { config.note(midier::Note::D); } 74 else if (config.note() == midier::Note::D) { config.note(midier::Note::E); } 75 else if (config.note() == midier::Note::E) { config.note(midier::Note::F); } 76 else if (config.note() == midier::Note::F) { config.note(midier::Note::G); } 77 else if (config.note() == midier::Note::G) { config.note(midier::Note::A); } 78 else if (config.note() == midier::Note::A) { config.note(midier::Note::B); } 79 else if (config.note() == midier::Note::B) { config.note(midier::Note::C); } 80 } 81} 82 83void Mode() 84{ 85 if (io::Mode.check() == controlino::Key::Event::Down) 86 { 87 const auto current = state::sequencer.config.mode(); 88 const auto next = (midier::Mode)(((unsigned)current + 1) % (unsigned)midier::Mode::Count); 89 90 state::sequencer.config.mode(next); 91 } 92} 93 94void Octave() 95{ 96 if (io::Octave.check() == controlino::Key::Event::Down) 97 { 98 const auto current = state::sequencer.config.octave(); 99 const auto next = (current % 7) + 1; 100 101 state::sequencer.config.octave(next); 102 } 103} 104 105void Perm() 106{ 107 if (io::Perm.check() == controlino::Key::Event::Down) 108 { 109 const auto current = state::sequencer.config.perm(); 110 const auto next = (current + 1) % midier::style::count(state::sequencer.config.steps()); 111 112 state::sequencer.config.perm(next); 113 } 114} 115 116void Steps() 117{ 118 if (io::Steps.check() == controlino::Key::Event::Down) 119 { 120 auto & config = state::sequencer.config; // a shortcut 121 122 if (config.looped() == false) // we set to loop if currently not looping 123 { 124 config.looped(true); 125 } 126 else 127 { 128 unsigned steps = config.steps() + 1; 129 130 if (steps > 6) 131 { 132 steps = 3; 133 } 134 135 config.steps(steps); 136 config.perm(0); // reset the permutation 137 config.looped(false); // set as non looping 138 } 139 } 140} 141 142void Rhythm() 143{ 144 if (io::Rhythm.check() == controlino::Key::Event::Down) 145 { 146 const auto current = state::sequencer.config.rhythm(); 147 const auto next = (midier::Rhythm)(((unsigned)current + 1) % (unsigned)midier::Rhythm::Count); 148 149 state::sequencer.config.rhythm(next); 150 } 151} 152 153Configurer All[] = 154 { 155 BPM, 156 Note, 157 Mode, 158 Octave, 159 Perm, 160 Steps, 161 Rhythm, 162 }; 163 164} // configurer 165 166namespace handle 167{ 168 169void configurers() 170{ 171 // configurers will update the configuration on I/O events 172 173 for (const auto & configurer : configurer::All) 174 { 175 configurer(); 176 } 177} 178 179void keys() 180{ 181 // we extend `controlino::Key` so we could hold a Midier handle with every key 182 struct Key : controlino::Key 183 { 184 Key(char pin) : controlino::Key(io::Multiplexer, pin) // keys are behind the multiplexer 185 {} 186 187 midier::Sequencer::Handle h; 188 }; 189 190 static Key __keys[] = { 15, 14, 13, 12, 11, 10, 9, 8 }; // channel numbers of the multiplexer 191 192 for (auto i = 0; i < sizeof(__keys) / sizeof(Key); ++i) 193 { 194 auto & key = __keys[i]; 195 196 const auto event = key.check(); 197 198 if (event == Key::Event::None) 199 { 200 continue; // nothing has changed 201 } 202 203 if (event == Key::Event::Down) // a key was pressed 204 { 205 key.h = state::sequencer.start(i + 1); // start playing an arpeggio of the respective scale degree 206 } 207 else if (event == Key::Event::Up) // a key was released 208 { 209 state::sequencer.stop(key.h); // stop playing the arpeggio 210 } 211 } 212} 213 214void click() 215{ 216 // actually click Midier for it to play the MIDI notes 217 state::sequencer.click(midier::Sequencer::Run::Async); 218} 219 220} // handle 221 222extern "C" void setup() 223{ 224 // initialize the Arduino "Serial" module and set the baud rate 225 // to the same value you are using in your software. 226 // if connected physically using a MIDI 5-DIN connection, use 31250. 227 Serial.begin(9600); 228} 229 230extern "C" void loop() 231{ 232 handle::configurers(); 233 handle::keys(); 234 handle::click(); 235} 236 237} // arpeggino 238
Tutorial: Step One - Playing Arpeggios - Sketch
c_cpp
1#include <Controlino.h> 2#include <Midier.h> 3 4namespace arpeggino 5{ 6 7namespace state 8{ 9 10midier::Layers<8> layers; // the number of layers chosen will affect the global variable size 11midier::Sequencer sequencer(layers); 12 13} // state 14 15namespace handle 16{ 17 18void keys() 19{ 20 // we extend `controlino::Key` so we could hold a Midier handle with every key 21 struct Key : controlino::Key 22 { 23 Key(char pin) : controlino::Key(pin) 24 {} 25 26 midier::Sequencer::Handle h; 27 }; 28 29 static Key __keys[] = { 2, 3, 4, 5, 6, 7, 8, 9 }; // initialize with pin numbers 30 31 for (auto i = 0; i < sizeof(__keys) / sizeof(Key); ++i) 32 { 33 auto & key = __keys[i]; 34 35 const auto event = key.check(); 36 37 if (event == Key::Event::None) 38 { 39 continue; // nothing has changed 40 } 41 42 if (event == Key::Event::Down) // a key was pressed 43 { 44 key.h = state::sequencer.start(i + 1); // start playing an arpeggio of the respective scale degree 45 } 46 else if (event == Key::Event::Up) // a key was released 47 { 48 state::sequencer.stop(key.h); // stop playing the arpeggio 49 } 50 } 51} 52 53void click() 54{ 55 // actually click Midier for it to play the MIDI notes 56 state::sequencer.click(midier::Sequencer::Run::Async); 57} 58 59} // handle 60 61extern "C" void setup() 62{ 63 // initialize the Arduino "Serial" module and set the baud rate 64 // to the same value you are using in your software. 65 // if connected physically using a MIDI 5-DIN connection, use 31250. 66 Serial.begin(9600); 67} 68 69extern "C" void loop() 70{ 71 handle::keys(); 72 handle::click(); 73} 74 75} // arpeggino 76
Tutorial: Step Three - LCD - Part 2 - Sketch
c_cpp
1#include <Controlino.h> 2#include <LiquidCrystal.h> 3#include <Midier.h> 4 5namespace arpeggino 6{ 7 8namespace state 9{ 10 11midier::Layers<8> layers; // the number of layers chosen will affect the global variable size 12midier::Sequencer sequencer(layers); 13 14} // state 15 16namespace io 17{ 18 19// here we declare all I/O controls with their corresponding pin numbers 20 21controlino::Selector Selector(/* s0 = */ 6, /* s1 = */ 5, /* s2 = */ 4, /* s3 = */ 3); 22controlino::Multiplexer Multiplexer(/* sig = */ 2, Selector); 23 24controlino::Potentiometer BPM(A0, /* min = */ 20, /* max = */ 230); // we limit the value of BPM to [20,230] 25 26// all configuration keys are behind the multiplexer 27controlino::Key Note(Multiplexer, 7); 28controlino::Key Mode(Multiplexer, 6); 29controlino::Key Octave(Multiplexer, 5); 30controlino::Key Perm(Multiplexer, 4); 31controlino::Key Steps(Multiplexer, 3); 32controlino::Key Rhythm(Multiplexer, 2); 33 34struct LCD : LiquidCrystal 35{ 36 LCD(uint8_t rs, uint8_t e, uint8_t d4, uint8_t d5, uint8_t d6, uint8_t d7) : LiquidCrystal(rs, e, d4, d5, d6, d7) 37 {} 38 39 template <typename T> 40 char print(const T & arg) 41 { 42 return LiquidCrystal::print(arg); 43 } 44 45 template <typename T> 46 char print(char col, char row, const T & arg) 47 { 48 setCursor(col, row); 49 return print(arg); 50 } 51 52 template <typename T> 53 char print(char col, char row, char max, const T & arg) 54 { 55 const auto written = print(col, row, arg); 56 57 for (unsigned i = 0; i < max - written; ++i) 58 { 59 write(' '); // make sure the non-used characters are clear 60 } 61 62 return written; 63 } 64}; 65 66LCD lcd(/* rs = */ 7, /* e = */ 8, /* d4 = */ 9, /* d5 = */ 10, /* d6 = */ 11, /* d7 = */ 12); 67 68} // io 69 70namespace configurer 71{ 72 73// a configurer is a method that is responsible for updating a single 74// configuration parameter according to changes of an I/O control 75using Configurer = bool(*)(); 76 77bool BPM() 78{ 79 if (io::BPM.check() == controlino::Potentiometer::Event::Changed) 80 { 81 state::sequencer.bpm = io::BPM.read(); 82 return true; 83 } 84 85 return false; 86} 87 88bool Note() 89{ 90 if (io::Note.check() != controlino::Key::Event::Down) 91 { 92 return false; // nothing to do 93 } 94 95 // the key was just pressed 96 97 auto & config = state::sequencer.config; // a shortcut 98 99 if (config.accidental() == midier::Accidental::Flat) 100 { 101 config.accidental(midier::Accidental::Natural); 102 } 103 else if (config.accidental() == midier::Accidental::Natural) 104 { 105 config.accidental(midier::Accidental::Sharp); 106 } 107 else if (config.accidental() == midier::Accidental::Sharp) 108 { 109 config.accidental(midier::Accidental::Flat); 110 111 if (config.note() == midier::Note::C) { config.note(midier::Note::D); } 112 else if (config.note() == midier::Note::D) { config.note(midier::Note::E); } 113 else if (config.note() == midier::Note::E) { config.note(midier::Note::F); } 114 else if (config.note() == midier::Note::F) { config.note(midier::Note::G); } 115 else if (config.note() == midier::Note::G) { config.note(midier::Note::A); } 116 else if (config.note() == midier::Note::A) { config.note(midier::Note::B); } 117 else if (config.note() == midier::Note::B) { config.note(midier::Note::C); } 118 } 119 120 return true; 121} 122 123bool Mode() 124{ 125 if (io::Mode.check() == controlino::Key::Event::Down) 126 { 127 const auto current = state::sequencer.config.mode(); 128 const auto next = (midier::Mode)(((unsigned)current + 1) % (unsigned)midier::Mode::Count); 129 130 state::sequencer.config.mode(next); 131 return true; 132 } 133 134 return false; 135} 136 137bool Octave() 138{ 139 if (io::Octave.check() == controlino::Key::Event::Down) 140 { 141 const auto current = state::sequencer.config.octave(); 142 const auto next = (current % 7) + 1; 143 144 state::sequencer.config.octave(next); 145 return true; 146 } 147 148 return false; 149} 150 151bool Perm() 152{ 153 if (io::Perm.check() == controlino::Key::Event::Down) 154 { 155 const auto current = state::sequencer.config.perm(); 156 const auto next = (current + 1) % midier::style::count(state::sequencer.config.steps()); 157 158 state::sequencer.config.perm(next); 159 return true; 160 } 161 162 return false; 163} 164 165bool Steps() 166{ 167 if (io::Steps.check() == controlino::Key::Event::Down) 168 { 169 auto & config = state::sequencer.config; // a shortcut 170 171 if (config.looped() == false) // we set to loop if currently not looping 172 { 173 config.looped(true); 174 } 175 else 176 { 177 unsigned steps = config.steps() + 1; 178 179 if (steps > 6) 180 { 181 steps = 3; 182 } 183 184 config.steps(steps); 185 config.perm(0); // reset the permutation 186 config.looped(false); // set as non looping 187 } 188 189 return true; 190 } 191 192 return false; 193} 194 195bool Rhythm() 196{ 197 if (io::Rhythm.check() == controlino::Key::Event::Down) 198 { 199 const auto current = state::sequencer.config.rhythm(); 200 const auto next = (midier::Rhythm)(((unsigned)current + 1) % (unsigned)midier::Rhythm::Count); 201 202 state::sequencer.config.rhythm(next); 203 return true; 204 } 205 206 return false; 207} 208 209} // configurer 210 211namespace viewer 212{ 213 214enum class What 215{ 216 Title, 217 Data, 218}; 219 220using Viewer = void(*)(What); 221 222void BPM(What what) 223{ 224 if (what == What::Title) 225 { 226 io::lcd.print(13, 1, "bpm"); 227 } 228 229 if (what == What::Data) 230 { 231 io::lcd.print(9, 1, 3, state::sequencer.bpm); 232 } 233} 234 235void Note(What what) 236{ 237 if (what == What::Data) 238 { 239 io::lcd.setCursor(0, 0); 240 241 const auto & config = state::sequencer.config; // a shortcut 242 243 if (config.note() == midier::Note::A) { io::lcd.print('A'); } 244 else if (config.note() == midier::Note::B) { io::lcd.print('B'); } 245 else if (config.note() == midier::Note::C) { io::lcd.print('C'); } 246 else if (config.note() == midier::Note::D) { io::lcd.print('D'); } 247 else if (config.note() == midier::Note::E) { io::lcd.print('E'); } 248 else if (config.note() == midier::Note::F) { io::lcd.print('F'); } 249 else if (config.note() == midier::Note::G) { io::lcd.print('G'); } 250 251 if (config.accidental() == midier::Accidental::Flat) { io::lcd.print('b'); } 252 else if (config.accidental() == midier::Accidental::Natural) { io::lcd.print(' '); } 253 else if (config.accidental() == midier::Accidental::Sharp) { io::lcd.print('#'); } 254 } 255} 256 257void Mode(What what) 258{ 259 if (what == What::Data) 260 { 261 midier::mode::Name name; 262 midier::mode::name(state::sequencer.config.mode(), /* out */ name); 263 name[3] = '\\0'; // trim the full name into a 3-letter shortcut 264 io::lcd.print(0, 1, name); 265 } 266} 267 268void Octave(What what) 269{ 270 if (what == What::Title) 271 { 272 io::lcd.print(3, 0, 'O'); 273 } 274 else if (what == What::Data) 275 { 276 io::lcd.print(4, 0, state::sequencer.config.octave()); 277 } 278} 279 280void Style(What what) 281{ 282 if (what == What::Title) 283 { 284 io::lcd.print(6, 0, 'S'); 285 } 286 else if (what == What::Data) 287 { 288 const auto & config = state::sequencer.config; // a shortcut 289 290 io::lcd.print(7, 0, config.steps()); 291 io::lcd.print(8, 0, config.looped() ? '+' : '-'); 292 io::lcd.print(9, 0, 3, config.perm() + 1); 293 } 294} 295 296void Rhythm(What what) 297{ 298 if (what == What::Title) 299 { 300 io::lcd.print(4, 1, 'R'); 301 } 302 else if (what == What::Data) 303 { 304 io::lcd.print(5, 1, 2, (unsigned)state::sequencer.config.rhythm() + 1); 305 } 306} 307 308} // viewer 309 310namespace component 311{ 312 313struct Component 314{ 315 configurer::Configurer configurer; 316 viewer::Viewer viewer; 317}; 318 319Component All[] = 320 { 321 { configurer::BPM, viewer::BPM }, 322 { configurer::Note, viewer::Note }, 323 { configurer::Mode, viewer::Mode }, 324 { configurer::Octave, viewer::Octave }, 325 { configurer::Perm, viewer::Style }, 326 { configurer::Steps, viewer::Style }, 327 { configurer::Rhythm, viewer::Rhythm }, 328 }; 329 330} // component 331 332namespace handle 333{ 334 335void components() 336{ 337 // components will update the configuration on I/O events 338 339 for (const auto & component : component::All) 340 { 341 if (component.configurer()) 342 { 343 component.viewer(viewer::What::Data); // reprint the value on the LCD if changed 344 } 345 } 346} 347 348void keys() 349{ 350 // we extend `controlino::Key` so we could hold a Midier handle with every key 351 struct Key : controlino::Key 352 { 353 Key(char pin) : controlino::Key(io::Multiplexer, pin) // keys are behind the multiplexer 354 {} 355 356 midier::Sequencer::Handle h; 357 }; 358 359 static Key __keys[] = { 15, 14, 13, 12, 11, 10, 9, 8 }; // channel numbers of the multiplexer 360 361 for (auto i = 0; i < sizeof(__keys) / sizeof(Key); ++i) 362 { 363 auto & key = __keys[i]; 364 365 const auto event = key.check(); 366 367 if (event == Key::Event::None) 368 { 369 continue; // nothing has changed 370 } 371 372 if (event == Key::Event::Down) // a key was pressed 373 { 374 key.h = state::sequencer.start(i + 1); // start playing an arpeggio of the respective scale degree 375 } 376 else if (event == Key::Event::Up) // a key was released 377 { 378 state::sequencer.stop(key.h); // stop playing the arpeggio 379 } 380 } 381} 382 383void click() 384{ 385 // actually click Midier for it to play the MIDI notes 386 state::sequencer.click(midier::Sequencer::Run::Async); 387} 388 389} // handle 390 391extern "C" void setup() 392{ 393 // initialize the Arduino "Serial" module and set the baud rate 394 // to the same value you are using in your software. 395 // if connected physically using a MIDI 5-DIN connection, use 31250. 396 Serial.begin(9600); 397 398 // initialize the LCD 399 io::lcd.begin(16, 2); 400 401 // print the initial configuration 402 for (const auto & component : component::All) 403 { 404 component.viewer(viewer::What::Title); 405 component.viewer(viewer::What::Data); 406 } 407} 408 409extern "C" void loop() 410{ 411 handle::components(); 412 handle::keys(); 413 handle::click(); 414} 415 416} // arpeggino 417
Tutorial: Step Four - Recording - Sketch
c_cpp
1#include <Controlino.h> 2#include <LiquidCrystal.h> 3#include <Midier.h> 4 5#include <assert.h> 6 7namespace arpeggino 8{ 9 10namespace utils 11{ 12 13struct Timer 14{ 15 // control 16 17 void start() // start (or restart) 18 { 19 _millis = millis(); 20 } 21 22 void reset() // restart only if ticking 23 { 24 if (ticking()) 25 { 26 start(); 27 } 28 } 29 30 void stop() 31 { 32 _millis = -1; 33 } 34 35 // query 36 37 bool elapsed(unsigned ms) const // only if ticking 38 { 39 return ticking() && millis() - _millis >= ms; 40 } 41 42 bool ticking() const 43 { 44 return _millis != -1; 45 } 46 47private: 48 unsigned long _millis = -1; 49}; 50 51} // utils 52 53namespace state 54{ 55 56midier::Layers<8> layers; // the number of layers chosen will affect the global variable size 57midier::Sequencer sequencer(layers); 58 59} // state 60 61namespace io 62{ 63 64// here we declare all I/O controls with their corresponding pin numbers 65 66controlino::Selector Selector(/* s0 = */ 6, /* s1 = */ 5, /* s2 = */ 4, /* s3 = */ 3); 67controlino::Multiplexer Multiplexer(/* sig = */ 2, Selector); 68 69controlino::Potentiometer BPM(A0, /* min = */ 20, /* max = */ 230); // we limit the value of BPM to [20,230] 70 71// all configuration keys are behind the multiplexer 72controlino::Key Note(Multiplexer, 7); 73controlino::Key Mode(Multiplexer, 6); 74controlino::Key Octave(Multiplexer, 5); 75controlino::Key Perm(Multiplexer, 4); 76controlino::Key Steps(Multiplexer, 3); 77controlino::Key Rhythm(Multiplexer, 2); 78 79// control buttons 80controlino::Button Record(Multiplexer, 0); 81 82struct LCD : LiquidCrystal 83{ 84 LCD(uint8_t rs, uint8_t e, uint8_t d4, uint8_t d5, uint8_t d6, uint8_t d7) : LiquidCrystal(rs, e, d4, d5, d6, d7) 85 {} 86 87 template <typename T> 88 char print(const T & arg) 89 { 90 return LiquidCrystal::print(arg); 91 } 92 93 template <typename T> 94 char print(char col, char row, const T & arg) 95 { 96 setCursor(col, row); 97 return print(arg); 98 } 99 100 template <typename T> 101 char print(char col, char row, char max, const T & arg) 102 { 103 const auto written = print(col, row, arg); 104 105 for (unsigned i = 0; i < max - written; ++i) 106 { 107 write(' '); // make sure the non-used characters are clear 108 } 109 110 return written; 111 } 112}; 113 114LCD lcd(/* rs = */ 7, /* e = */ 8, /* d4 = */ 9, /* d5 = */ 10, /* d6 = */ 11, /* d7 = */ 12); 115 116utils::Timer flashing; 117 118} // io 119 120namespace configurer 121{ 122 123enum class Action 124{ 125 None, 126 127 Summary, 128 Focus, 129}; 130 131// a configurer is responsible for updating a single configuration 132// parameter according to changes of an I/O control 133 134struct Configurer 135{ 136 Action(*check)(); 137 void(*update)(); 138}; 139 140Configurer BPM = 141 { 142 .check = []() 143 { 144 if (io::BPM.check() == controlino::Potentiometer::Event::Changed) 145 { 146 return Action::Summary; 147 } 148 149 return Action::None; 150 }, 151 .update = []() 152 { 153 state::sequencer.bpm = io::BPM.read(); 154 }, 155 }; 156 157Configurer Note = 158 { 159 .check = []() 160 { 161 if (io::Note.check() == controlino::Key::Event::Down) 162 { 163 return Action::Summary; 164 } 165 166 return Action::None; 167 }, 168 .update = []() 169 { 170 auto & config = state::sequencer.config; // a shortcut 171 172 if (config.accidental() == midier::Accidental::Flat) 173 { 174 config.accidental(midier::Accidental::Natural); 175 } 176 else if (config.accidental() == midier::Accidental::Natural) 177 { 178 config.accidental(midier::Accidental::Sharp); 179 } 180 else if (config.accidental() == midier::Accidental::Sharp) 181 { 182 config.accidental(midier::Accidental::Flat); 183 184 if (config.note() == midier::Note::C) { config.note(midier::Note::D); } 185 else if (config.note() == midier::Note::D) { config.note(midier::Note::E); } 186 else if (config.note() == midier::Note::E) { config.note(midier::Note::F); } 187 else if (config.note() == midier::Note::F) { config.note(midier::Note::G); } 188 else if (config.note() == midier::Note::G) { config.note(midier::Note::A); } 189 else if (config.note() == midier::Note::A) { config.note(midier::Note::B); } 190 else if (config.note() == midier::Note::B) { config.note(midier::Note::C); } 191 } 192 }, 193 }; 194 195Configurer Mode = 196 { 197 .check = []() 198 { 199 if (io::Mode.check() == controlino::Key::Event::Down) 200 { 201 return Action::Focus; 202 } 203 204 return Action::None; 205 }, 206 .update = []() 207 { 208 const auto current = state::sequencer.config.mode(); 209 const auto next = (midier::Mode)(((unsigned)current + 1) % (unsigned)midier::Mode::Count); 210 211 state::sequencer.config.mode(next); 212 }, 213 }; 214 215Configurer Octave = 216 { 217 .check = []() 218 { 219 if (io::Octave.check() == controlino::Key::Event::Down) 220 { 221 return Action::Summary; 222 } 223 224 return Action::None; 225 }, 226 .update = []() 227 { 228 const auto current = state::sequencer.config.octave(); 229 const auto next = (current % 7) + 1; 230 231 state::sequencer.config.octave(next); 232 }, 233 }; 234 235Configurer Perm = 236 { 237 .check = []() 238 { 239 if (io::Perm.check() == controlino::Key::Event::Down) 240 { 241 return Action::Focus; 242 } 243 244 return Action::None; 245 }, 246 .update = []() 247 { 248 const auto current = state::sequencer.config.perm(); 249 const auto next = (current + 1) % midier::style::count(state::sequencer.config.steps()); 250 251 state::sequencer.config.perm(next); 252 }, 253 }; 254 255Configurer Steps = 256 { 257 .check = []() 258 { 259 if (io::Steps.check() == controlino::Key::Event::Down) 260 { 261 return Action::Focus; 262 } 263 264 return Action::None; 265 }, 266 .update = []() 267 { 268 auto & config = state::sequencer.config; // a shortcut 269 270 if (config.looped() == false) // we set to loop if currently not looping 271 { 272 config.looped(true); 273 } 274 else 275 { 276 unsigned steps = config.steps() + 1; 277 278 if (steps > 6) 279 { 280 steps = 3; 281 } 282 283 config.steps(steps); 284 config.perm(0); // reset the permutation 285 config.looped(false); // set as non looping 286 } 287 }, 288 }; 289 290Configurer Rhythm = 291 { 292 .check = []() 293 { 294 if (io::Rhythm.check() == controlino::Key::Event::Down) 295 { 296 return Action::Focus; 297 } 298 299 return Action::None; 300 }, 301 .update = []() 302 { 303 const auto current = state::sequencer.config.rhythm(); 304 const auto next = (midier::Rhythm)(((unsigned)current + 1) % (unsigned)midier::Rhythm::Count); 305 306 state::sequencer.config.rhythm(next); 307 } 308 }; 309 310} // configurer 311 312namespace viewer 313{ 314 315enum class What 316{ 317 Title, 318 Data, 319}; 320 321enum class How 322{ 323 Summary, 324 Focus, 325}; 326 327using Viewer = void(*)(What, How); 328 329struct : utils::Timer 330{ 331 // query 332 bool operator==(Viewer other) const { return _viewer == other; } 333 bool operator!=(Viewer other) const { return _viewer != other; } 334 335 // assignment 336 void operator=(Viewer other) { _viewer = other; } 337 338 // access 339 void print(What what, How how) { _viewer(what, how); } 340 341private: 342 Viewer _viewer = nullptr; 343} focused; 344 345void BPM(What what, How how) 346{ 347 assert(how == How::Summary); 348 349 if (what == What::Title) 350 { 351 io::lcd.print(13, 1, "bpm"); 352 } 353 354 if (what == What::Data) 355 { 356 io::lcd.print(9, 1, 3, state::sequencer.bpm); 357 } 358} 359 360void Note(What what, How how) 361{ 362 assert(how == How::Summary); 363 364 if (what == What::Data) 365 { 366 io::lcd.setCursor(0, 0); 367 368 const auto & config = state::sequencer.config; // a shortcut 369 370 if (config.note() == midier::Note::A) { io::lcd.print('A'); } 371 else if (config.note() == midier::Note::B) { io::lcd.print('B'); } 372 else if (config.note() == midier::Note::C) { io::lcd.print('C'); } 373 else if (config.note() == midier::Note::D) { io::lcd.print('D'); } 374 else if (config.note() == midier::Note::E) { io::lcd.print('E'); } 375 else if (config.note() == midier::Note::F) { io::lcd.print('F'); } 376 else if (config.note() == midier::Note::G) { io::lcd.print('G'); } 377 378 if (config.accidental() == midier::Accidental::Flat) { io::lcd.print('b'); } 379 else if (config.accidental() == midier::Accidental::Natural) { io::lcd.print(' '); } 380 else if (config.accidental() == midier::Accidental::Sharp) { io::lcd.print('#'); } 381 } 382} 383 384void Mode(What what, How how) 385{ 386 if (what == What::Data) 387 { 388 midier::mode::Name name; 389 midier::mode::name(state::sequencer.config.mode(), /* out */ name); 390 391 if (how == How::Summary) 392 { 393 name[3] = '\\0'; // trim the full name into a 3-letter shortcut 394 io::lcd.print(0, 1, name); 395 } 396 else if (how == How::Focus) 397 { 398 io::lcd.print(0, 1, sizeof(name), name); 399 } 400 } 401 else if (what == What::Title && how == How::Focus) 402 { 403 io::lcd.print(0, 0, "Mode: "); 404 } 405} 406 407void Octave(What what, How how) 408{ 409 assert(how == How::Summary); 410 411 if (what == What::Title) 412 { 413 io::lcd.print(3, 0, 'O'); 414 } 415 else if (what == What::Data) 416 { 417 io::lcd.print(4, 0, state::sequencer.config.octave()); 418 } 419} 420 421void Style(What what, How how) 422{ 423 if (how == How::Summary) 424 { 425 if (what == What::Title) 426 { 427 io::lcd.print(6, 0, 'S'); 428 } 429 else if (what == What::Data) 430 { 431 const auto & config = state::sequencer.config; // a shortcut 432 433 io::lcd.print(7, 0, config.steps()); 434 io::lcd.print(8, 0, config.looped() ? '+' : '-'); 435 io::lcd.print(9, 0, 3, config.perm() + 1); 436 } 437 } 438 else if (how == How::Focus) 439 { 440 if (what == What::Title) 441 { 442 io::lcd.print(0, 0, "Style: "); 443 } 444 else if (what == What::Data) 445 { 446 const auto & config = state::sequencer.config; // a shortcut 447 448 io::lcd.print(7, 0, config.steps()); 449 io::lcd.print(8, 0, config.looped() ? '+' : '-'); 450 io::lcd.print(9, 0, 3, config.perm() + 1); 451 452 midier::style::Description desc; 453 midier::style::description(config.steps(), config.perm(), /* out */ desc); 454 io::lcd.print(0, 1, 16, desc); // all columns in the LCD 455 456 if (config.looped()) 457 { 458 io::lcd.setCursor(strlen(desc) + 1, 1); 459 460 for (unsigned i = 0; i < 3; ++i) 461 { 462 io::lcd.print('.'); 463 } 464 } 465 } 466 } 467} 468 469void Rhythm(What what, How how) 470{ 471 if (how == How::Summary) 472 { 473 if (what == What::Title) 474 { 475 io::lcd.print(4, 1, 'R'); 476 } 477 else if (what == What::Data) 478 { 479 io::lcd.print(5, 1, 2, (unsigned)state::sequencer.config.rhythm() + 1); 480 } 481 } 482 else if (how == How::Focus) 483 { 484 if (what == What::Title) 485 { 486 io::lcd.print(0, 0, "Rhythm #"); 487 } 488 else if (what == What::Data) 489 { 490 io::lcd.print(8, 0, 2, (unsigned)state::sequencer.config.rhythm() + 1); 491 492 midier::rhythm::Description desc; 493 midier::rhythm::description(state::sequencer.config.rhythm(), /* out */ desc); 494 io::lcd.print(0, 1, desc); 495 } 496 } 497} 498 499} // viewer 500 501namespace component 502{ 503 504struct Component 505{ 506 configurer::Configurer configurer; 507 viewer::Viewer viewer; 508}; 509 510Component All[] = 511 { 512 { configurer::BPM, viewer::BPM }, 513 { configurer::Note, viewer::Note }, 514 { configurer::Mode, viewer::Mode }, 515 { configurer::Octave, viewer::Octave }, 516 { configurer::Perm, viewer::Style }, 517 { configurer::Steps, viewer::Style }, 518 { configurer::Rhythm, viewer::Rhythm }, 519 }; 520 521} // component 522 523namespace control 524{ 525 526void flash() 527{ 528 if (io::flashing.ticking()) 529 { 530 return; // already flashing 531 } 532 533 digitalWrite(13, HIGH); 534 io::flashing.start(); 535} 536 537namespace view 538{ 539 540void summary(viewer::Viewer viewer = nullptr) // 'nullptr' means all components 541{ 542 if (viewer::focused != nullptr) // some viewer is currently in focus 543 { 544 viewer::focused.stop(); // stop the timer 545 viewer::focused = nullptr; // mark as there's no viewer currently in focus 546 io::lcd.clear(); // clear the screen entirely 547 viewer = nullptr; // mark to print all titles and values 548 } 549 550 if (viewer == nullptr) 551 { 552 for (const auto & component : component::All) 553 { 554 component.viewer(viewer::What::Title, viewer::How::Summary); 555 component.viewer(viewer::What::Data, viewer::How::Summary); 556 } 557 } 558 else 559 { 560 viewer(viewer::What::Data, viewer::How::Summary); 561 } 562} 563 564void focus(viewer::Viewer viewer) 565{ 566 if (viewer::focused != viewer) // either in summary mode or another viewer is currently in focus 567 { 568 io::lcd.clear(); // clear the screen entirely 569 viewer::focused = viewer; // mark this viewer as the one being in focus 570 viewer::focused.print(viewer::What::Title, viewer::How::Focus); // print the title (only if just became the one in focus) 571 } 572 573 viewer::focused.print(viewer::What::Data, viewer::How::Focus); // print the data anyways 574 viewer::focused.start(); // start the timer or restart it if ticking already 575} 576 577void bar(midier::Sequencer::Bar bar) 578{ 579 io::lcd.setCursor(14, 0); 580 581 char written = 0; 582 583 if (bar != midier::Sequencer::Bar::None) 584 { 585 written = io::lcd.print((unsigned)bar); 586 } 587 588 while (written++ < 2) 589 { 590 io::lcd.write(' '); 591 } 592} 593 594} // view 595 596} // control 597 598namespace handle 599{ 600 601void flashing() 602{ 603 if (io::flashing.elapsed(70)) 604 { 605 digitalWrite(13, LOW); 606 io::flashing.stop(); 607 } 608} 609 610void recording() 611{ 612 static bool __recording = false; 613 614 const auto recording = state::sequencer.recording(); // is recording at the moment? 615 616 if (__recording != recording) 617 { 618 digitalWrite(A1, recording ? HIGH : LOW); 619 __recording = recording; 620 } 621} 622 623void focus() 624{ 625 if (viewer::focused.elapsed(3200)) 626 { 627 control::view::summary(); // go back to summary view 628 } 629} 630 631void components() 632{ 633 // components will update the configuration on I/O events 634 635 for (const auto & component : component::All) 636 { 637 const auto action = component.configurer.check(); 638 639 if (action == configurer::Action::None) 640 { 641 continue; // nothing to do 642 } 643 644 // update the configuration only if in summary mode or if this configurer is in focus 645 646 if ((action == configurer::Action::Summary && viewer::focused == nullptr) || 647 (action == configurer::Action::Focus && viewer::focused == component.viewer)) 648 { 649 component.configurer.update(); 650 } 651 652 if (action == configurer::Action::Summary) 653 { 654 control::view::summary(component.viewer); 655 } 656 else if (action == configurer::Action::Focus) 657 { 658 control::view::focus(component.viewer); 659 } 660 } 661} 662 663void keys() 664{ 665 // we extend `controlino::Key` so we could hold a Midier handle with every key 666 struct Key : controlino::Key 667 { 668 Key(char pin) : controlino::Key(io::Multiplexer, pin) // keys are behind the multiplexer 669 {} 670 671 midier::Sequencer::Handle h; 672 }; 673 674 static Key __keys[] = { 15, 14, 13, 12, 11, 10, 9, 8 }; // channel numbers of the multiplexer 675 676 for (auto i = 0; i < sizeof(__keys) / sizeof(Key); ++i) 677 { 678 auto & key = __keys[i]; 679 680 const auto event = key.check(); 681 682 if (event == Key::Event::None) 683 { 684 continue; // nothing has changed 685 } 686 687 if (event == Key::Event::Down) // a key was pressed 688 { 689 key.h = state::sequencer.start(i + 1); // start playing an arpeggio of the respective scale degree 690 } 691 else if (event == Key::Event::Up) // a key was released 692 { 693 state::sequencer.stop(key.h); // stop playing the arpeggio 694 } 695 } 696} 697 698void record() 699{ 700 const auto event = io::Record.check(); 701 702 if (event == controlino::Button::Event::Click) 703 { 704 state::sequencer.record(); 705 } 706 else if (event == controlino::Button::Event::Press) 707 { 708 state::sequencer.revoke(); // revoke the last recorded layer 709 } 710 else if (event == controlino::Button::Event::ClickPress) 711 { 712 state::sequencer.wander(); 713 } 714} 715 716void click() 717{ 718 // actually click Midier for it to play the MIDI notes 719 const auto bar = state::sequencer.click(midier::Sequencer::Run::Async); 720 721 if (bar != midier::Sequencer::Bar::Same) 722 { 723 control::flash(); 724 725 if (viewer::focused == nullptr) 726 { 727 control::view::bar(bar); 728 } 729 } 730} 731 732} // handle 733 734extern "C" void setup() 735{ 736 // initialize the Arduino "Serial" module and set the baud rate 737 // to the same value you are using in your software. 738 // if connected physically using a MIDI 5-DIN connection, use 31250. 739 Serial.begin(9600); 740 741 // initialize the LEDs 742 pinMode(13, OUTPUT); 743 pinMode(A1, OUTPUT); 744 745 // initialize the LCD 746 io::lcd.begin(16, 2); 747 748 // print the initial configuration 749 control::view::summary(); 750} 751 752extern "C" void loop() 753{ 754 handle::flashing(); 755 handle::recording(); 756 handle::focus(); 757 handle::components(); 758 handle::keys(); 759 handle::record(); 760 handle::click(); 761} 762 763} // arpeggino 764
Tutorial: Step Three - LCD - Part 1 - Sketch
c_cpp
1#include <Controlino.h> 2#include <Midier.h> 3 4namespace arpeggino 5{ 6 7namespace state 8{ 9 10midier::Layers<8> layers; // the number of layers chosen will affect the global variable size 11midier::Sequencer sequencer(layers); 12 13} // state 14 15namespace io 16{ 17 18// here we declare all I/O controls with their corresponding pin numbers 19 20controlino::Selector Selector(/* s0 = */ 6, /* s1 = */ 5, /* s2 = */ 4, /* s3 = */ 3); 21controlino::Multiplexer Multiplexer(/* sig = */ 2, Selector); 22 23controlino::Potentiometer BPM(A0, /* min = */ 20, /* max = */ 230); // we limit the value of BPM to [20,230] 24 25// all configuration keys are behind the multiplexer 26controlino::Key Note(Multiplexer, 7); 27controlino::Key Mode(Multiplexer, 6); 28controlino::Key Octave(Multiplexer, 5); 29controlino::Key Perm(Multiplexer, 4); 30controlino::Key Steps(Multiplexer, 3); 31controlino::Key Rhythm(Multiplexer, 2); 32 33} // io 34 35namespace configurer 36{ 37 38// a configurer is a method that is responsible for updating a single 39// configuration parameter according to changes of an I/O control 40using Configurer = void(*)(); 41 42void BPM() 43{ 44 if (io::BPM.check() == controlino::Potentiometer::Event::Changed) 45 { 46 state::sequencer.bpm = io::BPM.read(); 47 } 48} 49 50void Note() 51{ 52 if (io::Note.check() != controlino::Key::Event::Down) 53 { 54 return; // nothing to do 55 } 56 57 // the key was just pressed 58 59 auto & config = state::sequencer.config; // a shortcut 60 61 if (config.accidental() == midier::Accidental::Flat) 62 { 63 config.accidental(midier::Accidental::Natural); 64 } 65 else if (config.accidental() == midier::Accidental::Natural) 66 { 67 config.accidental(midier::Accidental::Sharp); 68 } 69 else if (config.accidental() == midier::Accidental::Sharp) 70 { 71 config.accidental(midier::Accidental::Flat); 72 73 if (config.note() == midier::Note::C) { config.note(midier::Note::D); } 74 else if (config.note() == midier::Note::D) { config.note(midier::Note::E); } 75 else if (config.note() == midier::Note::E) { config.note(midier::Note::F); } 76 else if (config.note() == midier::Note::F) { config.note(midier::Note::G); } 77 else if (config.note() == midier::Note::G) { config.note(midier::Note::A); } 78 else if (config.note() == midier::Note::A) { config.note(midier::Note::B); } 79 else if (config.note() == midier::Note::B) { config.note(midier::Note::C); } 80 } 81} 82 83void Mode() 84{ 85 if (io::Mode.check() == controlino::Key::Event::Down) 86 { 87 const auto current = state::sequencer.config.mode(); 88 const auto next = (midier::Mode)(((unsigned)current + 1) % (unsigned)midier::Mode::Count); 89 90 state::sequencer.config.mode(next); 91 } 92} 93 94void Octave() 95{ 96 if (io::Octave.check() == controlino::Key::Event::Down) 97 { 98 const auto current = state::sequencer.config.octave(); 99 const auto next = (current % 7) + 1; 100 101 state::sequencer.config.octave(next); 102 } 103} 104 105void Perm() 106{ 107 if (io::Perm.check() == controlino::Key::Event::Down) 108 { 109 const auto current = state::sequencer.config.perm(); 110 const auto next = (current + 1) % midier::style::count(state::sequencer.config.steps()); 111 112 state::sequencer.config.perm(next); 113 } 114} 115 116void Steps() 117{ 118 if (io::Steps.check() == controlino::Key::Event::Down) 119 { 120 auto & config = state::sequencer.config; // a shortcut 121 122 if (config.looped() == false) // we set to loop if currently not looping 123 { 124 config.looped(true); 125 } 126 else 127 { 128 unsigned steps = config.steps() + 1; 129 130 if (steps > 6) 131 { 132 steps = 3; 133 } 134 135 config.steps(steps); 136 config.perm(0); // reset the permutation 137 config.looped(false); // set as non looping 138 } 139 } 140} 141 142void Rhythm() 143{ 144 if (io::Rhythm.check() == controlino::Key::Event::Down) 145 { 146 const auto current = state::sequencer.config.rhythm(); 147 const auto next = (midier::Rhythm)(((unsigned)current + 1) % (unsigned)midier::Rhythm::Count); 148 149 state::sequencer.config.rhythm(next); 150 } 151} 152 153Configurer All[] = 154 { 155 BPM, 156 Note, 157 Mode, 158 Octave, 159 Perm, 160 Steps, 161 Rhythm, 162 }; 163 164} // configurer 165 166namespace handle 167{ 168 169void configurers() 170{ 171 // configurers will update the configuration on I/O events 172 173 for (const auto & configurer : configurer::All) 174 { 175 configurer(); 176 } 177} 178 179void keys() 180{ 181 // we extend `controlino::Key` so we could hold a Midier handle with every key 182 struct Key : controlino::Key 183 { 184 Key(char pin) : controlino::Key(io::Multiplexer, pin) // keys are behind the multiplexer 185 {} 186 187 midier::Sequencer::Handle h; 188 }; 189 190 static Key __keys[] = { 15, 14, 13, 12, 11, 10, 9, 8 }; // channel numbers of the multiplexer 191 192 for (auto i = 0; i < sizeof(__keys) / sizeof(Key); ++i) 193 { 194 auto & key = __keys[i]; 195 196 const auto event = key.check(); 197 198 if (event == Key::Event::None) 199 { 200 continue; // nothing has changed 201 } 202 203 if (event == Key::Event::Down) // a key was pressed 204 { 205 key.h = state::sequencer.start(i + 1); // start playing an arpeggio of the respective scale degree 206 } 207 else if (event == Key::Event::Up) // a key was released 208 { 209 state::sequencer.stop(key.h); // stop playing the arpeggio 210 } 211 } 212} 213 214void click() 215{ 216 // actually click Midier for it to play the MIDI notes 217 state::sequencer.click(midier::Sequencer::Run::Async); 218} 219 220} // handle 221 222extern "C" void setup() 223{ 224 // initialize the Arduino "Serial" module and set the baud rate 225 // to the same value you are using in your software. 226 // if connected physically using a MIDI 5-DIN connection, use 31250. 227 Serial.begin(9600); 228} 229 230extern "C" void loop() 231{ 232 handle::configurers(); 233 handle::keys(); 234 handle::click(); 235} 236 237} // arpeggino 238
Tutorial: Step Four - Recording - Sketch
c_cpp
1#include <Controlino.h> 2#include <LiquidCrystal.h> 3#include <Midier.h> 4 5#include 6 <assert.h> 7 8namespace arpeggino 9{ 10 11namespace utils 12{ 13 14struct 15 Timer 16{ 17 // control 18 19 void start() // start (or restart) 20 { 21 22 _millis = millis(); 23 } 24 25 void reset() // restart only if 26 ticking 27 { 28 if (ticking()) 29 { 30 start(); 31 32 } 33 } 34 35 void stop() 36 { 37 _millis = -1; 38 39 } 40 41 // query 42 43 bool elapsed(unsigned ms) const // only if 44 ticking 45 { 46 return ticking() && millis() - _millis >= ms; 47 } 48 49 50 bool ticking() const 51 { 52 return _millis != -1; 53 } 54 55private: 56 57 unsigned long _millis = -1; 58}; 59 60} // utils 61 62namespace state 63{ 64 65midier::Layers<8> 66 layers; // the number of layers chosen will affect the global variable size 67midier::Sequencer 68 sequencer(layers); 69 70} // state 71 72namespace io 73{ 74 75// here we 76 declare all I/O controls with their corresponding pin numbers 77 78controlino::Selector 79 Selector(/* s0 = */ 6, /* s1 = */ 5, /* s2 = */ 4, /* s3 = */ 3); 80controlino::Multiplexer 81 Multiplexer(/* sig = */ 2, Selector); 82 83controlino::Potentiometer BPM(A0, /* 84 min = */ 20, /* max = */ 230); // we limit the value of BPM to [20,230] 85 86// 87 all configuration keys are behind the multiplexer 88controlino::Key Note(Multiplexer, 89 7); 90controlino::Key Mode(Multiplexer, 6); 91controlino::Key Octave(Multiplexer, 92 5); 93controlino::Key Perm(Multiplexer, 4); 94controlino::Key Steps(Multiplexer, 95 3); 96controlino::Key Rhythm(Multiplexer, 2); 97 98// control buttons 99controlino::Button 100 Record(Multiplexer, 0); 101 102struct LCD : LiquidCrystal 103{ 104 LCD(uint8_t 105 rs, uint8_t e, uint8_t d4, uint8_t d5, uint8_t d6, uint8_t d7) : LiquidCrystal(rs, 106 e, d4, d5, d6, d7) 107 {} 108 109 template <typename T> 110 char print(const 111 T & arg) 112 { 113 return LiquidCrystal::print(arg); 114 } 115 116 117 template <typename T> 118 char print(char col, char row, const T & arg) 119 120 { 121 setCursor(col, row); 122 return print(arg); 123 } 124 125 126 template <typename T> 127 char print(char col, char row, char max, const 128 T & arg) 129 { 130 const auto written = print(col, row, arg); 131 132 133 for (unsigned i = 0; i < max - written; ++i) 134 { 135 write(' 136 '); // make sure the non-used characters are clear 137 } 138 139 return 140 written; 141 } 142}; 143 144LCD lcd(/* rs = */ 7, /* e = */ 8, /* d4 = */ 9, 145 /* d5 = */ 10, /* d6 = */ 11, /* d7 = */ 12); 146 147utils::Timer flashing; 148 149} 150 // io 151 152namespace configurer 153{ 154 155enum class Action 156{ 157 None, 158 159 160 Summary, 161 Focus, 162}; 163 164// a configurer is responsible for updating 165 a single configuration 166// parameter according to changes of an I/O control 167 168struct 169 Configurer 170{ 171 Action(*check)(); 172 void(*update)(); 173}; 174 175Configurer 176 BPM = 177 { 178 .check = []() 179 { 180 if (io::BPM.check() 181 == controlino::Potentiometer::Event::Changed) 182 { 183 return 184 Action::Summary; 185 } 186 187 return Action::None; 188 189 }, 190 .update = []() 191 { 192 state::sequencer.bpm 193 = io::BPM.read(); 194 }, 195 }; 196 197Configurer Note = 198 { 199 200 .check = []() 201 { 202 if (io::Note.check() == 203 controlino::Key::Event::Down) 204 { 205 return 206 Action::Summary; 207 } 208 209 return Action::None; 210 211 }, 212 .update = []() 213 { 214 auto 215 & config = state::sequencer.config; // a shortcut 216 217 if (config.accidental() 218 == midier::Accidental::Flat) 219 { 220 config.accidental(midier::Accidental::Natural); 221 222 } 223 else if (config.accidental() == midier::Accidental::Natural) 224 225 { 226 config.accidental(midier::Accidental::Sharp); 227 228 } 229 else if (config.accidental() == midier::Accidental::Sharp) 230 231 { 232 config.accidental(midier::Accidental::Flat); 233 234 235 if (config.note() == midier::Note::C) { config.note(midier::Note::D); 236 } 237 else if (config.note() == midier::Note::D) { config.note(midier::Note::E); 238 } 239 else if (config.note() == midier::Note::E) { config.note(midier::Note::F); 240 } 241 else if (config.note() == midier::Note::F) { config.note(midier::Note::G); 242 } 243 else if (config.note() == midier::Note::G) { config.note(midier::Note::A); 244 } 245 else if (config.note() == midier::Note::A) { config.note(midier::Note::B); 246 } 247 else if (config.note() == midier::Note::B) { config.note(midier::Note::C); 248 } 249 } 250 }, 251 }; 252 253Configurer Mode = 254 255 { 256 .check = []() 257 { 258 if (io::Mode.check() 259 == controlino::Key::Event::Down) 260 { 261 return 262 Action::Focus; 263 } 264 265 return Action::None; 266 267 }, 268 .update = []() 269 { 270 const 271 auto current = state::sequencer.config.mode(); 272 const auto next 273 = (midier::Mode)(((unsigned)current + 1) % (unsigned)midier::Mode::Count); 274 275 276 state::sequencer.config.mode(next); 277 }, 278 }; 279 280Configurer 281 Octave = 282 { 283 .check = []() 284 { 285 if 286 (io::Octave.check() == controlino::Key::Event::Down) 287 { 288 return 289 Action::Summary; 290 } 291 292 return Action::None; 293 294 }, 295 .update = []() 296 { 297 const 298 auto current = state::sequencer.config.octave(); 299 const auto next 300 = (current % 7) + 1; 301 302 state::sequencer.config.octave(next); 303 304 }, 305 }; 306 307Configurer Perm = 308 { 309 .check = []() 310 311 { 312 if (io::Perm.check() == controlino::Key::Event::Down) 313 314 { 315 return Action::Focus; 316 } 317 318 319 return Action::None; 320 }, 321 .update = []() 322 323 { 324 const auto current = state::sequencer.config.perm(); 325 326 const auto next = (current + 1) % midier::style::count(state::sequencer.config.steps()); 327 328 329 state::sequencer.config.perm(next); 330 }, 331 }; 332 333Configurer 334 Steps = 335 { 336 .check = []() 337 { 338 if 339 (io::Steps.check() == controlino::Key::Event::Down) 340 { 341 return 342 Action::Focus; 343 } 344 345 return Action::None; 346 347 }, 348 .update = []() 349 { 350 auto 351 & config = state::sequencer.config; // a shortcut 352 353 if (config.looped() 354 == false) // we set to loop if currently not looping 355 { 356 config.looped(true); 357 358 } 359 else 360 { 361 unsigned 362 steps = config.steps() + 1; 363 364 if (steps > 6) 365 { 366 367 steps = 3; 368 } 369 370 config.steps(steps); 371 372 config.perm(0); // reset the permutation 373 config.looped(false); 374 // set as non looping 375 } 376 }, 377 }; 378 379Configurer 380 Rhythm = 381 { 382 .check = []() 383 { 384 if 385 (io::Rhythm.check() == controlino::Key::Event::Down) 386 { 387 return 388 Action::Focus; 389 } 390 391 return Action::None; 392 393 }, 394 .update = []() 395 { 396 const 397 auto current = state::sequencer.config.rhythm(); 398 const auto next 399 = (midier::Rhythm)(((unsigned)current + 1) % (unsigned)midier::Rhythm::Count); 400 401 402 state::sequencer.config.rhythm(next); 403 } 404 }; 405 406} 407 // configurer 408 409namespace viewer 410{ 411 412enum class What 413{ 414 Title, 415 416 Data, 417}; 418 419enum class How 420{ 421 Summary, 422 Focus, 423}; 424 425using 426 Viewer = void(*)(What, How); 427 428struct : utils::Timer 429{ 430 // query 431 432 bool operator==(Viewer other) const { return _viewer == other; } 433 bool 434 operator!=(Viewer other) const { return _viewer != other; } 435 436 // assignment 437 438 void operator=(Viewer other) { _viewer = other; } 439 440 // access 441 void 442 print(What what, How how) { _viewer(what, how); } 443 444private: 445 Viewer 446 _viewer = nullptr; 447} focused; 448 449void BPM(What what, How how) 450{ 451 assert(how 452 == How::Summary); 453 454 if (what == What::Title) 455 { 456 io::lcd.print(13, 457 1, "bpm"); 458 } 459 460 if (what == What::Data) 461 { 462 io::lcd.print(9, 463 1, 3, state::sequencer.bpm); 464 } 465} 466 467void Note(What what, How how) 468{ 469 470 assert(how == How::Summary); 471 472 if (what == What::Data) 473 { 474 475 io::lcd.setCursor(0, 0); 476 477 const auto & config = state::sequencer.config; 478 // a shortcut 479 480 if (config.note() == midier::Note::A) { io::lcd.print('A'); 481 } 482 else if (config.note() == midier::Note::B) { io::lcd.print('B'); } 483 484 else if (config.note() == midier::Note::C) { io::lcd.print('C'); } 485 else 486 if (config.note() == midier::Note::D) { io::lcd.print('D'); } 487 else if 488 (config.note() == midier::Note::E) { io::lcd.print('E'); } 489 else if (config.note() 490 == midier::Note::F) { io::lcd.print('F'); } 491 else if (config.note() == 492 midier::Note::G) { io::lcd.print('G'); } 493 494 if (config.accidental() 495 == midier::Accidental::Flat) { io::lcd.print('b'); } 496 else if (config.accidental() 497 == midier::Accidental::Natural) { io::lcd.print(' '); } 498 else if (config.accidental() 499 == midier::Accidental::Sharp) { io::lcd.print('#'); } 500 } 501} 502 503void 504 Mode(What what, How how) 505{ 506 if (what == What::Data) 507 { 508 midier::mode::Name 509 name; 510 midier::mode::name(state::sequencer.config.mode(), /* out */ name); 511 512 513 if (how == How::Summary) 514 { 515 name[3] = '\\0'; // 516 trim the full name into a 3-letter shortcut 517 io::lcd.print(0, 1, name); 518 519 } 520 else if (how == How::Focus) 521 { 522 io::lcd.print(0, 523 1, sizeof(name), name); 524 } 525 } 526 else if (what == What::Title 527 && how == How::Focus) 528 { 529 io::lcd.print(0, 0, "Mode: "); 530 } 531} 532 533void 534 Octave(What what, How how) 535{ 536 assert(how == How::Summary); 537 538 if 539 (what == What::Title) 540 { 541 io::lcd.print(3, 0, 'O'); 542 } 543 544 else if (what == What::Data) 545 { 546 io::lcd.print(4, 0, state::sequencer.config.octave()); 547 548 } 549} 550 551void Style(What what, How how) 552{ 553 if (how == How::Summary) 554 555 { 556 if (what == What::Title) 557 { 558 io::lcd.print(6, 559 0, 'S'); 560 } 561 else if (what == What::Data) 562 { 563 const 564 auto & config = state::sequencer.config; // a shortcut 565 566 io::lcd.print(7, 567 0, config.steps()); 568 io::lcd.print(8, 0, config.looped() ? '+' : '-'); 569 570 io::lcd.print(9, 0, 3, config.perm() + 1); 571 } 572 } 573 574 else if (how == How::Focus) 575 { 576 if (what == What::Title) 577 578 { 579 io::lcd.print(0, 0, "Style: "); 580 } 581 else 582 if (what == What::Data) 583 { 584 const auto & config = state::sequencer.config; 585 // a shortcut 586 587 io::lcd.print(7, 0, config.steps()); 588 io::lcd.print(8, 589 0, config.looped() ? '+' : '-'); 590 io::lcd.print(9, 0, 3, config.perm() 591 + 1); 592 593 midier::style::Description desc; 594 midier::style::description(config.steps(), 595 config.perm(), /* out */ desc); 596 io::lcd.print(0, 1, 16, desc); // 597 all columns in the LCD 598 599 if (config.looped()) 600 { 601 602 io::lcd.setCursor(strlen(desc) + 1, 1); 603 604 for 605 (unsigned i = 0; i < 3; ++i) 606 { 607 io::lcd.print('.'); 608 609 } 610 } 611 } 612 } 613} 614 615void Rhythm(What 616 what, How how) 617{ 618 if (how == How::Summary) 619 { 620 if (what 621 == What::Title) 622 { 623 io::lcd.print(4, 1, 'R'); 624 } 625 626 else if (what == What::Data) 627 { 628 io::lcd.print(5, 629 1, 2, (unsigned)state::sequencer.config.rhythm() + 1); 630 } 631 } 632 633 else if (how == How::Focus) 634 { 635 if (what == What::Title) 636 637 { 638 io::lcd.print(0, 0, "Rhythm #"); 639 } 640 else 641 if (what == What::Data) 642 { 643 io::lcd.print(8, 0, 2, (unsigned)state::sequencer.config.rhythm() 644 + 1); 645 646 midier::rhythm::Description desc; 647 midier::rhythm::description(state::sequencer.config.rhythm(), 648 /* out */ desc); 649 io::lcd.print(0, 1, desc); 650 } 651 } 652} 653 654} 655 // viewer 656 657namespace component 658{ 659 660struct Component 661{ 662 configurer::Configurer 663 configurer; 664 viewer::Viewer viewer; 665}; 666 667Component All[] = 668 { 669 670 { configurer::BPM, viewer::BPM }, 671 { configurer::Note, viewer::Note 672 }, 673 { configurer::Mode, viewer::Mode }, 674 { configurer::Octave, 675 viewer::Octave }, 676 { configurer::Perm, viewer::Style }, 677 { configurer::Steps, 678 viewer::Style }, 679 { configurer::Rhythm, viewer::Rhythm }, 680 }; 681 682} 683 // component 684 685namespace control 686{ 687 688void flash() 689{ 690 if (io::flashing.ticking()) 691 692 { 693 return; // already flashing 694 } 695 696 digitalWrite(13, 697 HIGH); 698 io::flashing.start(); 699} 700 701namespace view 702{ 703 704void 705 summary(viewer::Viewer viewer = nullptr) // 'nullptr' means all components 706{ 707 708 if (viewer::focused != nullptr) // some viewer is currently in focus 709 { 710 711 viewer::focused.stop(); // stop the timer 712 viewer::focused = nullptr; 713 // mark as there's no viewer currently in focus 714 io::lcd.clear(); // clear 715 the screen entirely 716 viewer = nullptr; // mark to print all titles and 717 values 718 } 719 720 if (viewer == nullptr) 721 { 722 for (const 723 auto & component : component::All) 724 { 725 component.viewer(viewer::What::Title, 726 viewer::How::Summary); 727 component.viewer(viewer::What::Data, viewer::How::Summary); 728 729 } 730 } 731 else 732 { 733 viewer(viewer::What::Data, viewer::How::Summary); 734 735 } 736} 737 738void focus(viewer::Viewer viewer) 739{ 740 if (viewer::focused 741 != viewer) // either in summary mode or another viewer is currently in focus 742 743 { 744 io::lcd.clear(); // clear the screen entirely 745 viewer::focused 746 = viewer; // mark this viewer as the one being in focus 747 viewer::focused.print(viewer::What::Title, 748 viewer::How::Focus); // print the title (only if just became the one in focus) 749 750 } 751 752 viewer::focused.print(viewer::What::Data, viewer::How::Focus); 753 // print the data anyways 754 viewer::focused.start(); // start the timer or 755 restart it if ticking already 756} 757 758void bar(midier::Sequencer::Bar bar) 759{ 760 761 io::lcd.setCursor(14, 0); 762 763 char written = 0; 764 765 if (bar != 766 midier::Sequencer::Bar::None) 767 { 768 written = io::lcd.print((unsigned)bar); 769 770 } 771 772 while (written++ < 2) 773 { 774 io::lcd.write(' '); 775 776 } 777} 778 779} // view 780 781} // control 782 783namespace handle 784{ 785 786void 787 flashing() 788{ 789 if (io::flashing.elapsed(70)) 790 { 791 digitalWrite(13, 792 LOW); 793 io::flashing.stop(); 794 } 795} 796 797void recording() 798{ 799 800 static bool __recording = false; 801 802 const auto recording = state::sequencer.recording(); 803 // is recording at the moment? 804 805 if (__recording != recording) 806 { 807 808 digitalWrite(A1, recording ? HIGH : LOW); 809 __recording = recording; 810 811 } 812} 813 814void focus() 815{ 816 if (viewer::focused.elapsed(3200)) 817 818 { 819 control::view::summary(); // go back to summary view 820 } 821} 822 823void 824 components() 825{ 826 // components will update the configuration on I/O events 827 828 829 for (const auto & component : component::All) 830 { 831 const auto 832 action = component.configurer.check(); 833 834 if (action == configurer::Action::None) 835 836 { 837 continue; // nothing to do 838 } 839 840 // 841 update the configuration only if in summary mode or if this configurer is in focus 842 843 844 if ((action == configurer::Action::Summary && viewer::focused == nullptr) 845 || 846 (action == configurer::Action::Focus && viewer::focused == component.viewer)) 847 848 { 849 component.configurer.update(); 850 } 851 852 if 853 (action == configurer::Action::Summary) 854 { 855 control::view::summary(component.viewer); 856 857 } 858 else if (action == configurer::Action::Focus) 859 { 860 861 control::view::focus(component.viewer); 862 } 863 } 864} 865 866void 867 keys() 868{ 869 // we extend `controlino::Key` so we could hold a Midier handle 870 with every key 871 struct Key : controlino::Key 872 { 873 Key(char 874 pin) : controlino::Key(io::Multiplexer, pin) // keys are behind the multiplexer 875 876 {} 877 878 midier::Sequencer::Handle h; 879 }; 880 881 static 882 Key __keys[] = { 15, 14, 13, 12, 11, 10, 9, 8 }; // channel numbers of the multiplexer 883 884 885 for (auto i = 0; i < sizeof(__keys) / sizeof(Key); ++i) 886 { 887 auto 888 & key = __keys[i]; 889 890 const auto event = key.check(); 891 892 if 893 (event == Key::Event::None) 894 { 895 continue; // nothing has 896 changed 897 } 898 899 if (event == Key::Event::Down) // a key was 900 pressed 901 { 902 key.h = state::sequencer.start(i + 1); // start 903 playing an arpeggio of the respective scale degree 904 } 905 else 906 if (event == Key::Event::Up) // a key was released 907 { 908 state::sequencer.stop(key.h); 909 // stop playing the arpeggio 910 } 911 } 912} 913 914void record() 915{ 916 917 const auto event = io::Record.check(); 918 919 if (event == controlino::Button::Event::Click) 920 921 { 922 state::sequencer.record(); 923 } 924 else if (event == controlino::Button::Event::Press) 925 926 { 927 state::sequencer.revoke(); // revoke the last recorded layer 928 929 } 930 else if (event == controlino::Button::Event::ClickPress) 931 { 932 933 state::sequencer.wander(); 934 } 935} 936 937void click() 938{ 939 // 940 actually click Midier for it to play the MIDI notes 941 const auto bar = state::sequencer.click(midier::Sequencer::Run::Async); 942 943 944 if (bar != midier::Sequencer::Bar::Same) 945 { 946 control::flash(); 947 948 949 if (viewer::focused == nullptr) 950 { 951 control::view::bar(bar); 952 953 } 954 } 955} 956 957} // handle 958 959extern "C" void setup() 960{ 961 962 // initialize the Arduino "Serial" module and set the baud rate 963 // 964 to the same value you are using in your software. 965 // if connected physically 966 using a MIDI 5-DIN connection, use 31250. 967 Serial.begin(9600); 968 969 // 970 initialize the LEDs 971 pinMode(13, OUTPUT); 972 pinMode(A1, OUTPUT); 973 974 975 // initialize the LCD 976 io::lcd.begin(16, 2); 977 978 // print the initial 979 configuration 980 control::view::summary(); 981} 982 983extern "C" void loop() 984{ 985 986 handle::flashing(); 987 handle::recording(); 988 handle::focus(); 989 990 handle::components(); 991 handle::keys(); 992 handle::record(); 993 handle::click(); 994} 995 996} 997 // arpeggino 998
Controlino GitHub repository
Controlino is the Arduino library that is used by Arpeggino for complex I/O controls that can be behind a multiplexer. It offers easy control of buttons and potentiometers, and supports both simple and complex clicking gestures such as: (1) Down (2) Up (3) Click (4) Double Click (Click-Click) (5) Long Click (Press) (6) Double Click and Press (Click-Press) It is fully documented and offers plenty of examples. You can use Controlino outside of Arpeggino to integrate complex click gestures in your projects, and control buttons and potentiometers behind a multiplexer.
Tutorial: Step Three - LCD - Part 3 - Sketch
c_cpp
1#include <Controlino.h> 2#include <LiquidCrystal.h> 3#include <Midier.h> 4 5#include 6 <assert.h> 7 8namespace arpeggino 9{ 10 11namespace utils 12{ 13 14struct 15 Timer 16{ 17 // control 18 19 void start() // start (or restart) 20 { 21 22 _millis = millis(); 23 } 24 25 void reset() // restart only if 26 ticking 27 { 28 if (ticking()) 29 { 30 start(); 31 32 } 33 } 34 35 void stop() 36 { 37 _millis = -1; 38 39 } 40 41 // query 42 43 bool elapsed(unsigned ms) const // only if 44 ticking 45 { 46 return ticking() && millis() - _millis >= ms; 47 } 48 49 50 bool ticking() const 51 { 52 return _millis != -1; 53 } 54 55private: 56 57 unsigned long _millis = -1; 58}; 59 60} // utils 61 62namespace state 63{ 64 65midier::Layers<8> 66 layers; // the number of layers chosen will affect the global variable size 67midier::Sequencer 68 sequencer(layers); 69 70} // state 71 72namespace io 73{ 74 75// here we 76 declare all I/O controls with their corresponding pin numbers 77 78controlino::Selector 79 Selector(/* s0 = */ 6, /* s1 = */ 5, /* s2 = */ 4, /* s3 = */ 3); 80controlino::Multiplexer 81 Multiplexer(/* sig = */ 2, Selector); 82 83controlino::Potentiometer BPM(A0, /* 84 min = */ 20, /* max = */ 230); // we limit the value of BPM to [20,230] 85 86// 87 all configuration keys are behind the multiplexer 88controlino::Key Note(Multiplexer, 89 7); 90controlino::Key Mode(Multiplexer, 6); 91controlino::Key Octave(Multiplexer, 92 5); 93controlino::Key Perm(Multiplexer, 4); 94controlino::Key Steps(Multiplexer, 95 3); 96controlino::Key Rhythm(Multiplexer, 2); 97 98struct LCD : LiquidCrystal 99{ 100 101 LCD(uint8_t rs, uint8_t e, uint8_t d4, uint8_t d5, uint8_t d6, uint8_t d7) : 102 LiquidCrystal(rs, e, d4, d5, d6, d7) 103 {} 104 105 template <typename T> 106 107 char print(const T & arg) 108 { 109 return LiquidCrystal::print(arg); 110 111 } 112 113 template <typename T> 114 char print(char col, char row, const 115 T & arg) 116 { 117 setCursor(col, row); 118 return print(arg); 119 120 } 121 122 template <typename T> 123 char print(char col, char row, char 124 max, const T & arg) 125 { 126 const auto written = print(col, row, arg); 127 128 129 for (unsigned i = 0; i < max - written; ++i) 130 { 131 write(' 132 '); // make sure the non-used characters are clear 133 } 134 135 return 136 written; 137 } 138}; 139 140LCD lcd(/* rs = */ 7, /* e = */ 8, /* d4 = */ 9, 141 /* d5 = */ 10, /* d6 = */ 11, /* d7 = */ 12); 142 143} // io 144 145namespace configurer 146{ 147 148enum 149 class Action 150{ 151 None, 152 153 Summary, 154 Focus, 155}; 156 157// 158 a configurer is responsible for updating a single configuration 159// parameter 160 according to changes of an I/O control 161 162struct Configurer 163{ 164 Action(*check)(); 165 166 void(*update)(); 167}; 168 169Configurer BPM = 170 { 171 .check = 172 []() 173 { 174 if (io::BPM.check() == controlino::Potentiometer::Event::Changed) 175 176 { 177 return Action::Summary; 178 } 179 180 181 return Action::None; 182 }, 183 .update = []() 184 185 { 186 state::sequencer.bpm = io::BPM.read(); 187 }, 188 189 }; 190 191Configurer Note = 192 { 193 .check = []() 194 { 195 196 if (io::Note.check() == controlino::Key::Event::Down) 197 { 198 199 return Action::Summary; 200 } 201 202 return 203 Action::None; 204 }, 205 .update = []() 206 { 207 auto 208 & config = state::sequencer.config; // a shortcut 209 210 if (config.accidental() 211 == midier::Accidental::Flat) 212 { 213 config.accidental(midier::Accidental::Natural); 214 215 } 216 else if (config.accidental() == midier::Accidental::Natural) 217 218 { 219 config.accidental(midier::Accidental::Sharp); 220 221 } 222 else if (config.accidental() == midier::Accidental::Sharp) 223 224 { 225 config.accidental(midier::Accidental::Flat); 226 227 228 if (config.note() == midier::Note::C) { config.note(midier::Note::D); 229 } 230 else if (config.note() == midier::Note::D) { config.note(midier::Note::E); 231 } 232 else if (config.note() == midier::Note::E) { config.note(midier::Note::F); 233 } 234 else if (config.note() == midier::Note::F) { config.note(midier::Note::G); 235 } 236 else if (config.note() == midier::Note::G) { config.note(midier::Note::A); 237 } 238 else if (config.note() == midier::Note::A) { config.note(midier::Note::B); 239 } 240 else if (config.note() == midier::Note::B) { config.note(midier::Note::C); 241 } 242 } 243 }, 244 }; 245 246Configurer Mode = 247 248 { 249 .check = []() 250 { 251 if (io::Mode.check() 252 == controlino::Key::Event::Down) 253 { 254 return 255 Action::Focus; 256 } 257 258 return Action::None; 259 260 }, 261 .update = []() 262 { 263 const 264 auto current = state::sequencer.config.mode(); 265 const auto next 266 = (midier::Mode)(((unsigned)current + 1) % (unsigned)midier::Mode::Count); 267 268 269 state::sequencer.config.mode(next); 270 }, 271 }; 272 273Configurer 274 Octave = 275 { 276 .check = []() 277 { 278 if 279 (io::Octave.check() == controlino::Key::Event::Down) 280 { 281 return 282 Action::Summary; 283 } 284 285 return Action::None; 286 287 }, 288 .update = []() 289 { 290 const 291 auto current = state::sequencer.config.octave(); 292 const auto next 293 = (current % 7) + 1; 294 295 state::sequencer.config.octave(next); 296 297 }, 298 }; 299 300Configurer Perm = 301 { 302 .check = []() 303 304 { 305 if (io::Perm.check() == controlino::Key::Event::Down) 306 307 { 308 return Action::Focus; 309 } 310 311 312 return Action::None; 313 }, 314 .update = []() 315 316 { 317 const auto current = state::sequencer.config.perm(); 318 319 const auto next = (current + 1) % midier::style::count(state::sequencer.config.steps()); 320 321 322 state::sequencer.config.perm(next); 323 }, 324 }; 325 326Configurer 327 Steps = 328 { 329 .check = []() 330 { 331 if 332 (io::Steps.check() == controlino::Key::Event::Down) 333 { 334 return 335 Action::Focus; 336 } 337 338 return Action::None; 339 340 }, 341 .update = []() 342 { 343 auto 344 & config = state::sequencer.config; // a shortcut 345 346 if (config.looped() 347 == false) // we set to loop if currently not looping 348 { 349 config.looped(true); 350 351 } 352 else 353 { 354 unsigned 355 steps = config.steps() + 1; 356 357 if (steps > 6) 358 { 359 360 steps = 3; 361 } 362 363 config.steps(steps); 364 365 config.perm(0); // reset the permutation 366 config.looped(false); 367 // set as non looping 368 } 369 }, 370 }; 371 372Configurer 373 Rhythm = 374 { 375 .check = []() 376 { 377 if 378 (io::Rhythm.check() == controlino::Key::Event::Down) 379 { 380 return 381 Action::Focus; 382 } 383 384 return Action::None; 385 386 }, 387 .update = []() 388 { 389 const 390 auto current = state::sequencer.config.rhythm(); 391 const auto next 392 = (midier::Rhythm)(((unsigned)current + 1) % (unsigned)midier::Rhythm::Count); 393 394 395 state::sequencer.config.rhythm(next); 396 } 397 }; 398 399} 400 // configurer 401 402namespace viewer 403{ 404 405enum class What 406{ 407 Title, 408 409 Data, 410}; 411 412enum class How 413{ 414 Summary, 415 Focus, 416}; 417 418using 419 Viewer = void(*)(What, How); 420 421struct : utils::Timer 422{ 423 // query 424 425 bool operator==(Viewer other) const { return _viewer == other; } 426 bool 427 operator!=(Viewer other) const { return _viewer != other; } 428 429 // assignment 430 431 void operator=(Viewer other) { _viewer = other; } 432 433 // access 434 void 435 print(What what, How how) { _viewer(what, how); } 436 437private: 438 Viewer 439 _viewer = nullptr; 440} focused; 441 442void BPM(What what, How how) 443{ 444 assert(how 445 == How::Summary); 446 447 if (what == What::Title) 448 { 449 io::lcd.print(13, 450 1, "bpm"); 451 } 452 453 if (what == What::Data) 454 { 455 io::lcd.print(9, 456 1, 3, state::sequencer.bpm); 457 } 458} 459 460void Note(What what, How how) 461{ 462 463 assert(how == How::Summary); 464 465 if (what == What::Data) 466 { 467 468 io::lcd.setCursor(0, 0); 469 470 const auto & config = state::sequencer.config; 471 // a shortcut 472 473 if (config.note() == midier::Note::A) { io::lcd.print('A'); 474 } 475 else if (config.note() == midier::Note::B) { io::lcd.print('B'); } 476 477 else if (config.note() == midier::Note::C) { io::lcd.print('C'); } 478 else 479 if (config.note() == midier::Note::D) { io::lcd.print('D'); } 480 else if 481 (config.note() == midier::Note::E) { io::lcd.print('E'); } 482 else if (config.note() 483 == midier::Note::F) { io::lcd.print('F'); } 484 else if (config.note() == 485 midier::Note::G) { io::lcd.print('G'); } 486 487 if (config.accidental() 488 == midier::Accidental::Flat) { io::lcd.print('b'); } 489 else if (config.accidental() 490 == midier::Accidental::Natural) { io::lcd.print(' '); } 491 else if (config.accidental() 492 == midier::Accidental::Sharp) { io::lcd.print('#'); } 493 } 494} 495 496void 497 Mode(What what, How how) 498{ 499 if (what == What::Data) 500 { 501 midier::mode::Name 502 name; 503 midier::mode::name(state::sequencer.config.mode(), /* out */ name); 504 505 506 if (how == How::Summary) 507 { 508 name[3] = '\\0'; // 509 trim the full name into a 3-letter shortcut 510 io::lcd.print(0, 1, name); 511 512 } 513 else if (how == How::Focus) 514 { 515 io::lcd.print(0, 516 1, sizeof(name), name); 517 } 518 } 519 else if (what == What::Title 520 && how == How::Focus) 521 { 522 io::lcd.print(0, 0, "Mode: "); 523 } 524} 525 526void 527 Octave(What what, How how) 528{ 529 assert(how == How::Summary); 530 531 if 532 (what == What::Title) 533 { 534 io::lcd.print(3, 0, 'O'); 535 } 536 537 else if (what == What::Data) 538 { 539 io::lcd.print(4, 0, state::sequencer.config.octave()); 540 541 } 542} 543 544void Style(What what, How how) 545{ 546 if (how == How::Summary) 547 548 { 549 if (what == What::Title) 550 { 551 io::lcd.print(6, 552 0, 'S'); 553 } 554 else if (what == What::Data) 555 { 556 const 557 auto & config = state::sequencer.config; // a shortcut 558 559 io::lcd.print(7, 560 0, config.steps()); 561 io::lcd.print(8, 0, config.looped() ? '+' : '-'); 562 563 io::lcd.print(9, 0, 3, config.perm() + 1); 564 } 565 } 566 567 else if (how == How::Focus) 568 { 569 if (what == What::Title) 570 571 { 572 io::lcd.print(0, 0, "Style: "); 573 } 574 else 575 if (what == What::Data) 576 { 577 const auto & config = state::sequencer.config; 578 // a shortcut 579 580 io::lcd.print(7, 0, config.steps()); 581 io::lcd.print(8, 582 0, config.looped() ? '+' : '-'); 583 io::lcd.print(9, 0, 3, config.perm() 584 + 1); 585 586 midier::style::Description desc; 587 midier::style::description(config.steps(), 588 config.perm(), /* out */ desc); 589 io::lcd.print(0, 1, 16, desc); // 590 all columns in the LCD 591 592 if (config.looped()) 593 { 594 595 io::lcd.setCursor(strlen(desc) + 1, 1); 596 597 for 598 (unsigned i = 0; i < 3; ++i) 599 { 600 io::lcd.print('.'); 601 602 } 603 } 604 } 605 } 606} 607 608void Rhythm(What 609 what, How how) 610{ 611 if (how == How::Summary) 612 { 613 if (what 614 == What::Title) 615 { 616 io::lcd.print(4, 1, 'R'); 617 } 618 619 else if (what == What::Data) 620 { 621 io::lcd.print(5, 622 1, 2, (unsigned)state::sequencer.config.rhythm() + 1); 623 } 624 } 625 626 else if (how == How::Focus) 627 { 628 if (what == What::Title) 629 630 { 631 io::lcd.print(0, 0, "Rhythm #"); 632 } 633 else 634 if (what == What::Data) 635 { 636 io::lcd.print(8, 0, 2, (unsigned)state::sequencer.config.rhythm() 637 + 1); 638 639 midier::rhythm::Description desc; 640 midier::rhythm::description(state::sequencer.config.rhythm(), 641 /* out */ desc); 642 io::lcd.print(0, 1, desc); 643 } 644 } 645} 646 647} 648 // viewer 649 650namespace component 651{ 652 653struct Component 654{ 655 configurer::Configurer 656 configurer; 657 viewer::Viewer viewer; 658}; 659 660Component All[] = 661 { 662 663 { configurer::BPM, viewer::BPM }, 664 { configurer::Note, viewer::Note 665 }, 666 { configurer::Mode, viewer::Mode }, 667 { configurer::Octave, 668 viewer::Octave }, 669 { configurer::Perm, viewer::Style }, 670 { configurer::Steps, 671 viewer::Style }, 672 { configurer::Rhythm, viewer::Rhythm }, 673 }; 674 675} 676 // component 677 678namespace control 679{ 680 681namespace view 682{ 683 684void 685 summary(viewer::Viewer viewer = nullptr) // 'nullptr' means all components 686{ 687 688 if (viewer::focused != nullptr) // some viewer is currently in focus 689 { 690 691 viewer::focused.stop(); // stop the timer 692 viewer::focused = nullptr; 693 // mark as there's no viewer currently in focus 694 io::lcd.clear(); // clear 695 the screen entirely 696 viewer = nullptr; // mark to print all titles and 697 values 698 } 699 700 if (viewer == nullptr) 701 { 702 for (const 703 auto & component : component::All) 704 { 705 component.viewer(viewer::What::Title, 706 viewer::How::Summary); 707 component.viewer(viewer::What::Data, viewer::How::Summary); 708 709 } 710 } 711 else 712 { 713 viewer(viewer::What::Data, viewer::How::Summary); 714 715 } 716} 717 718void focus(viewer::Viewer viewer) 719{ 720 if (viewer::focused 721 != viewer) // either in summary mode or another viewer is currently in focus 722 723 { 724 io::lcd.clear(); // clear the screen entirely 725 viewer::focused 726 = viewer; // mark this viewer as the one being in focus 727 viewer::focused.print(viewer::What::Title, 728 viewer::How::Focus); // print the title (only if just became the one in focus) 729 730 } 731 732 viewer::focused.print(viewer::What::Data, viewer::How::Focus); 733 // print the data anyways 734 viewer::focused.start(); // start the timer or 735 restart it if ticking already 736} 737 738} // view 739 740} // control 741 742namespace 743 handle 744{ 745 746void focus() 747{ 748 if (viewer::focused.elapsed(3200)) 749 750 { 751 control::view::summary(); // go back to summary view 752 } 753} 754 755void 756 components() 757{ 758 // components will update the configuration on I/O events 759 760 761 for (const auto & component : component::All) 762 { 763 const auto 764 action = component.configurer.check(); 765 766 if (action == configurer::Action::None) 767 768 { 769 continue; // nothing to do 770 } 771 772 // 773 update the configuration only if in summary mode or if this configurer is in focus 774 775 776 if ((action == configurer::Action::Summary && viewer::focused == nullptr) 777 || 778 (action == configurer::Action::Focus && viewer::focused == component.viewer)) 779 780 { 781 component.configurer.update(); 782 } 783 784 if 785 (action == configurer::Action::Summary) 786 { 787 control::view::summary(component.viewer); 788 789 } 790 else if (action == configurer::Action::Focus) 791 { 792 793 control::view::focus(component.viewer); 794 } 795 } 796} 797 798void 799 keys() 800{ 801 // we extend `controlino::Key` so we could hold a Midier handle 802 with every key 803 struct Key : controlino::Key 804 { 805 Key(char 806 pin) : controlino::Key(io::Multiplexer, pin) // keys are behind the multiplexer 807 808 {} 809 810 midier::Sequencer::Handle h; 811 }; 812 813 static 814 Key __keys[] = { 15, 14, 13, 12, 11, 10, 9, 8 }; // channel numbers of the multiplexer 815 816 817 for (auto i = 0; i < sizeof(__keys) / sizeof(Key); ++i) 818 { 819 auto 820 & key = __keys[i]; 821 822 const auto event = key.check(); 823 824 if 825 (event == Key::Event::None) 826 { 827 continue; // nothing has 828 changed 829 } 830 831 if (event == Key::Event::Down) // a key was 832 pressed 833 { 834 key.h = state::sequencer.start(i + 1); // start 835 playing an arpeggio of the respective scale degree 836 } 837 else 838 if (event == Key::Event::Up) // a key was released 839 { 840 state::sequencer.stop(key.h); 841 // stop playing the arpeggio 842 } 843 } 844} 845 846void click() 847{ 848 849 // actually click Midier for it to play the MIDI notes 850 state::sequencer.click(midier::Sequencer::Run::Async); 851} 852 853} 854 // handle 855 856extern "C" void setup() 857{ 858 // initialize the Arduino 859 "Serial" module and set the baud rate 860 // to the same value you are using 861 in your software. 862 // if connected physically using a MIDI 5-DIN connection, 863 use 31250. 864 Serial.begin(9600); 865 866 // initialize the LCD 867 io::lcd.begin(16, 868 2); 869 870 // print the initial configuration 871 control::view::summary(); 872} 873 874extern 875 "C" void loop() 876{ 877 handle::focus(); 878 handle::components(); 879 880 handle::keys(); 881 handle::click(); 882} 883 884} // arpeggino 885
Tutorial: Step Three - LCD - Part 2 - Sketch
c_cpp
1#include <Controlino.h> 2#include <LiquidCrystal.h> 3#include <Midier.h> 4 5namespace arpeggino 6{ 7 8namespace state 9{ 10 11midier::Layers<8> layers; // the number of layers chosen will affect the global variable size 12midier::Sequencer sequencer(layers); 13 14} // state 15 16namespace io 17{ 18 19// here we declare all I/O controls with their corresponding pin numbers 20 21controlino::Selector Selector(/* s0 = */ 6, /* s1 = */ 5, /* s2 = */ 4, /* s3 = */ 3); 22controlino::Multiplexer Multiplexer(/* sig = */ 2, Selector); 23 24controlino::Potentiometer BPM(A0, /* min = */ 20, /* max = */ 230); // we limit the value of BPM to [20,230] 25 26// all configuration keys are behind the multiplexer 27controlino::Key Note(Multiplexer, 7); 28controlino::Key Mode(Multiplexer, 6); 29controlino::Key Octave(Multiplexer, 5); 30controlino::Key Perm(Multiplexer, 4); 31controlino::Key Steps(Multiplexer, 3); 32controlino::Key Rhythm(Multiplexer, 2); 33 34struct LCD : LiquidCrystal 35{ 36 LCD(uint8_t rs, uint8_t e, uint8_t d4, uint8_t d5, uint8_t d6, uint8_t d7) : LiquidCrystal(rs, e, d4, d5, d6, d7) 37 {} 38 39 template <typename T> 40 char print(const T & arg) 41 { 42 return LiquidCrystal::print(arg); 43 } 44 45 template <typename T> 46 char print(char col, char row, const T & arg) 47 { 48 setCursor(col, row); 49 return print(arg); 50 } 51 52 template <typename T> 53 char print(char col, char row, char max, const T & arg) 54 { 55 const auto written = print(col, row, arg); 56 57 for (unsigned i = 0; i < max - written; ++i) 58 { 59 write(' '); // make sure the non-used characters are clear 60 } 61 62 return written; 63 } 64}; 65 66LCD lcd(/* rs = */ 7, /* e = */ 8, /* d4 = */ 9, /* d5 = */ 10, /* d6 = */ 11, /* d7 = */ 12); 67 68} // io 69 70namespace configurer 71{ 72 73// a configurer is a method that is responsible for updating a single 74// configuration parameter according to changes of an I/O control 75using Configurer = bool(*)(); 76 77bool BPM() 78{ 79 if (io::BPM.check() == controlino::Potentiometer::Event::Changed) 80 { 81 state::sequencer.bpm = io::BPM.read(); 82 return true; 83 } 84 85 return false; 86} 87 88bool Note() 89{ 90 if (io::Note.check() != controlino::Key::Event::Down) 91 { 92 return false; // nothing to do 93 } 94 95 // the key was just pressed 96 97 auto & config = state::sequencer.config; // a shortcut 98 99 if (config.accidental() == midier::Accidental::Flat) 100 { 101 config.accidental(midier::Accidental::Natural); 102 } 103 else if (config.accidental() == midier::Accidental::Natural) 104 { 105 config.accidental(midier::Accidental::Sharp); 106 } 107 else if (config.accidental() == midier::Accidental::Sharp) 108 { 109 config.accidental(midier::Accidental::Flat); 110 111 if (config.note() == midier::Note::C) { config.note(midier::Note::D); } 112 else if (config.note() == midier::Note::D) { config.note(midier::Note::E); } 113 else if (config.note() == midier::Note::E) { config.note(midier::Note::F); } 114 else if (config.note() == midier::Note::F) { config.note(midier::Note::G); } 115 else if (config.note() == midier::Note::G) { config.note(midier::Note::A); } 116 else if (config.note() == midier::Note::A) { config.note(midier::Note::B); } 117 else if (config.note() == midier::Note::B) { config.note(midier::Note::C); } 118 } 119 120 return true; 121} 122 123bool Mode() 124{ 125 if (io::Mode.check() == controlino::Key::Event::Down) 126 { 127 const auto current = state::sequencer.config.mode(); 128 const auto next = (midier::Mode)(((unsigned)current + 1) % (unsigned)midier::Mode::Count); 129 130 state::sequencer.config.mode(next); 131 return true; 132 } 133 134 return false; 135} 136 137bool Octave() 138{ 139 if (io::Octave.check() == controlino::Key::Event::Down) 140 { 141 const auto current = state::sequencer.config.octave(); 142 const auto next = (current % 7) + 1; 143 144 state::sequencer.config.octave(next); 145 return true; 146 } 147 148 return false; 149} 150 151bool Perm() 152{ 153 if (io::Perm.check() == controlino::Key::Event::Down) 154 { 155 const auto current = state::sequencer.config.perm(); 156 const auto next = (current + 1) % midier::style::count(state::sequencer.config.steps()); 157 158 state::sequencer.config.perm(next); 159 return true; 160 } 161 162 return false; 163} 164 165bool Steps() 166{ 167 if (io::Steps.check() == controlino::Key::Event::Down) 168 { 169 auto & config = state::sequencer.config; // a shortcut 170 171 if (config.looped() == false) // we set to loop if currently not looping 172 { 173 config.looped(true); 174 } 175 else 176 { 177 unsigned steps = config.steps() + 1; 178 179 if (steps > 6) 180 { 181 steps = 3; 182 } 183 184 config.steps(steps); 185 config.perm(0); // reset the permutation 186 config.looped(false); // set as non looping 187 } 188 189 return true; 190 } 191 192 return false; 193} 194 195bool Rhythm() 196{ 197 if (io::Rhythm.check() == controlino::Key::Event::Down) 198 { 199 const auto current = state::sequencer.config.rhythm(); 200 const auto next = (midier::Rhythm)(((unsigned)current + 1) % (unsigned)midier::Rhythm::Count); 201 202 state::sequencer.config.rhythm(next); 203 return true; 204 } 205 206 return false; 207} 208 209} // configurer 210 211namespace viewer 212{ 213 214enum class What 215{ 216 Title, 217 Data, 218}; 219 220using Viewer = void(*)(What); 221 222void BPM(What what) 223{ 224 if (what == What::Title) 225 { 226 io::lcd.print(13, 1, "bpm"); 227 } 228 229 if (what == What::Data) 230 { 231 io::lcd.print(9, 1, 3, state::sequencer.bpm); 232 } 233} 234 235void Note(What what) 236{ 237 if (what == What::Data) 238 { 239 io::lcd.setCursor(0, 0); 240 241 const auto & config = state::sequencer.config; // a shortcut 242 243 if (config.note() == midier::Note::A) { io::lcd.print('A'); } 244 else if (config.note() == midier::Note::B) { io::lcd.print('B'); } 245 else if (config.note() == midier::Note::C) { io::lcd.print('C'); } 246 else if (config.note() == midier::Note::D) { io::lcd.print('D'); } 247 else if (config.note() == midier::Note::E) { io::lcd.print('E'); } 248 else if (config.note() == midier::Note::F) { io::lcd.print('F'); } 249 else if (config.note() == midier::Note::G) { io::lcd.print('G'); } 250 251 if (config.accidental() == midier::Accidental::Flat) { io::lcd.print('b'); } 252 else if (config.accidental() == midier::Accidental::Natural) { io::lcd.print(' '); } 253 else if (config.accidental() == midier::Accidental::Sharp) { io::lcd.print('#'); } 254 } 255} 256 257void Mode(What what) 258{ 259 if (what == What::Data) 260 { 261 midier::mode::Name name; 262 midier::mode::name(state::sequencer.config.mode(), /* out */ name); 263 name[3] = '\\0'; // trim the full name into a 3-letter shortcut 264 io::lcd.print(0, 1, name); 265 } 266} 267 268void Octave(What what) 269{ 270 if (what == What::Title) 271 { 272 io::lcd.print(3, 0, 'O'); 273 } 274 else if (what == What::Data) 275 { 276 io::lcd.print(4, 0, state::sequencer.config.octave()); 277 } 278} 279 280void Style(What what) 281{ 282 if (what == What::Title) 283 { 284 io::lcd.print(6, 0, 'S'); 285 } 286 else if (what == What::Data) 287 { 288 const auto & config = state::sequencer.config; // a shortcut 289 290 io::lcd.print(7, 0, config.steps()); 291 io::lcd.print(8, 0, config.looped() ? '+' : '-'); 292 io::lcd.print(9, 0, 3, config.perm() + 1); 293 } 294} 295 296void Rhythm(What what) 297{ 298 if (what == What::Title) 299 { 300 io::lcd.print(4, 1, 'R'); 301 } 302 else if (what == What::Data) 303 { 304 io::lcd.print(5, 1, 2, (unsigned)state::sequencer.config.rhythm() + 1); 305 } 306} 307 308} // viewer 309 310namespace component 311{ 312 313struct Component 314{ 315 configurer::Configurer configurer; 316 viewer::Viewer viewer; 317}; 318 319Component All[] = 320 { 321 { configurer::BPM, viewer::BPM }, 322 { configurer::Note, viewer::Note }, 323 { configurer::Mode, viewer::Mode }, 324 { configurer::Octave, viewer::Octave }, 325 { configurer::Perm, viewer::Style }, 326 { configurer::Steps, viewer::Style }, 327 { configurer::Rhythm, viewer::Rhythm }, 328 }; 329 330} // component 331 332namespace handle 333{ 334 335void components() 336{ 337 // components will update the configuration on I/O events 338 339 for (const auto & component : component::All) 340 { 341 if (component.configurer()) 342 { 343 component.viewer(viewer::What::Data); // reprint the value on the LCD if changed 344 } 345 } 346} 347 348void keys() 349{ 350 // we extend `controlino::Key` so we could hold a Midier handle with every key 351 struct Key : controlino::Key 352 { 353 Key(char pin) : controlino::Key(io::Multiplexer, pin) // keys are behind the multiplexer 354 {} 355 356 midier::Sequencer::Handle h; 357 }; 358 359 static Key __keys[] = { 15, 14, 13, 12, 11, 10, 9, 8 }; // channel numbers of the multiplexer 360 361 for (auto i = 0; i < sizeof(__keys) / sizeof(Key); ++i) 362 { 363 auto & key = __keys[i]; 364 365 const auto event = key.check(); 366 367 if (event == Key::Event::None) 368 { 369 continue; // nothing has changed 370 } 371 372 if (event == Key::Event::Down) // a key was pressed 373 { 374 key.h = state::sequencer.start(i + 1); // start playing an arpeggio of the respective scale degree 375 } 376 else if (event == Key::Event::Up) // a key was released 377 { 378 state::sequencer.stop(key.h); // stop playing the arpeggio 379 } 380 } 381} 382 383void click() 384{ 385 // actually click Midier for it to play the MIDI notes 386 state::sequencer.click(midier::Sequencer::Run::Async); 387} 388 389} // handle 390 391extern "C" void setup() 392{ 393 // initialize the Arduino "Serial" module and set the baud rate 394 // to the same value you are using in your software. 395 // if connected physically using a MIDI 5-DIN connection, use 31250. 396 Serial.begin(9600); 397 398 // initialize the LCD 399 io::lcd.begin(16, 2); 400 401 // print the initial configuration 402 for (const auto & component : component::All) 403 { 404 component.viewer(viewer::What::Title); 405 component.viewer(viewer::What::Data); 406 } 407} 408 409extern "C" void loop() 410{ 411 handle::components(); 412 handle::keys(); 413 handle::click(); 414} 415 416} // arpeggino 417
Midier GitHub repository
Midier is the engine behind Arpeggino. It is a library written in C++ to play, record, loop and program MIDI notes, arpeggios and sequences on Arduino. It is comprehensively documented, and has plenty of plug-and-play examples available. You can use Midier outside Arpeggino, and integrate MIDI sequences and loops to your own projects easily.
Arpeggino GitHub repository
This is the GitHub repository for the Arpeggino project. It includes the Arduino sketch, all code needed, schemas, and extra files. You can upload it to your Arduino board as-is or you can easily modify the schema to support your own board configuration. The code is written in C++, and you can easily find the places you need to modify the code to adjust it to your boards. A few examples: (1) Instead of having 8 keys, you can start with just a few (2) If you are using a board that has more I/O pins, you can omit the usage of the multiplexer easily (3) Remove the usage of the LCD screen if you don't have one (4) Program your own MIDI sequences and play them when a button gets clicked
Tutorial: Step Five - Layers - Sketch
c_cpp
1#include <Controlino.h> 2#include <LiquidCrystal.h> 3#include <Midier.h> 4 5#include 6 <assert.h> 7 8namespace arpeggino 9{ 10 11namespace utils 12{ 13 14struct 15 Timer 16{ 17 // control 18 19 void start() // start (or restart) 20 { 21 22 _millis = millis(); 23 } 24 25 void reset() // restart only if 26 ticking 27 { 28 if (ticking()) 29 { 30 start(); 31 32 } 33 } 34 35 void stop() 36 { 37 _millis = -1; 38 39 } 40 41 // query 42 43 bool elapsed(unsigned ms) const // only if 44 ticking 45 { 46 return ticking() && millis() - _millis >= ms; 47 } 48 49 50 bool ticking() const 51 { 52 return _millis != -1; 53 } 54 55private: 56 57 unsigned long _millis = -1; 58}; 59 60} // utils 61 62namespace state 63{ 64 65midier::Layers<8> 66 layers; // the number of layers chosen will affect the global variable size 67midier::Sequencer 68 sequencer(layers); 69 70struct : utils::Timer 71{ 72 midier::Layer * layer 73 = nullptr; 74 unsigned char id; 75} layer; 76 77midier::Config * config = 78 &sequencer.config; 79 80} // state 81 82namespace io 83{ 84 85// here we 86 declare all I/O controls with their corresponding pin numbers 87 88controlino::Selector 89 Selector(/* s0 = */ 6, /* s1 = */ 5, /* s2 = */ 4, /* s3 = */ 3); 90controlino::Multiplexer 91 Multiplexer(/* sig = */ 2, Selector); 92 93controlino::Potentiometer BPM(A0, /* 94 min = */ 20, /* max = */ 230); // we limit the value of BPM to [20,230] 95 96// 97 all configuration keys are behind the multiplexer 98controlino::Key Note(Multiplexer, 99 7); 100controlino::Key Mode(Multiplexer, 6); 101controlino::Key Octave(Multiplexer, 102 5); 103controlino::Key Perm(Multiplexer, 4); 104controlino::Key Steps(Multiplexer, 105 3); 106controlino::Key Rhythm(Multiplexer, 2); 107 108// control buttons 109controlino::Button 110 Layer(Multiplexer, 1); 111controlino::Button Record(Multiplexer, 0); 112 113struct 114 LCD : LiquidCrystal 115{ 116 LCD(uint8_t rs, uint8_t e, uint8_t d4, uint8_t d5, 117 uint8_t d6, uint8_t d7) : LiquidCrystal(rs, e, d4, d5, d6, d7) 118 {} 119 120 121 template <typename T> 122 char print(const T & arg) 123 { 124 return 125 LiquidCrystal::print(arg); 126 } 127 128 template <typename T> 129 char 130 print(char col, char row, const T & arg) 131 { 132 setCursor(col, row); 133 134 return print(arg); 135 } 136 137 template <typename T> 138 char 139 print(char col, char row, char max, const T & arg) 140 { 141 const auto 142 written = print(col, row, arg); 143 144 for (unsigned i = 0; i < max - written; 145 ++i) 146 { 147 write(' '); // make sure the non-used characters 148 are clear 149 } 150 151 return written; 152 } 153}; 154 155LCD 156 lcd(/* rs = */ 7, /* e = */ 8, /* d4 = */ 9, /* d5 = */ 10, /* d6 = */ 11, /* d7 157 = */ 12); 158 159utils::Timer flashing; 160 161} // io 162 163namespace configurer 164{ 165 166enum 167 class Action 168{ 169 None, 170 171 Summary, 172 Focus, 173}; 174 175// 176 a configurer is responsible for updating a single configuration 177// parameter 178 according to changes of an I/O control 179 180struct Configurer 181{ 182 Action(*check)(); 183 184 void(*update)(); 185}; 186 187Configurer BPM = 188 { 189 .check = 190 []() 191 { 192 if (io::BPM.check() == controlino::Potentiometer::Event::Changed) 193 194 { 195 return Action::Summary; 196 } 197 198 199 return Action::None; 200 }, 201 .update = []() 202 203 { 204 state::sequencer.bpm = io::BPM.read(); 205 }, 206 207 }; 208 209Configurer Note = 210 { 211 .check = []() 212 { 213 214 if (io::Note.check() == controlino::Key::Event::Down) 215 { 216 217 return Action::Summary; 218 } 219 220 return 221 Action::None; 222 }, 223 .update = []() 224 { 225 if 226 (state::config->accidental() == midier::Accidental::Flat) 227 { 228 229 state::config->accidental(midier::Accidental::Natural); 230 231 } 232 else if (state::config->accidental() == midier::Accidental::Natural) 233 234 { 235 state::config->accidental(midier::Accidental::Sharp); 236 237 } 238 else if (state::config->accidental() == midier::Accidental::Sharp) 239 240 { 241 state::config->accidental(midier::Accidental::Flat); 242 243 244 if (state::config->note() == midier::Note::C) { state::config->note(midier::Note::D); 245 } 246 else if (state::config->note() == midier::Note::D) { state::config->note(midier::Note::E); 247 } 248 else if (state::config->note() == midier::Note::E) { state::config->note(midier::Note::F); 249 } 250 else if (state::config->note() == midier::Note::F) { state::config->note(midier::Note::G); 251 } 252 else if (state::config->note() == midier::Note::G) { state::config->note(midier::Note::A); 253 } 254 else if (state::config->note() == midier::Note::A) { state::config->note(midier::Note::B); 255 } 256 else if (state::config->note() == midier::Note::B) { state::config->note(midier::Note::C); 257 } 258 } 259 }, 260 }; 261 262Configurer Mode = 263 264 { 265 .check = []() 266 { 267 if (io::Mode.check() 268 == controlino::Key::Event::Down) 269 { 270 return 271 Action::Focus; 272 } 273 274 return Action::None; 275 276 }, 277 .update = []() 278 { 279 const 280 auto current = state::config->mode(); 281 const auto next = (midier::Mode)(((unsigned)current 282 + 1) % (unsigned)midier::Mode::Count); 283 284 state::config->mode(next); 285 286 }, 287 }; 288 289Configurer Octave = 290 { 291 .check = 292 []() 293 { 294 if (io::Octave.check() == controlino::Key::Event::Down) 295 296 { 297 return Action::Summary; 298 } 299 300 301 return Action::None; 302 }, 303 .update = []() 304 305 { 306 const auto current = state::config->octave(); 307 308 const auto next = (current % 7) + 1; 309 310 state::config->octave(next); 311 312 }, 313 }; 314 315Configurer Perm = 316 { 317 .check = []() 318 319 { 320 if (io::Perm.check() == controlino::Key::Event::Down) 321 322 { 323 return Action::Focus; 324 } 325 326 327 return Action::None; 328 }, 329 .update = []() 330 331 { 332 const auto current = state::config->perm(); 333 334 const auto next = (current + 1) % midier::style::count(state::config->steps()); 335 336 337 state::config->perm(next); 338 }, 339 }; 340 341Configurer 342 Steps = 343 { 344 .check = []() 345 { 346 if 347 (io::Steps.check() == controlino::Key::Event::Down) 348 { 349 return 350 Action::Focus; 351 } 352 353 return Action::None; 354 355 }, 356 .update = []() 357 { 358 if 359 (state::config->looped() == false) // we set to loop if currently not looping 360 361 { 362 state::config->looped(true); 363 } 364 365 else 366 { 367 unsigned steps 368 = state::config->steps() + 1; 369 370 if (steps > 6) 371 { 372 373 steps = 3; 374 } 375 376 state::config->steps(steps); 377 378 state::config->perm(0); // reset the permutation 379 state::config->looped(false); 380 // set as non looping 381 } 382 }, 383 }; 384 385Configurer 386 Rhythm = 387 { 388 .check = []() 389 { 390 if 391 (io::Rhythm.check() == controlino::Key::Event::Down) 392 { 393 return 394 Action::Focus; 395 } 396 397 return Action::None; 398 399 }, 400 .update = []() 401 { 402 const 403 auto current = state::config->rhythm(); 404 const auto next = (midier::Rhythm)(((unsigned)current 405 + 1) % (unsigned)midier::Rhythm::Count); 406 407 state::config->rhythm(next); 408 409 } 410 }; 411 412} // configurer 413 414namespace viewer 415{ 416 417enum 418 class What 419{ 420 Title, 421 Data, 422}; 423 424enum class How 425{ 426 427 Summary, 428 Focus, 429}; 430 431using Viewer = void(*)(What, How); 432 433struct 434 : utils::Timer 435{ 436 // query 437 bool operator==(Viewer other) const { 438 return _viewer == other; } 439 bool operator!=(Viewer other) const { return _viewer 440 != other; } 441 442 // assignment 443 void operator=(Viewer other) { _viewer 444 = other; } 445 446 // access 447 void print(What what, How how) { _viewer(what, 448 how); } 449 450private: 451 Viewer _viewer = nullptr; 452} focused; 453 454void 455 BPM(What what, How how) 456{ 457 assert(how == How::Summary); 458 459 if (what 460 == What::Title) 461 { 462 io::lcd.print(13, 1, "bpm"); 463 } 464 465 466 if (what == What::Data) 467 { 468 io::lcd.print(9, 1, 3, state::sequencer.bpm); 469 470 } 471} 472 473void Note(What what, How how) 474{ 475 assert(how == How::Summary); 476 477 478 if (what == What::Data) 479 { 480 io::lcd.setCursor(0, 0); 481 482 483 if (state::config->note() == midier::Note::A) { io::lcd.print('A'); 484 } 485 else if (state::config->note() == midier::Note::B) { io::lcd.print('B'); 486 } 487 else if (state::config->note() == midier::Note::C) { io::lcd.print('C'); 488 } 489 else if (state::config->note() == midier::Note::D) { io::lcd.print('D'); 490 } 491 else if (state::config->note() == midier::Note::E) { io::lcd.print('E'); 492 } 493 else if (state::config->note() == midier::Note::F) { io::lcd.print('F'); 494 } 495 else if (state::config->note() == midier::Note::G) { io::lcd.print('G'); 496 } 497 498 if (state::config->accidental() == midier::Accidental::Flat) 499 { io::lcd.print('b'); } 500 else if (state::config->accidental() == midier::Accidental::Natural) 501 { io::lcd.print(' '); } 502 else if (state::config->accidental() == midier::Accidental::Sharp) 503 { io::lcd.print('#'); } 504 } 505} 506 507void Mode(What what, How how) 508{ 509 510 if (what == What::Data) 511 { 512 midier::mode::Name name; 513 midier::mode::name(state::config->mode(), 514 /* out */ name); 515 516 if (how == How::Summary) 517 { 518 name[3] 519 = '\\0'; // trim the full name into a 3-letter shortcut 520 io::lcd.print(0, 521 1, name); 522 } 523 else if (how == How::Focus) 524 { 525 io::lcd.print(0, 526 1, sizeof(name), name); 527 } 528 } 529 else if (what == What::Title 530 && how == How::Focus) 531 { 532 io::lcd.print(0, 0, "Mode: "); 533 } 534} 535 536void 537 Octave(What what, How how) 538{ 539 assert(how == How::Summary); 540 541 if 542 (what == What::Title) 543 { 544 io::lcd.print(3, 0, 'O'); 545 } 546 547 else if (what == What::Data) 548 { 549 io::lcd.print(4, 0, state::config->octave()); 550 551 } 552} 553 554void Style(What what, How how) 555{ 556 if (how == How::Summary) 557 558 { 559 if (what == What::Title) 560 { 561 io::lcd.print(6, 562 0, 'S'); 563 } 564 else if (what == What::Data) 565 { 566 io::lcd.print(7, 567 0, state::config->steps()); 568 io::lcd.print(8, 0, state::config->looped() 569 ? '+' : '-'); 570 io::lcd.print(9, 0, 3, state::config->perm() + 1); 571 572 } 573 } 574 else if (how == How::Focus) 575 { 576 if (what 577 == What::Title) 578 { 579 io::lcd.print(0, 0, "Style: "); 580 581 } 582 else if (what == What::Data) 583 { 584 io::lcd.print(7, 585 0, state::config->steps()); 586 io::lcd.print(8, 0, state::config->looped() 587 ? '+' : '-'); 588 io::lcd.print(9, 0, 3, state::config->perm() + 1); 589 590 591 midier::style::Description desc; 592 midier::style::description(state::config->steps(), 593 state::config->perm(), /* out */ desc); 594 io::lcd.print(0, 1, 16, desc); 595 // all columns in the LCD 596 597 if (state::config->looped()) 598 { 599 600 io::lcd.setCursor(strlen(desc) + 1, 1); 601 602 for 603 (unsigned i = 0; i < 3; ++i) 604 { 605 io::lcd.print('.'); 606 607 } 608 } 609 } 610 } 611} 612 613void Rhythm(What 614 what, How how) 615{ 616 if (how == How::Summary) 617 { 618 if (what 619 == What::Title) 620 { 621 io::lcd.print(4, 1, 'R'); 622 } 623 624 else if (what == What::Data) 625 { 626 io::lcd.print(5, 627 1, 2, (unsigned)state::config->rhythm() + 1); 628 } 629 } 630 else 631 if (how == How::Focus) 632 { 633 if (what == What::Title) 634 { 635 636 io::lcd.print(0, 0, "Rhythm #"); 637 } 638 else if (what 639 == What::Data) 640 { 641 io::lcd.print(8, 0, 2, (unsigned)state::config->rhythm() 642 + 1); 643 644 midier::rhythm::Description desc; 645 midier::rhythm::description(state::config->rhythm(), 646 /* out */ desc); 647 io::lcd.print(0, 1, desc); 648 } 649 } 650} 651 652} 653 // viewer 654 655namespace component 656{ 657 658struct Component 659{ 660 configurer::Configurer 661 configurer; 662 viewer::Viewer viewer; 663}; 664 665Component All[] = 666 { 667 668 { configurer::BPM, viewer::BPM }, 669 { configurer::Note, viewer::Note 670 }, 671 { configurer::Mode, viewer::Mode }, 672 { configurer::Octave, 673 viewer::Octave }, 674 { configurer::Perm, viewer::Style }, 675 { configurer::Steps, 676 viewer::Style }, 677 { configurer::Rhythm, viewer::Rhythm }, 678 }; 679 680} 681 // component 682 683namespace control 684{ 685 686void flash() 687{ 688 if (io::flashing.ticking()) 689 690 { 691 return; // already flashing 692 } 693 694 digitalWrite(13, 695 HIGH); 696 io::flashing.start(); 697} 698 699namespace view 700{ 701 702void 703 summary(viewer::Viewer viewer = nullptr) // 'nullptr' means all components 704{ 705 706 if (viewer::focused != nullptr) // some viewer is currently in focus 707 { 708 709 viewer::focused.stop(); // stop the timer 710 viewer::focused = nullptr; 711 // mark as there's no viewer currently in focus 712 io::lcd.clear(); // clear 713 the screen entirely 714 viewer = nullptr; // mark to print all titles and 715 values 716 } 717 718 if (viewer == nullptr) 719 { 720 for (const 721 auto & component : component::All) 722 { 723 component.viewer(viewer::What::Title, 724 viewer::How::Summary); 725 component.viewer(viewer::What::Data, viewer::How::Summary); 726 727 } 728 729 // layers and bars 730 731 io::lcd.setCursor(13, 732 0); 733 734 char written = 0; 735 736 if (state::layer.layer != nullptr) 737 738 { 739 written += io::lcd.print('L'); 740 written += 741 io::lcd.print(state::layer.id); 742 } 743 744 while (written++ < 3) 745 746 { 747 io::lcd.write(' '); 748 } 749 } 750 else 751 752 { 753 viewer(viewer::What::Data, viewer::How::Summary); 754 } 755} 756 757void 758 focus(viewer::Viewer viewer) 759{ 760 if (viewer::focused != viewer) // either 761 in summary mode or another viewer is currently in focus 762 { 763 io::lcd.clear(); 764 // clear the screen entirely 765 viewer::focused = viewer; // mark this viewer 766 as the one being in focus 767 viewer::focused.print(viewer::What::Title, 768 viewer::How::Focus); // print the title (only if just became the one in focus) 769 770 } 771 772 viewer::focused.print(viewer::What::Data, viewer::How::Focus); 773 // print the data anyways 774 viewer::focused.start(); // start the timer or 775 restart it if ticking already 776} 777 778void bar(midier::Sequencer::Bar bar) 779{ 780 781 io::lcd.setCursor(14, 0); 782 783 char written = 0; 784 785 if (bar != 786 midier::Sequencer::Bar::None) 787 { 788 written = io::lcd.print((unsigned)bar); 789 790 } 791 792 while (written++ < 2) 793 { 794 io::lcd.write(' '); 795 796 } 797} 798 799} // view 800 801namespace config 802{ 803 804void layer(midier::Layer 805 * layer, unsigned char id) // `nullptr` means go back to global 806{ 807 if (state::layer.layer 808 == nullptr && layer == nullptr) 809 { 810 return; // nothing to do 811 812 } 813 814 // we allow setting the same layer for updating its config and 815 the timer 816 817 state::layer.layer = layer; 818 state::layer.id = id; 819 820 821 if (layer == nullptr) 822 { 823 // increase the volume of all layers 824 825 state::sequencer.layers.eval([](midier::Layer & layer) 826 { 827 828 layer.velocity = midier::midi::Velocity::High; 829 }); 830 831 832 state::layer.stop(); // stop the timer 833 state::config = &state::sequencer.config; 834 // point to global configuration 835 } 836 else 837 { 838 // lower 839 the volume of all layers 840 state::sequencer.layers.eval([](midier::Layer 841 & layer) 842 { 843 layer.velocity = midier::midi::Velocity::Low; 844 845 }); 846 847 // increase the volume of the selected layer 848 849 state::layer.layer->velocity = midier::midi::Velocity::High; 850 851 state::layer.start(); 852 // start ticking 853 state::config = layer->config.view(); // point to this 854 layer's configuration 855 } 856 857 control::view::summary(); 858} 859 860void 861 global() 862{ 863 layer(nullptr, 0); 864} 865 866} // config 867 868} // control 869 870namespace 871 handle 872{ 873 874void flashing() 875{ 876 if (io::flashing.elapsed(70)) 877 878 { 879 digitalWrite(13, LOW); 880 io::flashing.stop(); 881 } 882} 883 884void 885 recording() 886{ 887 static bool __recording = false; 888 889 const auto recording 890 = state::sequencer.recording(); // is recording at the moment? 891 892 if (__recording 893 != recording) 894 { 895 digitalWrite(A1, recording ? HIGH : LOW); 896 897 __recording = recording; 898 } 899} 900 901void focus() 902{ 903 if 904 (viewer::focused.elapsed(3200)) 905 { 906 state::layer.reset(); // restart 907 the layer timer 908 909 control::view::summary(); // go back to summary view 910 911 } 912} 913 914void components() 915{ 916 // components will update the configuration 917 on I/O events 918 919 for (const auto & component : component::All) 920 { 921 922 const auto action = component.configurer.check(); 923 924 if (action 925 == configurer::Action::None) 926 { 927 continue; // nothing to 928 do 929 } 930 931 const auto layered = (state::layer.layer != nullptr) 932 && (component.viewer != viewer::BPM); // all configurers but BPM are per layer 933 934 935 if (layered) 936 { 937 state::layer.start(); // start 938 ticking 939 } 940 941 // update the configuration only if in summary 942 mode or if this configurer is in focus 943 944 if ((action == configurer::Action::Summary 945 && viewer::focused == nullptr) || 946 (action == configurer::Action::Focus 947 && viewer::focused == component.viewer)) 948 { 949 if (layered 950 && state::layer.layer->config.outer()) 951 { 952 // the 953 selected layer should now detach from the global configuration as 954 // 955 it is being configured specifically. 956 state::layer.layer->config 957 = state::sequencer.config; // deep copy the global configuration 958 959 // 960 we also need to point to the configuration of this layer 961 state::config 962 = state::layer.layer->config.view(); 963 } 964 965 component.configurer.update(); 966 967 } 968 969 if (action == configurer::Action::Summary) 970 { 971 972 control::view::summary(component.viewer); 973 } 974 else 975 if (action == configurer::Action::Focus) 976 { 977 control::view::focus(component.viewer); 978 979 } 980 } 981} 982 983void keys() 984{ 985 // we extend `controlino::Key` 986 so we could hold a Midier handle with every key 987 struct Key : controlino::Key 988 989 { 990 Key(char pin) : controlino::Key(io::Multiplexer, pin) // keys are 991 behind the multiplexer 992 {} 993 994 midier::Sequencer::Handle h; 995 996 }; 997 998 static Key __keys[] = { 15, 14, 13, 12, 11, 10, 9, 8 }; // channel 999 numbers of the multiplexer 1000 1001 for (auto i = 0; i < sizeof(__keys) / sizeof(Key); 1002 ++i) 1003 { 1004 auto & key = __keys[i]; 1005 1006 const auto event 1007 = key.check(); 1008 1009 if (event == Key::Event::None) 1010 { 1011 continue; 1012 // nothing has changed 1013 } 1014 1015 if (event == Key::Event::Down) 1016 // a key was pressed 1017 { 1018 control::config::global(); // go 1019 back to global configutarion when playing new layers 1020 1021 key.h = 1022 state::sequencer.start(i + 1); // start playing an arpeggio of the respective scale 1023 degree 1024 } 1025 else if (event == Key::Event::Up) // a key was released 1026 1027 { 1028 state::sequencer.stop(key.h); // stop playing the arpeggio 1029 1030 } 1031 } 1032} 1033 1034void record() 1035{ 1036 const auto event = io::Record.check(); 1037 1038 1039 if (event == controlino::Button::Event::Click) 1040 { 1041 state::sequencer.record(); 1042 1043 } 1044 else if (event == controlino::Button::Event::Press) 1045 { 1046 if 1047 (state::layer.layer == nullptr) 1048 { 1049 state::sequencer.revoke(); 1050 // revoke the last recorded layer as no layer is selected 1051 } 1052 else 1053 1054 { 1055 state::layer.layer->revoke(); // revoke the selected layer 1056 1057 } 1058 } 1059 else if (event == controlino::Button::Event::ClickPress) 1060 1061 { 1062 state::sequencer.wander(); 1063 } 1064 else 1065 { 1066 return; 1067 1068 } 1069 1070 control::config::global(); // go back to global configuration 1071} 1072 1073void 1074 layer() 1075{ 1076 if (state::layer.elapsed(6000)) 1077 { 1078 control::config::global(); 1079 // go back to global configuration after 6 seconds 1080 } 1081 else 1082 { 1083 1084 const auto event = io::Layer.check(); 1085 1086 if (event == controlino::Button::Event::Click) 1087 // iterate layers 1088 { 1089 if (viewer::focused != nullptr) 1090 1091 { 1092 state::layer.reset(); // reset the layer timer 1093 only if there's one selected currently 1094 1095 control::view::summary(); 1096 // go back to summary view 1097 } 1098 else 1099 { 1100 1101 static const auto __count = state::sequencer.layers.count(); 1102 1103 1104 static unsigned char __index = 0; 1105 1106 if (state::layer.layer 1107 == nullptr || __index >= __count) 1108 { 1109 __index 1110 = 0; // search from the start again 1111 } 1112 1113 midier::Layer 1114 * layer = nullptr; 1115 1116 while (__index < __count) 1117 { 1118 1119 midier::Layer & prospect = state::sequencer.layers[__index++]; 1120 1121 1122 if (prospect.running()) 1123 { 1124 layer 1125 = &prospect; 1126 break; 1127 } 1128 } 1129 1130 1131 if (layer == nullptr) 1132 { 1133 control::config::global(); 1134 1135 } 1136 else 1137 { 1138 control::config::layer(layer, 1139 __index); 1140 } 1141 } 1142 } 1143 else if 1144 (event == controlino::Button::Event::Press) 1145 { 1146 if (state::layer.layer 1147 != nullptr) // a layer is selected 1148 { 1149 if (state::layer.layer->config.inner()) 1150 1151 { 1152 // we make it point to the global configuration 1153 1154 state::layer.layer->config = state::config = &state::sequencer.config; 1155 1156 1157 // reset the timer 1158 state::layer.reset(); 1159 1160 1161 // print the new (global) configuration 1162 control::view::summary(); 1163 1164 } 1165 } 1166 else // no layer is selected 1167 1168 { 1169 // making all previous dynamic layers static 1170 1171 1172 state::sequencer.layers.eval([](midier::Layer & layer) 1173 { 1174 1175 if (layer.config.outer()) 1176 { 1177 1178 layer.config = state::sequencer.config; // make it static 1179 and copy the current global configuration 1180 } 1181 }); 1182 1183 } 1184 } 1185 else if (event == controlino::Button::Event::ClickPress) 1186 1187 { 1188 // set all layers to be dynamically configured 1189 1190 1191 state::sequencer.layers.eval([](midier::Layer & layer) 1192 { 1193 1194 layer.config = &state::sequencer.config; 1195 }); 1196 1197 1198 control::config::global(); 1199 } 1200 } 1201} 1202 1203void click() 1204{ 1205 1206 // actually click Midier for it to play the MIDI notes 1207 const auto bar 1208 = state::sequencer.click(midier::Sequencer::Run::Async); 1209 1210 if (bar != midier::Sequencer::Bar::Same) 1211 1212 { 1213 control::flash(); 1214 1215 if (viewer::focused == nullptr 1216 && state::layer.layer == nullptr) 1217 { 1218 control::view::bar(bar); 1219 1220 } 1221 } 1222} 1223 1224} // handle 1225 1226extern "C" void setup() 1227{ 1228 1229 // initialize the Arduino "Serial" module and set the baud rate 1230 // 1231 to the same value you are using in your software. 1232 // if connected physically 1233 using a MIDI 5-DIN connection, use 31250. 1234 Serial.begin(9600); 1235 1236 // 1237 initialize the LEDs 1238 pinMode(13, OUTPUT); 1239 pinMode(A1, OUTPUT); 1240 1241 1242 // initialize the LCD 1243 io::lcd.begin(16, 2); 1244 1245 // print the initial 1246 configuration 1247 control::view::summary(); 1248} 1249 1250extern "C" void loop() 1251{ 1252 1253 handle::flashing(); 1254 handle::recording(); 1255 handle::focus(); 1256 1257 handle::components(); 1258 handle::keys(); 1259 handle::record(); 1260 handle::layer(); 1261 1262 handle::click(); 1263} 1264 1265} // arpeggino 1266
Tutorial: Step Two - Configuring the Arpeggios - Sketch
c_cpp
1#include <Controlino.h> 2#include <Midier.h> 3 4namespace arpeggino 5{ 6 7namespace state 8{ 9 10midier::Layers<8> layers; // the number of layers chosen will affect the global variable size 11midier::Sequencer sequencer(layers); 12 13} // state 14 15namespace io 16{ 17 18// here we declare all I/O controls with their corresponding pin numbers 19 20controlino::Potentiometer BPM(A0, /* min = */ 20, /* max = */ 230); // we limit the value of BPM to [20,230] 21controlino::Key Note(10); 22controlino::Key Mode(11); 23controlino::Key Octave(12); 24controlino::Key Perm(A5); 25controlino::Key Steps(A4); 26controlino::Key Rhythm(A3); 27 28} // io 29 30namespace configurer 31{ 32 33// a configurer is a method that is responsible for updating a single 34// configuration parameter according to changes of an I/O control 35using Configurer = void(*)(); 36 37void BPM() 38{ 39 if (io::BPM.check() == controlino::Potentiometer::Event::Changed) 40 { 41 state::sequencer.bpm = io::BPM.read(); 42 } 43} 44 45void Note() 46{ 47 if (io::Note.check() != controlino::Key::Event::Down) 48 { 49 return; // nothing to do 50 } 51 52 // the key was just pressed 53 54 auto & config = state::sequencer.config; // a shortcut 55 56 if (config.accidental() == midier::Accidental::Flat) 57 { 58 config.accidental(midier::Accidental::Natural); 59 } 60 else if (config.accidental() == midier::Accidental::Natural) 61 { 62 config.accidental(midier::Accidental::Sharp); 63 } 64 else if (config.accidental() == midier::Accidental::Sharp) 65 { 66 config.accidental(midier::Accidental::Flat); 67 68 if (config.note() == midier::Note::C) { config.note(midier::Note::D); } 69 else if (config.note() == midier::Note::D) { config.note(midier::Note::E); } 70 else if (config.note() == midier::Note::E) { config.note(midier::Note::F); } 71 else if (config.note() == midier::Note::F) { config.note(midier::Note::G); } 72 else if (config.note() == midier::Note::G) { config.note(midier::Note::A); } 73 else if (config.note() == midier::Note::A) { config.note(midier::Note::B); } 74 else if (config.note() == midier::Note::B) { config.note(midier::Note::C); } 75 } 76} 77 78void Mode() 79{ 80 if (io::Mode.check() == controlino::Key::Event::Down) 81 { 82 const auto current = state::sequencer.config.mode(); 83 const auto next = (midier::Mode)(((unsigned)current + 1) % (unsigned)midier::Mode::Count); 84 85 state::sequencer.config.mode(next); 86 } 87} 88 89void Octave() 90{ 91 if (io::Octave.check() == controlino::Key::Event::Down) 92 { 93 const auto current = state::sequencer.config.octave(); 94 const auto next = (current % 7) + 1; 95 96 state::sequencer.config.octave(next); 97 } 98} 99 100void Perm() 101{ 102 if (io::Perm.check() == controlino::Key::Event::Down) 103 { 104 const auto current = state::sequencer.config.perm(); 105 const auto next = (current + 1) % midier::style::count(state::sequencer.config.steps()); 106 107 state::sequencer.config.perm(next); 108 } 109} 110 111void Steps() 112{ 113 if (io::Steps.check() == controlino::Key::Event::Down) 114 { 115 auto & config = state::sequencer.config; // a shortcut 116 117 if (config.looped() == false) // we set to loop if currently not looping 118 { 119 config.looped(true); 120 } 121 else 122 { 123 unsigned steps = config.steps() + 1; 124 125 if (steps > 6) 126 { 127 steps = 3; 128 } 129 130 config.steps(steps); 131 config.perm(0); // reset the permutation 132 config.looped(false); // set as non looping 133 } 134 } 135} 136 137void Rhythm() 138{ 139 if (io::Rhythm.check() == controlino::Key::Event::Down) 140 { 141 const auto current = state::sequencer.config.rhythm(); 142 const auto next = (midier::Rhythm)(((unsigned)current + 1) % (unsigned)midier::Rhythm::Count); 143 144 state::sequencer.config.rhythm(next); 145 } 146} 147 148Configurer All[] = 149 { 150 BPM, 151 Note, 152 Mode, 153 Octave, 154 Perm, 155 Steps, 156 Rhythm, 157 }; 158 159} // configurer 160 161namespace handle 162{ 163 164void configurers() 165{ 166 // configurers will update the configuration on I/O events 167 168 for (const auto & configurer : configurer::All) 169 { 170 configurer(); 171 } 172} 173 174void keys() 175{ 176 // we extend `controlino::Key` so we could hold a Midier handle with every key 177 struct Key : controlino::Key 178 { 179 Key(char pin) : controlino::Key(pin) 180 {} 181 182 midier::Sequencer::Handle h; 183 }; 184 185 static Key __keys[] = { 2, 3, 4, 5, 6, 7, 8, 9 }; // initialize with pin numbers 186 187 for (auto i = 0; i < sizeof(__keys) / sizeof(Key); ++i) 188 { 189 auto & key = __keys[i]; 190 191 const auto event = key.check(); 192 193 if (event == Key::Event::None) 194 { 195 continue; // nothing has changed 196 } 197 198 if (event == Key::Event::Down) // a key was pressed 199 { 200 key.h = state::sequencer.start(i + 1); // start playing an arpeggio of the respective scale degree 201 } 202 else if (event == Key::Event::Up) // a key was released 203 { 204 state::sequencer.stop(key.h); // stop playing the arpeggio 205 } 206 } 207} 208 209void click() 210{ 211 // actually click Midier for it to play the MIDI notes 212 state::sequencer.click(midier::Sequencer::Run::Async); 213} 214 215} // handle 216 217extern "C" void setup() 218{ 219 // initialize the Arduino "Serial" module and set the baud rate 220 // to the same value you are using in your software. 221 // if connected physically using a MIDI 5-DIN connection, use 31250. 222 Serial.begin(9600); 223} 224 225extern "C" void loop() 226{ 227 handle::configurers(); 228 handle::keys(); 229 handle::click(); 230} 231 232} // arpeggino 233
Downloadable files
Tutorial: Step Two - Configuring the Arpeggios - Schema
Tutorial: Step Two - Configuring the Arpeggios - Schema
Tutorial: Step Three - LCD - Part 2 - Schema
Tutorial: Step Three - LCD - Part 2 - Schema
Tutorial: Step Three - LCD - Part 1 - Schema
Tutorial: Step Three - LCD - Part 1 - Schema
Tutorial: Step One - Playing Arpeggios - Schema
Tutorial: Step One - Playing Arpeggios - Schema
Tutorial: Step Five - Layers - Schema
Tutorial: Step Five - Layers - Schema
Arpeggino Final Schema
Arpeggino Final Schema
Tutorial: Step Four - Recording - Schema
Tutorial: Step Four - Recording - Schema
Tutorial: Step One - Playing Arpeggios - Sketch
Tutorial: Step One - Playing Arpeggios - Sketch
Tutorial: Step Five - Layers - Schema
Tutorial: Step Five - Layers - Schema
Tutorial: Step One - Playing Arpeggios - Schema
Tutorial: Step One - Playing Arpeggios - Schema
Tutorial: Step Two - Configuring the Arpeggios - Schema
Tutorial: Step Two - Configuring the Arpeggios - Schema
Tutorial: Step One - Playing Arpeggios - Sketch
Tutorial: Step One - Playing Arpeggios - Sketch
Tutorial: Step Four - Recording - Schema
Tutorial: Step Four - Recording - Schema
Tutorial: Step Three - LCD - Part 2 - Schema
Tutorial: Step Three - LCD - Part 2 - Schema
Tutorial: Step Three - LCD - Part 1 - Schema
Tutorial: Step Three - LCD - Part 1 - Schema
Arpeggino Final Schema
Arpeggino Final Schema
Tutorial: Step One - Playing Arpeggios - Sketch
Tutorial: Step One - Playing Arpeggios - Sketch
Comments
Only logged in users can leave comments
razrotenberg
0 Followers
•0 Projects
Table of contents
Intro
10
0