├── .github └── FUNDING.yml ├── LICENSE ├── README.md ├── firmware ├── Motherboard │ ├── Input.h │ ├── InputButton.h │ ├── InputGate.h │ ├── InputQuantized.h │ ├── InputRotary.h │ ├── InputTrigger.h │ ├── MidiCCInput.h │ ├── MidiCCOutput.h │ ├── MidiIO.h │ ├── MidiInput.h │ ├── MidiManager.h │ ├── MidiNoteInput.h │ ├── MidiNotesInput.h │ ├── MidiOutput.h │ ├── Motherboard.h │ ├── Output.h │ ├── OutputGate.h │ ├── OutputLed.h │ ├── OutputTrigger.h │ ├── Registrar.h │ ├── Setting.h │ ├── SystemExclusiveManager.h │ ├── Vca.h │ ├── Vcc.h │ └── lib │ │ ├── ArduinoJson-v7.1.0.h │ │ └── ArduinoStreamUtils │ │ ├── StreamUtils.h │ │ ├── StreamUtils.hpp │ │ └── StreamUtils │ │ ├── Buffers │ │ ├── CharArray.hpp │ │ ├── CircularBuffer.hpp │ │ └── LinearBuffer.hpp │ │ ├── Clients │ │ ├── ChunkDecodingClient.hpp │ │ ├── ClientProxy.hpp │ │ ├── HammingClient.hpp │ │ ├── HammingDecodingClient.hpp │ │ ├── HammingEncodingClient.hpp │ │ ├── LoggingClient.hpp │ │ ├── MemoryClient.hpp │ │ ├── ReadBufferingClient.hpp │ │ ├── ReadLoggingClient.hpp │ │ ├── SpyingClient.hpp │ │ ├── WriteBufferingClient.hpp │ │ ├── WriteLoggingClient.hpp │ │ └── WriteWaitingClient.hpp │ │ ├── Configuration.hpp │ │ ├── Helpers.hpp │ │ ├── Policies │ │ ├── ChunkDecodingPolicy.hpp │ │ ├── ConnectForwardingPolicy.hpp │ │ ├── ConnectSpyingPolicy.hpp │ │ ├── HammingDecodingPolicy.hpp │ │ ├── HammingEncodingPolicy.hpp │ │ ├── ReadBufferingPolicy.hpp │ │ ├── ReadForwardingPolicy.hpp │ │ ├── ReadLoggingPolicy.hpp │ │ ├── ReadSpyingPolicy.hpp │ │ ├── ReadThrottlingPolicy.hpp │ │ ├── WriteBufferingPolicy.hpp │ │ ├── WriteForwardingPolicy.hpp │ │ ├── WriteLoggingPolicy.hpp │ │ ├── WriteSpyingPolicy.hpp │ │ └── WriteWaitingPolicy.hpp │ │ ├── Polyfills.hpp │ │ ├── Ports │ │ ├── ArduinoThrottler.hpp │ │ └── DefaultAllocator.hpp │ │ ├── Prints │ │ ├── BufferingPrint.hpp │ │ ├── HammingPrint.hpp │ │ ├── LoggingPrint.hpp │ │ ├── PrintProxy.hpp │ │ ├── SpyingPrint.hpp │ │ ├── StringPrint.hpp │ │ └── WaitingPrint.hpp │ │ └── Streams │ │ ├── ChunkDecodingStream.hpp │ │ ├── EepromStream.hpp │ │ ├── HammingDecodingStream.hpp │ │ ├── HammingEncodingStream.hpp │ │ ├── HammingStream.hpp │ │ ├── LoggingStream.hpp │ │ ├── MemoryStream.hpp │ │ ├── ProgmemStream.hpp │ │ ├── ReadBufferingStream.hpp │ │ ├── ReadLoggingStream.hpp │ │ ├── ReadThrottlingStream.hpp │ │ ├── SpyingStream.hpp │ │ ├── StreamProxy.hpp │ │ ├── StringStream.hpp │ │ ├── WriteBufferingStream.hpp │ │ ├── WriteLoggingStream.hpp │ │ └── WriteWaitingStream.hpp ├── README.md └── examples │ ├── Calibration │ ├── Calibration.ino │ ├── Calibration.ino.hex │ └── Motherboard │ ├── Rotary │ ├── Motherboard │ └── Rotary.ino │ ├── Settings │ ├── Motherboard │ └── Settings.ino │ └── SimpleLeds │ ├── Motherboard │ └── SimpleLeds.ino └── hardware ├── Expander 1.0.3.pdf ├── Expander 1.0.4.pdf ├── Mouser parts.xls ├── README.md ├── Schematics.pdf ├── motherboard-1.WEBP └── motherboard-2.WEBP /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: ghostintranslation 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: ghostintranslation 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: ghostintranslation 10 | issuehunt: # Replace with a single IssueHunt username 11 | lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry 12 | polar: # Replace with a single Polar username 13 | buy_me_a_coffee: ghostintranslation 14 | thanks_dev: 15 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 16 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Ghost In Translation 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![GitHub version](https://img.shields.io/github/v/release/ghostintranslation/motherboard.svg?include_prereleases) 2 | 3 | # MOTHERBOARD 4 | 5 | Motherboard is a Eurorack and general modular synthesizers platform built around Teensy 4.0 that provides an easy way to build modules from a high level. 6 | 7 | The hardware consists of one PCB that allows to build modules as small as 8HP while the firmware consists of a collection of classes compatible with Teensy's audio library. 8 | 9 | 10 | 11 | ## Features 12 | 13 | * 16 analog inputs (12 bits, 5v, 44.1kHz) 14 | * 16 outputs (8bits Binary Code Modulation) 15 | * 2 audio channels over I2S or 8 channels over TDM 16 | * I2C to expand with analog outputs and more 17 | * MIDI in and out 18 | * Configurable via JSON over MIDI SysEx 19 | * 10 pin Eurorack either way power 20 | * Through hole and SMD for most footprints 21 | * Teensy 4.0 22 | 23 | ## The idea 24 | 25 | Wether analog or digital, often modules are designed from scratch as one-offs creations. This means going through the same challenges and solving them in slightly different ways. It’s possible to do better and not reinvent the wheel every time. 26 | 27 | By separating the functional circuitry from the user interface circuitry, a pattern will emerge and make it possible to reuse at least a few things. 28 | 29 | With analog it is more difficult to achieve as the circuit is very much dependent on the inputs and outputs, but it would still be possible to have a generic user interface connected to a specific functional circuit. 30 | 31 | With digital this gets interesting. The functional circuit can be completely generic and be reused for many modules, simply reading inputs and generating outputs, it doesn’t need to be designed around the final module. The low level code can be generic and reused too, while the high level code would be specific to each module. 32 | 33 | 34 | ## Instructions 35 | 36 | See the hardware's and firmware's instructions under their respective folder. 37 | 38 | ## Known issues 39 | 40 | - Actual inputs sampling frequency is reduced due to audible artifacts. 41 | 42 | 43 | ## TODO list 44 | 45 | - Web editor to edit settings and maybe visualize data 46 | - Use the CMSIS-DSP library to perform interpolation and filtering on the ADC readings? 47 | - Auto calibration, does that require a digital potentiometer such as the MCP4651? 48 | - Adding microSD card socket to store settings, MUC2 to move to pin 8? 49 | - Adding an inverter chip to produce -5v out of the +5v to keep the board reliant only on +5v power but add capability of processing negative signals. 50 | - Can Tx detect If MIDI or CLK type of signals 51 | - How to Teletype I2C compatibility 52 | - TDM input and output to send/receive 16 signals over one jack 53 | - Explore replacement of Teensy for direct integration to the board by STM32F7, Pi Pico, Linux chip, FPGA chip,... 54 | 55 | 56 | # About me 57 | 58 | You can buy PCBs on my website: 59 | 60 | https://www.ghostintranslation.com/ 61 | 62 | You can also find me on Bandcamp, Instagram, YouTube: 63 | 64 | https://ghostintranslation.bandcamp.com/ 65 | 66 | https://www.instagram.com/ghostintranslation/ 67 | 68 | https://www.youtube.com/ghostintranslation 69 | 70 | To support my work: 71 | 72 | https://ko-fi.com/ghostintranslation 73 | 74 | https://www.buymeacoffee.com/ghostintranslation 75 | 76 | https://liberapay.com/ghostintranslation 77 | 78 | https://github.com/sponsors/ghostintranslation 79 | 80 | # License 81 | 82 | This project is licensed under the MIT License - see the [LICENSE.md](LICENSE.md) file for details -------------------------------------------------------------------------------- /firmware/Motherboard/Input.h: -------------------------------------------------------------------------------- 1 | #ifndef Input_h 2 | #define Input_h 3 | 4 | #include 5 | #include "DMAChannel.h" 6 | #include "AudioStream.h" 7 | #include "MidiInput.h" 8 | 9 | /** 10 | Teensy 4.0 audio library analog inputs multiplexing. 11 | 12 | This class samples up to 16 inputs at 44.1kHz, or up to 32 inputs at 22.05kHz. 13 | 14 | Connect four 8:1 multiplexers like the CD74HCT4051 to A0, A1, A2, A3. 15 | Connect the 3 selector bits of the multiplexers of A0 and A2 to pins 2,3,4 and the 2 other to pins 5,6,10. 16 | Connect your inputs to the multiplexers. Make sure they don't go over 3.3v! 17 | */ 18 | class Input : public AudioStream 19 | { 20 | public: 21 | Input(byte index); 22 | void update(void); 23 | virtual int16_t *&updateBefore(int16_t *&blockData) { return blockData; }; 24 | void setLowPassCoeff(float coeff); 25 | void setMidiInput(MidiInput *midiInput); 26 | void onChange(void (*onChangeCallback)(int16_t value)); 27 | 28 | protected: 29 | byte index; 30 | int16_t *readBuffer(); 31 | MidiInput *midiInput = nullptr; 32 | 33 | static unsigned int muxIndex1; 34 | static unsigned int muxIndex2; 35 | static unsigned int inputsRealCount; 36 | static unsigned int inputsCount; 37 | static uint8_t downSamplingFactor; 38 | static unsigned int buffSize; 39 | static const unsigned int inputsMax = 32; 40 | static const unsigned int maxBuffers = 8; 41 | static float accumulator[inputsMax]; 42 | static float prevAccumulator[inputsMax]; 43 | static float lowPassCoeff[inputsMax]; 44 | static int16_t queue[inputsMax][maxBuffers][AUDIO_BLOCK_SAMPLES]; 45 | static uint16_t head[inputsMax]; 46 | static uint16_t headQueueTempCount[inputsMax]; 47 | static int16_t headQueueTemp[inputsMax][AUDIO_BLOCK_SAMPLES]; 48 | static uint16_t tail[inputsMax]; 49 | static ADC *adc; 50 | static DMAChannel dmaChannel1; 51 | static DMAChannel dmaChannel2; 52 | static uint8_t isr1Count; 53 | static uint8_t isr2Count; 54 | static uint8_t pinToChannel[4]; 55 | static void adc0Isr(); 56 | static void adc1Isr(); 57 | static void addSample(uint16_t val, uint8_t inputIndex); 58 | static void iterateMux1(); 59 | static void iterateMux2(); 60 | static uint16_t adc1Val; 61 | static uint16_t adc2Val; 62 | 63 | void (*onChangeCallback)(int16_t value) = nullptr; 64 | }; 65 | 66 | // Static initializations 67 | // bool Input::updateStarted = false; 68 | unsigned int Input::muxIndex1 = 0; 69 | unsigned int Input::muxIndex2 = 0; 70 | unsigned int Input::inputsRealCount = 0; 71 | unsigned int Input::inputsCount = 0; 72 | uint8_t Input::downSamplingFactor = 0; 73 | unsigned int Input::buffSize = AUDIO_BLOCK_SAMPLES; 74 | int16_t Input::queue[inputsMax][maxBuffers][AUDIO_BLOCK_SAMPLES] = {{{0}}}; 75 | uint16_t Input::head[inputsMax] = {0}; 76 | uint16_t Input::headQueueTempCount[inputsMax] = {0}; 77 | int16_t Input::headQueueTemp[inputsMax][AUDIO_BLOCK_SAMPLES] = {{0}}; 78 | uint16_t Input::tail[inputsMax] = {0}; 79 | float Input::accumulator[inputsMax] = {0}; 80 | float Input::prevAccumulator[inputsMax] = {0}; 81 | float Input::lowPassCoeff[inputsMax] = {1.0f}; 82 | ADC *Input::adc = nullptr; 83 | DMAChannel Input::dmaChannel1; 84 | DMAChannel Input::dmaChannel2; 85 | 86 | uint8_t Input::isr1Count = 0; 87 | uint8_t Input::isr2Count = 0; 88 | uint8_t Input::pinToChannel[4] = { 89 | 7, // 14/A0 AD_B1_02 90 | 8, // 15/A1 AD_B1_03 91 | 12, // 16/A2 AD_B1_07 92 | 11, // 17/A3 AD_B1_06 93 | }; 94 | uint16_t Input::adc1Val = 0; 95 | uint16_t Input::adc2Val = 0; 96 | 97 | inline Input::Input(byte index) 98 | : AudioStream(0, NULL) 99 | { 100 | this->index = index; 101 | this->active = true; 102 | inputsRealCount++; 103 | inputsCount = ((inputsRealCount - 1) / 8 + 1) * 8; 104 | downSamplingFactor = inputsCount > 16 ? 2 : 1; 105 | // buffSize = AUDIO_BLOCK_SAMPLES / downSamplingFactor; 106 | 107 | pinMode(A0, INPUT); 108 | pinMode(A1, INPUT); 109 | pinMode(A2, INPUT); 110 | pinMode(A3, INPUT); 111 | pinMode(2, OUTPUT); 112 | pinMode(3, OUTPUT); 113 | pinMode(4, OUTPUT); 114 | pinMode(5, OUTPUT); 115 | pinMode(6, OUTPUT); 116 | pinMode(10, OUTPUT); 117 | 118 | // Reset multiplexer to channel 0 119 | digitalWriteFast(2, LOW); 120 | digitalWriteFast(3, LOW); 121 | digitalWriteFast(4, LOW); 122 | digitalWriteFast(5, LOW); 123 | digitalWriteFast(6, LOW); 124 | digitalWriteFast(10, LOW); 125 | 126 | adc = new ADC(); 127 | 128 | adc->adc0->setAveraging(1); // set number of averages 129 | adc->adc0->setResolution(12); // set bits of resolution 130 | adc->adc0->setConversionSpeed(ADC_CONVERSION_SPEED::HIGH_SPEED); 131 | adc->adc0->setSamplingSpeed(ADC_SAMPLING_SPEED::HIGH_SPEED); 132 | // adc->adc0->enableInterrupts(adc0Isr); 133 | adc->adc0->enableDMA(); 134 | adc->adc0->startSingleRead(A0); 135 | 136 | dmaChannel1.source(ADC1_R0); 137 | dmaChannel1.destination((volatile uint16_t &)adc1Val); 138 | dmaChannel1.transferSize(2); 139 | dmaChannel1.transferCount(1); 140 | dmaChannel1.interruptAtCompletion(); 141 | dmaChannel1.attachInterrupt(adc0Isr); 142 | dmaChannel1.triggerAtHardwareEvent(DMAMUX_SOURCE_ADC1); 143 | dmaChannel1.enable(); 144 | 145 | if (inputsCount > 8) 146 | { 147 | adc->adc1->setAveraging(1); // set number of averages 148 | adc->adc1->setResolution(12); // set bits of resolution 149 | adc->adc1->setConversionSpeed(ADC_CONVERSION_SPEED::HIGH_SPEED); 150 | adc->adc1->setSamplingSpeed(ADC_SAMPLING_SPEED::HIGH_SPEED); 151 | // adc->adc1->enableInterrupts(adc1Isr); 152 | adc->adc1->enableDMA(); 153 | adc->adc1->startSingleRead(A1); 154 | 155 | dmaChannel2.source(ADC2_R0); 156 | dmaChannel2.destination((volatile uint16_t &)adc2Val); 157 | dmaChannel2.transferSize(2); 158 | dmaChannel2.transferCount(1); 159 | dmaChannel2.interruptAtCompletion(); 160 | dmaChannel2.attachInterrupt(adc1Isr); 161 | dmaChannel2.triggerAtHardwareEvent(DMAMUX_SOURCE_ADC2); 162 | dmaChannel2.enable(); 163 | } 164 | 165 | // Start conversions 166 | // TODO: Should be AUDIO_SAMPLE_RATE * 8 to reach 44.1kHz sampling per input, 167 | // but it becomes noisy. Possibly too fast for the Teensy, missing samples, could interpolation fix it? 168 | adc->adc0->startTimer(AUDIO_SAMPLE_RATE * 6); 169 | if (inputsCount > 8) 170 | { 171 | adc->adc1->startTimer(AUDIO_SAMPLE_RATE * 6); 172 | } 173 | 174 | lowPassCoeff[index] = 0.0005; 175 | accumulator[index] = INT16_MIN; 176 | headQueueTemp[index][0] = INT16_MIN; 177 | prevAccumulator[index] = 0; 178 | } 179 | 180 | inline void Input::update(void) 181 | { 182 | // TODO: Interpolate instead of filling the remainings 183 | // If the sampling was too slow then samples are missing, filling the remaining with the last value 184 | if (headQueueTempCount[this->index] < buffSize) 185 | { 186 | for (unsigned int i = headQueueTempCount[this->index]; i < buffSize; i++) 187 | { 188 | headQueueTemp[this->index][i] = headQueueTemp[this->index][i - 1]; 189 | headQueueTempCount[this->index]++; 190 | } 191 | } 192 | 193 | // Try to move head, we'll see if that works with the tail just bellow before actually doing it 194 | uint32_t h = head[this->index] + 1; 195 | 196 | // Ciruclar buffer, the head goes back to index 0 after reaching the max 197 | if (h >= maxBuffers) 198 | { 199 | h = 0; 200 | } 201 | 202 | if (h != tail[this->index]) 203 | { 204 | // The temp buffer is full and head is in a good position to write 205 | // let's write the temp buffer in the queue 206 | memcpy(queue[this->index][head[this->index]], headQueueTemp[this->index], AUDIO_BLOCK_SAMPLES * sizeof *headQueueTemp[this->index]); 207 | 208 | // Reset also the samples count of the temp buffer 209 | headQueueTempCount[this->index] = 0; 210 | 211 | // Moving the head 212 | head[this->index] = h; 213 | } 214 | 215 | audio_block_t *block; 216 | 217 | // allocate the audio blocks to transmit 218 | block = allocate(); 219 | 220 | // Raw output 221 | if (block) 222 | { 223 | int16_t *inputBuffer = this->readBuffer(); 224 | 225 | if (inputBuffer != NULL) 226 | { 227 | // Allows for derived class to alter the data before being transmitted 228 | inputBuffer = this->updateBefore(inputBuffer); 229 | memcpy(block->data, inputBuffer, AUDIO_BLOCK_SAMPLES * sizeof *inputBuffer); 230 | 231 | if (this->onChangeCallback && prevAccumulator[this->index] != accumulator[this->index]) 232 | { 233 | this->onChangeCallback(accumulator[this->index]); 234 | prevAccumulator[this->index] = accumulator[this->index]; 235 | } 236 | } 237 | 238 | if (this->midiInput != nullptr) 239 | { 240 | // Combining the MIDI input's value with the input's value 241 | int16_t *midiBlockData = this->midiInput->getBlockData(); 242 | 243 | for (uint8_t i = 0; i < AUDIO_BLOCK_SAMPLES; i++) 244 | { 245 | block->data[i] = constrain((block->data[i] + INT16_MAX) + (midiBlockData[i] + INT16_MAX) - INT16_MAX, INT16_MIN, INT16_MAX); 246 | } 247 | } 248 | 249 | transmit(block, 0); 250 | release(block); 251 | } 252 | } 253 | 254 | inline int16_t *Input::readBuffer() 255 | { 256 | uint32_t t = tail[this->index]; 257 | int16_t *result = queue[this->index][tail[this->index]]; 258 | 259 | if (++t >= maxBuffers) 260 | { 261 | t = 0; 262 | } 263 | 264 | tail[this->index] = t; 265 | 266 | return result; 267 | } 268 | 269 | elapsedMicros count; 270 | 271 | inline void Input::adc0Isr() 272 | { 273 | if (isr1Count == 0) 274 | { 275 | addSample(adc1Val, muxIndex1); 276 | 277 | if (inputsCount > 16) 278 | { 279 | // ADC1_HC0 = pinToChannel[2] | ADC_HC_AIEN; 280 | adc->adc0->startSingleRead(A2); 281 | isr1Count = 1; 282 | } 283 | else 284 | { 285 | iterateMux1(); 286 | } 287 | } 288 | else if (isr1Count == 1) 289 | { 290 | addSample(adc1Val, muxIndex1 + 16); 291 | iterateMux1(); 292 | isr1Count = 0; 293 | // ADC1_HC0 = pinToChannel[0] | ADC_HC_AIEN; 294 | adc->adc0->startSingleRead(A0); 295 | } 296 | 297 | dmaChannel1.clearInterrupt(); 298 | asm("DSB"); 299 | } 300 | 301 | inline void Input::adc1Isr() 302 | { 303 | if (isr2Count == 0) 304 | { 305 | addSample(adc2Val, muxIndex2 + 8); 306 | 307 | if (inputsCount > 24) 308 | { 309 | // ADC2_HC0 = pinToChannel[3] | ADC_HC_AIEN; 310 | adc->adc1->startSingleRead(A3); 311 | isr2Count = 1; 312 | } 313 | else 314 | { 315 | iterateMux2(); 316 | } 317 | } 318 | else if (isr2Count == 1) 319 | { 320 | addSample(adc2Val, muxIndex2 + 24); 321 | iterateMux2(); 322 | isr2Count = 0; 323 | // ADC2_HC0 = pinToChannel[1] | ADC_HC_AIEN; 324 | adc->adc1->startSingleRead(A1); 325 | } 326 | 327 | dmaChannel2.clearInterrupt(); 328 | asm("DSB"); 329 | } 330 | 331 | inline void Input::iterateMux1() 332 | { 333 | muxIndex1++; 334 | muxIndex1 = muxIndex1 % 8; 335 | 336 | digitalWriteFast(2, muxIndex1 & 1); 337 | digitalWriteFast(3, muxIndex1 & 2); 338 | digitalWriteFast(4, muxIndex1 & 4); 339 | } 340 | 341 | inline void Input::iterateMux2() 342 | { 343 | muxIndex2++; 344 | muxIndex2 = muxIndex2 % 8; 345 | 346 | digitalWriteFast(5, muxIndex2 & 1); 347 | digitalWriteFast(6, muxIndex2 & 2); 348 | digitalWriteFast(10, muxIndex2 & 4); 349 | } 350 | 351 | inline void Input::setLowPassCoeff(float coeff) 352 | { 353 | if (coeff < 0.0f) 354 | { 355 | coeff = 0; 356 | } 357 | if (coeff > 1.0f) 358 | { 359 | coeff = 1; 360 | } 361 | lowPassCoeff[this->index] = coeff; 362 | } 363 | 364 | inline void Input::addSample(uint16_t val, uint8_t inputIndex) 365 | { 366 | if (inputIndex >= inputsCount) 367 | { 368 | return; 369 | } 370 | 371 | if (headQueueTempCount[inputIndex] >= buffSize) 372 | { 373 | return; 374 | } 375 | 376 | int32_t newVal = val * 16 - 32768; 377 | newVal = constrain(newVal, -32300, 32750); 378 | newVal = (float)(newVal + 32300) / (32300 + 32750) * UINT16_MAX - INT16_MIN; 379 | if (newVal < INT16_MIN) 380 | { 381 | newVal = INT16_MIN; 382 | } 383 | 384 | for (uint8_t i = 0; i < downSamplingFactor; i++) 385 | { 386 | accumulator[inputIndex] = (lowPassCoeff[inputIndex] * newVal) + (1.0f - lowPassCoeff[inputIndex]) * accumulator[inputIndex]; 387 | headQueueTemp[inputIndex][headQueueTempCount[inputIndex]] = accumulator[inputIndex]; 388 | headQueueTempCount[inputIndex]++; 389 | } 390 | } 391 | 392 | inline void Input::setMidiInput(MidiInput *midiInput) 393 | { 394 | this->midiInput = midiInput; 395 | } 396 | 397 | inline void Input::onChange(void (*onChangeCallback)(int16_t value)) 398 | { 399 | this->onChangeCallback = onChangeCallback; 400 | } 401 | 402 | #endif 403 | -------------------------------------------------------------------------------- /firmware/Motherboard/InputButton.h: -------------------------------------------------------------------------------- 1 | #ifndef InputButton_h 2 | #define InputButton_h 3 | 4 | #include "Registrar.h" 5 | #include "Input.h" 6 | 7 | class InputButton : public Input, public Registrar 8 | { 9 | public: 10 | InputButton(int8_t index); 11 | void update(void); 12 | int16_t *&updateBefore(int16_t *&blockData); 13 | void setThresholds(int16_t onThreshold, int16_t offThreshold); 14 | void onChange(void (*onChangeCallback)(bool value)); 15 | void onPush(void (*onPushCallback)()); 16 | void onRelease(void (*onReleaseCallback)()); 17 | 18 | private: 19 | uint8_t index; 20 | int16_t offThreshold = 100; 21 | int16_t onThreshold = -100; 22 | bool value; 23 | void (*onChangeCallback)(bool value) = nullptr; 24 | void (*onPushCallback)() = nullptr; 25 | void (*onReleaseCallback)() = nullptr; 26 | }; 27 | 28 | inline InputButton::InputButton(int8_t index) : Input(index) 29 | { 30 | } 31 | 32 | inline void InputButton::update(void) 33 | { 34 | Input::update(); 35 | } 36 | 37 | inline int16_t *&InputButton::updateBefore(int16_t *&blockData) 38 | { 39 | for (int i = 0; i < AUDIO_BLOCK_SAMPLES; i++) 40 | { 41 | if (blockData[i] > offThreshold) 42 | { 43 | blockData[i] = INT16_MIN; 44 | } 45 | else if (blockData[i] < onThreshold) 46 | { 47 | blockData[i] = INT16_MAX; 48 | } 49 | } 50 | 51 | if (blockData[0] == INT16_MIN) 52 | { 53 | if (this->value) 54 | { 55 | if (this->onChangeCallback) 56 | { 57 | this->onChangeCallback(false); 58 | } 59 | 60 | if (this->onReleaseCallback) 61 | { 62 | this->onReleaseCallback(); 63 | } 64 | } 65 | this->value = false; 66 | } 67 | else 68 | { 69 | if (!this->value) 70 | { 71 | if (this->onChangeCallback) 72 | { 73 | this->onChangeCallback(true); 74 | } 75 | 76 | if (this->onPushCallback) 77 | { 78 | this->onPushCallback(); 79 | } 80 | } 81 | this->value = true; 82 | } 83 | 84 | return blockData; 85 | } 86 | 87 | inline void InputButton::setThresholds(int16_t onThreshold, int16_t offThreshold) 88 | { 89 | this->onThreshold = onThreshold; 90 | this->offThreshold = offThreshold; 91 | } 92 | 93 | inline void InputButton::onChange(void (*onChangeCallback)(bool value)) 94 | { 95 | this->onChangeCallback = onChangeCallback; 96 | } 97 | 98 | inline void InputButton::onPush(void (*onPushCallback)()) 99 | { 100 | this->onPushCallback = onPushCallback; 101 | } 102 | 103 | inline void InputButton::onRelease(void (*onReleaseCallback)()) 104 | { 105 | this->onReleaseCallback = onReleaseCallback; 106 | } 107 | 108 | #endif 109 | -------------------------------------------------------------------------------- /firmware/Motherboard/InputGate.h: -------------------------------------------------------------------------------- 1 | #ifndef InputGate_h 2 | #define InputGate_h 3 | 4 | #include "Registrar.h" 5 | #include "Input.h" 6 | 7 | class InputGate : public Input, public Registrar 8 | { 9 | public: 10 | InputGate(int8_t index); 11 | void update(void); 12 | int16_t *&updateBefore(int16_t *&blockData); 13 | void setThresholds(int16_t openThreshold, int16_t closeThreshold); 14 | void onOpen(void (*onOpenCallback)()); 15 | void onClose(void (*onCloseCallback)()); 16 | 17 | protected: 18 | uint8_t index; 19 | int16_t openThreshold = 100; 20 | int16_t closeThreshold = -100; 21 | bool isOpen = false; 22 | void (*onOpenCallback)() = nullptr; 23 | void (*onCloseCallback)() = nullptr; 24 | }; 25 | 26 | inline InputGate::InputGate(int8_t index) : Input(index) 27 | { 28 | } 29 | 30 | inline void InputGate::update(void) 31 | { 32 | Input::update(); 33 | } 34 | 35 | inline int16_t *&InputGate::updateBefore(int16_t *&blockData) 36 | { 37 | bool newIsOpen = false; 38 | for (int i = 0; i < AUDIO_BLOCK_SAMPLES; i++) 39 | { 40 | if (blockData[i] > openThreshold) 41 | { 42 | blockData[i] = INT16_MAX; 43 | newIsOpen = true; 44 | } 45 | else if (blockData[i] < closeThreshold) 46 | { 47 | blockData[i] = INT16_MIN; 48 | } 49 | } 50 | 51 | // Calling callback 52 | if(!this->isOpen && newIsOpen){ 53 | if(this->onOpenCallback){ 54 | this->onOpenCallback(); 55 | } 56 | }else if(this->isOpen && !newIsOpen){ 57 | if(this->onCloseCallback){ 58 | this->onCloseCallback(); 59 | } 60 | } 61 | 62 | this->isOpen = newIsOpen; 63 | 64 | return blockData; 65 | } 66 | 67 | inline void InputGate::setThresholds(int16_t openThreshold, int16_t closeThreshold) 68 | { 69 | this->openThreshold = openThreshold; 70 | this->closeThreshold = closeThreshold; 71 | } 72 | 73 | inline void InputGate::onOpen(void (*onOpenCallback)()) 74 | { 75 | this->onOpenCallback = onOpenCallback; 76 | } 77 | 78 | inline void InputGate::onClose(void (*onCloseCallback)()) 79 | { 80 | this->onCloseCallback = onCloseCallback; 81 | } 82 | 83 | #endif 84 | -------------------------------------------------------------------------------- /firmware/Motherboard/InputQuantized.h: -------------------------------------------------------------------------------- 1 | #ifndef InputQuantized_h 2 | #define InputQuantized_h 3 | 4 | #include "Registrar.h" 5 | #include "Input.h" 6 | 7 | class InputQuantized : public Input, public Registrar 8 | { 9 | public: 10 | InputQuantized(int8_t index); 11 | void update(void); 12 | int16_t *&updateBefore(int16_t *&blockData); 13 | void onNoteChange(void (*onNoteCallback)(byte note)); 14 | 15 | protected: 16 | byte note; 17 | void (*onNoteCallback)(byte) = nullptr; 18 | }; 19 | 20 | inline InputQuantized::InputQuantized(int8_t index) : Input(index) 21 | { 22 | this->setLowPassCoeff(0.01); 23 | } 24 | 25 | inline void InputQuantized::update(void) 26 | { 27 | Input::update(); 28 | } 29 | 30 | inline int16_t *&InputQuantized::updateBefore(int16_t *&blockData) 31 | { 32 | byte newNote = (int)((float)(blockData[0] + 32768) / 65535.0 * 12 * 5); 33 | 34 | if(newNote != this->note){ 35 | if(this->onNoteCallback){ 36 | this->onNoteCallback(newNote); 37 | } 38 | } 39 | this->note = newNote; 40 | 41 | return blockData; 42 | } 43 | 44 | 45 | inline void InputQuantized::onNoteChange(void (*onNoteCallback)(byte)) 46 | { 47 | this->onNoteCallback = onNoteCallback; 48 | } 49 | #endif 50 | -------------------------------------------------------------------------------- /firmware/Motherboard/InputRotary.h: -------------------------------------------------------------------------------- 1 | #ifndef InputRotary_h 2 | #define InputRotary_h 3 | 4 | #include "Registrar.h" 5 | #include "Input.h" 6 | 7 | /** 8 | * Rotary Encoder type of input 9 | * Pins should be connected this way: 10 | * - Out A -> 1k -> Input 11 | * - Out B -> 10k -> Input 12 | * - The pin in the middle to GND 13 | * - Switch pin 1 -> Input 14 | * - Switch pin 2 -> GND 15 | * - There should be a pull up of 10k on the Input 16 | */ 17 | class InputRotary : public Input, public Registrar 18 | { 19 | public: 20 | InputRotary(int8_t index); 21 | void update(void); 22 | int16_t *&updateBefore(int16_t *&blockData); 23 | // void setThresholds(int16_t onThreshold, int16_t offThreshold); 24 | void onChange(void (*onChangeCallback)(uint16_t value)); 25 | void onIncrease(void (*onIncreaseCallback)(uint16_t value)); 26 | void onDecrease(void (*onDecreaseCallback)(uint16_t value)); 27 | void onPush(void (*onPushCallback)()); 28 | void onRelease(void (*onReleaseCallback)()); 29 | void onClick(void (*onClickCallback)()); 30 | void onLongClick(void (*onLongClickCallback)()); 31 | void setMin(int16_t min); 32 | void setMax(int16_t max); 33 | 34 | private: 35 | uint8_t index; 36 | int state = 0; 37 | int8_t thresholdA = -100; 38 | int8_t thresholdB = -120; 39 | int8_t thresholdC = 10; 40 | int8_t thresholdD = -10; 41 | int8_t thresholdE = -125; 42 | int8_t thresholdF = 125; 43 | int16_t value = 0; 44 | int16_t min = INT16_MIN; 45 | int16_t max = INT16_MAX; 46 | bool isPushed; 47 | elapsedMillis millisSincePushed; 48 | void (*onChangeCallback)(uint16_t value) = nullptr; 49 | void (*onIncreaseCallback)(uint16_t value) = nullptr; 50 | void (*onDecreaseCallback)(uint16_t value) = nullptr; 51 | void (*onPushCallback)() = nullptr; 52 | void (*onReleaseCallback)() = nullptr; 53 | void (*onClickCallback)() = nullptr; 54 | void (*onLongClickCallback)() = nullptr; 55 | }; 56 | 57 | inline InputRotary::InputRotary(int8_t index) : Input(index) 58 | { 59 | this->setLowPassCoeff(1); 60 | } 61 | 62 | inline void InputRotary::update(void) 63 | { 64 | Input::update(); 65 | } 66 | 67 | inline int16_t *&InputRotary::updateBefore(int16_t *&blockData) 68 | { 69 | for (int i = 0; i < AUDIO_BLOCK_SAMPLES; i++) 70 | { 71 | 72 | int8_t val = blockData[i] >> 8; 73 | 74 | switch (state) 75 | { 76 | case 0: 77 | if (val < thresholdA && val > thresholdB) 78 | { 79 | state = 1; 80 | } 81 | else if (val < thresholdC && val > thresholdD) 82 | { 83 | state = -1; 84 | } 85 | break; 86 | case 1: 87 | if (val < thresholdC && val > thresholdD) 88 | { 89 | state = 2; 90 | } 91 | break; 92 | case -1: 93 | if (val < thresholdA && val > thresholdB) 94 | { 95 | state = -2; 96 | } 97 | break; 98 | case 2: 99 | if (value < this->max) 100 | { 101 | value++; 102 | } 103 | 104 | state = 0; 105 | 106 | if (this->onChangeCallback) 107 | { 108 | this->onChangeCallback(value); 109 | } 110 | if (this->onIncreaseCallback) 111 | { 112 | this->onIncreaseCallback(value); 113 | } 114 | break; 115 | case -2: 116 | if (value > this->min) 117 | { 118 | value--; 119 | } 120 | 121 | state = 0; 122 | 123 | if (this->onChangeCallback) 124 | { 125 | this->onChangeCallback(value); 126 | } 127 | if (this->onDecreaseCallback) 128 | { 129 | this->onDecreaseCallback(value); 130 | } 131 | break; 132 | } 133 | 134 | if (!isPushed && val < thresholdE) 135 | { 136 | isPushed = true; 137 | millisSincePushed = 0; 138 | if (this->onPushCallback) 139 | { 140 | this->onPushCallback(); 141 | } 142 | } 143 | else if (isPushed && val > thresholdE) 144 | { 145 | isPushed = false; 146 | if (this->onReleaseCallback) 147 | { 148 | this->onReleaseCallback(); 149 | } 150 | if (millisSincePushed > 200) 151 | { 152 | if (this->onLongClickCallback) 153 | { 154 | this->onLongClickCallback(); 155 | } 156 | } 157 | else 158 | { 159 | if (this->onClickCallback) 160 | { 161 | this->onClickCallback(); 162 | } 163 | } 164 | } 165 | 166 | if (blockData[i] > thresholdF) 167 | { 168 | state = 0; 169 | } 170 | } 171 | 172 | for (int i = 0; i < AUDIO_BLOCK_SAMPLES; i++) 173 | { 174 | blockData[i] = map(value, this->min, this->max, INT16_MIN, INT16_MAX); 175 | } 176 | 177 | return blockData; 178 | } 179 | 180 | // inline void InputRotary::setThresholds(int16_t onThreshold, int16_t offThreshold) 181 | // { 182 | // this->onThreshold = onThreshold; 183 | // this->offThreshold = offThreshold; 184 | // } 185 | 186 | inline void InputRotary::onChange(void (*onChangeCallback)(uint16_t value)) 187 | { 188 | this->onChangeCallback = onChangeCallback; 189 | } 190 | 191 | inline void InputRotary::onIncrease(void (*onIncreaseCallback)(uint16_t value)) 192 | { 193 | this->onIncreaseCallback = onIncreaseCallback; 194 | } 195 | 196 | inline void InputRotary::onDecrease(void (*onDecreaseCallback)(uint16_t value)) 197 | { 198 | this->onDecreaseCallback = onDecreaseCallback; 199 | } 200 | 201 | inline void InputRotary::onPush(void (*onPushCallback)()) 202 | { 203 | this->onPushCallback = onPushCallback; 204 | } 205 | 206 | inline void InputRotary::onRelease(void (*onReleaseCallback)()) 207 | { 208 | this->onReleaseCallback = onReleaseCallback; 209 | } 210 | 211 | inline void InputRotary::onClick(void (*onClickCallback)()) 212 | { 213 | this->onClickCallback = onClickCallback; 214 | } 215 | 216 | inline void InputRotary::onLongClick(void (*onLongClickCallback)()) 217 | { 218 | this->onLongClickCallback = onLongClickCallback; 219 | } 220 | 221 | inline void InputRotary::setMin(int16_t min) 222 | { 223 | this->min = min; 224 | } 225 | 226 | inline void InputRotary::setMax(int16_t max) 227 | { 228 | this->max = max; 229 | } 230 | #endif 231 | -------------------------------------------------------------------------------- /firmware/Motherboard/InputTrigger.h: -------------------------------------------------------------------------------- 1 | #ifndef InputTrigger_h 2 | #define InputTrigger_h 3 | 4 | #include "Registrar.h" 5 | #include "Input.h" 6 | 7 | class InputTrigger : public Input, public Registrar 8 | { 9 | public: 10 | InputTrigger(int8_t index); 11 | void update(void); 12 | int16_t *&updateBefore(int16_t *&blockData); 13 | void setThreshold(int16_t threshold); 14 | 15 | private: 16 | uint8_t index; 17 | int16_t threshold = 0; 18 | uint16_t triggerDuration = 256; // 256 samples at 44100Hz means 5.8ms 19 | uint16_t triggerSamplesleft = 0; 20 | int16_t previousSample = 0; 21 | }; 22 | 23 | inline InputTrigger::InputTrigger(int8_t index) : Input(index) 24 | { 25 | } 26 | 27 | inline void InputTrigger::update(void) 28 | { 29 | Input::update(); 30 | } 31 | 32 | inline int16_t *&InputTrigger::updateBefore(int16_t *&blockData) 33 | { 34 | 35 | for (int i = 0; i < AUDIO_BLOCK_SAMPLES; i++) 36 | { 37 | if (blockData[i] > this->threshold && this->previousSample < this->threshold && this->triggerSamplesleft == 0) 38 | { 39 | this->triggerSamplesleft = this->triggerDuration; 40 | } 41 | this->previousSample = blockData[i]; 42 | 43 | if (this->triggerSamplesleft > 0) 44 | { 45 | this->triggerSamplesleft--; 46 | blockData[i] = INT16_MAX; 47 | } 48 | else 49 | { 50 | blockData[i] = INT16_MIN; 51 | } 52 | } 53 | 54 | return blockData; 55 | } 56 | 57 | inline void InputTrigger::setThreshold(int16_t threshold) 58 | { 59 | this->threshold = threshold; 60 | } 61 | 62 | #endif 63 | -------------------------------------------------------------------------------- /firmware/Motherboard/MidiCCInput.h: -------------------------------------------------------------------------------- 1 | #ifndef MidiCCInput_h 2 | #define MidiCCInput_h 3 | 4 | #include "Setting.h" 5 | #include "MidiInput.h" 6 | 7 | // TODO: add callbacks 8 | 9 | class MidiCCInput : public MidiInput, public Registrar 10 | { 11 | public: 12 | MidiCCInput(Setting *ccSetting); 13 | MidiCCInput(byte control); 14 | 15 | void setMin(byte min); 16 | void setMax(byte max); 17 | byte getMin(); 18 | byte getMax(); 19 | byte getControl(); 20 | static void handleMidiControlChange(byte channel, byte control, byte value); 21 | 22 | private: 23 | byte min = 0; 24 | byte max = 127; 25 | byte control = 0; 26 | }; 27 | 28 | inline MidiCCInput::MidiCCInput(Setting *ccSetting) : MidiInput(ccSetting) 29 | { 30 | // TODO: this->control = ccSetting->getValue() ? 31 | // Could make things easier but would require a reboot after settings change 32 | } 33 | 34 | inline MidiCCInput::MidiCCInput(byte control) : MidiInput(nullptr) 35 | { 36 | this->control = control; 37 | } 38 | 39 | inline void MidiCCInput::setMin(byte min) 40 | { 41 | // Min has to be max - 1; 42 | if (min > 126) 43 | { 44 | min = 126; 45 | } 46 | 47 | this->min = min; 48 | } 49 | 50 | inline void MidiCCInput::setMax(byte max) 51 | { 52 | 53 | if (max > 127) 54 | { 55 | max = 127; 56 | } 57 | 58 | this->max = max; 59 | } 60 | 61 | inline byte MidiCCInput::getMin() 62 | { 63 | return this->min; 64 | } 65 | 66 | inline byte MidiCCInput::getMax() 67 | { 68 | return this->max; 69 | } 70 | 71 | inline byte MidiCCInput::getControl() 72 | { 73 | return this->control; 74 | } 75 | 76 | #endif -------------------------------------------------------------------------------- /firmware/Motherboard/MidiCCOutput.h: -------------------------------------------------------------------------------- 1 | #ifndef MidiCCOutput_h 2 | #define MidiCCOutput_h 3 | 4 | #include "Setting.h" 5 | #include "MidiOutput.h" 6 | 7 | class MidiCCOutput : public MidiOutput, public Registrar 8 | { 9 | public: 10 | MidiCCOutput(Setting *ccSetting); 11 | MidiCCOutput(byte control); 12 | void setMin(byte min); 13 | void setMax(byte max); 14 | byte getMin(); 15 | byte getMax(); 16 | byte getControl(); 17 | void send(); 18 | 19 | private: 20 | byte min = 0; 21 | byte max = 127; 22 | byte control = 0; 23 | }; 24 | 25 | inline MidiCCOutput::MidiCCOutput(Setting *ccSetting) : MidiOutput(ccSetting) 26 | { 27 | // TODO: this->control = ccSetting->getValue() ? 28 | // Could make things easier but would require a reboot after settings change 29 | } 30 | 31 | inline MidiCCOutput::MidiCCOutput(byte control) : MidiOutput(nullptr) 32 | { 33 | this->control = control; 34 | } 35 | 36 | inline void MidiCCOutput::setMin(byte min) 37 | { 38 | // Min has to be max - 1; 39 | if (min > 126) 40 | { 41 | min = 126; 42 | } 43 | 44 | this->min = min; 45 | } 46 | 47 | inline void MidiCCOutput::setMax(byte max) 48 | { 49 | 50 | if (max > 127) 51 | { 52 | max = 127; 53 | } 54 | 55 | this->max = max; 56 | } 57 | 58 | inline byte MidiCCOutput::getMin() 59 | { 60 | return this->min; 61 | } 62 | 63 | inline byte MidiCCOutput::getMax() 64 | { 65 | return this->max; 66 | } 67 | 68 | inline byte MidiCCOutput::getControl() 69 | { 70 | return this->control; 71 | } 72 | 73 | inline void MidiCCOutput::send() 74 | { 75 | usbMIDI.sendControlChange(this->control, this->value, 1); 76 | MIDI.sendControlChange(this->control, this->value, 1); 77 | } 78 | 79 | #endif -------------------------------------------------------------------------------- /firmware/Motherboard/MidiIO.h: -------------------------------------------------------------------------------- 1 | #ifndef MidiIO_h 2 | #define MidiIO_h 3 | 4 | class MidiIO 5 | { 6 | public: 7 | MidiIO(Setting *setting); 8 | byte getValue(); 9 | void setValue(byte value); 10 | int16_t *getBlockData(); 11 | Setting *getSetting(); 12 | void onChange(void (*onChangeCallback)(byte value)); 13 | // TODO: setChannelSetting 14 | 15 | protected: 16 | Setting *setting = nullptr; 17 | byte value = 0; 18 | int16_t mappedValue = 0; // TODO: rename 19 | int16_t blockData[AUDIO_BLOCK_SAMPLES] = {0}; 20 | void (*onChangeCallback)(byte) = nullptr; 21 | }; 22 | 23 | inline MidiIO::MidiIO(Setting *setting = nullptr) 24 | { 25 | this->setting = setting; 26 | } 27 | 28 | inline byte MidiIO::getValue() 29 | { 30 | return this->value; 31 | } 32 | 33 | inline void MidiIO::setValue(byte value) 34 | { 35 | if(this->value != value){ 36 | if(this->onChangeCallback){ 37 | this->onChangeCallback(value); 38 | } 39 | } 40 | this->value = value; 41 | } 42 | 43 | inline int16_t *MidiIO::getBlockData() 44 | { 45 | return this->blockData; 46 | } 47 | 48 | inline Setting *MidiIO::getSetting() 49 | { 50 | return this->setting; 51 | } 52 | 53 | inline void MidiIO::onChange(void (*onChangeCallback)(byte)) 54 | { 55 | this->onChangeCallback = onChangeCallback; 56 | } 57 | #endif -------------------------------------------------------------------------------- /firmware/Motherboard/MidiInput.h: -------------------------------------------------------------------------------- 1 | #ifndef MidiInput_h 2 | #define MidiInput_h 3 | 4 | #include "MidiIO.h" 5 | 6 | // TODO: Add a change callback 7 | using MidiControlChangeCallbackFunction = void (*)(int16_t); 8 | 9 | class MidiInput : public MidiIO, public AudioStream 10 | { 11 | public: 12 | MidiInput(Setting *setting); 13 | void update(void); 14 | // TODO: setChannelSetting 15 | }; 16 | 17 | inline MidiInput::MidiInput(Setting *setting = nullptr) : MidiIO(setting), AudioStream(0, NULL) 18 | { 19 | this->active = true; 20 | } 21 | 22 | inline void MidiInput::update(void) 23 | { 24 | audio_block_t *block; 25 | 26 | // allocate the audio blocks to transmit 27 | block = allocate(); 28 | 29 | if (block) 30 | { 31 | int16_t newMappedValue = map(this->value, 0, 127, INT16_MIN, INT16_MAX); 32 | for (int i = 0; i < AUDIO_BLOCK_SAMPLES; i++) 33 | { 34 | this->mappedValue = (float)this->mappedValue * 0.9 + (float)newMappedValue * 0.1; 35 | block->data[i] = this->mappedValue; 36 | this->blockData[i] = this->mappedValue; 37 | } 38 | 39 | transmit(block, 0); 40 | release(block); 41 | } 42 | } 43 | #endif -------------------------------------------------------------------------------- /firmware/Motherboard/MidiManager.h: -------------------------------------------------------------------------------- 1 | #ifndef MidiManager_h 2 | #define MidiManager_h 3 | 4 | #include 5 | 6 | MIDI_CREATE_INSTANCE(HardwareSerial, Serial1, MIDI); // MIDI library init 7 | 8 | #include "MidiCCInput.h" 9 | #include "MidiNoteInput.h" 10 | #include "MidiNotesInput.h" 11 | #include "MidiCCOutput.h" 12 | 13 | class MidiManager : public AudioStream 14 | { 15 | public: 16 | static MidiManager *getInstance(); 17 | void update(void); 18 | 19 | private: 20 | static MidiManager *instance; 21 | MidiManager(); 22 | static void handleMidiControlChange(byte channel, byte control, byte value); 23 | static void handleMidiNoteOn(byte channel, byte control, byte velocity); 24 | static void handleMidiNoteOff(byte channel, byte control, byte velocity); 25 | }; 26 | 27 | // Singleton pre init 28 | MidiManager *MidiManager::instance = nullptr; 29 | 30 | inline MidiManager::MidiManager() : AudioStream(0, NULL) 31 | { 32 | this->active = true; 33 | 34 | // MIDI init 35 | MIDI.begin(MIDI_CHANNEL_OMNI); 36 | Serial1.begin(31250, SERIAL_8N1_TXINV); 37 | 38 | // Callbacks 39 | MIDI.setHandleControlChange(handleMidiControlChange); 40 | usbMIDI.setHandleControlChange(handleMidiControlChange); 41 | MIDI.setHandleNoteOn(handleMidiNoteOn); 42 | usbMIDI.setHandleNoteOn(handleMidiNoteOn); 43 | MIDI.setHandleNoteOff(handleMidiNoteOff); 44 | usbMIDI.setHandleNoteOff(handleMidiNoteOff); 45 | } 46 | 47 | /** 48 | Singleton instance 49 | */ 50 | inline MidiManager *MidiManager::getInstance() 51 | { 52 | if (!instance) 53 | instance = new MidiManager; 54 | return instance; 55 | } 56 | 57 | inline void MidiManager::update(void) 58 | { 59 | usbMIDI.read(); 60 | MIDI.read(); 61 | } 62 | 63 | inline void MidiManager::handleMidiControlChange(byte channel, byte control, byte value) 64 | { 65 | 66 | for (unsigned int i = 0; i < MidiCCInput::getCount(); i++) 67 | { 68 | MidiCCInput *midiInput = MidiCCInput::get(i); 69 | byte mappedValue = map(value, 0, 127, midiInput->getMin(), midiInput->getMax()); 70 | 71 | if (midiInput->getSetting() != nullptr) 72 | { 73 | if ((!isnan(midiInput->getSetting()->getValue()) && (byte)midiInput->getSetting()->getValue() == control) || (isnan(midiInput->getSetting()->getValue()) && (byte)midiInput->getSetting()->getDefaultValue() == control)) 74 | { 75 | midiInput->setValue(mappedValue); 76 | } 77 | } 78 | else if (control == midiInput->getControl()) 79 | { 80 | midiInput->setValue(mappedValue); 81 | } 82 | } 83 | } 84 | 85 | inline void MidiManager::handleMidiNoteOn(byte channel, byte note, byte velocity) 86 | { 87 | // MidiNoteInput reacts to 1 note and value is the velocity 88 | for (unsigned int i = 0; i < MidiNoteInput::getCount(); i++) 89 | { 90 | MidiNoteInput *midiInput = MidiNoteInput::get(i); 91 | byte mappedValue = map(velocity, 0, 127, midiInput->getMin(), midiInput->getMax()); 92 | 93 | if (midiInput->getSetting() != nullptr) 94 | { 95 | if ((!isnan(midiInput->getSetting()->getValue()) && (byte)midiInput->getSetting()->getValue() == note) || (isnan(midiInput->getSetting()->getValue()) && (byte)midiInput->getSetting()->getDefaultValue() == note)) 96 | { 97 | midiInput->setValue(mappedValue); 98 | } 99 | } 100 | else if (note == midiInput->getNote()) 101 | { 102 | midiInput->setValue(mappedValue); 103 | } 104 | } 105 | 106 | // MidiNotesInput reacts to all notes and value is the note 107 | for (unsigned int i = 0; i < MidiNotesInput::getCount(); i++) 108 | { 109 | MidiNotesInput *midiInput = MidiNotesInput::get(i); 110 | midiInput->setValue(note); 111 | if(velocity > 0){ 112 | midiInput->noteOn(note, velocity); 113 | }else{ 114 | midiInput->noteOff(note); 115 | } 116 | } 117 | } 118 | 119 | inline void MidiManager::handleMidiNoteOff(byte channel, byte note, byte velocity) 120 | { 121 | MidiManager::handleMidiNoteOn(channel, note, 0); 122 | } 123 | 124 | #endif -------------------------------------------------------------------------------- /firmware/Motherboard/MidiNoteInput.h: -------------------------------------------------------------------------------- 1 | #ifndef MidiNoteInput_h 2 | #define MidiNoteInput_h 3 | 4 | #include "Setting.h" 5 | #include "MidiInput.h" 6 | 7 | // TODO: add callbacks 8 | 9 | class MidiNoteInput : public MidiInput, public Registrar 10 | { 11 | public: 12 | MidiNoteInput(Setting *noteSetting); 13 | MidiNoteInput(byte note); 14 | void setMin(byte min); 15 | void setMax(byte max); 16 | byte getMin(); 17 | byte getMax(); 18 | byte getNote(); 19 | 20 | private: 21 | byte min = 0; 22 | byte max = 127; 23 | byte note = 0; 24 | }; 25 | 26 | inline MidiNoteInput::MidiNoteInput(Setting *setting) : MidiInput(setting) 27 | { 28 | // TODO: this->note = noteSetting->getValue() ? 29 | // Could make things easier but would require a reboot after settings change 30 | } 31 | 32 | inline MidiNoteInput::MidiNoteInput(byte note) : MidiInput(nullptr) 33 | { 34 | this->note = note; 35 | } 36 | 37 | inline void MidiNoteInput::setMin(byte min) 38 | { 39 | // Min has to be max - 1; 40 | if (min > 126) 41 | { 42 | min = 126; 43 | } 44 | 45 | this->min = min; 46 | } 47 | 48 | inline void MidiNoteInput::setMax(byte max) 49 | { 50 | 51 | if (max > 127) 52 | { 53 | max = 127; 54 | } 55 | 56 | this->max = max; 57 | } 58 | 59 | inline byte MidiNoteInput::getMin() 60 | { 61 | return this->min; 62 | } 63 | 64 | inline byte MidiNoteInput::getMax() 65 | { 66 | return this->max; 67 | } 68 | 69 | inline byte MidiNoteInput::getNote() 70 | { 71 | return this->note; 72 | } 73 | 74 | #endif -------------------------------------------------------------------------------- /firmware/Motherboard/MidiNotesInput.h: -------------------------------------------------------------------------------- 1 | #ifndef MidiNotesInput_h 2 | #define MidiNotesInput_h 3 | 4 | // #include "Setting.h" 5 | #include "MidiInput.h" 6 | 7 | // TODO: add callbacks 8 | 9 | class MidiNotesInput : public MidiInput, public Registrar 10 | { 11 | public: 12 | MidiNotesInput(); 13 | void noteOn(byte note, byte velocity); 14 | void noteOff(byte note); 15 | void onNoteOn(void (*onNoteOnCallback)(byte note, byte velocity)); 16 | void onNoteOff(void (*onNoteOffCallback)(byte note)); 17 | 18 | private: 19 | void (*onNoteOnCallback)(byte note, byte velocity) = nullptr; 20 | void (*onNoteOffCallback)(byte note) = nullptr; 21 | }; 22 | 23 | inline MidiNotesInput::MidiNotesInput() : MidiInput(nullptr) 24 | { 25 | } 26 | 27 | inline void MidiNotesInput::noteOn(byte note, byte velocity){ 28 | if(this->onNoteOnCallback){ 29 | this->onNoteOnCallback(note, velocity); 30 | } 31 | } 32 | inline void MidiNotesInput::noteOff(byte note){ 33 | if(this->onNoteOffCallback){ 34 | this->onNoteOffCallback(note); 35 | } 36 | } 37 | 38 | inline void MidiNotesInput::onNoteOn(void (*onNoteOnCallback)(byte, byte)) 39 | { 40 | this->onNoteOnCallback = onNoteOnCallback; 41 | } 42 | 43 | inline void MidiNotesInput::onNoteOff(void (*onNoteOffCallback)(byte)) 44 | { 45 | this->onNoteOffCallback = onNoteOffCallback; 46 | } 47 | #endif -------------------------------------------------------------------------------- /firmware/Motherboard/MidiOutput.h: -------------------------------------------------------------------------------- 1 | #ifndef MidiOutput_h 2 | #define MidiOutput_h 3 | 4 | #include "MidiIO.h" 5 | 6 | class MidiOutput : public MidiIO, public AudioStream 7 | { 8 | public: 9 | MidiOutput(Setting *setting); 10 | void update(void); 11 | virtual void send() = 0; 12 | 13 | private: 14 | uint32_t testval = 0; 15 | audio_block_t *inputQueueArray[1]; 16 | }; 17 | 18 | inline MidiOutput::MidiOutput(Setting *setting = nullptr) : MidiIO(setting), AudioStream(1, inputQueueArray) 19 | { 20 | this->active = true; 21 | } 22 | 23 | inline void MidiOutput::update(void) 24 | { 25 | // Receive input data 26 | audio_block_t *block; 27 | block = receiveReadOnly(0); 28 | 29 | if (block) 30 | { 31 | this->mappedValue = (float)this->mappedValue * 0.95 + (float)block->data[0] * 0.05; 32 | byte newValue = (this->mappedValue + INT16_MAX) >> 9; 33 | 34 | if (newValue != this->value) 35 | { 36 | this->value = newValue; 37 | this->send(); 38 | } 39 | 40 | release(block); 41 | } 42 | } 43 | 44 | #endif -------------------------------------------------------------------------------- /firmware/Motherboard/Motherboard.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Motherboard 3 | * Ghost In Translation 4 | * version: 2.0.2 5 | */ 6 | 7 | #ifndef Motherboard_h 8 | #define Motherboard_h 9 | 10 | #include "SystemExclusiveManager.h" 11 | #include "Input.h" 12 | #include "InputButton.h" 13 | #include "InputGate.h" 14 | #include "InputRotary.h" 15 | #include "InputTrigger.h" 16 | #include "InputQuantized.h" 17 | #include "MidiManager.h" 18 | #include "Output.h" 19 | #include "OutputGate.h" 20 | #include "OutputLed.h" 21 | #include "OutputTrigger.h" 22 | #include "Vca.h" 23 | #include "Vcc.h" 24 | 25 | class Motherboard 26 | { 27 | 28 | private: 29 | // Singleton 30 | static Motherboard *instance; 31 | Motherboard(); 32 | 33 | public: 34 | static Motherboard *getInstance(); 35 | }; 36 | 37 | // Singleton pre init 38 | Motherboard *Motherboard::instance = nullptr; 39 | 40 | inline Motherboard::Motherboard() 41 | { 42 | // TODO: If this class only instanciate classes, just instanciate the classes 43 | MidiManager::getInstance(); 44 | SystemExclusiveManager::getInstance(); 45 | } 46 | 47 | /** 48 | Singleton instance 49 | */ 50 | inline Motherboard *Motherboard::getInstance() 51 | { 52 | if (!instance) 53 | instance = new Motherboard; 54 | return instance; 55 | } 56 | 57 | Motherboard *motherboard = Motherboard::getInstance(); 58 | #endif -------------------------------------------------------------------------------- /firmware/Motherboard/Output.h: -------------------------------------------------------------------------------- 1 | #ifndef Output_h 2 | #define Output_h 3 | 4 | #include "AudioStream.h" 5 | #include 6 | 7 | #define REGISTERS_LATCH_PIN 9 8 | elapsedMicros counter; 9 | uint16_t cc = 0; 10 | class Output : public AudioStream 11 | { 12 | public: 13 | Output(int8_t index); 14 | void update(void); 15 | virtual int16_t *&updateBefore(int16_t *&blockData) { return blockData; }; 16 | static void timerCallback(); 17 | void setSmoothing(float smoothing); 18 | 19 | protected: 20 | audio_block_t *inputQueueArray[1]; 21 | uint8_t index; 22 | static const uint8_t maxOutputs = 16; 23 | static uint16_t error[maxOutputs]; 24 | static const uint16_t resolution = 256; 25 | static uint16_t ticks; 26 | static uint16_t currentBit; 27 | static const uint32_t pwmSampleRate = (786000 / resolution); 28 | static const uint16_t buffSize = (1.0 / AUDIO_SAMPLE_RATE * AUDIO_BLOCK_SAMPLES) / (1.0 / pwmSampleRate) + 0.5; 29 | static const uint16_t audioSampleRateToPwmSampleRateRatio = ((uint16_t)AUDIO_SAMPLE_RATE / pwmSampleRate); 30 | static uint16_t queue[maxOutputs][buffSize * 2]; 31 | static uint16_t head[maxOutputs]; 32 | float smoothing = 0; 33 | double smoothingPrevValue = 0; 34 | }; 35 | 36 | uint16_t Output::error[maxOutputs] = {0}; 37 | uint16_t Output::head[maxOutputs] = {0}; 38 | uint16_t Output::queue[maxOutputs][buffSize * 2] = {{0}}; 39 | uint16_t Output::ticks = 1; 40 | uint16_t Output::currentBit = 1; 41 | 42 | inline Output::Output(int8_t index) 43 | : AudioStream(1, inputQueueArray) 44 | { 45 | this->index = index; 46 | this->active = true; 47 | 48 | // SPI 49 | pinMode(REGISTERS_LATCH_PIN, OUTPUT); 50 | SPI.setBitOrder(MSBFIRST); 51 | SPI.setDataMode(SPI_MODE0); 52 | SPI.setClockDivider(SPI_CLOCK_DIV2); 53 | SPI.begin(); 54 | 55 | // Timer 56 | PIT_MCR = 0; // Enable PIT 57 | PIT_LDVAL0 = 60; // 60=786000Hz 70=675000Hz // 135 = 352800Hz 58 | PIT_TCTRL0 = PIT_TCTRL_TIE; // PIT Interrupt enable for Timer0 59 | PIT_TCTRL0 |= PIT_TCTRL_TEN; // start Timer0 60 | attachInterruptVector(IRQ_PIT, timerCallback); 61 | NVIC_ENABLE_IRQ(IRQ_PIT); 62 | // NVIC_SET_PRIORITY(IRQ_PIT, 200); // Needed so that this timer doesn't interferes with the audio 63 | } 64 | 65 | inline void Output::update(void) 66 | { 67 | // Receive input data 68 | audio_block_t *block; 69 | block = receiveReadOnly(0); 70 | 71 | int16_t *blockDataPointer; 72 | int16_t blockData[AUDIO_BLOCK_SAMPLES]{INT16_MIN}; 73 | blockDataPointer = blockData; 74 | 75 | if (block) 76 | { 77 | for (uint16_t i = 0; i < AUDIO_BLOCK_SAMPLES; i++) 78 | { 79 | blockDataPointer[i] = block->data[i]; 80 | } 81 | release(block); 82 | } 83 | 84 | // Allows for derived class to alter the block before being processed here 85 | blockDataPointer = this->updateBefore(blockDataPointer); 86 | 87 | uint8_t sampleIndex = head[this->index]; 88 | 89 | for (uint16_t i = 0; i < AUDIO_BLOCK_SAMPLES; i++) 90 | { 91 | 92 | this->smoothingPrevValue = (double)blockDataPointer[i] * (1.0f - this->smoothing) + this->smoothingPrevValue * this->smoothing; 93 | blockDataPointer[i] = this->smoothingPrevValue; 94 | 95 | if (audioSampleRateToPwmSampleRateRatio <= 1 || i % audioSampleRateToPwmSampleRateRatio == 0) 96 | { 97 | 98 | if (++sampleIndex >= buffSize) 99 | { 100 | sampleIndex = 0; 101 | } 102 | 103 | uint16_t offsetValue = (blockDataPointer[i] + INT16_MAX) / 65535.0 * resolution - 0.5; 104 | queue[this->index][sampleIndex] = offsetValue; 105 | } 106 | } 107 | } 108 | 109 | /** 110 | * Implementation of the Binary Code Modulation method 111 | * https://www.batsocks.co.uk/readme/art_bcm_3.htm 112 | * 113 | */ 114 | inline void Output::timerCallback() 115 | { 116 | if (ticks == currentBit) 117 | { 118 | uint32_t bits = 0; 119 | for (uint8_t i = 0; i < maxOutputs; i++) 120 | { 121 | if (queue[i][head[i]] & currentBit) 122 | { 123 | bits = bits | 1 << i; 124 | } 125 | } 126 | 127 | SPI.beginTransaction(SPISettings(50000000, MSBFIRST, SPI_MODE0)); 128 | 129 | // Set the latch to low (activate the shift registers) 130 | digitalWriteFast(REGISTERS_LATCH_PIN, LOW); 131 | 132 | SPI.transfer16(bits & 0xFFFF); 133 | 134 | // Set the latch to high (shift registers actually set their pins and stop listening) 135 | digitalWriteFast(REGISTERS_LATCH_PIN, HIGH); 136 | 137 | SPI.endTransaction(); 138 | 139 | currentBit <<= 1; 140 | 141 | if (currentBit > resolution / 2) 142 | { 143 | currentBit = 1; 144 | } 145 | } 146 | 147 | ticks++; 148 | if (ticks > resolution) 149 | { 150 | ticks = 1; 151 | 152 | for (uint8_t i = 0; i < maxOutputs; i++) 153 | { 154 | head[i]++; 155 | if (head[i] >= buffSize) 156 | { 157 | head[i] = 0; 158 | } 159 | } 160 | } 161 | 162 | PIT_TFLG0 |= PIT_TFLG_TIF; // to enable interrupt again 163 | } 164 | 165 | /** 166 | * @brief Set the smoothing coefficient 167 | * 168 | * @param smoothing Between 0 and 1; 169 | */ 170 | inline void Output::setSmoothing(float smoothing) 171 | { 172 | if (smoothing < 0) 173 | { 174 | smoothing = 0; 175 | } 176 | else if (smoothing >= 1) 177 | { 178 | smoothing = 0.999999; 179 | } 180 | 181 | this->smoothing = smoothing; 182 | } 183 | 184 | #endif 185 | -------------------------------------------------------------------------------- /firmware/Motherboard/OutputGate.h: -------------------------------------------------------------------------------- 1 | #ifndef OutputGate_h 2 | #define OutputGate_h 3 | 4 | #include "Registrar.h" 5 | #include "Output.h" 6 | 7 | class OutputGate : public Output, public Registrar 8 | { 9 | public: 10 | OutputGate(int8_t index); 11 | void update(void); 12 | int16_t *&updateBefore(int16_t *&blockData); 13 | // void open(); 14 | // void close(); 15 | void setThresholds(int16_t openThreshold, int16_t closeThreshold); 16 | 17 | private: 18 | audio_block_t *inputQueueArray[1]; 19 | uint8_t index; 20 | // bool isOpen = false; 21 | int16_t openThreshold = 100; 22 | int16_t closeThreshold = -100; 23 | }; 24 | 25 | inline OutputGate::OutputGate(int8_t index) : Output(index) 26 | { 27 | } 28 | 29 | inline void OutputGate::update(void) 30 | { 31 | Output::update(); 32 | } 33 | 34 | inline int16_t *&OutputGate::updateBefore(int16_t *&blockData) 35 | { 36 | if (blockData) 37 | { 38 | for (int i = 0; i < AUDIO_BLOCK_SAMPLES; i++) 39 | { 40 | if (blockData[i] > openThreshold) 41 | { 42 | blockData[i] = INT16_MAX; 43 | } 44 | else if (blockData[i] < closeThreshold) 45 | { 46 | blockData[i] = INT16_MIN; 47 | } 48 | } 49 | } 50 | 51 | return blockData; 52 | } 53 | 54 | // inline void OutputGate::open() 55 | // { 56 | // this->isOpen = true; 57 | // } 58 | 59 | // inline void OutputGate::close() 60 | // { 61 | // this->isOpen = false; 62 | // } 63 | 64 | inline void OutputGate::setThresholds(int16_t openThreshold, int16_t closeThreshold) 65 | { 66 | this->openThreshold = openThreshold; 67 | this->closeThreshold = closeThreshold; 68 | } 69 | 70 | #endif 71 | -------------------------------------------------------------------------------- /firmware/Motherboard/OutputLed.h: -------------------------------------------------------------------------------- 1 | #ifndef OutputLed_h 2 | #define OutputLed_h 3 | 4 | #include "Registrar.h" 5 | #include "Output.h" 6 | 7 | class OutputLed : public Output, public Registrar 8 | { 9 | public: 10 | enum Status 11 | { 12 | Off, 13 | On, 14 | Blink, 15 | BlinkFast, 16 | BlinkOnce, 17 | Default 18 | }; 19 | 20 | OutputLed(int8_t index); 21 | void update(void); 22 | int16_t *&updateBefore(int16_t *&blockData); 23 | void setStatus(OutputLed::Status status); 24 | 25 | private: 26 | audio_block_t *inputQueueArray[1]; 27 | uint8_t index; 28 | uint8_t multiplier = 0; 29 | Status status = Default; 30 | uint16_t blinkingDuration = 400.0 / 1000 / (1.0 / AUDIO_SAMPLE_RATE); // 17640 samples at 44100Hz means 400ms 31 | uint16_t fastBlinkingDuration = 200.0 / 1000 / (1.0 / AUDIO_SAMPLE_RATE); // 8820 samples at 44100Hz means 200ms 32 | uint16_t blinkOnceDuration = 10.0 / 1000 / (1.0 / AUDIO_SAMPLE_RATE); // 441 samples at 44100Hz means 10ms 33 | uint16_t statusSamplesleft = 0; 34 | }; 35 | 36 | inline OutputLed::OutputLed(int8_t index) : Output(index) 37 | { 38 | this->smoothing = 0.9995; 39 | } 40 | 41 | inline int16_t *&OutputLed::updateBefore(int16_t *&blockData) 42 | { 43 | 44 | for (int i = 0; i < AUDIO_BLOCK_SAMPLES; i++) 45 | { 46 | switch (this->status) 47 | { 48 | case Blink: 49 | if (this->statusSamplesleft > this->blinkingDuration / 2) 50 | { 51 | blockData[i] = INT16_MIN; 52 | } 53 | else if (this->statusSamplesleft == 0) 54 | { 55 | this->statusSamplesleft = this->blinkingDuration; 56 | } 57 | else 58 | { 59 | blockData[i] = INT16_MAX; 60 | } 61 | break; 62 | 63 | case BlinkFast: 64 | if (this->statusSamplesleft > this->fastBlinkingDuration / 2) 65 | { 66 | blockData[i] = INT16_MIN; 67 | } 68 | else if (this->statusSamplesleft == 0) 69 | { 70 | this->statusSamplesleft = this->fastBlinkingDuration; 71 | } 72 | else 73 | { 74 | blockData[i] = INT16_MAX; 75 | } 76 | break; 77 | 78 | case BlinkOnce: 79 | if (this->statusSamplesleft > 0) 80 | { 81 | blockData[i] = INT16_MAX; 82 | } 83 | else 84 | { 85 | blockData[i] = INT16_MIN; 86 | } 87 | break; 88 | 89 | case Off: 90 | blockData[i] = INT16_MIN; 91 | break; 92 | 93 | case On: 94 | blockData[i] = INT16_MAX; 95 | break; 96 | 97 | default: 98 | case Default: 99 | break; 100 | } 101 | 102 | if (this->statusSamplesleft > 0) 103 | { 104 | this->statusSamplesleft--; 105 | } 106 | } 107 | 108 | return blockData; 109 | } 110 | 111 | inline void OutputLed::update(void) 112 | { 113 | Output::update(); 114 | } 115 | 116 | inline void OutputLed::setStatus(OutputLed::Status status) 117 | { 118 | this->status = status; 119 | 120 | if (status == BlinkOnce) 121 | { 122 | this->statusSamplesleft = blinkOnceDuration; 123 | } 124 | } 125 | #endif 126 | -------------------------------------------------------------------------------- /firmware/Motherboard/OutputTrigger.h: -------------------------------------------------------------------------------- 1 | #ifndef OutputTrigger_h 2 | #define OutputTrigger_h 3 | 4 | #include "Registrar.h" 5 | #include "Output.h" 6 | 7 | class OutputTrigger : public Output, public Registrar 8 | { 9 | public: 10 | OutputTrigger(int8_t index); 11 | void update(void); 12 | int16_t *&updateBefore(int16_t *&blockData); 13 | void trigger(); 14 | void setThreshold(int16_t threshold); 15 | 16 | private: 17 | audio_block_t *inputQueueArray[1]; 18 | uint8_t index; 19 | int16_t threshold = 0; 20 | uint16_t triggerDuration = 256; // 256 samples at 44100Hz means 5.8ms 21 | uint16_t triggerSamplesleft = 0; 22 | int16_t previousSample = 0; 23 | }; 24 | 25 | inline OutputTrigger::OutputTrigger(int8_t index) : Output(index) 26 | { 27 | } 28 | 29 | inline void OutputTrigger::update(void) 30 | { 31 | Output::update(); 32 | } 33 | 34 | inline int16_t *&OutputTrigger::updateBefore(int16_t *&blockData) 35 | { 36 | if (blockData) 37 | { 38 | for (int i = 0; i < AUDIO_BLOCK_SAMPLES; i++) 39 | { 40 | if (blockData[i] > this->threshold && this->previousSample < this->threshold && this->triggerSamplesleft == 0) 41 | { 42 | this->triggerSamplesleft = this->triggerDuration; 43 | } 44 | this->previousSample = blockData[i]; 45 | 46 | if (this->triggerSamplesleft > 0) 47 | { 48 | this->triggerSamplesleft--; 49 | blockData[i] = INT16_MAX; 50 | } 51 | else 52 | { 53 | blockData[i] = INT16_MIN; 54 | } 55 | } 56 | } 57 | 58 | return blockData; 59 | } 60 | 61 | inline void OutputTrigger::trigger() 62 | { 63 | this->triggerSamplesleft = this->triggerDuration; 64 | } 65 | 66 | inline void OutputTrigger::setThreshold(int16_t threshold) 67 | { 68 | this->threshold = threshold; 69 | } 70 | 71 | #endif 72 | -------------------------------------------------------------------------------- /firmware/Motherboard/Registrar.h: -------------------------------------------------------------------------------- 1 | #ifndef Registrar_h 2 | #define Registrar_h 3 | 4 | #include 5 | 6 | /** 7 | * This provides to any derived class a static array of pointers to all instantiated 8 | * objects of the class. 9 | * 10 | * This follows the CRTP pattern. 11 | * 12 | * @see https://en.wikipedia.org/wiki/Curiously_recurring_template_pattern 13 | */ 14 | template 15 | class Registrar 16 | { 17 | public: 18 | static void registerEntity(T *entity) 19 | { 20 | T **newArr = new T *[count + 1]; 21 | std::copy(entities, entities + std::min(count, count + 1), newArr); 22 | delete[] entities; 23 | entities = newArr; 24 | 25 | entities[count] = entity; 26 | 27 | count++; 28 | } 29 | 30 | Registrar() 31 | { 32 | registerEntity((T *)this); 33 | } 34 | 35 | static T **getAll() 36 | { 37 | return entities; 38 | } 39 | 40 | static T *get(unsigned int index) 41 | { 42 | return entities[index]; 43 | } 44 | 45 | static unsigned int getCount() 46 | { 47 | return count; 48 | } 49 | 50 | // private: // TODO: HOW TO MAKE IT PRIVATE 51 | static unsigned int count; 52 | static T **entities; 53 | }; 54 | 55 | template 56 | unsigned int Registrar::count = 0; 57 | 58 | template 59 | T **Registrar::entities = new T *[0]; 60 | 61 | // TODO: SEE HOW TO LIMIT THESE TEMPLATES TO REGISTRAR TYPE 62 | template 63 | _Tp *begin(_Tp *&t) 64 | { 65 | return (&(*t)->entities[0]); 66 | } 67 | 68 | template 69 | _Tp *end(_Tp *&t) 70 | { 71 | return (&(*t)->entities[(*t)->count]); 72 | } 73 | #endif 74 | -------------------------------------------------------------------------------- /firmware/Motherboard/Setting.h: -------------------------------------------------------------------------------- 1 | #ifndef Setting_h 2 | #define Setting_h 3 | 4 | #include "Registrar.h" 5 | #include 6 | #include "lib/ArduinoJson-v7.1.0.h" 7 | #include "lib/ArduinoStreamUtils/StreamUtils.h" 8 | 9 | class Setting : public Registrar 10 | { 11 | public: 12 | Setting(String id, String name, float defaultValue, float min, float max, float step); 13 | String getId(); 14 | String getName(); 15 | float getDefaultValue(); 16 | float getValue(); 17 | float getMin(); 18 | float getMax(); 19 | float getStep(); 20 | void setValue(float value); 21 | JsonDocument serialize(bool onlyIdandValue); 22 | static JsonDocument serializeAll(bool onlyIdandValue); 23 | static void loadFromMemory(); 24 | static void loadFromJson(JsonDocument doc); 25 | static void save(); 26 | 27 | private: 28 | String id; 29 | String name; 30 | float defaultValue = NAN; 31 | float value = NAN; 32 | float min = NAN; 33 | float max = NAN; 34 | float step = NAN; 35 | }; 36 | 37 | inline Setting::Setting(String id, String name, float defaultValue, float min, float max, float step) 38 | { 39 | this->id = id; 40 | this->name = name; 41 | this->defaultValue = defaultValue; 42 | this->min = min; 43 | this->max = max; 44 | this->step = step; 45 | 46 | Setting::loadFromMemory(); 47 | } 48 | 49 | inline String Setting::getId() 50 | { 51 | return this->id; 52 | } 53 | 54 | inline String Setting::getName() 55 | { 56 | return this->name; 57 | } 58 | 59 | inline float Setting::getValue() 60 | { 61 | return this->value; 62 | } 63 | 64 | inline float Setting::getDefaultValue() 65 | { 66 | return this->defaultValue; 67 | } 68 | 69 | inline float Setting::getMin() 70 | { 71 | return this->min; 72 | } 73 | 74 | inline float Setting::getMax() 75 | { 76 | return this->max; 77 | } 78 | 79 | inline float Setting::getStep() 80 | { 81 | return this->step; 82 | } 83 | 84 | inline void Setting::setValue(float value) 85 | { 86 | this->value = value; 87 | } 88 | 89 | inline JsonDocument Setting::serialize(bool onlyIdandValue = false) 90 | { 91 | JsonDocument doc; 92 | 93 | // create an object 94 | JsonObject setting = doc.to(); 95 | setting["id"] = this->getId(); 96 | setting["value"] = this->getValue(); 97 | if (!onlyIdandValue) 98 | { 99 | setting["name"] = this->getName(); 100 | setting["defaultValue"] = this->getDefaultValue(); 101 | setting["min"] = this->getMin(); 102 | setting["max"] = this->getMax(); 103 | setting["step"] = this->getStep(); 104 | } 105 | return doc; 106 | } 107 | 108 | inline JsonDocument Setting::serializeAll(bool onlyIdandValue = false) 109 | { 110 | JsonDocument doc; 111 | 112 | for (unsigned int i = 0; i < Setting::getCount(); i++) 113 | { 114 | doc["settings"][i] = Setting::get(i)->serialize(onlyIdandValue); 115 | } 116 | 117 | doc.shrinkToFit(); 118 | 119 | return doc; 120 | } 121 | 122 | inline void Setting::loadFromMemory() 123 | { 124 | // Find EOF index to know the length 125 | uint16_t eofIndex = 0; 126 | for (int i = 0; i < EEPROM.length(); i++) 127 | { 128 | // Serial.print((char)EEPROM.read(i)); 129 | if (EEPROM.read(i) == 26) 130 | { 131 | eofIndex = i; 132 | break; 133 | } 134 | } 135 | 136 | JsonDocument doc; 137 | EepromStream eepromStream(0, eofIndex + 1); 138 | 139 | DeserializationError error = deserializeJson(doc, eepromStream); 140 | 141 | if (error) 142 | { 143 | Serial.print(F("deserializeJson() failed: ")); 144 | Serial.println(error.c_str()); 145 | return; 146 | } 147 | 148 | Setting::loadFromJson(doc); 149 | } 150 | 151 | inline void Setting::loadFromJson(JsonDocument doc) 152 | { 153 | JsonArray settings = doc["settings"]; 154 | for (JsonVariant item : settings) 155 | { 156 | for (unsigned int i = 0; i < Setting::getCount(); i++) 157 | { 158 | Setting *setting = Setting::get(i); 159 | if (item["id"] == setting->get(i)->getId()) 160 | { 161 | setting->setValue(item["value"]); 162 | // TODO: Add callback on value change 163 | } 164 | } 165 | } 166 | } 167 | 168 | inline void Setting::save() 169 | { 170 | EepromStream eepromStream(0, EEPROM.length()); 171 | 172 | JsonDocument doc = Setting::serializeAll(true); 173 | uint16_t length = serializeJson(doc, eepromStream); 174 | EEPROM.write(length, 26); // End of file char 175 | } 176 | 177 | #endif -------------------------------------------------------------------------------- /firmware/Motherboard/SystemExclusiveManager.h: -------------------------------------------------------------------------------- 1 | #ifndef SystemExclusiveManager_h 2 | #define SystemExclusiveManager_h 3 | 4 | // void printBytes(const byte *data, unsigned int size) 5 | // { 6 | // while (size > 0) 7 | // { 8 | // byte b = *data++; 9 | // if (b < 16) 10 | // Serial.print(b); 11 | // Serial.print((char)b); 12 | // // Serial.print(b); 13 | // // if (size > 1) 14 | // // Serial.print(' '); 15 | // size = size - 1; 16 | // } 17 | // Serial.println(""); 18 | // } 19 | 20 | #include "Setting.h" 21 | #include "lib/ArduinoJson-v7.1.0.h" 22 | 23 | class SystemExclusiveManager 24 | { 25 | private: 26 | // Singleton 27 | static SystemExclusiveManager *instance; 28 | SystemExclusiveManager(); 29 | static void mySystemExclusiveChunk(const byte *data, uint16_t length, bool last); 30 | static void sendConfiguration(); 31 | static void receiveConfiguration(const byte *data); 32 | static void handleSystemExclusiveMessage(); 33 | static const char CONFIG_REQUEST = 0x00; 34 | static const char CONFIG_EDIT = 0x01; 35 | static byte myDataBuffer[1080]; 36 | static uint32_t dataBufferIndex; 37 | 38 | public: 39 | static SystemExclusiveManager *getInstance(); 40 | }; 41 | 42 | // Singleton pre init 43 | SystemExclusiveManager *SystemExclusiveManager::instance = nullptr; 44 | // byte *SystemExclusiveManager::myDataBuffer = nullptr; 45 | byte SystemExclusiveManager::myDataBuffer[1080] = {0}; 46 | uint32_t SystemExclusiveManager::dataBufferIndex = 0; 47 | 48 | inline SystemExclusiveManager::SystemExclusiveManager() 49 | { 50 | // myDataBuffer = new byte[1080]; 51 | usbMIDI.setHandleSystemExclusive(SystemExclusiveManager::mySystemExclusiveChunk); // TODO: maybe move this to MidiManager 52 | } 53 | 54 | /** 55 | Singleton instance 56 | */ 57 | inline SystemExclusiveManager *SystemExclusiveManager::getInstance() 58 | { 59 | if (!instance) 60 | instance = new SystemExclusiveManager; 61 | return instance; 62 | } 63 | 64 | inline void SystemExclusiveManager::mySystemExclusiveChunk(const byte *data, uint16_t length, bool last) 65 | { 66 | // Starting byte + at least one byte of data + ending byte 67 | if (last && length < 3) 68 | { 69 | return; 70 | } 71 | 72 | // Copying data into dataBuffer 73 | dataBufferIndex = 0; 74 | for (uint16_t i = 0; i < length; i++) 75 | { 76 | myDataBuffer[dataBufferIndex++] = data[i]; 77 | } 78 | 79 | if (last) 80 | { 81 | // After handling the last chunk, now let's handle the whole message 82 | handleSystemExclusiveMessage(); 83 | } 84 | } 85 | 86 | inline void SystemExclusiveManager::handleSystemExclusiveMessage() 87 | { 88 | if (dataBufferIndex < 3) 89 | { 90 | return; 91 | } 92 | 93 | // printBytes(myDataBuffer, dataBufferIndex); 94 | 95 | // Requesting configuration 96 | if (myDataBuffer[1] == CONFIG_REQUEST) 97 | { 98 | sendConfiguration(); 99 | } 100 | // Receiving configuration 101 | else if (myDataBuffer[1] == CONFIG_EDIT) 102 | { 103 | byte *arrayPtr; // length - the 2 bytes of SysEx and 1 byte of message type 104 | byte array[dataBufferIndex - 3] = {0}; 105 | for (uint32_t i = 2; i < dataBufferIndex - 1; i++) 106 | { 107 | array[i - 2] = myDataBuffer[i]; 108 | } 109 | arrayPtr = array; 110 | 111 | SystemExclusiveManager::receiveConfiguration(arrayPtr); 112 | } 113 | } 114 | 115 | inline void SystemExclusiveManager::sendConfiguration() 116 | { 117 | JsonDocument doc = Setting::serializeAll(); 118 | 119 | uint8_t output[1080]; 120 | 121 | size_t length = serializeJson(doc, output); 122 | 123 | usbMIDI.sendSysEx(length, output, false); 124 | } 125 | 126 | inline void SystemExclusiveManager::receiveConfiguration(const byte *data) 127 | { 128 | JsonDocument doc; 129 | 130 | // Trying to deserualize it to validate before saving 131 | DeserializationError error = deserializeJson(doc, data); 132 | 133 | if (error) 134 | { 135 | Serial.print(F("deserializeJson() failed: ")); 136 | Serial.println(error.c_str()); 137 | return; 138 | } 139 | 140 | Setting::loadFromJson(doc); 141 | Setting::save(); 142 | } 143 | #endif -------------------------------------------------------------------------------- /firmware/Motherboard/Vca.h: -------------------------------------------------------------------------------- 1 | #ifndef Vca_h 2 | #define Vca_h 3 | 4 | #include "AudioStream.h" 5 | 6 | class Vca : public AudioStream 7 | { 8 | public: 9 | Vca(); 10 | void update(void); 11 | 12 | private: 13 | audio_block_t *inputQueueArray[2]; 14 | float multiplier = 1; 15 | float lowPassCoeff = 0.01; 16 | }; 17 | 18 | inline Vca::Vca() : AudioStream(2, inputQueueArray) 19 | { 20 | } 21 | 22 | inline void Vca::update(void) 23 | { 24 | // Receive input data 25 | audio_block_t *block1, *block2; 26 | block1 = receiveReadOnly(0); 27 | block2 = receiveReadOnly(1); 28 | 29 | audio_block_t *outputBlock = allocate(); 30 | 31 | // If outputBlock not available no point in processing data 32 | if (!outputBlock || !block1) 33 | { 34 | return; 35 | } 36 | 37 | // if receiving data on both input 38 | if (block1 && block2) 39 | { 40 | for (uint8_t i = 0; i < AUDIO_BLOCK_SAMPLES; i++) 41 | { 42 | // Converting the multiplier from -32768/32767 to 0/1 43 | float mult = ((float)block2->data[i] + INT16_MAX) / (float)(INT16_MAX * 2); 44 | 45 | // Update multipler with incoming data and lowpass 46 | this->multiplier = (this->lowPassCoeff * mult) + (1.0f - this->lowPassCoeff) * this->multiplier; 47 | 48 | // Set output data 49 | outputBlock->data[i] = block1->data[i] * this->multiplier; 50 | } 51 | 52 | release(block1); 53 | release(block2); 54 | } 55 | 56 | // if receiving only data on input 1 57 | else if (block1) 58 | { 59 | for (uint8_t i = 0; i < AUDIO_BLOCK_SAMPLES; i++) 60 | { 61 | outputBlock->data[i] = block1->data[i] * this->multiplier; 62 | } 63 | 64 | release(block1); 65 | } 66 | 67 | transmit(outputBlock, 0); 68 | release(outputBlock); 69 | } 70 | 71 | #endif 72 | -------------------------------------------------------------------------------- /firmware/Motherboard/Vcc.h: -------------------------------------------------------------------------------- 1 | #ifndef Vcc_h 2 | #define Vcc_h 3 | 4 | #include "AudioStream.h" 5 | 6 | /** 7 | * @brief Voltage Controlled Crossfader 8 | * 9 | */ 10 | class Vcc : public AudioStream 11 | { 12 | public: 13 | Vcc(); 14 | void update(void); 15 | 16 | private: 17 | audio_block_t *inputQueueArray[3]; 18 | float crossfade = 0; 19 | float lowPassCoeff = 0.01; 20 | }; 21 | 22 | inline Vcc::Vcc() : AudioStream(3, inputQueueArray) 23 | { 24 | } 25 | 26 | inline void Vcc::update(void) 27 | { 28 | // Receive input data 29 | audio_block_t *block1, *block2, *block3; 30 | block1 = receiveReadOnly(0); 31 | block2 = receiveReadOnly(1); 32 | block3 = receiveReadOnly(2); 33 | 34 | audio_block_t *outputBlock = allocate(); 35 | 36 | // If outputBlock not available no point in processing data 37 | if (!outputBlock || !block1 || !block2) 38 | { 39 | return; 40 | } 41 | 42 | for (uint8_t i = 0; i < AUDIO_BLOCK_SAMPLES; i++) 43 | { 44 | if (block3) 45 | { 46 | // Converting the crossfade from -32768/32767 to 0/1 47 | float cf = ((float)block3->data[i] + INT16_MAX) / (float)(INT16_MAX * 2); 48 | 49 | // Update crossfade with incoming data and lowpass 50 | this->crossfade = (this->lowPassCoeff * cf) + (1.0f - this->lowPassCoeff) * this->crossfade; 51 | } 52 | 53 | // Set output data 54 | outputBlock->data[i] = block1->data[i] * (1 - this->crossfade) + block2->data[i] * this->crossfade; 55 | } 56 | 57 | release(block1); 58 | release(block2); 59 | 60 | if (block3) 61 | { 62 | release(block3); 63 | } 64 | 65 | transmit(outputBlock, 0); 66 | release(outputBlock); 67 | } 68 | 69 | #endif 70 | -------------------------------------------------------------------------------- /firmware/Motherboard/lib/ArduinoStreamUtils/StreamUtils.h: -------------------------------------------------------------------------------- 1 | // StreamUtils - github.com/bblanchon/ArduinoStreamUtils 2 | // Copyright Benoit Blanchon 2019-2024 3 | // MIT License 4 | 5 | #include "StreamUtils.hpp" 6 | 7 | using namespace StreamUtils; -------------------------------------------------------------------------------- /firmware/Motherboard/lib/ArduinoStreamUtils/StreamUtils.hpp: -------------------------------------------------------------------------------- 1 | // StreamUtils - github.com/bblanchon/ArduinoStreamUtils 2 | // Copyright Benoit Blanchon 2019-2024 3 | // MIT License 4 | 5 | #include "StreamUtils/Clients/HammingClient.hpp" 6 | #include "StreamUtils/Clients/HammingDecodingClient.hpp" 7 | #include "StreamUtils/Clients/HammingEncodingClient.hpp" 8 | #include "StreamUtils/Clients/LoggingClient.hpp" 9 | #include "StreamUtils/Clients/ReadBufferingClient.hpp" 10 | #include "StreamUtils/Clients/ReadLoggingClient.hpp" 11 | #include "StreamUtils/Clients/WriteBufferingClient.hpp" 12 | #include "StreamUtils/Clients/WriteLoggingClient.hpp" 13 | #include "StreamUtils/Prints/BufferingPrint.hpp" 14 | #include "StreamUtils/Prints/HammingPrint.hpp" 15 | #include "StreamUtils/Prints/LoggingPrint.hpp" 16 | #include "StreamUtils/Prints/StringPrint.hpp" 17 | #include "StreamUtils/Streams/ChunkDecodingStream.hpp" 18 | #include "StreamUtils/Streams/EepromStream.hpp" 19 | #include "StreamUtils/Streams/HammingDecodingStream.hpp" 20 | #include "StreamUtils/Streams/HammingEncodingStream.hpp" 21 | #include "StreamUtils/Streams/HammingStream.hpp" 22 | #include "StreamUtils/Streams/LoggingStream.hpp" 23 | #include "StreamUtils/Streams/ProgmemStream.hpp" 24 | #include "StreamUtils/Streams/ReadBufferingStream.hpp" 25 | #include "StreamUtils/Streams/ReadLoggingStream.hpp" 26 | #include "StreamUtils/Streams/ReadThrottlingStream.hpp" 27 | #include "StreamUtils/Streams/StringStream.hpp" 28 | #include "StreamUtils/Streams/WriteBufferingStream.hpp" 29 | #include "StreamUtils/Streams/WriteLoggingStream.hpp" 30 | #include "StreamUtils/Streams/WriteWaitingStream.hpp" 31 | -------------------------------------------------------------------------------- /firmware/Motherboard/lib/ArduinoStreamUtils/StreamUtils/Buffers/CharArray.hpp: -------------------------------------------------------------------------------- 1 | // StreamUtils - github.com/bblanchon/ArduinoStreamUtils 2 | // Copyright Benoit Blanchon 2019-2024 3 | // MIT License 4 | 5 | #pragma once 6 | 7 | #include 8 | #include // size_t 9 | #include // memcpy 10 | 11 | namespace StreamUtils { 12 | 13 | template 14 | class CharArray { 15 | public: 16 | CharArray(size_t size, TAllocator allocator = TAllocator()) 17 | : _allocator(allocator) { 18 | _data = reinterpret_cast(_allocator.allocate(size)); 19 | _size = _data ? size : 0; 20 | } 21 | 22 | CharArray(const CharArray &src) : CharArray(src._size, src._allocator) { 23 | if (_data != nullptr) 24 | memcpy(_data, src._data, _size); 25 | } 26 | 27 | ~CharArray() { 28 | _allocator.deallocate(_data); 29 | } 30 | 31 | size_t size() const { 32 | return _size; 33 | } 34 | 35 | operator bool() const { 36 | return _size > 0; 37 | } 38 | 39 | char *operator&() { 40 | return _data; 41 | } 42 | 43 | char &operator[](size_t i) { 44 | return _data[i]; 45 | } 46 | 47 | char operator[](size_t i) const { 48 | return _data[i]; 49 | } 50 | 51 | protected: 52 | TAllocator _allocator; 53 | char *_data; 54 | size_t _size; 55 | }; 56 | 57 | } // namespace StreamUtils -------------------------------------------------------------------------------- /firmware/Motherboard/lib/ArduinoStreamUtils/StreamUtils/Buffers/CircularBuffer.hpp: -------------------------------------------------------------------------------- 1 | // StreamUtils - github.com/bblanchon/ArduinoStreamUtils 2 | // Copyright Benoit Blanchon 2019-2024 3 | // MIT License 4 | 5 | #pragma once 6 | 7 | #include 8 | 9 | #include "CharArray.hpp" 10 | 11 | namespace StreamUtils { 12 | 13 | template 14 | class CircularBuffer { 15 | public: 16 | CircularBuffer(size_t capacity, TAllocator allocator = TAllocator()) 17 | : _data(capacity, allocator) { 18 | _begin = _end = _size = 0; 19 | } 20 | 21 | CircularBuffer(const CircularBuffer &src) 22 | : CircularBuffer(src._data.size(), src._allocator) { 23 | if (_data) { 24 | _begin = src._begin; 25 | _end = src._end; 26 | _size = src._size; 27 | } 28 | } 29 | 30 | size_t available() const { 31 | return _size; 32 | } 33 | 34 | size_t capacity() const { 35 | return _data.size(); 36 | } 37 | 38 | void clear() { 39 | _begin = _end = _size = 0; 40 | } 41 | 42 | bool isEmpty() const { 43 | return _size == 0; 44 | } 45 | 46 | bool isFull() const { 47 | return _size == _data.size(); 48 | } 49 | 50 | char peek() const { 51 | assert(_size > 0); 52 | return _data[_begin]; 53 | } 54 | 55 | char read() { 56 | assert(_size > 0); 57 | char result = _data[_begin]; 58 | _begin = (_begin + 1) % _data.size(); 59 | _size--; 60 | return result; 61 | } 62 | 63 | size_t readBytes(char *data, size_t size) { 64 | // don't read more that available 65 | if (size > _size) 66 | size = _size; 67 | 68 | for (size_t i = 0; i < size; i++) 69 | data[i] = read(); 70 | 71 | return size; 72 | } 73 | 74 | size_t write(uint8_t data) { 75 | assert(_size < _data.size()); 76 | _data[_end] = data; 77 | _end = (_end + 1) % _data.size(); 78 | _size++; 79 | return 1; 80 | } 81 | 82 | size_t write(const uint8_t *data, size_t size) { 83 | // don't read more that available 84 | size_t roomLeft = _data.size() - _size; 85 | if (size > roomLeft) 86 | size = roomLeft; 87 | 88 | for (size_t i = 0; i < size; i++) 89 | write(data[i]); 90 | 91 | return size; 92 | } 93 | 94 | private: 95 | CharArray _data; 96 | size_t _size; 97 | size_t _begin; 98 | size_t _end; 99 | }; 100 | 101 | } // namespace StreamUtils -------------------------------------------------------------------------------- /firmware/Motherboard/lib/ArduinoStreamUtils/StreamUtils/Buffers/LinearBuffer.hpp: -------------------------------------------------------------------------------- 1 | // StreamUtils - github.com/bblanchon/ArduinoStreamUtils 2 | // Copyright Benoit Blanchon 2019-2024 3 | // MIT License 4 | 5 | #pragma once 6 | 7 | #include 8 | #include 9 | 10 | #include "../Helpers.hpp" 11 | #include "CharArray.hpp" 12 | 13 | namespace StreamUtils { 14 | 15 | template 16 | class LinearBuffer { 17 | public: 18 | LinearBuffer(size_t capacity, TAllocator allocator = TAllocator()) 19 | : _data(capacity, allocator) { 20 | _begin = _data ? &_data : nullptr; 21 | _end = _begin; 22 | } 23 | 24 | LinearBuffer(const LinearBuffer &src) : _data(src._data) { 25 | if (_data) { 26 | memcpy(&_data, src._begin, src.available()); 27 | _begin = &_data; 28 | _end = &_data + src.available(); 29 | } else { 30 | _begin = nullptr; 31 | _end = nullptr; 32 | } 33 | } 34 | 35 | size_t available() const { 36 | return _end - _begin; 37 | } 38 | 39 | size_t capacity() const { 40 | return _data.size(); 41 | } 42 | 43 | void clear() { 44 | _begin = _end = _data; 45 | } 46 | 47 | bool isEmpty() const { 48 | return available() == 0; 49 | } 50 | 51 | bool isFull() const { 52 | return available() == capacity(); 53 | } 54 | 55 | operator bool() const { 56 | return _data; 57 | } 58 | 59 | char peek() const { 60 | return *_begin; 61 | } 62 | 63 | char read() { 64 | return *_begin++; 65 | } 66 | 67 | size_t readBytes(char *dstPtr, size_t dstSize) { 68 | size_t srcSize = available(); 69 | size_t n = srcSize < dstSize ? srcSize : dstSize; 70 | memcpy(dstPtr, _begin, n); 71 | _begin += n; 72 | return n; 73 | } 74 | 75 | size_t write(uint8_t data) { 76 | assert(!isFull()); 77 | *_end++ = data; 78 | return 1; 79 | } 80 | 81 | size_t write(const uint8_t *data, size_t size) { 82 | size_t roomLeft = capacity() - available(); 83 | if (size > roomLeft) 84 | size = roomLeft; 85 | memcpy(_end, data, size); 86 | _end += size; 87 | return size; 88 | } 89 | 90 | template // Stream or Client 91 | void reloadFrom(TTarget &source, size_t size) { 92 | if (size > _data.size()) 93 | size = _data.size(); 94 | size_t n = readOrReadBytes(source, &_data, size); 95 | _begin = &_data; 96 | _end = &_data + n; 97 | } 98 | 99 | void flushInto(Print &destination) { 100 | if (_begin != _end) 101 | destination.write(_begin, _end - _begin); 102 | _begin = _end = &_data; 103 | } 104 | 105 | private: 106 | CharArray _data; 107 | char *_begin; 108 | char *_end; 109 | }; 110 | 111 | } // namespace StreamUtils -------------------------------------------------------------------------------- /firmware/Motherboard/lib/ArduinoStreamUtils/StreamUtils/Clients/ChunkDecodingClient.hpp: -------------------------------------------------------------------------------- 1 | // StreamUtils - github.com/bblanchon/ArduinoStreamUtils 2 | // Copyright Benoit Blanchon 2019-2024 3 | // MIT License 4 | 5 | #pragma once 6 | 7 | #include "StreamUtils/Policies/ChunkDecodingPolicy.hpp" 8 | #include "StreamUtils/Policies/ConnectForwardingPolicy.hpp" 9 | #include "StreamUtils/Policies/WriteForwardingPolicy.hpp" 10 | 11 | #include "ClientProxy.hpp" 12 | 13 | namespace StreamUtils { 14 | 15 | struct ChunkDecodingClient 16 | : ClientProxy { 18 | ChunkDecodingClient(Client &target) 19 | : ClientProxy(target) {} 21 | 22 | bool error() const { 23 | return _reader.error(); 24 | } 25 | 26 | bool ended() const { 27 | return _reader.ended(); 28 | } 29 | }; 30 | 31 | } // namespace StreamUtils 32 | -------------------------------------------------------------------------------- /firmware/Motherboard/lib/ArduinoStreamUtils/StreamUtils/Clients/ClientProxy.hpp: -------------------------------------------------------------------------------- 1 | // StreamUtils - github.com/bblanchon/ArduinoStreamUtils 2 | // Copyright Benoit Blanchon 2019-2024 3 | // MIT License 4 | 5 | #pragma once 6 | 7 | #include 8 | 9 | #include "../Configuration.hpp" 10 | #include "../Streams/StreamProxy.hpp" 11 | 12 | namespace StreamUtils { 13 | 14 | template 15 | class ClientProxy : public Client { 16 | public: 17 | explicit ClientProxy(Client &upstream, ReadPolicy reader = ReadPolicy(), 18 | WritePolicy writer = WritePolicy(), 19 | ConnectPolicy connection = ConnectPolicy()) 20 | : _target(upstream), 21 | _reader(reader), 22 | _writer(Polyfills::move(writer)), 23 | _connection(connection) {} 24 | 25 | ClientProxy(const ClientProxy &other) 26 | : _target(other._target), 27 | _reader(other._reader), 28 | _writer(other._writer), 29 | _connection(other._connection) {} 30 | 31 | ~ClientProxy() { 32 | _writer.implicitFlush(_target); 33 | } 34 | 35 | // --- Print --- 36 | 37 | size_t write(const uint8_t *buffer, size_t size) override { 38 | return _writer.write(_target, buffer, size); 39 | } 40 | 41 | size_t write(uint8_t data) override { 42 | return _writer.write(_target, data); 43 | } 44 | 45 | #if STREAMUTILS_PRINT_WRITE_VOID_UINT32 46 | size_t write(const void *buffer, uint32 size) override { 47 | return write(reinterpret_cast(buffer), 48 | static_cast(size)); 49 | } 50 | #endif 51 | 52 | using Print::write; 53 | 54 | // --- Stream --- 55 | 56 | int available() override { 57 | return _reader.available(_target); 58 | } 59 | 60 | int read() override { 61 | return _reader.read(_target); 62 | } 63 | 64 | int peek() override { 65 | return _reader.peek(_target); 66 | } 67 | 68 | #if STREAMUTILS_STREAM_READBYTES_IS_VIRTUAL 69 | size_t readBytes(char *buffer, size_t size) override { 70 | return _reader.readBytes(_target, buffer, size); 71 | } 72 | #endif 73 | 74 | // --- Client --- 75 | 76 | int connect(IPAddress ip, uint16_t port) override { 77 | return _connection.connect(_target, ip, port); 78 | } 79 | 80 | int connect(const char *ip, uint16_t port) override { 81 | return _connection.connect(_target, ip, port); 82 | } 83 | 84 | uint8_t connected() override { 85 | return _connection.connected(_target); 86 | } 87 | 88 | void stop() override { 89 | _writer.implicitFlush(_target); 90 | _connection.stop(_target); 91 | } 92 | 93 | operator bool() override { 94 | return _connection.operator_bool(_target); 95 | } 96 | 97 | int read(uint8_t *buf, size_t size) override { 98 | return _reader.read(_target, buf, size); 99 | } 100 | 101 | void flush() override { 102 | _writer.flush(_target); 103 | } 104 | 105 | protected: 106 | Client &_target; 107 | ReadPolicy _reader; 108 | WritePolicy _writer; 109 | ConnectPolicy _connection; 110 | }; 111 | 112 | } // namespace StreamUtils -------------------------------------------------------------------------------- /firmware/Motherboard/lib/ArduinoStreamUtils/StreamUtils/Clients/HammingClient.hpp: -------------------------------------------------------------------------------- 1 | // StreamUtils - github.com/bblanchon/ArduinoStreamUtils 2 | // Copyright Benoit Blanchon 2019-2024 3 | // MIT License 4 | 5 | #pragma once 6 | 7 | #include "../Policies/ConnectForwardingPolicy.hpp" 8 | #include "../Policies/HammingDecodingPolicy.hpp" 9 | #include "../Policies/HammingEncodingPolicy.hpp" 10 | #include "../Ports/DefaultAllocator.hpp" 11 | #include "ClientProxy.hpp" 12 | 13 | namespace StreamUtils { 14 | 15 | template 16 | using BasicHammingClient = ClientProxy, 17 | HammingEncodingPolicy, 18 | ConnectForwardingPolicy>; 19 | 20 | template 21 | using HammingClient = BasicHammingClient; 22 | 23 | } // namespace StreamUtils 24 | -------------------------------------------------------------------------------- /firmware/Motherboard/lib/ArduinoStreamUtils/StreamUtils/Clients/HammingDecodingClient.hpp: -------------------------------------------------------------------------------- 1 | // StreamUtils - github.com/bblanchon/ArduinoStreamUtils 2 | // Copyright Benoit Blanchon 2019-2024 3 | // MIT License 4 | 5 | #pragma once 6 | 7 | #include "../Policies/ConnectForwardingPolicy.hpp" 8 | #include "../Policies/HammingDecodingPolicy.hpp" 9 | #include "../Policies/WriteForwardingPolicy.hpp" 10 | #include "../Ports/DefaultAllocator.hpp" 11 | #include "ClientProxy.hpp" 12 | 13 | namespace StreamUtils { 14 | 15 | template 16 | using BasicHammingDecodingClient = 17 | ClientProxy, WriteForwardingPolicy, 18 | ConnectForwardingPolicy>; 19 | 20 | template 21 | using HammingDecodingClient = 22 | BasicHammingDecodingClient; 23 | 24 | } // namespace StreamUtils 25 | -------------------------------------------------------------------------------- /firmware/Motherboard/lib/ArduinoStreamUtils/StreamUtils/Clients/HammingEncodingClient.hpp: -------------------------------------------------------------------------------- 1 | // StreamUtils - github.com/bblanchon/ArduinoStreamUtils 2 | // Copyright Benoit Blanchon 2019-2024 3 | // MIT License 4 | 5 | #pragma once 6 | 7 | #include "../Policies/ConnectForwardingPolicy.hpp" 8 | #include "../Policies/HammingEncodingPolicy.hpp" 9 | #include "../Policies/ReadForwardingPolicy.hpp" 10 | #include "../Ports/DefaultAllocator.hpp" 11 | #include "ClientProxy.hpp" 12 | 13 | namespace StreamUtils { 14 | 15 | template 16 | using BasicHammingEncodingClient = 17 | ClientProxy, 18 | ConnectForwardingPolicy>; 19 | 20 | template 21 | using HammingEncodingClient = 22 | BasicHammingEncodingClient; 23 | 24 | } // namespace StreamUtils 25 | -------------------------------------------------------------------------------- /firmware/Motherboard/lib/ArduinoStreamUtils/StreamUtils/Clients/LoggingClient.hpp: -------------------------------------------------------------------------------- 1 | // StreamUtils - github.com/bblanchon/ArduinoStreamUtils 2 | // Copyright Benoit Blanchon 2019-2024 3 | // MIT License 4 | 5 | #pragma once 6 | 7 | #include "../Policies/ConnectForwardingPolicy.hpp" 8 | #include "../Policies/ReadLoggingPolicy.hpp" 9 | #include "../Policies/WriteLoggingPolicy.hpp" 10 | #include "ClientProxy.hpp" 11 | 12 | namespace StreamUtils { 13 | 14 | struct LoggingClient : ClientProxy { 16 | LoggingClient(Client &target, Print &log) 17 | : ClientProxy(target, ReadLoggingPolicy{log}, 19 | WriteLoggingPolicy{log}, 20 | ConnectForwardingPolicy{}) {} 21 | }; 22 | 23 | } // namespace StreamUtils -------------------------------------------------------------------------------- /firmware/Motherboard/lib/ArduinoStreamUtils/StreamUtils/Clients/MemoryClient.hpp: -------------------------------------------------------------------------------- 1 | // StreamUtils - github.com/bblanchon/ArduinoStreamUtils 2 | // Copyright Benoit Blanchon 2019-2024 3 | // MIT License 4 | 5 | #pragma once 6 | 7 | #include 8 | 9 | #include "../Buffers/CircularBuffer.hpp" 10 | #include "../Configuration.hpp" 11 | #include "../Ports/DefaultAllocator.hpp" 12 | #include "../Streams/MemoryStream.hpp" 13 | 14 | namespace StreamUtils { 15 | 16 | template 17 | class BasicMemoryClient : public Client { 18 | public: 19 | BasicMemoryClient(size_t capacity, TAllocator allocator = TAllocator()) 20 | : _stream(capacity, allocator), _connected(false) {} 21 | 22 | BasicMemoryClient(const BasicMemoryClient &src) : _stream(src._stream) {} 23 | 24 | // --- Print --- 25 | 26 | size_t write(uint8_t data) override { 27 | return _stream.write(data); 28 | } 29 | 30 | size_t write(const uint8_t *data, size_t size) override { 31 | return _stream.write(data, size); 32 | } 33 | 34 | #if STREAMUTILS_PRINT_WRITE_VOID_UINT32 35 | size_t write(const void *data, uint32 size) override { 36 | return _stream.write(data, size); 37 | } 38 | #endif 39 | 40 | // --- Stream --- 41 | 42 | int available() override { 43 | return _stream.available(); 44 | } 45 | 46 | int peek() override { 47 | return _stream.peek(); 48 | } 49 | 50 | int read() override { 51 | return _stream.read(); 52 | } 53 | 54 | #if STREAMUTILS_STREAM_READBYTES_IS_VIRTUAL 55 | size_t readBytes(char *data, size_t size) override { 56 | return _stream.readBytes(data, size); 57 | } 58 | #endif 59 | 60 | void flush() override { 61 | _stream.flush(); 62 | } 63 | 64 | // --- Client --- 65 | 66 | int connect(IPAddress, uint16_t) override { 67 | _connected = true; 68 | return 1; 69 | } 70 | 71 | int connect(const char *, uint16_t) override { 72 | _connected = true; 73 | return 1; 74 | } 75 | 76 | uint8_t connected() override { 77 | return _connected; 78 | } 79 | 80 | void stop() override { 81 | _connected = false; 82 | } 83 | 84 | operator bool() override { 85 | return true; 86 | } 87 | 88 | int read(uint8_t *buf, size_t size) override { 89 | return static_cast( 90 | _stream.readBytes(reinterpret_cast(buf), size)); 91 | } 92 | 93 | private: 94 | BasicMemoryStream _stream; 95 | bool _connected; 96 | }; 97 | using MemoryClient = BasicMemoryClient; 98 | 99 | } // namespace StreamUtils -------------------------------------------------------------------------------- /firmware/Motherboard/lib/ArduinoStreamUtils/StreamUtils/Clients/ReadBufferingClient.hpp: -------------------------------------------------------------------------------- 1 | // StreamUtils - github.com/bblanchon/ArduinoStreamUtils 2 | // Copyright Benoit Blanchon 2019-2024 3 | // MIT License 4 | 5 | #pragma once 6 | 7 | #include "../Policies/ConnectForwardingPolicy.hpp" 8 | #include "../Policies/ReadBufferingPolicy.hpp" 9 | #include "../Policies/WriteForwardingPolicy.hpp" 10 | #include "../Ports/DefaultAllocator.hpp" 11 | #include "ClientProxy.hpp" 12 | 13 | namespace StreamUtils { 14 | 15 | template 16 | class BasicReadBufferingClient 17 | : public ClientProxy, WriteForwardingPolicy, 18 | ConnectForwardingPolicy> { 19 | using base_type = ClientProxy, 20 | WriteForwardingPolicy, ConnectForwardingPolicy>; 21 | 22 | public: 23 | explicit BasicReadBufferingClient(Client &target, size_t capacity, 24 | TAllocator allocator = TAllocator()) 25 | : base_type(target, ReadBufferingPolicy{capacity, allocator}, 26 | WriteForwardingPolicy{}, ConnectForwardingPolicy{}) {} 27 | }; 28 | 29 | using ReadBufferingClient = BasicReadBufferingClient; 30 | } // namespace StreamUtils -------------------------------------------------------------------------------- /firmware/Motherboard/lib/ArduinoStreamUtils/StreamUtils/Clients/ReadLoggingClient.hpp: -------------------------------------------------------------------------------- 1 | // StreamUtils - github.com/bblanchon/ArduinoStreamUtils 2 | // Copyright Benoit Blanchon 2019-2024 3 | // MIT License 4 | 5 | #pragma once 6 | 7 | #include "../Policies/ConnectForwardingPolicy.hpp" 8 | #include "../Policies/ReadLoggingPolicy.hpp" 9 | #include "../Policies/WriteForwardingPolicy.hpp" 10 | #include "ClientProxy.hpp" 11 | 12 | namespace StreamUtils { 13 | 14 | struct ReadLoggingClient : ClientProxy { 16 | ReadLoggingClient(Client &target, Print &log) 17 | : ClientProxy(target, ReadLoggingPolicy{log}, 19 | WriteForwardingPolicy{}, 20 | ConnectForwardingPolicy{}) {} 21 | }; 22 | 23 | } // namespace StreamUtils -------------------------------------------------------------------------------- /firmware/Motherboard/lib/ArduinoStreamUtils/StreamUtils/Clients/SpyingClient.hpp: -------------------------------------------------------------------------------- 1 | // StreamUtils - github.com/bblanchon/ArduinoStreamUtils 2 | // Copyright Benoit Blanchon 2019-2024 3 | // MIT License 4 | 5 | #pragma once 6 | 7 | #include "../Policies/ConnectSpyingPolicy.hpp" 8 | #include "../Policies/ReadSpyingPolicy.hpp" 9 | #include "../Policies/WriteSpyingPolicy.hpp" 10 | #include "ClientProxy.hpp" 11 | 12 | namespace StreamUtils { 13 | 14 | struct SpyingClient 15 | : ClientProxy { 16 | SpyingClient(Client &target, Print &log) 17 | : ClientProxy( 18 | target, {log}, {log}, {log}) {} 19 | }; 20 | 21 | } // namespace StreamUtils 22 | -------------------------------------------------------------------------------- /firmware/Motherboard/lib/ArduinoStreamUtils/StreamUtils/Clients/WriteBufferingClient.hpp: -------------------------------------------------------------------------------- 1 | // StreamUtils - github.com/bblanchon/ArduinoStreamUtils 2 | // Copyright Benoit Blanchon 2019-2024 3 | // MIT License 4 | 5 | #pragma once 6 | 7 | #include "../Policies/ConnectForwardingPolicy.hpp" 8 | #include "../Policies/ReadForwardingPolicy.hpp" 9 | #include "../Policies/WriteBufferingPolicy.hpp" 10 | #include "../Ports/DefaultAllocator.hpp" 11 | #include "ClientProxy.hpp" 12 | 13 | namespace StreamUtils { 14 | 15 | template 16 | struct BasicWriteBufferingClient 17 | : ClientProxy, 18 | ConnectForwardingPolicy> { 19 | explicit BasicWriteBufferingClient(Client &target, size_t capacity, 20 | TAllocator allocator = TAllocator()) 21 | : ClientProxy, 22 | ConnectForwardingPolicy>( 23 | target, ReadForwardingPolicy{}, 24 | WriteBufferingPolicy{capacity, allocator}, 25 | ConnectForwardingPolicy{}) {} 26 | }; 27 | 28 | using WriteBufferingClient = BasicWriteBufferingClient; 29 | } // namespace StreamUtils -------------------------------------------------------------------------------- /firmware/Motherboard/lib/ArduinoStreamUtils/StreamUtils/Clients/WriteLoggingClient.hpp: -------------------------------------------------------------------------------- 1 | // StreamUtils - github.com/bblanchon/ArduinoStreamUtils 2 | // Copyright Benoit Blanchon 2019-2024 3 | // MIT License 4 | 5 | #pragma once 6 | 7 | #include "../Policies/ConnectForwardingPolicy.hpp" 8 | #include "../Policies/ReadForwardingPolicy.hpp" 9 | #include "../Policies/WriteLoggingPolicy.hpp" 10 | #include "ClientProxy.hpp" 11 | 12 | namespace StreamUtils { 13 | 14 | struct WriteLoggingClient 15 | : ClientProxy { 17 | WriteLoggingClient(Client &target, Print &log) 18 | : ClientProxy(target, ReadForwardingPolicy{}, 20 | WriteLoggingPolicy{log}, 21 | ConnectForwardingPolicy{}) {} 22 | }; 23 | 24 | } // namespace StreamUtils -------------------------------------------------------------------------------- /firmware/Motherboard/lib/ArduinoStreamUtils/StreamUtils/Clients/WriteWaitingClient.hpp: -------------------------------------------------------------------------------- 1 | // StreamUtils - github.com/bblanchon/ArduinoStreamUtils 2 | // Copyright Benoit Blanchon 2019-2024 3 | // MIT License 4 | 5 | #pragma once 6 | 7 | #include "../Policies/ConnectForwardingPolicy.hpp" 8 | #include "../Policies/ReadForwardingPolicy.hpp" 9 | #include "../Policies/WriteWaitingPolicy.hpp" 10 | #include "ClientProxy.hpp" 11 | 12 | namespace StreamUtils { 13 | 14 | struct WriteWaitingClient 15 | : ClientProxy { 17 | WriteWaitingClient(Client &target, Polyfills::function wait = yield) 18 | : ClientProxy( 20 | target, ReadForwardingPolicy{}, 21 | WriteWaitingPolicy{Polyfills::move(wait)}, 22 | ConnectForwardingPolicy{}) {} 23 | 24 | void setTimeout(unsigned long timeout) { 25 | Client::setTimeout(timeout); 26 | _writer.setTimeout(timeout); 27 | } 28 | }; 29 | 30 | } // namespace StreamUtils -------------------------------------------------------------------------------- /firmware/Motherboard/lib/ArduinoStreamUtils/StreamUtils/Configuration.hpp: -------------------------------------------------------------------------------- 1 | // StreamUtils - github.com/bblanchon/ArduinoStreamUtils 2 | // Copyright Benoit Blanchon 2019-2024 3 | // MIT License 4 | 5 | #pragma once 6 | 7 | #ifndef STREAMUTILS_PRINT_FLUSH_EXISTS 8 | #if defined(ARDUINO_ARCH_ESP8266) || defined(ARDUINO_ARCH_SAMD) || \ 9 | defined(ARDUINO_ARCH_AVR) || \ 10 | (defined(ARDUINO_ARCH_ESP32) && ESP_ARDUINO_VERSION_MAJOR >= 2 && \ 11 | ESP_ARDUINO_VERSION_PATCH >= 3) || \ 12 | (defined(ARDUINO_ARCH_STM32) && STM32_CORE_VERSION_MAJOR >= 2) 13 | #define STREAMUTILS_PRINT_FLUSH_EXISTS 1 14 | #else 15 | #define STREAMUTILS_PRINT_FLUSH_EXISTS 0 16 | #endif 17 | #endif 18 | 19 | #ifndef STREAMUTILS_STREAM_READBYTES_IS_VIRTUAL 20 | #if defined(ARDUINO_ARCH_ESP8266) || defined(ARDUINO_ARCH_ESP32) || \ 21 | defined(ARDUINO_ARCH_STM32) 22 | #define STREAMUTILS_STREAM_READBYTES_IS_VIRTUAL 1 23 | #else 24 | #define STREAMUTILS_STREAM_READBYTES_IS_VIRTUAL 0 25 | #endif 26 | #endif 27 | 28 | #ifndef STREAMUTILS_PRINT_WRITE_VOID_UINT32 29 | #if defined(ARDUINO_ARCH_STM32F1) || defined(ARDUINO_ARCH_STM32F4) 30 | #define STREAMUTILS_PRINT_WRITE_VOID_UINT32 1 31 | #else 32 | #define STREAMUTILS_PRINT_WRITE_VOID_UINT32 0 33 | #endif 34 | #endif 35 | 36 | #ifndef STREAMUTILS_ENABLE_EEPROM 37 | #if defined(ARDUINO_ARCH_AVR) || defined(ARDUINO_ARCH_ESP8266) || \ 38 | defined(ARDUINO_ARCH_ESP32) || defined(ARDUINO_PICO_MAJOR) || \ 39 | defined(ARDUINO_ARCH_STM32) || defined(CORE_TEENSY) || \ 40 | defined(ARDUINO_ARCH_MEGAAVR) 41 | #define STREAMUTILS_ENABLE_EEPROM 1 42 | #else 43 | #define STREAMUTILS_ENABLE_EEPROM 0 44 | #endif 45 | #endif 46 | 47 | #ifndef STREAMUTILS_USE_EEPROM_COMMIT 48 | #if defined(ARDUINO_ARCH_ESP8266) || defined(ARDUINO_ARCH_ESP32) || \ 49 | defined(ARDUINO_ARCH_RP2040) 50 | #define STREAMUTILS_USE_EEPROM_COMMIT 1 51 | #else 52 | #define STREAMUTILS_USE_EEPROM_COMMIT 0 53 | #endif 54 | #endif 55 | 56 | #ifndef STREAMUTILS_USE_EEPROM_UPDATE 57 | #if defined(ARDUINO_ARCH_AVR) || defined(CORE_TEENSY) || \ 58 | defined(ARDUINO_ARCH_MEGAAVR) 59 | #define STREAMUTILS_USE_EEPROM_UPDATE 1 60 | #else 61 | #define STREAMUTILS_USE_EEPROM_UPDATE 0 62 | #endif 63 | #endif 64 | 65 | #ifndef STREAMUTILS_STACK_BUFFER_MAX_SIZE 66 | #define STREAMUTILS_STACK_BUFFER_MAX_SIZE 32 67 | #endif 68 | -------------------------------------------------------------------------------- /firmware/Motherboard/lib/ArduinoStreamUtils/StreamUtils/Helpers.hpp: -------------------------------------------------------------------------------- 1 | // StreamUtils - github.com/bblanchon/ArduinoStreamUtils 2 | // Copyright Benoit Blanchon 2019-2024 3 | // MIT License 4 | 5 | #pragma once 6 | 7 | namespace StreamUtils { 8 | 9 | inline size_t readOrReadBytes(Stream &stream, char *buffer, size_t size) { 10 | return stream.readBytes(buffer, size); 11 | } 12 | 13 | inline size_t readOrReadBytes(Client &client, char *buffer, size_t size) { 14 | return client.read(reinterpret_cast(buffer), size); 15 | } 16 | 17 | } // namespace StreamUtils -------------------------------------------------------------------------------- /firmware/Motherboard/lib/ArduinoStreamUtils/StreamUtils/Policies/ChunkDecodingPolicy.hpp: -------------------------------------------------------------------------------- 1 | // StreamUtils - github.com/bblanchon/ArduinoStreamUtils 2 | // Copyright Benoit Blanchon 2019-2024 3 | // MIT License 4 | 5 | #pragma once 6 | 7 | #include 8 | #include 9 | #include "../Helpers.hpp" 10 | 11 | namespace StreamUtils { 12 | 13 | class ChunkDecodingPolicy { 14 | enum class State { 15 | ChunkSize, 16 | ChunkExtensions, 17 | ChunkStart, 18 | ChunkBody, 19 | ChunkEndCr, 20 | ChunkEndLf, 21 | TrailerStart, 22 | Trailer, 23 | TrailerEnd, 24 | FinalCrLf, 25 | Ended, 26 | Error, 27 | }; 28 | 29 | public: 30 | int available(Stream &target) { 31 | if (!goToChunkBody(target)) 32 | return 0; 33 | return min_(target.available(), remaining_); 34 | } 35 | 36 | int read(Stream &target) { 37 | if (!goToChunkBody(target)) 38 | return -1; 39 | int c = target.read(); 40 | if (c >= 0) 41 | decreaseRemaining(1); 42 | return c; 43 | } 44 | 45 | int peek(Stream &target) { 46 | if (!goToChunkBody(target)) 47 | return -1; 48 | return target.peek(); 49 | } 50 | 51 | size_t readBytes(Stream &target, char *buffer, size_t size) { 52 | return doReadBytes(target, buffer, size); 53 | } 54 | 55 | int read(Client &target, uint8_t *buffer, size_t size) { 56 | return static_cast( 57 | doReadBytes(target, reinterpret_cast(buffer), size)); 58 | } 59 | 60 | bool error() const { 61 | return state_ == State::Error; 62 | } 63 | 64 | bool ended() const { 65 | return state_ == State::Ended; 66 | } 67 | 68 | private: 69 | template // Stream or Client 70 | size_t doReadBytes(TTarget &target, char *buffer, size_t size) { 71 | size_t result = 0; 72 | while (size > 0 && !error() && !ended() && goToChunkBody(target, true)) { 73 | size_t n = readOrReadBytes(target, buffer, min_(size, remaining_)); 74 | decreaseRemaining(n); 75 | result += n; 76 | size -= n; 77 | buffer += n; 78 | } 79 | return result; 80 | } 81 | 82 | bool inBody() const { 83 | return state_ == State::ChunkBody; 84 | } 85 | 86 | template // Stream or Client 87 | bool goToChunkBody(TTarget &target, bool wait = false) { 88 | while (!error() && !ended() && !inBody()) { 89 | int c = readNextChar(target, wait); 90 | if (c < 0) 91 | return false; 92 | state_ = interpret(static_cast(c)); 93 | } 94 | return state_ == State::ChunkBody; 95 | } 96 | 97 | template // Stream or Client 98 | int readNextChar(TTarget &target, bool wait = false) { 99 | if (wait) { 100 | char c; 101 | if (readOrReadBytes(target, &c, 1) == 1) 102 | return c; 103 | else 104 | return -1; 105 | } else { 106 | return target.read(); 107 | } 108 | } 109 | 110 | State interpret(char c) { 111 | switch (state_) { 112 | case State::ChunkSize: 113 | if (c >= '0' && c <= '9') 114 | return appendSizeHexDigit(c - '0'); 115 | else if (c >= 'A' && c <= 'F') 116 | return appendSizeHexDigit(c - 'A' + 10); 117 | else if (c >= 'a' && c <= 'f') 118 | return appendSizeHexDigit(c - 'a' + 10); 119 | else if (c == '\r') 120 | return State::ChunkStart; 121 | else if (c == ' ' || c == '\t' || c == ';') 122 | return State::ChunkExtensions; 123 | else 124 | return State::Error; 125 | 126 | case State::ChunkExtensions: 127 | if (c == '\r') 128 | return State::ChunkStart; 129 | else 130 | return State::ChunkExtensions; 131 | 132 | case State::ChunkStart: 133 | if (c == '\n') { 134 | if (remaining_ == 0) 135 | return State::TrailerStart; 136 | else 137 | return State::ChunkBody; 138 | } else 139 | return State::Error; 140 | 141 | case State::ChunkEndCr: 142 | if (c == '\r') 143 | return State::ChunkEndLf; 144 | else 145 | return State::Error; 146 | 147 | case State::ChunkEndLf: 148 | if (c == '\n') 149 | return State::ChunkSize; 150 | else 151 | return State::Error; 152 | 153 | case State::TrailerStart: 154 | if (c == '\r') 155 | return State::FinalCrLf; 156 | else 157 | return State::Trailer; 158 | 159 | case State::Trailer: 160 | if (c == '\r') 161 | return State::TrailerEnd; 162 | else if (c == '\n') 163 | return State::Error; 164 | else 165 | return State::Trailer; 166 | 167 | case State::TrailerEnd: 168 | if (c == '\n') 169 | return State::TrailerStart; 170 | else 171 | return State::Error; 172 | 173 | case State::FinalCrLf: 174 | if (c == '\n') { 175 | assert(remaining_ == 0); 176 | return State::Ended; 177 | } else 178 | return State::Error; 179 | 180 | default: 181 | return State::Error; 182 | } 183 | } 184 | 185 | State appendSizeHexDigit(uint8_t digit) { 186 | auto oldRemaining = remaining_; 187 | remaining_ = oldRemaining * 16 + digit; 188 | if (remaining_ < oldRemaining) { // overflow 189 | remaining_ = 0; 190 | return State::Error; 191 | } 192 | return State::ChunkSize; 193 | } 194 | 195 | void decreaseRemaining(size_t n) { 196 | assert(remaining_ >= n); 197 | remaining_ -= n; 198 | if (remaining_ == 0) 199 | state_ = State::ChunkEndCr; 200 | } 201 | 202 | size_t min_(size_t a, size_t b) { 203 | return a < b ? a : b; 204 | } 205 | 206 | size_t remaining_ = 0; 207 | State state_ = State::ChunkSize; 208 | }; 209 | 210 | } // namespace StreamUtils 211 | -------------------------------------------------------------------------------- /firmware/Motherboard/lib/ArduinoStreamUtils/StreamUtils/Policies/ConnectForwardingPolicy.hpp: -------------------------------------------------------------------------------- 1 | // StreamUtils - github.com/bblanchon/ArduinoStreamUtils 2 | // Copyright Benoit Blanchon 2019-2024 3 | // MIT License 4 | 5 | #pragma once 6 | 7 | #include 8 | 9 | #include "../Configuration.hpp" 10 | 11 | namespace StreamUtils { 12 | 13 | struct ConnectForwardingPolicy { 14 | int connect(Client& target, const IPAddress& ip, uint16_t port) { 15 | return target.connect(ip, port); 16 | } 17 | 18 | int connect(Client& target, const char* ip, uint16_t port) { 19 | return target.connect(ip, port); 20 | } 21 | 22 | uint8_t connected(Client& target) { 23 | return target.connected(); 24 | } 25 | 26 | void stop(Client& target) { 27 | target.stop(); 28 | } 29 | 30 | bool operator_bool(Client& target) { 31 | return target.operator bool(); 32 | } 33 | }; 34 | 35 | } // namespace StreamUtils -------------------------------------------------------------------------------- /firmware/Motherboard/lib/ArduinoStreamUtils/StreamUtils/Policies/ConnectSpyingPolicy.hpp: -------------------------------------------------------------------------------- 1 | // StreamUtils - github.com/bblanchon/ArduinoStreamUtils 2 | // Copyright Benoit Blanchon 2019-2024 3 | // MIT License 4 | 5 | #pragma once 6 | 7 | #include 8 | 9 | namespace StreamUtils { 10 | 11 | class ConnectSpyingPolicy { 12 | public: 13 | ConnectSpyingPolicy(Print& log) : _log(log) {} 14 | 15 | int connect(Client& target, const IPAddress& ip, uint16_t port) { 16 | _log.print("connect('"); 17 | _log.print(ip); 18 | _log.print("', "); 19 | _log.print(port); 20 | _log.print(") -> "); 21 | 22 | int result = target.connect(ip, port); 23 | _log.println(result); 24 | 25 | return result; 26 | } 27 | 28 | int connect(Client& target, const char* ip, uint16_t port) { 29 | _log.print("connect('"); 30 | _log.print(ip); 31 | _log.print("', "); 32 | _log.print(port); 33 | _log.print(") -> "); 34 | 35 | int result = target.connect(ip, port); 36 | _log.println(result); 37 | 38 | return result; 39 | } 40 | 41 | uint8_t connected(Client& target) { 42 | _log.print("connected() -> "); 43 | 44 | uint8_t result = target.connected(); 45 | _log.println(result); 46 | 47 | return result; 48 | } 49 | 50 | void stop(Client& target) { 51 | _log.print("stop()"); 52 | target.stop(); 53 | } 54 | 55 | bool operator_bool(Client& target) { 56 | _log.print("operator bool() -> "); 57 | 58 | bool result = target.operator bool(); 59 | _log.println(result ? "true" : "false"); 60 | 61 | return result; 62 | } 63 | 64 | private: 65 | Print& _log; 66 | }; 67 | 68 | } // namespace StreamUtils -------------------------------------------------------------------------------- /firmware/Motherboard/lib/ArduinoStreamUtils/StreamUtils/Policies/HammingDecodingPolicy.hpp: -------------------------------------------------------------------------------- 1 | // StreamUtils - github.com/bblanchon/ArduinoStreamUtils 2 | // Copyright Benoit Blanchon 2019-2024 3 | // MIT License 4 | 5 | #pragma once 6 | 7 | #if defined(WIN32) || defined(__WIN32) || defined(__WIN32__) 8 | #include 9 | #else 10 | #include 11 | #endif 12 | 13 | #include 14 | #include 15 | 16 | #include "../Configuration.hpp" 17 | #include "../Helpers.hpp" 18 | 19 | namespace StreamUtils { 20 | 21 | template 22 | class HammingDecodingPolicy; 23 | 24 | template 25 | class HammingDecodingPolicy<7, 4, TAllocator> { 26 | const static size_t sizeAllowedOnStack = STREAMUTILS_STACK_BUFFER_MAX_SIZE; 27 | 28 | public: 29 | HammingDecodingPolicy(TAllocator allocator = TAllocator()) 30 | : _allocator(allocator) {} 31 | 32 | int available(Stream &stream) { 33 | int n = stream.available(); 34 | if (_remainder >= 0) 35 | n++; 36 | return n / 2; 37 | } 38 | 39 | template // Stream or Client 40 | int read(TTarget &target) { 41 | if (_remainder < 0) { 42 | _remainder = target.read(); 43 | if (_remainder < 0) 44 | return -1; 45 | } 46 | int c = target.read(); 47 | if (c < 0) 48 | return -1; 49 | int result = decode(_remainder, c); 50 | _remainder = -1; 51 | return result; 52 | } 53 | 54 | int peek(Stream &stream) { 55 | if (_remainder < 0) { 56 | _remainder = stream.read(); 57 | if (_remainder < 0) 58 | return -1; 59 | } 60 | int c = stream.peek(); 61 | if (c < 0) 62 | return -1; 63 | return decode(_remainder, c); 64 | } 65 | 66 | size_t readBytes(Stream &stream, char *buffer, size_t size) { 67 | return doReadBytes(stream, buffer, size); 68 | } 69 | 70 | int read(Client &client, uint8_t *buffer, size_t size) { 71 | return static_cast( 72 | doReadBytes(client, reinterpret_cast(buffer), size)); 73 | } 74 | 75 | private: 76 | // Decode 7-bits to 4-bits using Hamming(7,4) 77 | uint8_t decode(uint8_t input) { 78 | // table is packed: each element contains two 4-bits values 79 | static uint8_t table[] = { 80 | 0x00, 0x03, 0x05, 0xE7, 0x09, 0xEB, 0xED, 0xEE, 0x03, 0x33, 0x4D, 81 | 0x63, 0x8D, 0xA3, 0xDD, 0xED, 0x05, 0x2B, 0x55, 0x65, 0x8B, 0xBB, 82 | 0xC5, 0xEB, 0x81, 0x63, 0x65, 0x66, 0x88, 0x8B, 0x8D, 0x6F, 0x09, 83 | 0x27, 0x47, 0x77, 0x99, 0xA9, 0xC9, 0xE7, 0x41, 0xA3, 0x44, 0x47, 84 | 0xA9, 0xAA, 0x4D, 0xAF, 0x21, 0x22, 0xC5, 0x27, 0xC9, 0x2B, 0xCC, 85 | 0xCF, 0x11, 0x21, 0x41, 0x6F, 0x81, 0xAF, 0xCF, 0xFF}; 86 | uint8_t elem = table[input / 2]; 87 | if (input % 2) 88 | return elem & 0x0f; 89 | 90 | else 91 | return elem >> 4; 92 | } 93 | 94 | uint8_t decode(uint8_t first, uint8_t second) { 95 | return decode(first) << 4 | decode(second); 96 | } 97 | 98 | template // Stream or Client 99 | size_t doReadBytes(TTarget &target, char *output, size_t outputSize) { 100 | char *buffer; 101 | size_t bufferSize = outputSize * 2; 102 | 103 | if (bufferSize > sizeAllowedOnStack) { 104 | buffer = static_cast(_allocator.allocate(bufferSize)); 105 | if (!buffer) { 106 | // allocation failed => use the input buffer 107 | bufferSize = outputSize; 108 | buffer = output; 109 | } 110 | } else { 111 | buffer = static_cast(alloca(bufferSize)); 112 | } 113 | 114 | size_t loadedSize = 0; 115 | if (_remainder >= 0) 116 | buffer[loadedSize++] = _remainder; 117 | 118 | loadedSize += 119 | readOrReadBytes(target, buffer + loadedSize, bufferSize - loadedSize); 120 | for (size_t i = 0; i < loadedSize / 2; i++) 121 | output[i] = decode(buffer[2 * i], buffer[2 * i + 1]); 122 | 123 | if (loadedSize % 2) 124 | _remainder = buffer[loadedSize - 1]; 125 | else 126 | _remainder = -1; 127 | 128 | if (bufferSize > sizeAllowedOnStack) 129 | _allocator.deallocate(buffer); 130 | 131 | return loadedSize / 2; 132 | } 133 | 134 | TAllocator _allocator; 135 | int _remainder = -1; 136 | }; 137 | 138 | } // namespace StreamUtils 139 | -------------------------------------------------------------------------------- /firmware/Motherboard/lib/ArduinoStreamUtils/StreamUtils/Policies/HammingEncodingPolicy.hpp: -------------------------------------------------------------------------------- 1 | // StreamUtils - github.com/bblanchon/ArduinoStreamUtils 2 | // Copyright Benoit Blanchon 2019-2024 3 | // MIT License 4 | 5 | #pragma once 6 | 7 | #if defined(WIN32) || defined(__WIN32) || defined(__WIN32__) 8 | #include 9 | #else 10 | #include 11 | #endif 12 | 13 | #include "../Configuration.hpp" 14 | 15 | namespace StreamUtils { 16 | 17 | template 18 | class HammingEncodingPolicy; 19 | 20 | template 21 | class HammingEncodingPolicy<7, 4, TAllocator> { 22 | const static size_t sizeAllowedOnStack = STREAMUTILS_STACK_BUFFER_MAX_SIZE; 23 | 24 | public: 25 | HammingEncodingPolicy(TAllocator allocator = TAllocator()) 26 | : _allocator(allocator) {} 27 | 28 | size_t write(Print &target, const uint8_t *data, size_t size) { 29 | if (!flushRemainder(target)) 30 | return 0; 31 | 32 | size_t bufferSize = size * 2; 33 | uint8_t *buffer; 34 | if (bufferSize > sizeAllowedOnStack) { 35 | buffer = static_cast(_allocator.allocate(bufferSize)); 36 | if (!buffer) { 37 | bufferSize = sizeAllowedOnStack; 38 | buffer = static_cast(alloca(bufferSize)); 39 | } 40 | } else { 41 | buffer = static_cast(alloca(bufferSize)); 42 | } 43 | 44 | for (size_t i = 0, j = 0; j < bufferSize; i++) { 45 | buffer[j++] = encode(data[i] >> 4); 46 | buffer[j++] = encode(data[i] & 0x0f); 47 | } 48 | size_t n = target.write(buffer, bufferSize); 49 | if (n & 1) { 50 | _remainder = buffer[n]; 51 | ++n; 52 | } 53 | 54 | if (bufferSize > sizeAllowedOnStack) 55 | _allocator.deallocate(buffer); 56 | 57 | return n / 2; 58 | } 59 | 60 | size_t write(Print &target, uint8_t data) { 61 | uint8_t first = encode(data >> 4); 62 | uint8_t second = encode(data & 0x0f); 63 | 64 | if (!flushRemainder(target)) 65 | return 0; 66 | 67 | if (!target.write(first)) 68 | return 0; 69 | 70 | if (!target.write(second)) 71 | _remainder = second; 72 | 73 | return 1; 74 | } 75 | 76 | void flush(Stream &target) { 77 | flushRemainder(target); 78 | target.flush(); 79 | } 80 | 81 | void flush(Print &target) { 82 | flushRemainder(target); 83 | #if STREAMUTILS_PRINT_FLUSH_EXISTS 84 | target.flush(); 85 | #endif 86 | } 87 | 88 | void implicitFlush(Print &target) { 89 | flushRemainder(target); 90 | } 91 | 92 | private: 93 | // Encode 4-bits to 7-bits using Hamming(7,4) 94 | uint8_t encode(uint8_t input) { 95 | static uint8_t table[] = {0x00, 0x71, 0x62, 0x13, 0x54, 0x25, 0x36, 0x47, 96 | 0x38, 0x49, 0x5A, 0x2B, 0x6C, 0x1D, 0x0E, 0x7F}; 97 | return table[input]; 98 | } 99 | 100 | template 101 | bool flushRemainder(TTarget &target) { 102 | if (_remainder < 0) 103 | return true; 104 | 105 | if (!target.write(_remainder)) 106 | return false; 107 | 108 | _remainder = -1; 109 | return true; 110 | } 111 | 112 | TAllocator _allocator; 113 | int8_t _remainder = -1; 114 | }; 115 | 116 | } // namespace StreamUtils -------------------------------------------------------------------------------- /firmware/Motherboard/lib/ArduinoStreamUtils/StreamUtils/Policies/ReadBufferingPolicy.hpp: -------------------------------------------------------------------------------- 1 | // StreamUtils - github.com/bblanchon/ArduinoStreamUtils 2 | // Copyright Benoit Blanchon 2019-2024 3 | // MIT License 4 | 5 | #pragma once 6 | 7 | #include 8 | 9 | #include "../Buffers/LinearBuffer.hpp" 10 | #include "../Helpers.hpp" 11 | 12 | namespace StreamUtils { 13 | 14 | template 15 | struct ReadBufferingPolicy { 16 | ReadBufferingPolicy(size_t capacity, TAllocator allocator = TAllocator()) 17 | : _buffer(capacity, allocator) {} 18 | 19 | ReadBufferingPolicy(const ReadBufferingPolicy &other) 20 | : _buffer(other._buffer) {} 21 | 22 | int available(Stream &stream) { 23 | return static_cast(stream.available() + _buffer.available()); 24 | } 25 | 26 | template // Stream or Client 27 | int read(TTarget &target) { 28 | if (!_buffer) 29 | return target.read(); 30 | 31 | if (_buffer.available() > 0) 32 | return _buffer.read(); 33 | 34 | size_t avail = static_cast(target.available()); 35 | if (avail <= 1) 36 | return target.read(); 37 | 38 | _buffer.reloadFrom(target, avail); 39 | return _buffer.read(); 40 | } 41 | 42 | int peek(Stream &stream) { 43 | return isEmpty() ? stream.peek() : _buffer.peek(); 44 | } 45 | 46 | size_t readBytes(Stream &stream, char *buffer, size_t size) { 47 | return doReadBytes(stream, buffer, size); 48 | } 49 | 50 | int read(Client &client, uint8_t *buffer, size_t size) { 51 | return static_cast( 52 | doReadBytes(client, reinterpret_cast(buffer), size)); 53 | } 54 | 55 | private: 56 | bool isEmpty() const { 57 | return _buffer.available() == 0; 58 | } 59 | 60 | LinearBuffer _buffer; 61 | 62 | template // Stream or Client 63 | size_t doReadBytes(TTarget &target, char *buffer, size_t size) { 64 | if (!_buffer) 65 | return readOrReadBytes(target, buffer, size); 66 | 67 | size_t result = 0; 68 | 69 | // can we read from buffer? 70 | if (_buffer.available() > 0) { 71 | size_t bytesRead = _buffer.readBytes(buffer, size); 72 | result += bytesRead; 73 | buffer += bytesRead; 74 | size -= bytesRead; 75 | } 76 | 77 | // still something to read? 78 | if (size > 0) { 79 | // (at this point, the buffer is empty) 80 | 81 | size_t avail = static_cast(target.available()); 82 | 83 | // should we use the buffer? 84 | if (avail > size && size < _buffer.capacity()) { 85 | _buffer.reloadFrom(target, avail); 86 | size_t bytesRead = _buffer.readBytes(buffer, size); 87 | result += bytesRead; 88 | } else { 89 | // we can bypass the buffer 90 | result += readOrReadBytes(target, buffer, size); 91 | } 92 | } 93 | 94 | return result; 95 | } 96 | }; // namespace StreamUtils 97 | 98 | } // namespace StreamUtils -------------------------------------------------------------------------------- /firmware/Motherboard/lib/ArduinoStreamUtils/StreamUtils/Policies/ReadForwardingPolicy.hpp: -------------------------------------------------------------------------------- 1 | // StreamUtils - github.com/bblanchon/ArduinoStreamUtils 2 | // Copyright Benoit Blanchon 2019-2024 3 | // MIT License 4 | 5 | #pragma once 6 | 7 | #include 8 | 9 | namespace StreamUtils { 10 | 11 | struct ReadForwardingPolicy { 12 | int available(Stream &target) { 13 | return target.available(); 14 | } 15 | 16 | int read(Stream &target) { 17 | return target.read(); 18 | } 19 | 20 | int peek(Stream &target) { 21 | return target.peek(); 22 | } 23 | 24 | size_t readBytes(Stream &target, char *buffer, size_t size) { 25 | return target.readBytes(buffer, size); 26 | } 27 | 28 | int read(Client &target, uint8_t *buffer, size_t size) { 29 | return target.read(buffer, size); 30 | } 31 | }; 32 | 33 | } // namespace StreamUtils -------------------------------------------------------------------------------- /firmware/Motherboard/lib/ArduinoStreamUtils/StreamUtils/Policies/ReadLoggingPolicy.hpp: -------------------------------------------------------------------------------- 1 | // StreamUtils - github.com/bblanchon/ArduinoStreamUtils 2 | // Copyright Benoit Blanchon 2019-2024 3 | // MIT License 4 | 5 | #pragma once 6 | 7 | #include 8 | 9 | namespace StreamUtils { 10 | 11 | class ReadLoggingPolicy { 12 | public: 13 | ReadLoggingPolicy(Print &log) : _log(log) {} 14 | 15 | int available(Stream &stream) { 16 | return stream.available(); 17 | } 18 | 19 | int read(Stream &stream) { 20 | int result = stream.read(); 21 | if (result >= 0) 22 | _log.write(result); 23 | return result; 24 | } 25 | 26 | int peek(Stream &stream) { 27 | return stream.peek(); 28 | } 29 | 30 | size_t readBytes(Stream &stream, char *buffer, size_t size) { 31 | size_t result = stream.readBytes(buffer, size); 32 | _log.write(buffer, result); 33 | return result; 34 | } 35 | 36 | int read(Client &client, uint8_t *buffer, size_t size) { 37 | int result = client.read(buffer, size); 38 | _log.write(buffer, result); 39 | return result; 40 | } 41 | 42 | private: 43 | Print &_log; 44 | }; 45 | 46 | } // namespace StreamUtils -------------------------------------------------------------------------------- /firmware/Motherboard/lib/ArduinoStreamUtils/StreamUtils/Policies/ReadSpyingPolicy.hpp: -------------------------------------------------------------------------------- 1 | // StreamUtils - github.com/bblanchon/ArduinoStreamUtils 2 | // Copyright Benoit Blanchon 2019-2024 3 | // MIT License 4 | 5 | #pragma once 6 | 7 | #include 8 | 9 | namespace StreamUtils { 10 | 11 | class ReadSpyingPolicy { 12 | public: 13 | ReadSpyingPolicy(Print &log) : _log(log) {} 14 | 15 | int available(Stream &target) { 16 | int result = target.available(); 17 | _log.print("available() -> "); 18 | _log.println(result); 19 | return result; 20 | } 21 | 22 | int read(Stream &target) { 23 | int result = target.read(); 24 | _log.print("read() -> "); 25 | _log.println(result); 26 | return result; 27 | } 28 | 29 | int peek(Stream &target) { 30 | int result = target.peek(); 31 | _log.print("peek() -> "); 32 | _log.println(result); 33 | return result; 34 | } 35 | 36 | size_t readBytes(Stream &target, char *buffer, size_t size) { 37 | size_t result = target.readBytes(buffer, size); 38 | _log.print("readBytes("); 39 | _log.print(size); 40 | _log.print(") -> "); 41 | _log.print(result); 42 | if (size > result) 43 | _log.print(" [timeout]"); 44 | _log.println(); 45 | return result; 46 | } 47 | 48 | int read(Client &target, uint8_t *buffer, size_t size) { 49 | int result = target.read(buffer, size); 50 | _log.print("read("); 51 | _log.print(size); 52 | _log.print(") -> "); 53 | _log.print(result); 54 | if (static_cast(size) > result) 55 | _log.print(" [timeout]"); 56 | _log.println(); 57 | return result; 58 | } 59 | 60 | private: 61 | Print &_log; 62 | }; 63 | 64 | } // namespace StreamUtils 65 | -------------------------------------------------------------------------------- /firmware/Motherboard/lib/ArduinoStreamUtils/StreamUtils/Policies/ReadThrottlingPolicy.hpp: -------------------------------------------------------------------------------- 1 | // StreamUtils - github.com/bblanchon/ArduinoStreamUtils 2 | // Copyright Benoit Blanchon 2019-2024 3 | // MIT License 4 | 5 | #pragma once 6 | 7 | #include 8 | 9 | namespace StreamUtils { 10 | 11 | template 12 | struct ReadThrottlingPolicy { 13 | ReadThrottlingPolicy(TThrottler throttler) : _throttler(throttler) {} 14 | 15 | int available(Stream &stream) { 16 | return stream.available(); 17 | } 18 | 19 | int read(Stream &stream) { 20 | _throttler.throttle(); 21 | return stream.read(); 22 | } 23 | 24 | int peek(Stream &stream) { 25 | _throttler.throttle(); 26 | return stream.peek(); 27 | } 28 | 29 | size_t readBytes(Stream &stream, char *buffer, size_t size) { 30 | for (size_t i = 0; i < size; i++) { 31 | int c = read(stream); 32 | if (c < 0) 33 | return i; 34 | buffer[i] = c; 35 | } 36 | return size; 37 | } 38 | 39 | const TThrottler &throttler() const { 40 | return _throttler; 41 | } 42 | 43 | private: 44 | TThrottler _throttler; 45 | }; 46 | 47 | } // namespace StreamUtils -------------------------------------------------------------------------------- /firmware/Motherboard/lib/ArduinoStreamUtils/StreamUtils/Policies/WriteBufferingPolicy.hpp: -------------------------------------------------------------------------------- 1 | // StreamUtils - github.com/bblanchon/ArduinoStreamUtils 2 | // Copyright Benoit Blanchon 2019-2024 3 | // MIT License 4 | 5 | #pragma once 6 | 7 | #include 8 | 9 | #include "../Buffers/LinearBuffer.hpp" 10 | #include "../Configuration.hpp" 11 | 12 | namespace StreamUtils { 13 | 14 | template 15 | struct WriteBufferingPolicy { 16 | public: 17 | WriteBufferingPolicy(size_t capacity, TAllocator allocator) 18 | : _buffer(capacity, allocator) {} 19 | 20 | size_t write(Print &target, const uint8_t *data, size_t size) { 21 | size_t result = 0; 22 | 23 | // continue to fill the buffer? 24 | if (!_buffer.isEmpty()) { 25 | size_t n = _buffer.write(data, size); 26 | data += n; 27 | size -= n; 28 | result += n; 29 | 30 | // time to flush? 31 | if (_buffer.isFull()) { 32 | _buffer.flushInto(target); 33 | } 34 | } 35 | 36 | // something left to write? 37 | if (size > 0) { 38 | // can we bypass the buffer? 39 | if (size >= _buffer.capacity()) { 40 | result += target.write(data, size); 41 | } else { 42 | result += _buffer.write(data, size); 43 | } 44 | } 45 | return result; 46 | } 47 | 48 | size_t write(Print &target, uint8_t data) { 49 | if (!_buffer) 50 | return target.write(data); 51 | 52 | _buffer.write(data); 53 | if (_buffer.isFull()) 54 | _buffer.flushInto(target); 55 | return 1; 56 | } 57 | 58 | void flush(Stream &target) { 59 | _buffer.flushInto(target); 60 | target.flush(); 61 | } 62 | 63 | void flush(Print &target) { 64 | _buffer.flushInto(target); 65 | #if STREAMUTILS_PRINT_FLUSH_EXISTS 66 | target.flush(); 67 | #endif 68 | } 69 | 70 | void implicitFlush(Print &target) { 71 | _buffer.flushInto(target); 72 | } 73 | 74 | private: 75 | LinearBuffer _buffer; 76 | }; 77 | 78 | } // namespace StreamUtils -------------------------------------------------------------------------------- /firmware/Motherboard/lib/ArduinoStreamUtils/StreamUtils/Policies/WriteForwardingPolicy.hpp: -------------------------------------------------------------------------------- 1 | // StreamUtils - github.com/bblanchon/ArduinoStreamUtils 2 | // Copyright Benoit Blanchon 2019-2024 3 | // MIT License 4 | 5 | #pragma once 6 | 7 | #include 8 | #include 9 | 10 | #include "../Configuration.hpp" 11 | 12 | namespace StreamUtils { 13 | 14 | struct WriteForwardingPolicy { 15 | template 16 | size_t write(Stream &stream, Args... args) { 17 | return stream.write(args...); 18 | } 19 | 20 | void flush(Stream &stream) { 21 | stream.flush(); 22 | } 23 | 24 | void implicitFlush(Stream &) {} 25 | }; 26 | 27 | } // namespace StreamUtils -------------------------------------------------------------------------------- /firmware/Motherboard/lib/ArduinoStreamUtils/StreamUtils/Policies/WriteLoggingPolicy.hpp: -------------------------------------------------------------------------------- 1 | // StreamUtils - github.com/bblanchon/ArduinoStreamUtils 2 | // Copyright Benoit Blanchon 2019-2024 3 | // MIT License 4 | 5 | #pragma once 6 | 7 | #include 8 | #include "../Configuration.hpp" 9 | 10 | namespace StreamUtils { 11 | 12 | class WriteLoggingPolicy { 13 | public: 14 | WriteLoggingPolicy(Print &log) : _log(log) {} 15 | 16 | size_t write(Print &target, const uint8_t *buffer, size_t size) { 17 | size_t result = target.write(buffer, size); 18 | _log.write(buffer, result); 19 | return result; 20 | } 21 | 22 | size_t write(Print &target, uint8_t c) { 23 | size_t result = target.write(c); 24 | _log.write(c); 25 | return result; 26 | } 27 | 28 | template 29 | void flush(TTarget &target) { 30 | target.flush(); 31 | } 32 | 33 | void implicitFlush(Print &) {} 34 | 35 | private: 36 | Print &_log; 37 | }; 38 | 39 | } // namespace StreamUtils -------------------------------------------------------------------------------- /firmware/Motherboard/lib/ArduinoStreamUtils/StreamUtils/Policies/WriteSpyingPolicy.hpp: -------------------------------------------------------------------------------- 1 | // StreamUtils - github.com/bblanchon/ArduinoStreamUtils 2 | // Copyright Benoit Blanchon 2019-2024 3 | // MIT License 4 | 5 | #pragma once 6 | 7 | #include 8 | 9 | #include "../Configuration.hpp" 10 | 11 | namespace StreamUtils { 12 | 13 | class WriteSpyingPolicy { 14 | public: 15 | WriteSpyingPolicy(Print &log) : _log(log) {} 16 | 17 | size_t write(Print &stream, const uint8_t *buffer, size_t size) { 18 | _log.print("write('"); 19 | for (size_t i = 0; i < size; i++) { 20 | _log.write(buffer[i]); 21 | } 22 | _log.print("', "); 23 | _log.print(size); 24 | _log.print(") -> "); 25 | 26 | size_t result = stream.write(buffer, size); 27 | _log.println(result); 28 | 29 | return result; 30 | } 31 | 32 | size_t write(Print &stream, uint8_t data) { 33 | _log.print("write('"); 34 | _log.write(data); 35 | _log.print("') -> "); 36 | 37 | size_t result = stream.write(data); 38 | _log.println(result); 39 | 40 | return result; 41 | } 42 | 43 | template 44 | void flush(TTarget &target) { 45 | _log.println("flush()"); 46 | target.flush(); 47 | } 48 | 49 | void implicitFlush(Print &) {} 50 | 51 | private: 52 | Print &_log; 53 | }; 54 | 55 | } // namespace StreamUtils 56 | -------------------------------------------------------------------------------- /firmware/Motherboard/lib/ArduinoStreamUtils/StreamUtils/Policies/WriteWaitingPolicy.hpp: -------------------------------------------------------------------------------- 1 | // StreamUtils - github.com/bblanchon/ArduinoStreamUtils 2 | // Copyright Benoit Blanchon 2019-2024 3 | // MIT License 4 | 5 | #pragma once 6 | 7 | #include 8 | 9 | #include "../Buffers/LinearBuffer.hpp" 10 | #include "../Configuration.hpp" 11 | #include "../Polyfills.hpp" 12 | 13 | namespace StreamUtils { 14 | 15 | struct WriteWaitingPolicy { 16 | public: 17 | WriteWaitingPolicy(Polyfills::function wait) 18 | : _wait(Polyfills::move(wait)), _timeout(1000) {} 19 | 20 | size_t write(Print &target, const uint8_t *data, size_t size) { 21 | unsigned long startTime = millis(); 22 | size_t totalWritten = 0; 23 | 24 | for (;;) { 25 | size_t n = target.write(data, size); 26 | size -= n; 27 | data += n; 28 | totalWritten += n; 29 | if (size == 0 || millis() - startTime >= _timeout) 30 | return totalWritten; 31 | _wait(); 32 | } 33 | } 34 | 35 | size_t write(Print &target, uint8_t data) { 36 | unsigned long startTime = millis(); 37 | 38 | for (;;) { 39 | if (target.write(data)) 40 | return 1; 41 | if (millis() - startTime >= _timeout) 42 | return 0; 43 | _wait(); 44 | } 45 | } 46 | 47 | template 48 | void flush(TTarget &target) { 49 | target.flush(); 50 | } 51 | 52 | void implicitFlush(Print &) {} 53 | 54 | void setTimeout(unsigned long timeout) { 55 | _timeout = timeout; 56 | } 57 | 58 | private: 59 | Polyfills::function _wait; 60 | unsigned long _timeout; 61 | }; 62 | 63 | } // namespace StreamUtils -------------------------------------------------------------------------------- /firmware/Motherboard/lib/ArduinoStreamUtils/StreamUtils/Polyfills.hpp: -------------------------------------------------------------------------------- 1 | // StreamUtils - github.com/bblanchon/ArduinoStreamUtils 2 | // Copyright Benoit Blanchon 2019-2024 3 | // MIT License 4 | 5 | #pragma once 6 | 7 | namespace StreamUtils { 8 | namespace Polyfills { 9 | 10 | template 11 | struct remove_reference { 12 | using type = T; 13 | }; 14 | 15 | template 16 | struct remove_reference { 17 | using type = T; 18 | }; 19 | 20 | template 21 | typename remove_reference::type &&move(T &&t) { 22 | return static_cast::type &&>(t); 23 | } 24 | 25 | // poor man's std::function 26 | class function { 27 | struct callable_base { 28 | virtual void operator()() = 0; 29 | virtual ~callable_base() {} 30 | }; 31 | 32 | template 33 | struct callable : callable_base { 34 | Functor functor; 35 | callable(Functor functor) : functor(functor) {} 36 | virtual void operator()() { 37 | functor(); 38 | } 39 | }; 40 | 41 | callable_base *_callable; 42 | 43 | public: 44 | template 45 | function(Functor f) { 46 | _callable = new callable(f); 47 | } 48 | function(function &&src) { 49 | _callable = src._callable, src._callable = 0; 50 | } 51 | ~function() { 52 | delete _callable; 53 | } 54 | void operator()() const { 55 | (*_callable)(); 56 | } 57 | }; 58 | 59 | } // namespace Polyfills 60 | } // namespace StreamUtils -------------------------------------------------------------------------------- /firmware/Motherboard/lib/ArduinoStreamUtils/StreamUtils/Ports/ArduinoThrottler.hpp: -------------------------------------------------------------------------------- 1 | // StreamUtils - github.com/bblanchon/ArduinoStreamUtils 2 | // Copyright Benoit Blanchon 2019-2024 3 | // MIT License 4 | 5 | #pragma once 6 | 7 | namespace StreamUtils { 8 | 9 | class ArduinoThrottler { 10 | public: 11 | ArduinoThrottler(uint32_t rate) : _interval(1000000 / rate), _last(0) {} 12 | 13 | void throttle() { 14 | auto now = micros(); 15 | auto elapsed = now - _last; 16 | 17 | if (elapsed < _interval) { 18 | delayMicroseconds(_interval - elapsed); 19 | } 20 | 21 | _last = now; 22 | } 23 | 24 | private: 25 | unsigned long _interval; 26 | unsigned long _last; 27 | }; 28 | 29 | } // namespace StreamUtils -------------------------------------------------------------------------------- /firmware/Motherboard/lib/ArduinoStreamUtils/StreamUtils/Ports/DefaultAllocator.hpp: -------------------------------------------------------------------------------- 1 | // StreamUtils - github.com/bblanchon/ArduinoStreamUtils 2 | // Copyright Benoit Blanchon 2019-2024 3 | // MIT License 4 | 5 | #pragma once 6 | 7 | namespace StreamUtils { 8 | 9 | #include // malloc, free, size_t 10 | 11 | struct DefaultAllocator { 12 | void* allocate(size_t n) { 13 | return malloc(n); 14 | } 15 | 16 | void deallocate(void* p) { 17 | free(p); 18 | } 19 | }; 20 | 21 | } // namespace StreamUtils -------------------------------------------------------------------------------- /firmware/Motherboard/lib/ArduinoStreamUtils/StreamUtils/Prints/BufferingPrint.hpp: -------------------------------------------------------------------------------- 1 | // StreamUtils - github.com/bblanchon/ArduinoStreamUtils 2 | // Copyright Benoit Blanchon 2019-2024 3 | // MIT License 4 | 5 | #pragma once 6 | 7 | #include "../Policies/WriteBufferingPolicy.hpp" 8 | #include "../Ports/DefaultAllocator.hpp" 9 | #include "PrintProxy.hpp" 10 | 11 | namespace StreamUtils { 12 | 13 | template 14 | struct BasicBufferingPrint : PrintProxy> { 15 | explicit BasicBufferingPrint(Print &upstream, size_t capacity, 16 | TAllocator allocator = TAllocator()) 17 | : PrintProxy>(upstream, 18 | {capacity, allocator}) {} 19 | }; 20 | 21 | using BufferingPrint = BasicBufferingPrint; 22 | } // namespace StreamUtils -------------------------------------------------------------------------------- /firmware/Motherboard/lib/ArduinoStreamUtils/StreamUtils/Prints/HammingPrint.hpp: -------------------------------------------------------------------------------- 1 | // StreamUtils - github.com/bblanchon/ArduinoStreamUtils 2 | // Copyright Benoit Blanchon 2019-2024 3 | // MIT License 4 | 5 | #pragma once 6 | 7 | #include "../Policies/HammingEncodingPolicy.hpp" 8 | #include "../Ports/DefaultAllocator.hpp" 9 | #include "PrintProxy.hpp" 10 | 11 | namespace StreamUtils { 12 | 13 | template 14 | using BasicHammingPrint = PrintProxy>; 15 | 16 | template 17 | using HammingPrint = BasicHammingPrint; 18 | 19 | } // namespace StreamUtils -------------------------------------------------------------------------------- /firmware/Motherboard/lib/ArduinoStreamUtils/StreamUtils/Prints/LoggingPrint.hpp: -------------------------------------------------------------------------------- 1 | // StreamUtils - github.com/bblanchon/ArduinoStreamUtils 2 | // Copyright Benoit Blanchon 2019-2024 3 | // MIT License 4 | 5 | #pragma once 6 | 7 | #include "../Policies/WriteLoggingPolicy.hpp" 8 | #include "PrintProxy.hpp" 9 | 10 | namespace StreamUtils { 11 | 12 | struct LoggingPrint : PrintProxy { 13 | LoggingPrint(Print &upstream, Print &log) 14 | : PrintProxy(upstream, {log}) {} 15 | }; 16 | 17 | } // namespace StreamUtils -------------------------------------------------------------------------------- /firmware/Motherboard/lib/ArduinoStreamUtils/StreamUtils/Prints/PrintProxy.hpp: -------------------------------------------------------------------------------- 1 | // StreamUtils - github.com/bblanchon/ArduinoStreamUtils 2 | // Copyright Benoit Blanchon 2019-2024 3 | // MIT License 4 | 5 | #pragma once 6 | 7 | #include 8 | 9 | #include "../Configuration.hpp" 10 | #include "../Polyfills.hpp" 11 | 12 | namespace StreamUtils { 13 | 14 | template 15 | class PrintProxy : public Print { 16 | public: 17 | explicit PrintProxy(Print &upstream, WritePolicy writer = WritePolicy{}) 18 | : _target(upstream), _writer(Polyfills::move(writer)) {} 19 | 20 | PrintProxy(const PrintProxy &other) 21 | : _target(other._target), _writer(other._writer) {} 22 | 23 | ~PrintProxy() { 24 | _writer.implicitFlush(_target); 25 | } 26 | 27 | #if STREAMUTILS_PRINT_WRITE_VOID_UINT32 28 | size_t write(const void *data, uint32 size) override { 29 | const uint8_t *buffer = reinterpret_cast(data); 30 | #else 31 | size_t write(const uint8_t *buffer, size_t size) override { 32 | #endif 33 | return _writer.write(_target, buffer, size); 34 | } 35 | 36 | size_t write(uint8_t data) override { 37 | return _writer.write(_target, data); 38 | } 39 | 40 | #if STREAMUTILS_PRINT_FLUSH_EXISTS 41 | void flush() override { 42 | #else 43 | void flush() { 44 | #endif 45 | _writer.flush(_target); 46 | } 47 | 48 | using Print::write; 49 | 50 | protected: 51 | Print &_target; 52 | WritePolicy _writer; 53 | }; 54 | 55 | } // namespace StreamUtils -------------------------------------------------------------------------------- /firmware/Motherboard/lib/ArduinoStreamUtils/StreamUtils/Prints/SpyingPrint.hpp: -------------------------------------------------------------------------------- 1 | // StreamUtils - github.com/bblanchon/ArduinoStreamUtils 2 | // Copyright Benoit Blanchon 2019-2024 3 | // MIT License 4 | 5 | #pragma once 6 | 7 | #include "../Policies/WriteSpyingPolicy.hpp" 8 | #include "PrintProxy.hpp" 9 | 10 | namespace StreamUtils { 11 | 12 | struct SpyingPrint : PrintProxy { 13 | SpyingPrint(Print &target, Print &log) 14 | : PrintProxy(target, WriteSpyingPolicy{log}) {} 15 | }; 16 | 17 | } // namespace StreamUtils 18 | -------------------------------------------------------------------------------- /firmware/Motherboard/lib/ArduinoStreamUtils/StreamUtils/Prints/StringPrint.hpp: -------------------------------------------------------------------------------- 1 | // StreamUtils - github.com/bblanchon/ArduinoStreamUtils 2 | // Copyright Benoit Blanchon 2019-2024 3 | // MIT License 4 | 5 | #pragma once 6 | 7 | #include 8 | #include 9 | 10 | #include "../Configuration.hpp" 11 | #include "../Polyfills.hpp" 12 | 13 | namespace StreamUtils { 14 | 15 | class StringPrint : public Print { 16 | public: 17 | StringPrint() {} 18 | 19 | explicit StringPrint(String str) : _str(Polyfills::move(str)) {} 20 | 21 | #if STREAMUTILS_PRINT_WRITE_VOID_UINT32 22 | size_t write(const void* data, uint32 n) override { 23 | const uint8_t* p = reinterpret_cast(data); 24 | #else 25 | size_t write(const uint8_t* p, size_t n) override { 26 | #endif 27 | for (size_t i = 0; i < n; i++) { 28 | uint8_t c = p[i]; 29 | if (c == 0) 30 | return i; 31 | write(c); 32 | } 33 | return n; 34 | } 35 | 36 | size_t write(uint8_t c) override { 37 | if (c == 0) 38 | return 0; 39 | _str += static_cast(c); 40 | return 1; 41 | } 42 | 43 | const String& str() const { 44 | return _str; 45 | } 46 | 47 | void str(String str) { 48 | _str = Polyfills::move(str); 49 | } 50 | 51 | void clear() { 52 | _str = ""; 53 | } 54 | 55 | private: 56 | String _str; 57 | }; 58 | } // namespace StreamUtils -------------------------------------------------------------------------------- /firmware/Motherboard/lib/ArduinoStreamUtils/StreamUtils/Prints/WaitingPrint.hpp: -------------------------------------------------------------------------------- 1 | // StreamUtils - github.com/bblanchon/ArduinoStreamUtils 2 | // Copyright Benoit Blanchon 2019-2024 3 | // MIT License 4 | 5 | #pragma once 6 | 7 | #include "../Policies/WriteWaitingPolicy.hpp" 8 | #include "PrintProxy.hpp" 9 | 10 | namespace StreamUtils { 11 | 12 | struct WaitingPrint : PrintProxy { 13 | WaitingPrint(Print &target, Polyfills::function wait = yield) 14 | : PrintProxy( 15 | target, WriteWaitingPolicy{Polyfills::move(wait)}) {} 16 | 17 | void setTimeout(unsigned long timeout) { 18 | _writer.setTimeout(timeout); 19 | } 20 | }; 21 | 22 | } // namespace StreamUtils -------------------------------------------------------------------------------- /firmware/Motherboard/lib/ArduinoStreamUtils/StreamUtils/Streams/ChunkDecodingStream.hpp: -------------------------------------------------------------------------------- 1 | // StreamUtils - github.com/bblanchon/ArduinoStreamUtils 2 | // Copyright Benoit Blanchon 2019-2024 3 | // MIT License 4 | 5 | #pragma once 6 | 7 | #include "../Policies/ChunkDecodingPolicy.hpp" 8 | #include "../Policies/WriteForwardingPolicy.hpp" 9 | 10 | #include "StreamProxy.hpp" 11 | 12 | namespace StreamUtils { 13 | 14 | struct ChunkDecodingStream 15 | : StreamProxy { 16 | ChunkDecodingStream(Stream &target) 17 | : StreamProxy(target) {} 18 | 19 | bool error() const { 20 | return _reader.error(); 21 | } 22 | 23 | bool ended() const { 24 | return _reader.ended(); 25 | } 26 | }; 27 | 28 | } // namespace StreamUtils 29 | -------------------------------------------------------------------------------- /firmware/Motherboard/lib/ArduinoStreamUtils/StreamUtils/Streams/EepromStream.hpp: -------------------------------------------------------------------------------- 1 | // StreamUtils - github.com/bblanchon/ArduinoStreamUtils 2 | // Copyright Benoit Blanchon 2019-2024 3 | // MIT License 4 | 5 | #pragma once 6 | 7 | #include "../Configuration.hpp" 8 | 9 | #if STREAMUTILS_ENABLE_EEPROM 10 | 11 | #include 12 | #include 13 | 14 | namespace StreamUtils { 15 | 16 | class EepromStream : public Stream { 17 | public: 18 | EepromStream(size_t address, size_t size) 19 | : _readAddress(address), _writeAddress(address), _end(address + size) {} 20 | 21 | int available() override { 22 | return static_cast(_end - _readAddress); 23 | } 24 | 25 | int read() override { 26 | if (_readAddress >= _end) 27 | return -1; 28 | return EEPROM.read(static_cast(_readAddress++)); 29 | } 30 | 31 | int peek() override { 32 | if (_readAddress >= _end) 33 | return -1; 34 | return EEPROM.read(static_cast(_readAddress)); 35 | } 36 | 37 | void flush() override { 38 | #if STREAMUTILS_USE_EEPROM_COMMIT 39 | EEPROM.commit(); 40 | #endif 41 | } 42 | 43 | using Print::write; 44 | 45 | size_t write(const uint8_t *buffer, size_t size) override { 46 | size_t remaining = _end - _writeAddress; 47 | if (size > remaining) 48 | size = remaining; 49 | for (size_t i = 0; i < size; i++) { 50 | int address = static_cast(_writeAddress++); 51 | #if STREAMUTILS_USE_EEPROM_UPDATE 52 | EEPROM.update(address, buffer[i]); 53 | #else 54 | EEPROM.write(address, buffer[i]); 55 | #endif 56 | } 57 | return size; 58 | } 59 | 60 | size_t write(uint8_t data) override { 61 | if (_writeAddress >= _end) 62 | return 0; 63 | int address = static_cast(_writeAddress++); 64 | #if STREAMUTILS_USE_EEPROM_UPDATE 65 | EEPROM.update(address, data); 66 | #else 67 | EEPROM.write(address, data); 68 | #endif 69 | return 1; 70 | } 71 | 72 | private: 73 | size_t _readAddress, _writeAddress, _end; 74 | }; 75 | 76 | } // namespace StreamUtils 77 | 78 | #endif 79 | -------------------------------------------------------------------------------- /firmware/Motherboard/lib/ArduinoStreamUtils/StreamUtils/Streams/HammingDecodingStream.hpp: -------------------------------------------------------------------------------- 1 | // StreamUtils - github.com/bblanchon/ArduinoStreamUtils 2 | // Copyright Benoit Blanchon 2019-2024 3 | // MIT License 4 | 5 | #pragma once 6 | 7 | #include "../Policies/HammingDecodingPolicy.hpp" 8 | #include "../Policies/WriteForwardingPolicy.hpp" 9 | #include "../Ports/DefaultAllocator.hpp" 10 | #include "StreamProxy.hpp" 11 | 12 | namespace StreamUtils { 13 | 14 | template 15 | using BasicHammingDecodingStream = 16 | StreamProxy, WriteForwardingPolicy>; 17 | 18 | template 19 | using HammingDecodingStream = 20 | BasicHammingDecodingStream; 21 | 22 | } // namespace StreamUtils 23 | -------------------------------------------------------------------------------- /firmware/Motherboard/lib/ArduinoStreamUtils/StreamUtils/Streams/HammingEncodingStream.hpp: -------------------------------------------------------------------------------- 1 | // StreamUtils - github.com/bblanchon/ArduinoStreamUtils 2 | // Copyright Benoit Blanchon 2019-2024 3 | // MIT License 4 | 5 | #pragma once 6 | 7 | #include "../Policies/HammingEncodingPolicy.hpp" 8 | #include "../Policies/ReadForwardingPolicy.hpp" 9 | #include "../Ports/DefaultAllocator.hpp" 10 | #include "StreamProxy.hpp" 11 | 12 | namespace StreamUtils { 13 | 14 | template 15 | using BasicHammingEncodingStream = 16 | StreamProxy>; 17 | 18 | template 19 | using HammingEncodingStream = 20 | BasicHammingEncodingStream; 21 | 22 | } // namespace StreamUtils 23 | -------------------------------------------------------------------------------- /firmware/Motherboard/lib/ArduinoStreamUtils/StreamUtils/Streams/HammingStream.hpp: -------------------------------------------------------------------------------- 1 | // StreamUtils - github.com/bblanchon/ArduinoStreamUtils 2 | // Copyright Benoit Blanchon 2019-2024 3 | // MIT License 4 | 5 | #pragma once 6 | 7 | #include "../Policies/HammingDecodingPolicy.hpp" 8 | #include "../Policies/HammingEncodingPolicy.hpp" 9 | #include "../Ports/DefaultAllocator.hpp" 10 | #include "StreamProxy.hpp" 11 | 12 | namespace StreamUtils { 13 | 14 | template 15 | using BasicHammingStream = StreamProxy, 16 | HammingEncodingPolicy>; 17 | 18 | template 19 | using HammingStream = BasicHammingStream; 20 | 21 | } // namespace StreamUtils 22 | -------------------------------------------------------------------------------- /firmware/Motherboard/lib/ArduinoStreamUtils/StreamUtils/Streams/LoggingStream.hpp: -------------------------------------------------------------------------------- 1 | // StreamUtils - github.com/bblanchon/ArduinoStreamUtils 2 | // Copyright Benoit Blanchon 2019-2024 3 | // MIT License 4 | 5 | #pragma once 6 | 7 | #include "../Policies/ReadLoggingPolicy.hpp" 8 | #include "../Policies/WriteLoggingPolicy.hpp" 9 | #include "StreamProxy.hpp" 10 | 11 | namespace StreamUtils { 12 | 13 | struct LoggingStream : StreamProxy { 14 | LoggingStream(Stream& target, Print& log) 15 | : StreamProxy( 16 | target, ReadLoggingPolicy{log}, WriteLoggingPolicy{log}) {} 17 | }; 18 | 19 | } // namespace StreamUtils -------------------------------------------------------------------------------- /firmware/Motherboard/lib/ArduinoStreamUtils/StreamUtils/Streams/MemoryStream.hpp: -------------------------------------------------------------------------------- 1 | // StreamUtils - github.com/bblanchon/ArduinoStreamUtils 2 | // Copyright Benoit Blanchon 2019-2024 3 | // MIT License 4 | 5 | #pragma once 6 | 7 | #include 8 | 9 | #include "../Buffers/CircularBuffer.hpp" 10 | #include "../Configuration.hpp" 11 | #include "../Ports/DefaultAllocator.hpp" 12 | 13 | namespace StreamUtils { 14 | 15 | template 16 | class BasicMemoryStream : public Stream { 17 | public: 18 | BasicMemoryStream(size_t capacity, TAllocator allocator = TAllocator()) 19 | : _buffer(capacity, allocator) {} 20 | 21 | BasicMemoryStream(const BasicMemoryStream &src) : _buffer(src._buffer) {} 22 | 23 | int available() override { 24 | return static_cast(_buffer.available()); 25 | } 26 | 27 | int peek() override { 28 | return _buffer.isEmpty() ? -1 : _buffer.peek(); 29 | } 30 | 31 | int read() override { 32 | return _buffer.isEmpty() ? -1 : _buffer.read(); 33 | } 34 | 35 | #if STREAMUTILS_STREAM_READBYTES_IS_VIRTUAL 36 | size_t readBytes(char *data, size_t size) override { 37 | return _buffer.readBytes(data, size); 38 | } 39 | #endif 40 | 41 | size_t write(uint8_t data) override { 42 | return _buffer.isFull() ? 0 : _buffer.write(data); 43 | } 44 | 45 | #if STREAMUTILS_PRINT_WRITE_VOID_UINT32 46 | size_t write(const void *p, uint32 size) override { 47 | const uint8_t *data = reinterpret_cast(p); 48 | #else 49 | size_t write(const uint8_t *data, size_t size) override { 50 | #endif 51 | return _buffer.write(data, size); 52 | } 53 | 54 | using Stream::write; 55 | 56 | void flush() override { 57 | _buffer.clear(); 58 | } 59 | 60 | private: 61 | CircularBuffer _buffer; 62 | }; 63 | 64 | using MemoryStream = BasicMemoryStream; 65 | 66 | } // namespace StreamUtils -------------------------------------------------------------------------------- /firmware/Motherboard/lib/ArduinoStreamUtils/StreamUtils/Streams/ProgmemStream.hpp: -------------------------------------------------------------------------------- 1 | // StreamUtils - github.com/bblanchon/ArduinoStreamUtils 2 | // Copyright Benoit Blanchon 2019-2024 3 | // MIT License 4 | 5 | #pragma once 6 | 7 | #include 8 | 9 | #include "../Configuration.hpp" 10 | 11 | namespace StreamUtils { 12 | 13 | class ProgmemStream : public Stream { 14 | public: 15 | ProgmemStream(const void* ptr, size_t size) 16 | : _ptr(reinterpret_cast(ptr)), _size(size) {} 17 | 18 | ProgmemStream(const char* ptr) : _ptr(ptr), _size(ptr ? strlen_P(ptr) : 0) {} 19 | 20 | ProgmemStream(const __FlashStringHelper* ptr) 21 | : ProgmemStream{reinterpret_cast(ptr)} {} 22 | 23 | int available() override { 24 | return _size; 25 | } 26 | 27 | int read() override { 28 | if (_size <= 0) 29 | return -1; 30 | _size--; 31 | return pgm_read_byte(_ptr++); 32 | } 33 | 34 | #if STREAMUTILS_STREAM_READBYTES_IS_VIRTUAL 35 | size_t readBytes(char* buffer, size_t size) override { 36 | if (size > _size) 37 | size = _size; 38 | memcpy_P(buffer, _ptr, size); 39 | _ptr += size; 40 | _size -= size; 41 | return size; 42 | } 43 | #endif 44 | 45 | int peek() override { 46 | if (_size <= 0) 47 | return -1; 48 | return pgm_read_byte(_ptr); 49 | } 50 | 51 | void flush() override {} 52 | 53 | #if STREAMUTILS_PRINT_WRITE_VOID_UINT32 54 | size_t write(const void*, uint32) override { 55 | return 0; 56 | } 57 | #else 58 | size_t write(const uint8_t*, size_t) override { 59 | return 0; 60 | } 61 | #endif 62 | 63 | size_t write(uint8_t) override { 64 | return 0; 65 | } 66 | 67 | private: 68 | const char* _ptr; 69 | size_t _size; 70 | }; 71 | 72 | } // namespace StreamUtils -------------------------------------------------------------------------------- /firmware/Motherboard/lib/ArduinoStreamUtils/StreamUtils/Streams/ReadBufferingStream.hpp: -------------------------------------------------------------------------------- 1 | // StreamUtils - github.com/bblanchon/ArduinoStreamUtils 2 | // Copyright Benoit Blanchon 2019-2024 3 | // MIT License 4 | 5 | #pragma once 6 | 7 | #include "../Policies/ReadBufferingPolicy.hpp" 8 | #include "../Policies/WriteForwardingPolicy.hpp" 9 | #include "../Ports/DefaultAllocator.hpp" 10 | #include "StreamProxy.hpp" 11 | 12 | namespace StreamUtils { 13 | 14 | template 15 | class BasicReadBufferingStream 16 | : public StreamProxy, 17 | WriteForwardingPolicy> { 18 | using base_type = 19 | StreamProxy, WriteForwardingPolicy>; 20 | 21 | public: 22 | explicit BasicReadBufferingStream(Stream &upstream, size_t capacity, 23 | TAllocator allocator = TAllocator()) 24 | : base_type(upstream, {capacity, allocator}, {}) {} 25 | }; 26 | 27 | using ReadBufferingStream = BasicReadBufferingStream; 28 | } // namespace StreamUtils -------------------------------------------------------------------------------- /firmware/Motherboard/lib/ArduinoStreamUtils/StreamUtils/Streams/ReadLoggingStream.hpp: -------------------------------------------------------------------------------- 1 | // StreamUtils - github.com/bblanchon/ArduinoStreamUtils 2 | // Copyright Benoit Blanchon 2019-2024 3 | // MIT License 4 | 5 | #pragma once 6 | 7 | #include "../Policies/ReadLoggingPolicy.hpp" 8 | #include "../Policies/WriteForwardingPolicy.hpp" 9 | #include "StreamProxy.hpp" 10 | 11 | namespace StreamUtils { 12 | 13 | struct ReadLoggingStream 14 | : StreamProxy { 15 | ReadLoggingStream(Stream &target, Print &log) 16 | : StreamProxy( 17 | target, ReadLoggingPolicy{log}, WriteForwardingPolicy{}) {} 18 | }; 19 | } // namespace StreamUtils -------------------------------------------------------------------------------- /firmware/Motherboard/lib/ArduinoStreamUtils/StreamUtils/Streams/ReadThrottlingStream.hpp: -------------------------------------------------------------------------------- 1 | // StreamUtils - github.com/bblanchon/ArduinoStreamUtils 2 | // Copyright Benoit Blanchon 2019-2024 3 | // MIT License 4 | 5 | #pragma once 6 | 7 | #include "../Policies/ReadThrottlingPolicy.hpp" 8 | #include "../Policies/WriteForwardingPolicy.hpp" 9 | #include "StreamProxy.hpp" 10 | 11 | #ifdef ARDUINO 12 | #include "../Ports/ArduinoThrottler.hpp" 13 | #endif 14 | 15 | namespace StreamUtils { 16 | 17 | template 18 | class BasicReadThrottlingStream 19 | : public StreamProxy, 20 | WriteForwardingPolicy> { 21 | public: 22 | BasicReadThrottlingStream(Stream& upstream, 23 | TThrottler throttler = TThrottler()) 24 | : StreamProxy, WriteForwardingPolicy>( 25 | upstream, ReadThrottlingPolicy(throttler), {}) {} 26 | 27 | const TThrottler& throttler() const { 28 | return this->_reader.throttler(); 29 | } 30 | }; 31 | 32 | #ifdef ARDUINO 33 | using ReadThrottlingStream = BasicReadThrottlingStream; 34 | #endif 35 | } // namespace StreamUtils -------------------------------------------------------------------------------- /firmware/Motherboard/lib/ArduinoStreamUtils/StreamUtils/Streams/SpyingStream.hpp: -------------------------------------------------------------------------------- 1 | // StreamUtils - github.com/bblanchon/ArduinoStreamUtils 2 | // Copyright Benoit Blanchon 2019-2024 3 | // MIT License 4 | 5 | #pragma once 6 | 7 | #include "../Policies/ReadSpyingPolicy.hpp" 8 | #include "../Policies/WriteSpyingPolicy.hpp" 9 | #include "StreamProxy.hpp" 10 | 11 | namespace StreamUtils { 12 | 13 | struct SpyingStream : StreamProxy { 14 | SpyingStream(Stream &target, Print &log) 15 | : StreamProxy( 16 | target, ReadSpyingPolicy{log}, WriteSpyingPolicy{log}) {} 17 | }; 18 | 19 | } // namespace StreamUtils 20 | -------------------------------------------------------------------------------- /firmware/Motherboard/lib/ArduinoStreamUtils/StreamUtils/Streams/StreamProxy.hpp: -------------------------------------------------------------------------------- 1 | // StreamUtils - github.com/bblanchon/ArduinoStreamUtils 2 | // Copyright Benoit Blanchon 2019-2024 3 | // MIT License 4 | 5 | #pragma once 6 | 7 | #include 8 | #include "../Polyfills.hpp" 9 | 10 | namespace StreamUtils { 11 | 12 | template 13 | class StreamProxy : public Stream { 14 | public: 15 | explicit StreamProxy(Stream &upstream, ReadPolicy reader = ReadPolicy{}, 16 | WritePolicy writer = WritePolicy{}) 17 | : _upstream(upstream), 18 | _reader(reader), 19 | _writer(Polyfills::move(writer)) {} 20 | 21 | StreamProxy(const StreamProxy &other) 22 | : _upstream(other._upstream), 23 | _reader(other._reader), 24 | _writer(other._writer) {} 25 | 26 | ~StreamProxy() { 27 | _writer.implicitFlush(_upstream); 28 | } 29 | 30 | #if STREAMUTILS_PRINT_WRITE_VOID_UINT32 31 | size_t write(const void *data, uint32 size) override { 32 | const uint8_t *buffer = reinterpret_cast(data); 33 | #else 34 | size_t write(const uint8_t *buffer, size_t size) override { 35 | #endif 36 | return _writer.write(_upstream, buffer, size); 37 | } 38 | 39 | size_t write(uint8_t data) override { 40 | return _writer.write(_upstream, data); 41 | } 42 | 43 | using Stream::write; 44 | 45 | int available() override { 46 | return _reader.available(_upstream); 47 | } 48 | 49 | int read() override { 50 | return _reader.read(_upstream); 51 | } 52 | 53 | int peek() override { 54 | return _reader.peek(_upstream); 55 | } 56 | 57 | void flush() override { 58 | _writer.flush(_upstream); 59 | } 60 | 61 | #if STREAMUTILS_STREAM_READBYTES_IS_VIRTUAL 62 | size_t readBytes(char *buffer, size_t size) override { 63 | return _reader.readBytes(_upstream, buffer, size); 64 | } 65 | #endif 66 | 67 | protected: 68 | Stream &_upstream; 69 | ReadPolicy _reader; 70 | WritePolicy _writer; 71 | }; 72 | 73 | } // namespace StreamUtils -------------------------------------------------------------------------------- /firmware/Motherboard/lib/ArduinoStreamUtils/StreamUtils/Streams/StringStream.hpp: -------------------------------------------------------------------------------- 1 | // StreamUtils - github.com/bblanchon/ArduinoStreamUtils 2 | // Copyright Benoit Blanchon 2019-2024 3 | // MIT License 4 | 5 | #pragma once 6 | 7 | #include 8 | #include 9 | 10 | #include "../Configuration.hpp" 11 | #include "../Polyfills.hpp" 12 | 13 | namespace StreamUtils { 14 | 15 | class StringStream : public Stream { 16 | public: 17 | StringStream() {} 18 | 19 | explicit StringStream(String str) : _str(Polyfills::move(str)) {} 20 | 21 | #if STREAMUTILS_PRINT_WRITE_VOID_UINT32 22 | size_t write(const void* data, uint32 n) override { 23 | const uint8_t* p = reinterpret_cast(data); 24 | #else 25 | size_t write(const uint8_t* p, size_t n) override { 26 | #endif 27 | for (size_t i = 0; i < n; i++) { 28 | uint8_t c = p[i]; 29 | if (c == 0) 30 | return i; 31 | write(c); 32 | } 33 | return n; 34 | } 35 | 36 | size_t write(uint8_t c) override { 37 | if (c == 0) 38 | return 0; 39 | _str += static_cast(c); 40 | return 1; 41 | } 42 | 43 | const String& str() const { 44 | return _str; 45 | } 46 | 47 | void str(String str) { 48 | _str = Polyfills::move(str); 49 | } 50 | 51 | int available() override { 52 | return static_cast(_str.length()); 53 | } 54 | 55 | int read() override { 56 | if (_str.length() == 0) 57 | return -1; 58 | char c = _str[0]; 59 | _str.remove(0, 1); 60 | return c; 61 | } 62 | 63 | #if STREAMUTILS_STREAM_READBYTES_IS_VIRTUAL 64 | size_t readBytes(char* buffer, size_t length) override { 65 | if (length > _str.length()) 66 | length = _str.length(); 67 | // Don't use _str.ToCharArray() because it inserts a terminator 68 | memcpy(buffer, _str.c_str(), length); 69 | _str.remove(0, static_cast(length)); 70 | return length; 71 | } 72 | #endif 73 | 74 | int peek() override { 75 | return _str.length() > 0 ? _str[0] : -1; 76 | } 77 | 78 | void flush() override {} 79 | 80 | private: 81 | String _str; 82 | }; 83 | } // namespace StreamUtils -------------------------------------------------------------------------------- /firmware/Motherboard/lib/ArduinoStreamUtils/StreamUtils/Streams/WriteBufferingStream.hpp: -------------------------------------------------------------------------------- 1 | // StreamUtils - github.com/bblanchon/ArduinoStreamUtils 2 | // Copyright Benoit Blanchon 2019-2024 3 | // MIT License 4 | 5 | #pragma once 6 | 7 | #include "../Policies/ReadForwardingPolicy.hpp" 8 | #include "../Policies/WriteBufferingPolicy.hpp" 9 | #include "../Ports/DefaultAllocator.hpp" 10 | #include "StreamProxy.hpp" 11 | 12 | namespace StreamUtils { 13 | 14 | template 15 | struct BasicWriteBufferingStream 16 | : StreamProxy> { 17 | explicit BasicWriteBufferingStream(Stream &upstream, size_t capacity, 18 | TAllocator allocator = TAllocator()) 19 | : StreamProxy>( 20 | upstream, {}, {capacity, allocator}) {} 21 | }; 22 | 23 | using WriteBufferingStream = BasicWriteBufferingStream; 24 | } // namespace StreamUtils -------------------------------------------------------------------------------- /firmware/Motherboard/lib/ArduinoStreamUtils/StreamUtils/Streams/WriteLoggingStream.hpp: -------------------------------------------------------------------------------- 1 | // StreamUtils - github.com/bblanchon/ArduinoStreamUtils 2 | // Copyright Benoit Blanchon 2019-2024 3 | // MIT License 4 | 5 | #pragma once 6 | 7 | #include "../Policies/ReadForwardingPolicy.hpp" 8 | #include "../Policies/WriteLoggingPolicy.hpp" 9 | #include "StreamProxy.hpp" 10 | 11 | namespace StreamUtils { 12 | 13 | struct WriteLoggingStream 14 | : StreamProxy { 15 | WriteLoggingStream(Stream &target, Print &log) 16 | : StreamProxy( 17 | target, ReadForwardingPolicy{}, WriteLoggingPolicy{log}) {} 18 | }; 19 | 20 | } // namespace StreamUtils -------------------------------------------------------------------------------- /firmware/Motherboard/lib/ArduinoStreamUtils/StreamUtils/Streams/WriteWaitingStream.hpp: -------------------------------------------------------------------------------- 1 | // StreamUtils - github.com/bblanchon/ArduinoStreamUtils 2 | // Copyright Benoit Blanchon 2019-2024 3 | // MIT License 4 | 5 | #pragma once 6 | 7 | #include "../Policies/ReadForwardingPolicy.hpp" 8 | #include "../Policies/WriteWaitingPolicy.hpp" 9 | #include "StreamProxy.hpp" 10 | 11 | namespace StreamUtils { 12 | 13 | struct WriteWaitingStream 14 | : StreamProxy { 15 | WriteWaitingStream(Stream &target, Polyfills::function wait = yield) 16 | : StreamProxy( 17 | target, ReadForwardingPolicy{}, 18 | WriteWaitingPolicy{Polyfills::move(wait)}) {} 19 | 20 | void setTimeout(unsigned long timeout) { 21 | Stream::setTimeout(timeout); 22 | _writer.setTimeout(timeout); 23 | } 24 | }; 25 | 26 | } // namespace StreamUtils -------------------------------------------------------------------------------- /firmware/README.md: -------------------------------------------------------------------------------- 1 | # Firmware 2 | 3 | ## Uploading to Teensy 4 | 5 | In order to run any sketch on the Teensy you have to install the Arduino IDE. Follow the instructions from the official page, section "Arduino 2.0.x Software Development": https://www.pjrc.com/teensy/td_download.html 6 | 7 | 1. Then open Motherboard.ino located in the firmaware/example folder of this repo. 8 | 2. In the Tools -> USB Type menu, choose Serial. 9 | 3. **Make sure the Motherboard is not powered with external power when pluggin Teensy to a computer!** 10 | 3. Plug the Teensy to your computer with a micro USB cable. 11 | 4. Then just click the arrow button to upload the code 12 | 13 | ## How to 14 | 15 | TODO -------------------------------------------------------------------------------- /firmware/examples/Calibration/Calibration.ino: -------------------------------------------------------------------------------- 1 | /* 2 | Calibration helper. 3 | 4 | On the Motherboard: 5 | - connect a wire from 5v to IN 1 6 | - connect a wire from 5v to IN 9 7 | 8 | The inputs are patched to a sine oscillator each, outputting the signals 9 | to the left and right channels. By sending 5v to the inputs the oscillator should 10 | output a tone of 880Hz. 11 | 12 | Turn the trim potentiometers to find where the tones get to their maximum. 13 | 14 | 15 | Support my work: 16 | https://patreon.com/ghostintranslation 17 | https://www.buymeacoffee.com/ghostintranslation 18 | 19 | Music and Merch: 20 | https://ghostintranslation.bandcamp.com/ 21 | 22 | Social: 23 | https://www.instagram.com/ghostintranslation/ 24 | https://www.youtube.com/c/ghostintranslation 25 | 26 | Code: 27 | https://github.com/ghostintranslation 28 | 29 | My website: 30 | https://www.ghostintranslation.com/ 31 | */ 32 | 33 | #include "Audio.h" 34 | #include "Motherboard/Motherboard.h" 35 | 36 | // Inputs of the first ADC 37 | // As of now the second ADC kicks in only if there is more than 8 inputs so we have to instantiate all of them 38 | Input *input1; 39 | Input *input2; 40 | Input *input3; 41 | Input *input4; 42 | Input *input5; 43 | Input *input6; 44 | Input *input7; 45 | Input *input8; 46 | 47 | // Input of the second ADC 48 | Input *input9; 49 | 50 | // Sines to connect to the inputs 51 | AudioSynthWaveformModulated *sine1; 52 | AudioSynthWaveformModulated *sine2; 53 | 54 | // The AudioOutputI2S is required for the audio library's clock to tick 55 | AudioOutputI2S i2s; 56 | 57 | void setup() 58 | { 59 | // Audio connections require memory to work. 60 | AudioMemory(40); 61 | 62 | // Inputs of the first ADC 63 | input1 = new Input(0); 64 | input2 = new Input(1); 65 | input3 = new Input(2); 66 | input4 = new Input(3); 67 | input5 = new Input(4); 68 | input6 = new Input(5); 69 | input7 = new Input(6); 70 | input8 = new Input(7); 71 | // Input of the second ADC 72 | input9 = new Input(8); 73 | 74 | sine1 = new AudioSynthWaveformModulated(); 75 | sine1->begin(WAVEFORM_SINE); 76 | sine1->frequencyModulation(5); 77 | sine1->frequency(27.5); 78 | sine1->amplitude(0.005); 79 | 80 | sine2 = new AudioSynthWaveformModulated(); 81 | sine2->begin(WAVEFORM_SINE); 82 | sine2->frequencyModulation(5); 83 | sine2->frequency(27.5); 84 | sine2->amplitude(0.005); 85 | 86 | new AudioConnection(*input1, 0, *sine1, 0); 87 | new AudioConnection(*input9, 0, *sine2, 0); 88 | new AudioConnection(*sine1, 0, i2s, 0); 89 | new AudioConnection(*sine2, 0, i2s, 1); 90 | } 91 | 92 | void loop() 93 | { 94 | } -------------------------------------------------------------------------------- /firmware/examples/Calibration/Motherboard: -------------------------------------------------------------------------------- 1 | ../../Motherboard -------------------------------------------------------------------------------- /firmware/examples/Rotary/Motherboard: -------------------------------------------------------------------------------- 1 | ../../Motherboard -------------------------------------------------------------------------------- /firmware/examples/Rotary/Rotary.ino: -------------------------------------------------------------------------------- 1 | /* 2 | Simple example with one rotary encoder controlling an LED and an oscillator. 3 | 4 | Support my work: 5 | https://patreon.com/ghostintranslation 6 | https://www.buymeacoffee.com/ghostintranslation 7 | 8 | Music and Merch: 9 | https://ghostintranslation.bandcamp.com/ 10 | 11 | Social: 12 | https://www.instagram.com/ghostintranslation/ 13 | https://www.youtube.com/c/ghostintranslation 14 | 15 | Code: 16 | https://github.com/ghostintranslation 17 | 18 | My website: 19 | https://www.ghostintranslation.com/ 20 | */ 21 | 22 | #include "Audio.h" 23 | #include "Motherboard/Motherboard.h" 24 | 25 | // Input of type Rotary 26 | InputRotary *input1; 27 | // Led 28 | OutputLed *led1; 29 | // A sine oscillator 30 | AudioSynthWaveformDc *dc1; 31 | // 32 | AudioSynthWaveformModulated *sine1; 33 | // The AudioOutputI2S is required for the audio library's clock to tick 34 | AudioOutputI2S i2s; 35 | 36 | void setup() { 37 | Serial.begin(115200); 38 | 39 | while (!Serial && millis() < 2500) 40 | ; // wait for serial monitor 41 | 42 | // Audio connections require memory to work. 43 | AudioMemory(40); 44 | 45 | // Input on index 0 46 | input1 = new InputRotary(0); 47 | input1->onChange(changeCallback); 48 | input1->onPush(pushCallback); 49 | input1->onRelease(releaseCallback); 50 | 51 | // LED on index 0 52 | led1 = new OutputLed(0); 53 | 54 | // Setting up the DC object 55 | dc1 = new AudioSynthWaveformDc(); 56 | dc1->amplitude(-1.0); 57 | 58 | // Setting up the sine1 oscillator 59 | sine1 = new AudioSynthWaveformModulated(); 60 | sine1->begin(WAVEFORM_SINE); 61 | sine1->frequency(20); 62 | sine1->amplitude(1); 63 | 64 | // Connecting dc1's output to the led1's input 65 | new AudioConnection(*dc1, 0, *led1, 0); 66 | // Connecting sine1's output to the audio DAC's input 67 | new AudioConnection(*sine1, 0, i2s, 0); 68 | } 69 | 70 | void loop() { 71 | } 72 | 73 | /** 74 | * Gets called when turning the rotary. 75 | * Sets a DC object's value which is connected to the LED, so it changes the LED's brightness, 76 | * and sets the sine's frequency, which can be heard on the audio output. 77 | */ 78 | void changeCallback(uint16_t value) { 79 | float dcAmplitude = map((float)constrain(value, 0, 100), 0, 100, -1.0, 1.0); 80 | dc1->amplitude(dcAmplitude); 81 | uint16_t sineFrequency = map((float)constrain(value, 0, 100), 0, 100, 20, 2000); 82 | sine1->frequency(sineFrequency); 83 | } 84 | 85 | /** 86 | * Gets called when pushing on the rotary's button. 87 | * Sets the LED to blink. 88 | */ 89 | void pushCallback() { 90 | led1->setStatus(OutputLed::Status::Blink); 91 | } 92 | 93 | /** 94 | * Gets called when releasing the rotary's button 95 | * Sets the LED back to default, meaning it uses the value passed to its input. 96 | */ 97 | void releaseCallback() { 98 | led1->setStatus(OutputLed::Status::Default); 99 | } -------------------------------------------------------------------------------- /firmware/examples/Settings/Motherboard: -------------------------------------------------------------------------------- 1 | ../../Motherboard -------------------------------------------------------------------------------- /firmware/examples/Settings/Settings.ino: -------------------------------------------------------------------------------- 1 | /* 2 | Simple example with configurable MIDI input that controls an led. 3 | 4 | MIDI CC 1 is the default. It can be changed using the web interface. 5 | 6 | Support my work: 7 | https://patreon.com/ghostintranslation 8 | https://www.buymeacoffee.com/ghostintranslation 9 | 10 | Music and Merch: 11 | https://ghostintranslation.bandcamp.com/ 12 | 13 | Social: 14 | https://www.instagram.com/ghostintranslation/ 15 | https://www.youtube.com/c/ghostintranslation 16 | 17 | Code: 18 | https://github.com/ghostintranslation 19 | 20 | My website: 21 | https://www.ghostintranslation.com/ 22 | */ 23 | 24 | #include "Audio.h" 25 | #include "Motherboard/Motherboard.h" 26 | 27 | // Leds on indexes 0 and 1 28 | OutputLed *led1; 29 | OutputLed *led2; 30 | // Potentiometer input on index 0 31 | Input *input1; 32 | // Inputs on indexes 0 and 1 33 | MidiCCInput *midiInput1; 34 | MidiCCInput *midiInput2; 35 | // Setting for the MIDI CC value 36 | Setting *midiCCSetting1; 37 | Setting *midiCCSetting2; 38 | // The AudioOutputI2S is required for the audio library's clock to tick 39 | AudioOutputI2S i2s1; 40 | 41 | void setup() { 42 | Serial.begin(115200); 43 | 44 | while (!Serial && millis() < 2500) 45 | ; // wait for serial monitor 46 | 47 | // Audio connections require memory to work. 48 | AudioMemory(40); 49 | 50 | midiCCSetting1 = new Setting("midi-cc-led-1", "MIDI CC led 1", 1, 1, 127, 1); 51 | midiCCSetting2 = new Setting("midi-cc-led-2", "MIDI CC led 2", 2, 1, 127, 1); 52 | midiInput1 = new MidiCCInput(midiCCSetting1); 53 | midiInput2 = new MidiCCInput(midiCCSetting2); 54 | led1 = new OutputLed(0); 55 | led2 = new OutputLed(1); 56 | input1 = new Input(0); 57 | input1->setMidiInput(midiInput1); 58 | 59 | // Connecting input1's output to the frequency input of sine1 60 | new AudioConnection(*input1, 0, *led1, 0); 61 | new AudioConnection(*midiInput2, 0, *led2, 0); 62 | } 63 | 64 | void loop() { 65 | } 66 | -------------------------------------------------------------------------------- /firmware/examples/SimpleLeds/Motherboard: -------------------------------------------------------------------------------- 1 | ../../Motherboard -------------------------------------------------------------------------------- /firmware/examples/SimpleLeds/SimpleLeds.ino: -------------------------------------------------------------------------------- 1 | /* 2 | Simple example with inputs controlling leds. 3 | 4 | One input controls an oscilator which in turns control an LED, and another input controls and LED directly. 5 | 6 | Additionally there is 2 MIDI CC outputs, CC 1 being the sine and CC 2 being the second input. 7 | 8 | Support my work: 9 | https://patreon.com/ghostintranslation 10 | https://www.buymeacoffee.com/ghostintranslation 11 | 12 | Music and Merch: 13 | https://ghostintranslation.bandcamp.com/ 14 | 15 | Social: 16 | https://www.instagram.com/ghostintranslation/ 17 | https://www.youtube.com/c/ghostintranslation 18 | 19 | Code: 20 | https://github.com/ghostintranslation 21 | 22 | My website: 23 | https://www.ghostintranslation.com/ 24 | */ 25 | 26 | #include "Audio.h" 27 | #include "Motherboard/Motherboard.h" 28 | 29 | // Leds on indexes 0 and 1 30 | OutputLed *led1; 31 | OutputLed *led2; 32 | // Inputs on indexes 0 and 1 33 | Input *input1; 34 | Input *input2; 35 | // A sine oscillator 36 | AudioSynthWaveformModulated *sine1; 37 | // Midi inputs 38 | MidiCCInput *midiInput1; 39 | MidiNoteInput *midiInput2; 40 | // Midi CC outputs 41 | MidiCCOutput *midiOutput1; 42 | MidiCCOutput *midiOutput2; 43 | 44 | // The AudioOutputI2S is required for the audio library's clock to tick 45 | AudioOutputI2S i2s1; 46 | 47 | void setup() 48 | { 49 | Serial.begin(115200); 50 | 51 | while (!Serial && millis() < 2500) 52 | ; // wait for serial monitor 53 | 54 | // Audio connections require memory to work. 55 | AudioMemory(40); 56 | 57 | midiInput1 = new MidiCCInput(1); //C1 58 | midiInput2 = new MidiNoteInput(36); //C1 59 | input1 = new Input(0); 60 | input1->setMidiInput(midiInput1); 61 | input2 = new Input(1); 62 | input2->setMidiInput(midiInput2); 63 | led1 = new OutputLed(0); 64 | led2 = new OutputLed(1); 65 | // Setting up the sine1 oscillator 66 | sine1 = new AudioSynthWaveformModulated(); 67 | sine1->begin(WAVEFORM_SINE); 68 | sine1->frequency(0.5); 69 | sine1->amplitude(1); 70 | 71 | // MIDI CC outputs 72 | midiOutput1 = new MidiCCOutput(1); 73 | midiOutput2 = new MidiCCOutput(2); 74 | 75 | // Connecting input1's output to the frequency input of sine1 76 | new AudioConnection(*input1, 0, *sine1, 0); 77 | // Connecting sine1's output to led1's input 78 | new AudioConnection(*sine1, 0, *led1, 0); 79 | // Connecting input2's output to led2's input 80 | new AudioConnection(*input2, 0, *led2, 0); 81 | // Connection the sine and input2 to the MIDI CC outputs 82 | new AudioConnection(*sine1, 0, *midiOutput1, 0); 83 | new AudioConnection(*input2, 0, *midiOutput2, 0); 84 | } 85 | 86 | void loop() 87 | { 88 | } 89 | -------------------------------------------------------------------------------- /hardware/Expander 1.0.3.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ghostintranslation/motherboard/62c6a7bed69b406e501717e7ec49dee12f80623e/hardware/Expander 1.0.3.pdf -------------------------------------------------------------------------------- /hardware/Expander 1.0.4.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ghostintranslation/motherboard/62c6a7bed69b406e501717e7ec49dee12f80623e/hardware/Expander 1.0.4.pdf -------------------------------------------------------------------------------- /hardware/Mouser parts.xls: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ghostintranslation/motherboard/62c6a7bed69b406e501717e7ec49dee12f80623e/hardware/Mouser parts.xls -------------------------------------------------------------------------------- /hardware/README.md: -------------------------------------------------------------------------------- 1 | # Hardware 2 | 3 | The PCB is made so that it is possible to use either through hole or SMD for most components. 4 | 5 | ## Bill Of Materials 6 | 7 | ### Motherboard 8 | 9 | Note: A Mouser list of parts file that can be imported on the website is available in this folder. 10 | 11 | | Qty | Description | Ref | SMD package | TH available | 12 | |-----|----------------------------|--------------------|-------------------|--------------| 13 | | 4 | 10k resistor | R1, R3, R10, R13 | 0805 | Yes | 14 | | 2 | 2.7k resistor | R2, R11 | 0805 | Yes | 15 | | 3 | 2k resistor | R6, R7, R8 | 0805 | Yes | 16 | | 2 | 1k resistor | R4, R12 | 0805 | Yes | 17 | | 1 | ~157 resistor | R5 | 0805 | Yes | 18 | | 2 | 10k trim potentiometer | R9, R14 | 0805 | Yes | 19 | | 4 | BAT43 diode | D1, D2, D3, D4 | SOD-323 / SOD-123 | Yes | 20 | | 1 | 4148 diode | D5 | SOD-323 / SOD-123 | Yes | 21 | | 1 | 10uf capacitor | C1 | 0805 | Yes | 22 | | 1 | 22uf capacitor | C2 | 0805 | Yes | 23 | | 2 | SN74HC4851 multiplexer | IC1, IC3 | 16-TSSOP | Yes | 24 | | 2 | SN74HC595 shift register | IC2, IC4 | 16-TSSOP | Yes | 25 | | 2 | MCP6292 / MCP6022 op-amp | IC5, IC6 | 8-SOIC | Yes | 26 | | 1 | 6N137S optocoupler | OPTOCOUPLER | 8-SOIC | No | 27 | | 1 | MB1S bridge rectifier | B1 | 4-SOIC | No | 28 | | 1 | VX7805-500 DC-DC converter | CONVERTER | - | Yes | 29 | | 2 | 24 pins female header | JP4, JP5 | - | Yes | 30 | | 2 | 14 pins female header | TEENSY | - | Yes | 31 | | 2 | 8 pins female header | JP2 | - | Yes | 32 | | 2 | 4 pins female header | JP1 | - | Yes | 33 | | 2 | 5 pins male header | POWER | - | Yes | 34 | | 4 | 16 pins IC socket (TH only)| IC1, IC2, IC3, IC4 | - | - | 35 | | 2 | 8 pins IC socket (TH only) | IC5, IC6 | - | - | 36 | 37 | ### Audio-MIDI Expander 38 | 39 | | Qty | Description | Ref | SMD package | TH available | 40 | |-----|------------------------------|---------------------------|-------------|--------------| 41 | | 3 | Stereo Thonkicon jack socket | Stereo, MIDI_OUT, MIDI_IN | - | Yes | 42 | | 2 | Mono Thonkicon jack socket | L, R | - | Yes | 43 | | 2 | 8 pins male header | - | - | Yes | 44 | | 1 | 4 pins male header | - | - | Yes | 45 | 46 | 47 | ### Notes 48 | 49 | Some parts could be replaced by other alternatives, consider the following: 50 | 51 | - MCP6292/MCP6022 52 | - high slew rate, 53 | - single supply 0-5v 54 | - high bandwidth 55 | - Rail-to-Rail 56 | 57 | - SN74HC4851 58 | - included inputs protection from voltages outside the supply range. 59 | 60 | - BAT43 61 | - I only tried 2N4007 – *Be careful they protect the Teensy from frying in case of voltages outside 0-3.3v ± the forward voltage, which should not exceed 0.7v* 62 | 63 | - VX7805-500 is obsolete and can be replaced by Recom Power R-78K5.0-0.5 or R-78E5.0-0.5 or similar. 64 | 65 | - If you don't wish to use MIDI you can omit: 66 | - the optocoupler and its resistors and diode 67 | 68 | - Unless using I2C, which is not the case with barebone Motherboard and its audio expander, you can omit: 69 | - the 2 pullup resistors R11 and R12 70 | 71 | - IC sockets could be replaced by round female headers 72 | 73 | ## Schematics 74 | 75 | An export of the schematics out of Eagle is available in this repo. 76 | 77 | ### Notes 78 | 79 | The op-amps, multiplexers, and shift registers are displayed twice in the schematics and BOM file as if there were 2 sets of each but that is because one set is for through hole and one set is for SMD to have the choice. There is only 2 op-amps, 2 multiplexers and 2 shift registers. 80 | 81 | ## Audio-Midi expander 82 | 83 | The Motherboard itself does not include any audio DAC, this is because of limited space and to reduce cost for modules that would not require audio. However an expander connector is there to provide the necessary for adding such feature. 84 | 85 | I designed an audio-midi expander board that direcly attached on the back of the Motherboard. It provides 2 mono audio jacks, 1 stereo audio jack, and 2 MIDI TRS (Type A) jacks in the back to allow reducing clutter on the front, but the audio and MIDI signals are also available on the main headers to be passed to the front panels. 86 | 87 | ## Known issues 88 | 89 | In some earlier versions of the audio-midi expander, the audio outputs are quite noisy. To reduce noise I added a through hole 0.33uF capacitor myself. This is fixed in later batches. -------------------------------------------------------------------------------- /hardware/Schematics.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ghostintranslation/motherboard/62c6a7bed69b406e501717e7ec49dee12f80623e/hardware/Schematics.pdf -------------------------------------------------------------------------------- /hardware/motherboard-1.WEBP: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ghostintranslation/motherboard/62c6a7bed69b406e501717e7ec49dee12f80623e/hardware/motherboard-1.WEBP -------------------------------------------------------------------------------- /hardware/motherboard-2.WEBP: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ghostintranslation/motherboard/62c6a7bed69b406e501717e7ec49dee12f80623e/hardware/motherboard-2.WEBP --------------------------------------------------------------------------------