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