├── .gitignore ├── LICENSE ├── MCPController ├── MCPController.ino ├── README.md └── mcp23s17.h ├── README.md └── RotaryController ├── MIDIEncoder.cpp ├── MIDIEncoder.h ├── README.md └── RotaryController.ino /.gitignore: -------------------------------------------------------------------------------- 1 | app/bin/ 2 | app/pde.jar 3 | build/macosx/work/ 4 | arduino-core/bin/ 5 | arduino-core/arduino-core.jar 6 | hardware/arduino/bootloaders/caterina_LUFA/Descriptors.o 7 | hardware/arduino/bootloaders/caterina_LUFA/Descriptors.lst 8 | hardware/arduino/bootloaders/caterina_LUFA/Caterina.sym 9 | hardware/arduino/bootloaders/caterina_LUFA/Caterina.o 10 | hardware/arduino/bootloaders/caterina_LUFA/Caterina.map 11 | hardware/arduino/bootloaders/caterina_LUFA/Caterina.lst 12 | hardware/arduino/bootloaders/caterina_LUFA/Caterina.lss 13 | hardware/arduino/bootloaders/caterina_LUFA/Caterina.elf 14 | hardware/arduino/bootloaders/caterina_LUFA/Caterina.eep 15 | hardware/arduino/bootloaders/caterina_LUFA/.dep/ 16 | build/*.zip 17 | build/*.tar.bz2 18 | build/windows/work/ 19 | build/windows/*.zip 20 | build/windows/*.tgz 21 | build/windows/*.tar.bz2 22 | build/windows/libastylej* 23 | build/windows/arduino-*.zip 24 | build/windows/dist/*.tar.gz 25 | build/windows/dist/*.tar.bz2 26 | build/windows/launch4j-*.tgz 27 | build/windows/launch4j-*.zip 28 | build/windows/launcher/launch4j 29 | build/windows/WinAVR-*.zip 30 | build/macosx/arduino-*.zip 31 | build/macosx/dist/*.tar.gz 32 | build/macosx/dist/*.tar.bz2 33 | build/macosx/*.tar.bz2 34 | build/macosx/libastylej* 35 | build/macosx/appbundler*.jar 36 | build/macosx/appbundler*.zip 37 | build/macosx/appbundler 38 | build/macosx/appbundler-1.0ea-arduino? 39 | build/macosx/appbundler-1.0ea-arduino*.zip 40 | build/macosx/appbundler-1.0ea-upstream*.zip 41 | build/linux/work/ 42 | build/linux/dist/*.tar.gz 43 | build/linux/dist/*.tar.bz2 44 | build/linux/*.tgz 45 | build/linux/*.tar.xz 46 | build/linux/*.tar.bz2 47 | build/linux/*.zip 48 | build/linux/libastylej* 49 | build/linux/liblistSerials* 50 | build/shared/reference*.zip 51 | build/shared/Edison*.zip 52 | build/shared/Galileo*.zip 53 | build/shared/WiFi101-Updater-ArduinoIDE-Plugin*.zip 54 | test-bin 55 | *.iml 56 | .idea 57 | .DS_Store 58 | .directory 59 | hardware/arduino/avr/libraries/Bridge/examples/XivelyClient/passwords.h 60 | avr-toolchain-*.zip 61 | /app/nbproject/private/ 62 | /arduino-core/nbproject/private/ 63 | /app/build/ 64 | /arduino-core/build/ 65 | 66 | manifest.mf 67 | nbbuild.xml 68 | nbproject 69 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Paul Williamson 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /MCPController/MCPController.ino: -------------------------------------------------------------------------------- 1 | /* 2 | MCP23S17 Test 3 | 4 | Enc1 5 | A - Green 6 | B - Red 7 | C - Yellow 8 | 9 | 10 | Enc2 11 | A - Green 12 | B - Blue 13 | C - Yellow 14 | 15 | SS - 10 16 | SCK - 13 17 | MOSI - 11 18 | MISO - 12 19 | 20 | MCP1 = 0 21 | MCP2 = 1 22 | 23 | ENC1A - MCP1GPA0 24 | ENC1B - MCP1GPA1 25 | ENC2A - MCP2GPA0 26 | ENC2B - MCP2GPA1 27 | 28 | */ 29 | 30 | #include 31 | #include "MCP23S17.h" 32 | 33 | const byte chipSelect = 10; 34 | byte oldValue = 0; 35 | 36 | void setup() { 37 | // Set chip select to be output high 38 | pinMode(chipSelect, OUTPUT); 39 | digitalWrite(chipSelect, HIGH); 40 | 41 | // Set SS to be output high 42 | pinMode(SS, OUTPUT); 43 | digitalWrite(SS, HIGH); 44 | 45 | // Start SPI 46 | SPI.begin(); 47 | 48 | // Set GPIOA to input 49 | Mcp23S17Write(chipSelect, IODIRA, 0xff); 50 | 51 | // Use weak pullup resistors 52 | Mcp23S17Write(chipSelect, GPPUA, 0xff); 53 | 54 | // Invert the polarity of the input 55 | Mcp23S17Write(chipSelect, IPOLA, 0xff); 56 | 57 | // Setup Serial 58 | Serial.begin(9600); 59 | delay(500); 60 | Serial.println("Begin"); 61 | } 62 | 63 | void loop() { 64 | uint8_t value = Mcp23S17Read(chipSelect, GPIOA); 65 | if (oldValue != value) { 66 | Serial.print("Value changed: "); 67 | Serial.println(value, BIN); 68 | oldValue = value; 69 | } 70 | } 71 | 72 | 73 | void Mcp23S17Write(byte cs, byte reg, byte value) { 74 | digitalWrite(cs, LOW); 75 | SPI.transfer(0x40); 76 | SPI.transfer(reg); 77 | SPI.transfer(value); 78 | digitalWrite(cs, HIGH); 79 | } 80 | 81 | byte Mcp23S17Read(byte cs, byte reg) { 82 | byte value; 83 | digitalWrite(cs, LOW); 84 | SPI.transfer(0x41); 85 | SPI.transfer(reg); 86 | value = SPI.transfer(0); 87 | digitalWrite(cs, HIGH); 88 | return value; 89 | } 90 | 91 | -------------------------------------------------------------------------------- /MCPController/README.md: -------------------------------------------------------------------------------- 1 | # MCP Controller 2 | 3 | This project uses 16 Bourns PEC16 encoders, with 3 `MCP23S17` GPIO expanders. This means we can connect up 16 encoders and buttons, using as little as 4 Teensy pins. 4 | 5 | One of the biggest advantages of the Teensy over an Arduino is any digital input can be used as an interrupt pin. Each `MCP23S17` offers two interrupts - one for `GPIOA` one for `GPIOB`. Therefore, we can use 6 Teensy input pins to monitor interrupts from the `MCP23S17` chips. 6 | 7 | ## Work in progress... 8 | 9 | -------------------------------------------------------------------------------- /MCPController/mcp23s17.h: -------------------------------------------------------------------------------- 1 | /* 2 | MCP23S17.h - mostly based on https://github.com/n0mjs710/MCP23S17 3 | */ 4 | 5 | #define IODIRA (0x00) // MCP23x17 I/O Direction Register 6 | #define IODIRB (0x01) // 1 = Input (default), 0 = Output 7 | 8 | #define IPOLA (0x02) // MCP23x17 Input Polarity Register 9 | #define IPOLB (0x03) // 0 = Normal (default)(low reads as 0), 1 = Inverted (low reads as 1) 10 | 11 | #define GPINTENA (0x04) // MCP23x17 Interrupt on Change Pin Assignements 12 | #define GPINTENB (0x05) // 0 = No Interrupt on Change (default), 1 = Interrupt on Change 13 | 14 | #define DEFVALA (0x06) // MCP23x17 Default Compare Register for Interrupt on Change 15 | #define DEFVALB (0x07) // Opposite of what is here will trigger an interrupt (default = 0) 16 | 17 | #define INTCONA (0x08) // MCP23x17 Interrupt on Change Control Register 18 | #define INTCONB (0x09) // 1 = pin is compared to DEFVAL, 0 = pin is compared to previous state (default) 19 | 20 | #define IOCON (0x0A) // MCP23x17 Configuration Register 21 | // (0x0B) // Also Configuration Register 22 | 23 | #define GPPUA (0x0C) // MCP23x17 Weak Pull-Up Resistor Register 24 | #define GPPUB (0x0D) // INPUT ONLY: 0 = No Internal 100k Pull-Up (default) 1 = Internal 100k Pull-Up 25 | 26 | #define INTFA (0x0E) // MCP23x17 Interrupt Flag Register 27 | #define INTFB (0x0F) // READ ONLY: 1 = This Pin Triggered the Interrupt 28 | 29 | #define INTCAPA (0x10) // MCP23x17 Interrupt Captured Value for Port Register 30 | #define INTCAPB (0x11) // READ ONLY: State of the Pin at the Time the Interrupt Occurred 31 | 32 | #define GPIOA (0x12) // MCP23x17 GPIO Port Register 33 | #define GPIOB (0x13) // Value on the Port - Writing Sets Bits in the Output Latch 34 | 35 | #define OLATA (0x14) // MCP23x17 Output Latch Register 36 | #define OLATB (0x15) // 1 = Latch High, 0 = Latch Low (default) Reading Returns Latch State, Not Port Value! 37 | 38 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Teensy MIDI Encoder Box 2 | 3 | This project uses 16 rotary encoders to generate relative MIDI CC messages. It uses a [Teensy 3.2](https://www.pjrc.com/teensy/teensy31.html), which features 34 digital input pins, each with the ability to use interrupts. 4 | 5 | ## Dependencies 6 | 7 | The only non-standard Arduino library used is: 8 | 9 | - [Encoder](https://github.com/PaulStoffregen/Encoder) 10 | 11 | ## Building the software 12 | 13 | First download the latest [Arduino Software](https://www.arduino.cc/en/Main/OldSoftwareReleases#previous) that is compatible with [Teensyduino](https://www.pjrc.com/teensy/teensyduino.html). At the time of writing, this is [Arduino](https://www.arduino.cc/en/Main/OldSoftwareReleases#previous) `1.6.9`, and [Teensyduino](https://www.pjrc.com/teensy/teensyduino.html) `1.29`. 14 | 15 | Ensure you install the `Encoder` library when installing [Teensyduino](https://www.pjrc.com/teensy/teensyduino.html). 16 | 17 | Now open up `RotaryController.ino` located in the `RotaryController` directory. 18 | 19 | At the top of the file you can customise the number of encoders in use, MIDI channel and CC numbers to suit your project. 20 | 21 | Verify the sketch, which will open [Teensy Loader](https://www.pjrc.com/teensy/loader.html). Now press the button on the Teensy to load the firmware. 22 | 23 | Now connect up all your encoders, following the schematic. 24 | 25 | Finally, test each encoder using a MIDI monitor. I recommend [Snoize MIDI Monitor](https://www.snoize.com/MIDIMonitor/) on macOS, and [MIDI-OX](http://www.midiox.com) on Windows. If you know of a Linux MIDI monitor, I'd welcome a [Pull Request](https://github.com/squarefrog/teensy-midi-encoder-box/compare)! 26 | 27 | Speaking of which... 28 | 29 | ## Pull Requests 30 | 31 | Pull Requests are most welcome! Do you have a suggestion for code improvement? Or just spot a spelling mistake in this `README` file? Send me a [Pull Request](https://github.com/squarefrog/teensy-midi-encoder-box/compare). 32 | 33 | ## To do list 34 | 35 | - [x] Write sketch code 36 | - [x] Test on bread board 37 | - [ ] Create schematic 38 | - [ ] Design breakout PCB 39 | - [ ] Design 3D enclosure 40 | - [ ] Ableton Instant Mapping files 41 | 42 | ## Bill of Materials 43 | 44 | Work in progress... 45 | 46 | | Quantity | Part Number | Description | Line Price £ | Supplier | 47 | |----------|--------------|---------------------------------------------------------|--------------|---| 48 | | 16 | EC12E2420802 | ALPS Incremental Rotary Encoder, 24 Pulses, 24 Detents | 14.24 | [Farnell](http://uk.farnell.com/alps/ec12e2420802/encoder-vertical-12mm-24det-24ppr/dp/2065067) | 49 | | 16 | CR-BAB-6-D | MULTICOMP,CR-BAB-6-D,Knob, Round, 6 mm, | 6.50 | [Farnell](http://uk.farnell.com/multicomp/cr-bab-6-d/knob-15-7mm-black-no-line/dp/1441143) | 50 | | 1 | | Teensy 3.2 | 18.00 | [Pimoroni](https://shop.pimoroni.com/products/teensy-3-1) | 51 | | 1 | N/A | 40-pin 0.1" Pin Headers (Male) | | | 52 | | 1 | N/A | 2x7 SMD pin headers | | | 53 | | 16 | N/A | M8 nuts | | | 54 | | 16 | N/A | M8 nuts | | | 55 | | 16 | N/A | Washers | | | 56 | | 1 | N/A | Breakout PCB (optional) | | [Ragworm.eu](http://ragworm.eu) | 57 | 58 | ## References 59 | 60 | - [MIDI Control Change Messages](https://www.midi.org/specifications/item/table-3-control-change-messages-data-bytes-2) 61 | - A more readable [MIDI Control Change Messages](http://nickfever.com/music/midi-cc-list) table 62 | - [Encoder Library](https://www.pjrc.com/teensy/td_libs_Encoder.html) 63 | - [Relative MIDI Mapping](http://tutorials.renoise.com/wiki/MIDI_Mapping) 64 | - [Notes and Volts](http://www.notesandvolts.com) 65 | 66 | -------------------------------------------------------------------------------- /RotaryController/MIDIEncoder.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | MIDIEncoder 3 | A library for creating relative MIDI CC messages from a rotary encoder. 4 | 5 | Project source available at: 6 | http://github.com/squarefrog/teensy-midi-encoder-box 7 | 8 | Uses Paul Stoffregen's Teensy Encoder library 9 | https://github.com/PaulStoffregen/Encoder 10 | 11 | Copyright (c) 2016 Paul Williamson 12 | 13 | Permission is hereby granted, free of charge, to any person obtaining a copy 14 | of this software and associated documentation files (the "Software"), to deal 15 | in the Software without restriction, including without limitation the rights 16 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 17 | copies of the Software, and to permit persons to whom the Software is 18 | furnished to do so, subject to the following conditions: 19 | 20 | The above copyright notice and this permission notice shall be included in 21 | all copies or substantial portions of the Software. 22 | 23 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 24 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 25 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 26 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 27 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 28 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 29 | THE SOFTWARE. 30 | */ 31 | 32 | #include "MIDIEncoder.h" 33 | 34 | /* 35 | Ableton Relative signed bit: Increase at [1-63], decrease at [65 - 127]. 36 | Values are +1, as setting to minimum means you have to turn two clicks to 37 | register 1 jump in Ableton 38 | */ 39 | const byte incrementValue = 2; // A constant for the start of increment values 40 | const byte decrementValue = 66; // A constant for the start of decrement values 41 | 42 | MIDIEncoder::MIDIEncoder(byte pin1, byte pin2, byte midiChannel, byte midiCCNumber) : _enc(pin1, pin2) 43 | { 44 | channel = midiChannel; 45 | ccNumber = midiCCNumber; 46 | _oldPosition = -999; 47 | _lastTurnedTime = millis(); 48 | } 49 | 50 | byte MIDIEncoder::read() 51 | { 52 | long newPosition = _enc.read(); 53 | 54 | // If position hasn't changed, ignore. 55 | if (newPosition == _oldPosition) { 56 | return 0; 57 | } 58 | 59 | // The Encoder library counts 4 steps per detent. 60 | // If position is not divisible by 4, ignore. 61 | if (newPosition % 4 != 0) { 62 | return 0; 63 | } 64 | 65 | // Fetch the acceleration offset 66 | byte offset = accelerationOffset(); 67 | 68 | // Create the MIDI CC value, adding any offset required. 69 | byte value = 0; 70 | if (newPosition > _oldPosition) { 71 | value = incrementValue + offset; 72 | } else { 73 | value = decrementValue + offset; 74 | } 75 | 76 | // Store the new position 77 | _oldPosition = newPosition; 78 | 79 | // Return the MIDI CC value 80 | return value; 81 | } 82 | 83 | byte MIDIEncoder::accelerationOffset() 84 | { 85 | // Calculate the time since last change 86 | unsigned long currTime = millis(); 87 | unsigned long delta = currTime - _lastTurnedTime; 88 | // Serial.println(delta); 89 | 90 | // Apply crude acceleration 91 | // Warning: here be magic numbers... 92 | byte offset = 0; 93 | if (delta < 100) { 94 | offset = 4; 95 | } else if (delta > 99 && delta < 180) { 96 | offset = 2; 97 | } else if (delta > 179 && delta <= 250) { 98 | offset = 1; 99 | } 100 | 101 | // Update lastTurnedTime variable 102 | _lastTurnedTime = currTime; 103 | 104 | return offset; 105 | } 106 | 107 | 108 | -------------------------------------------------------------------------------- /RotaryController/MIDIEncoder.h: -------------------------------------------------------------------------------- 1 | /* 2 | MIDIEncoder 3 | A library for creating relative MIDI CC messages from a rotary encoder. 4 | 5 | Project source available at: 6 | http://github.com/squarefrog/teensy-midi-encoder-box 7 | 8 | Uses Paul Stoffregen's Teensy Encoder library 9 | https://github.com/PaulStoffregen/Encoder 10 | 11 | Copyright (c) 2016 Paul Williamson 12 | 13 | Permission is hereby granted, free of charge, to any person obtaining a copy 14 | of this software and associated documentation files (the "Software"), to deal 15 | in the Software without restriction, including without limitation the rights 16 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 17 | copies of the Software, and to permit persons to whom the Software is 18 | furnished to do so, subject to the following conditions: 19 | 20 | The above copyright notice and this permission notice shall be included in 21 | all copies or substantial portions of the Software. 22 | 23 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 24 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 25 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 26 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 27 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 28 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 29 | THE SOFTWARE. 30 | */ 31 | 32 | #ifndef MIDIEncoder_h 33 | #define MIDIEncoder_h 34 | 35 | #include "Arduino.h" 36 | #include 37 | 38 | class MIDIEncoder 39 | { 40 | public: 41 | MIDIEncoder(byte pin1, byte pin2, byte midiChannel, byte midiCCNumber); 42 | byte channel; 43 | byte ccNumber; 44 | byte read(); 45 | 46 | private: 47 | unsigned long _lastTurnedTime; 48 | long _oldPosition; 49 | Encoder _enc; 50 | byte accelerationOffset(); 51 | }; 52 | 53 | #endif 54 | 55 | -------------------------------------------------------------------------------- /RotaryController/README.md: -------------------------------------------------------------------------------- 1 | # Rotary Controller 2 | 3 | This project is a simple rotary encoder project which uses ALPS EC12 encoders, with each pin connected to a pin on the Teensy 3.1. 4 | 5 | The downside is that after hooking up 16 encoders, there are only 2 digital pins left over. 6 | 7 | -------------------------------------------------------------------------------- /RotaryController/RotaryController.ino: -------------------------------------------------------------------------------- 1 | /* 2 | RotaryController 3 | 4 | This project sends relative signed bit MIDI CC messages when an encoder 5 | is turned. 6 | 7 | Project source available at: 8 | http://github.com/squarefrog/teensy-midi-encoder-box 9 | 10 | For more information about relative MIDI mapping, see: 11 | http://tutorials.renoise.com/wiki/MIDI_Mapping 12 | Ableton Relative signed bit: Increase at [1-63], decrease at [65 - 127]. 13 | */ 14 | 15 | #include "MIDIEncoder.h" 16 | 17 | // Constants 18 | const byte midiChannel = 1; // The MIDI Channel to send the commands over 19 | const byte numberEncoders = 16; // The number of encoders in the project 20 | 21 | // Encoder Variables - Avoid Pin 13! Encoder constructer as follows: 22 | // MIDIEncoder(byte pin1, byte pin2, byte midiChannel, byte midiCCNumber); 23 | MIDIEncoder enc1(0, 1, midiChannel, 16); 24 | MIDIEncoder enc2(2, 3, midiChannel, 17); 25 | MIDIEncoder enc3(4, 5, midiChannel, 18); 26 | MIDIEncoder enc4(6, 7, midiChannel, 19); 27 | MIDIEncoder enc5(8, 9, midiChannel, 20); 28 | MIDIEncoder enc6(10, 11, midiChannel, 21); 29 | MIDIEncoder enc7(14, 15, midiChannel, 22); 30 | MIDIEncoder enc8(16, 17, midiChannel, 23); 31 | MIDIEncoder enc9(18, 19, midiChannel, 24); 32 | MIDIEncoder enc10(20, 21, midiChannel, 25); 33 | MIDIEncoder enc11(22, 23, midiChannel, 26); 34 | MIDIEncoder enc12(24, 25, midiChannel, 27); 35 | MIDIEncoder enc13(26, 27, midiChannel, 28); 36 | MIDIEncoder enc14(28, 29, midiChannel, 29); 37 | MIDIEncoder enc15(30, 31, midiChannel, 30); 38 | MIDIEncoder enc16(32, 33, midiChannel, 31); 39 | 40 | // Store encoder pointers for easier lookups 41 | MIDIEncoder *encoders[numberEncoders] { 42 | &enc1, &enc2, &enc3, &enc4, 43 | &enc5, &enc6, &enc7, &enc8, 44 | &enc9, &enc10, &enc11, &enc12, 45 | &enc13, &enc14, &enc15, &enc16 46 | }; 47 | 48 | void setup() { 49 | Serial.begin(9600); 50 | } 51 | 52 | void loop() { 53 | readEncoders(); 54 | } 55 | 56 | void readEncoders() { 57 | // Loop through each encoder and read the value 58 | for (byte i = 0; i < numberEncoders; i++) { 59 | byte value = encoders[i]->read(); 60 | if (value > 0) { 61 | Serial.print("Encoder changed: "); 62 | Serial.print(i, DEC); 63 | Serial.print(" "); 64 | Serial.println(value, DEC); 65 | byte cc = encoders[i]->ccNumber; 66 | byte channel = encoders[i]->channel; 67 | usbMIDI.sendControlChange(cc, value, channel); 68 | } 69 | } 70 | } 71 | 72 | --------------------------------------------------------------------------------