├── src ├── eurorack_hc595.h ├── eurorack_mcp23s17.h ├── eeprom │ ├── Config.cpp │ └── Config.h ├── eurorack_dac8164.h ├── eurorack_max11300.h ├── debug │ ├── Profiler.cpp │ └── Profiler.h ├── eurorack_is32fl3738.h ├── eurorack_sd.h ├── test_graphics.cpp ├── filesystem │ ├── reader │ │ ├── FileReader.h │ │ ├── BufferFileReader.h │ │ └── BufferFileReader.cpp │ ├── FileInfo.h │ ├── FileSystem.h │ └── FileSystem.cpp ├── dsp │ ├── base │ │ ├── Function.h │ │ └── WaveShape.h │ ├── oscillator │ │ ├── BaseOscillator.h │ │ ├── PhaseDistortionOscillator.h │ │ └── WaveOscillator.h │ ├── waveshapes │ │ ├── Sine.h │ │ ├── Saw.h │ │ ├── interpolation │ │ │ ├── WaveInterpolator2D.h │ │ │ ├── WaveInterpolator3D.h │ │ │ └── WaveInterpolator.h │ │ ├── AsymmetricalTriangle.h │ │ ├── Triangle.h │ │ ├── Pulse.h │ │ ├── wavetable │ │ │ ├── WaveTableFactory.h │ │ │ ├── RollOffFunction.h │ │ │ ├── WaveTable.h │ │ │ └── WaveTableFactory.cpp │ │ ├── WaveSelector.h │ │ ├── Line.h │ │ └── WaveSequence.h │ ├── sample │ │ ├── DelayLine.cpp │ │ ├── DelayLine.h │ │ ├── SamplePlayer.cpp │ │ ├── SampleBuffer.cpp │ │ ├── SamplePlayer.h │ │ └── SampleBuffer.h │ ├── clock │ │ ├── Clock.cpp │ │ ├── Clock.h │ │ ├── ClockGate.h │ │ ├── InternalExternalClock.h │ │ ├── ClockDivider.h │ │ └── InternalExternalClock.cpp │ ├── distortionfunctions │ │ ├── TwoLineFunction.h │ │ ├── PowerFunction.h │ │ └── SmoothStepFunction.h │ ├── filter │ │ ├── AnalogInputFilter.h │ │ ├── StateVariableFilter.cpp │ │ └── StateVariableFilter.h │ └── envelope │ │ ├── Envelope.h │ │ └── EnvelopePlayer.h ├── util │ ├── util.h │ ├── pitchutil.h │ ├── Timer.h │ ├── mathutil.h │ ├── SlewLimiter.h │ ├── Timer.cpp │ ├── Debounce.h │ ├── CycleSelectEnum.h │ ├── CycleEnum.h │ ├── stringutil.h │ ├── RangeScale.h │ ├── Array.h │ ├── TypeSelector.h │ └── Debounce.cpp ├── eurorack_graphics.h ├── controller │ ├── AbstractController.h │ ├── AbstractParameterizedController.h │ ├── SingleEncoderController.h │ └── DoubleEncoderController.h ├── io │ ├── analoginputs │ │ ├── PowInput.h │ │ ├── ExpInput.h │ │ ├── CrossfadeInput.h │ │ ├── LinearInput.h │ │ ├── IntegerInput.h │ │ ├── AnalogGateInput.h │ │ ├── FilterInput.h │ │ └── AbstractAnalogInput.h │ ├── analogoutputs │ │ ├── AnalogGateOutput.h │ │ └── AnalogTriggerOutput.h │ ├── digitalinputs │ │ ├── GateInput.h │ │ ├── PushButton.h │ │ └── TriggerInput.h │ └── digitaloutputs │ │ └── TriggerOutput.h ├── graphics │ ├── manager │ │ └── FocusManager.h │ ├── containers │ │ ├── VerticalContainer.h │ │ ├── HorizontalContainer.h │ │ └── Container.h │ ├── GraphicsContext.h │ ├── components │ │ ├── TextComponent.h │ │ ├── MessageBoxComponent.h │ │ └── FieldComponent.h │ └── Component.h ├── hardware │ ├── device │ │ ├── AnalogInputSumPin.h │ │ ├── Device.h │ │ └── AnalogPinCalibration.h │ ├── hc595 │ │ ├── HC595Device.h │ │ └── HC595Device.cpp │ ├── RotaryEncoderButton.h │ ├── RotaryEncoderButton.cpp │ ├── RotaryEncoder.h │ ├── dac8164 │ │ ├── DAC8164Device.h │ │ ├── DAC8164.h │ │ └── DAC8164.cpp │ ├── native │ │ ├── NativeDevice.cpp │ │ └── NativeDevice.h │ ├── max11300 │ │ ├── MAX11300Device.cpp │ │ └── MAX11300Device.h │ ├── is32fl3738 │ │ ├── IS32FL3738Device.h │ │ └── IS32FL3738Device.cpp │ ├── RotaryEncoder.cpp │ └── mcp23s17 │ │ └── MCP23S17Device.h ├── eurorack_dsp.h ├── test_dsp.cpp ├── memory │ └── MemPool.h ├── eurorack.h └── test_io.cpp ├── test ├── README ├── dsp │ └── test_clock │ │ ├── ClockTest.h │ │ ├── InternalExternalClockTest.h │ │ └── ClockTest.cpp └── test.cpp ├── docs ├── images │ ├── ref--5v.png │ ├── input_analogue.png │ ├── input_digital.png │ ├── waveshape_line.drawio.png │ ├── waveshape_pulse.drawio.png │ ├── waveshape_saw.drawio.png │ ├── waveshape_sine.drawio.png │ ├── device_input_output.drawio.png │ ├── waveshape_triangle.drawio.png │ ├── device_initialisation.drawio.png │ ├── waveshape_interpolator.drawio.png │ ├── hardware_teensy_motherboard_4.0.png │ ├── hardware_teensy_motherboard_4.1.png │ ├── hardware_teensy_motherboard_LC.png │ ├── waveshape_interpolator2d.drawio.png │ ├── waveshape_wave_sequence.drawio.png │ ├── hardware_teensy_motherboard_8cv_mki.png │ ├── waveshape_asymmetrical_triangle.drawio.png │ └── hardware_teensy_motherboard_8cv_mki.drawio.png ├── _config.yml ├── device.md ├── util.md ├── usage.md ├── index.md └── hardware.md ├── .gitmodules ├── README.md ├── .gitignore ├── library.json ├── platformio.ini ├── lib └── README ├── LICENSE └── include └── README /src/eurorack_hc595.h: -------------------------------------------------------------------------------- 1 | 2 | #include "hardware/hc595/HC595Device.h" 3 | -------------------------------------------------------------------------------- /src/eurorack_mcp23s17.h: -------------------------------------------------------------------------------- 1 | #include "hardware/mcp23s17/MCP23S17Device.h" 2 | -------------------------------------------------------------------------------- /src/eeprom/Config.cpp: -------------------------------------------------------------------------------- 1 | #include "Config.h" 2 | 3 | Config Config::config; 4 | -------------------------------------------------------------------------------- /src/eurorack_dac8164.h: -------------------------------------------------------------------------------- 1 | 2 | #include "hardware/dac8164/DAC8164Device.h" 3 | -------------------------------------------------------------------------------- /src/eurorack_max11300.h: -------------------------------------------------------------------------------- 1 | 2 | #include "hardware/max11300/MAX11300Device.h" 3 | -------------------------------------------------------------------------------- /src/debug/Profiler.cpp: -------------------------------------------------------------------------------- 1 | #include "Profiler.h" 2 | 3 | Profiler Profiler::profiler; 4 | -------------------------------------------------------------------------------- /src/eurorack_is32fl3738.h: -------------------------------------------------------------------------------- 1 | 2 | #include "hardware/is32fl3738/IS32FL3738Device.h" 3 | -------------------------------------------------------------------------------- /test/README: -------------------------------------------------------------------------------- 1 | 2 | Run native unit tests with: 3 | 4 | pio test -e arduino_eurorack_test -------------------------------------------------------------------------------- /src/eurorack_sd.h: -------------------------------------------------------------------------------- 1 | #include "filesystem/FileSystem.h" 2 | #include "filesystem/reader/BufferFileReader.h" -------------------------------------------------------------------------------- /test/dsp/test_clock/ClockTest.h: -------------------------------------------------------------------------------- 1 | #include "../src/dsp/clock/Clock.h" 2 | 3 | void test_Clock(); 4 | -------------------------------------------------------------------------------- /docs/images/ref--5v.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pigatron-industries/arduino_eurorack/HEAD/docs/images/ref--5v.png -------------------------------------------------------------------------------- /docs/images/input_analogue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pigatron-industries/arduino_eurorack/HEAD/docs/images/input_analogue.png -------------------------------------------------------------------------------- /docs/images/input_digital.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pigatron-industries/arduino_eurorack/HEAD/docs/images/input_digital.png -------------------------------------------------------------------------------- /docs/images/waveshape_line.drawio.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pigatron-industries/arduino_eurorack/HEAD/docs/images/waveshape_line.drawio.png -------------------------------------------------------------------------------- /docs/images/waveshape_pulse.drawio.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pigatron-industries/arduino_eurorack/HEAD/docs/images/waveshape_pulse.drawio.png -------------------------------------------------------------------------------- /docs/images/waveshape_saw.drawio.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pigatron-industries/arduino_eurorack/HEAD/docs/images/waveshape_saw.drawio.png -------------------------------------------------------------------------------- /docs/images/waveshape_sine.drawio.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pigatron-industries/arduino_eurorack/HEAD/docs/images/waveshape_sine.drawio.png -------------------------------------------------------------------------------- /test/dsp/test_clock/InternalExternalClockTest.h: -------------------------------------------------------------------------------- 1 | #include "../src/dsp/clock/InternalExternalClock.h" 2 | 3 | void test_InternalExternalClock(); 4 | -------------------------------------------------------------------------------- /docs/images/device_input_output.drawio.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pigatron-industries/arduino_eurorack/HEAD/docs/images/device_input_output.drawio.png -------------------------------------------------------------------------------- /docs/images/waveshape_triangle.drawio.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pigatron-industries/arduino_eurorack/HEAD/docs/images/waveshape_triangle.drawio.png -------------------------------------------------------------------------------- /src/test_graphics.cpp: -------------------------------------------------------------------------------- 1 | #ifdef TEST_COMPILE 2 | #include "eurorack_graphics.h" 3 | 4 | void testGraphics() { 5 | 6 | } 7 | 8 | #endif 9 | -------------------------------------------------------------------------------- /docs/images/device_initialisation.drawio.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pigatron-industries/arduino_eurorack/HEAD/docs/images/device_initialisation.drawio.png -------------------------------------------------------------------------------- /docs/images/waveshape_interpolator.drawio.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pigatron-industries/arduino_eurorack/HEAD/docs/images/waveshape_interpolator.drawio.png -------------------------------------------------------------------------------- /docs/images/hardware_teensy_motherboard_4.0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pigatron-industries/arduino_eurorack/HEAD/docs/images/hardware_teensy_motherboard_4.0.png -------------------------------------------------------------------------------- /docs/images/hardware_teensy_motherboard_4.1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pigatron-industries/arduino_eurorack/HEAD/docs/images/hardware_teensy_motherboard_4.1.png -------------------------------------------------------------------------------- /docs/images/hardware_teensy_motherboard_LC.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pigatron-industries/arduino_eurorack/HEAD/docs/images/hardware_teensy_motherboard_LC.png -------------------------------------------------------------------------------- /docs/images/waveshape_interpolator2d.drawio.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pigatron-industries/arduino_eurorack/HEAD/docs/images/waveshape_interpolator2d.drawio.png -------------------------------------------------------------------------------- /docs/images/waveshape_wave_sequence.drawio.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pigatron-industries/arduino_eurorack/HEAD/docs/images/waveshape_wave_sequence.drawio.png -------------------------------------------------------------------------------- /docs/images/hardware_teensy_motherboard_8cv_mki.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pigatron-industries/arduino_eurorack/HEAD/docs/images/hardware_teensy_motherboard_8cv_mki.png -------------------------------------------------------------------------------- /docs/images/waveshape_asymmetrical_triangle.drawio.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pigatron-industries/arduino_eurorack/HEAD/docs/images/waveshape_asymmetrical_triangle.drawio.png -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "docs/pigatron-industries.common"] 2 | path = docs/pigatron-industries.common 3 | url = https://github.com/pigatron-industries/pigatron-industries.common.git 4 | -------------------------------------------------------------------------------- /docs/images/hardware_teensy_motherboard_8cv_mki.drawio.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pigatron-industries/arduino_eurorack/HEAD/docs/images/hardware_teensy_motherboard_8cv_mki.drawio.png -------------------------------------------------------------------------------- /src/filesystem/reader/FileReader.h: -------------------------------------------------------------------------------- 1 | #ifndef FileReader_h 2 | #define FileReader_h 3 | 4 | #include // teensy only 5 | #include 6 | 7 | class FileReader { 8 | public: 9 | virtual bool read(FsFile& file) = 0; 10 | }; 11 | 12 | #endif -------------------------------------------------------------------------------- /src/dsp/base/Function.h: -------------------------------------------------------------------------------- 1 | #ifndef Function_h 2 | #define Function_h 3 | 4 | namespace eurorack { 5 | 6 | class Function { 7 | public: 8 | Function() {} 9 | virtual float get(float phase) = 0; 10 | }; 11 | 12 | } 13 | 14 | #endif -------------------------------------------------------------------------------- /test/test.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "dsp/test_clock/ClockTest.h" 4 | #include "dsp/test_clock/InternalExternalClockTest.h" 5 | 6 | int main() { 7 | UNITY_BEGIN(); 8 | test_Clock(); 9 | test_InternalExternalClock(); 10 | return UNITY_END(); 11 | } 12 | -------------------------------------------------------------------------------- /src/filesystem/FileInfo.h: -------------------------------------------------------------------------------- 1 | #ifndef FileInfo_h 2 | #define FileInfo_h 3 | 4 | #define MAX_FILENAME_SIZE 32 5 | #define MAX_FILEPATH_SIZE 64 6 | 7 | class FileInfo { 8 | public: 9 | char filename[MAX_FILENAME_SIZE]; 10 | char filepath[MAX_FILEPATH_SIZE]; 11 | }; 12 | 13 | 14 | #endif -------------------------------------------------------------------------------- /src/util/util.h: -------------------------------------------------------------------------------- 1 | #ifndef util_h 2 | #define util_h 3 | 4 | inline int cycleIndex(int value, int size, int direction) { 5 | if(direction < 0) { 6 | return value == 0 ? size-1 : value-1; 7 | } else if (direction > 0) { 8 | return (value+1) % size; 9 | } 10 | } 11 | 12 | #endif -------------------------------------------------------------------------------- /src/dsp/oscillator/BaseOscillator.h: -------------------------------------------------------------------------------- 1 | #ifndef BaseOscillator_h 2 | #define BaseOscillator_h 3 | 4 | namespace eurorack { 5 | 6 | class BaseOscillator 7 | { 8 | public: 9 | virtual void setFrequency(const float f) = 0; 10 | virtual float process() = 0; 11 | }; 12 | 13 | } 14 | 15 | #endif -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Arduino Eurorack 2 | 3 | Utilities for creating Eurorack modules using Arduino devices. 4 | 5 | Tested with the following devices, but will likely work with any 3.3v logic level device (may require changes for a 5v logic level): 6 | 7 | Teensy LC / 4.0 / 4.1 8 | Electrosmith Daisy 9 | 10 | 11 | ## Documentation 12 | 13 | [Documentation](https://pigatron-industries.github.io/arduino_eurorack/) 14 | 15 | -------------------------------------------------------------------------------- /src/dsp/waveshapes/Sine.h: -------------------------------------------------------------------------------- 1 | #ifndef Sine_h 2 | #define Sine_h 3 | 4 | #include "../base/WaveShape.h" 5 | #include "math.h" 6 | 7 | namespace eurorack { 8 | 9 | class Sine : public WaveShape { 10 | public: 11 | Sine() {} 12 | virtual float get(float phase); 13 | }; 14 | 15 | inline float Sine::get(float phase) { 16 | return sinf(phase * M_PI * 2.0f); 17 | } 18 | 19 | } 20 | 21 | #endif -------------------------------------------------------------------------------- /docs/_config.yml: -------------------------------------------------------------------------------- 1 | remote_theme: pages-themes/tactile@v0.2.0 2 | plugins: 3 | - jekyll-remote-theme 4 | layouts: /pigatron-industries.common/_layouts 5 | includes_dir: /pigatron-industries.common/_includes 6 | exclude: /pigatron-industries.common 7 | basepath: /arduino_eurorack 8 | repolink: https://github.com/pigatron-industries/arduino_eurorack/blob/main/ 9 | title: Arduino Eurorack 10 | description: [A library for creating Eurorack modules using Arduino devices] -------------------------------------------------------------------------------- /src/eurorack_graphics.h: -------------------------------------------------------------------------------- 1 | #ifndef EurorackGraphics_h 2 | #define EurorackGraphics_h 3 | 4 | #include "graphics/GraphicsContext.h" 5 | 6 | #include "graphics/components/TextComponent.h" 7 | #include "graphics/components/FieldComponent.h" 8 | #include "graphics/components/MessageBoxComponent.h" 9 | 10 | #include "graphics/containers/VerticalContainer.h" 11 | #include "graphics/containers/HorizontalContainer.h" 12 | 13 | #include "graphics/manager/FocusManager.h" 14 | 15 | #endif -------------------------------------------------------------------------------- /src/util/pitchutil.h: -------------------------------------------------------------------------------- 1 | #ifndef pitchutil_h 2 | #define pitchutil_h 3 | 4 | #include 5 | 6 | inline float midiToFrequency(float midi) { 7 | return 440.0 * powf(2, (midi - 69.0) / 12.0); 8 | } 9 | 10 | inline float frequencyToMidi(float frequency) { 11 | return 69.0 + 12.0 * log2(frequency / 440.0); 12 | } 13 | 14 | inline float octaveToFrequency(float octave, float midFrequency = 440) { 15 | return midFrequency * powf(2, octave); 16 | } 17 | 18 | 19 | #endif -------------------------------------------------------------------------------- /src/controller/AbstractController.h: -------------------------------------------------------------------------------- 1 | #ifndef AbstractController_h 2 | #define AbstractController_h 3 | 4 | class AbstractController { 5 | public: 6 | AbstractController() {} 7 | virtual void load() {} 8 | virtual void save() {} 9 | virtual void init() {} 10 | virtual void update() {} 11 | virtual int cycleParameter(int amount) {} 12 | virtual void cycleValue(int amount) {} 13 | virtual void selectValue() {} 14 | }; 15 | 16 | 17 | #endif 18 | -------------------------------------------------------------------------------- /src/util/Timer.h: -------------------------------------------------------------------------------- 1 | #ifndef Timer_h 2 | #define Timer_h 3 | 4 | #include 5 | 6 | 7 | class Timer { 8 | 9 | public: 10 | Timer(); 11 | void start(unsigned long waitTime); 12 | void start(); 13 | bool isRunning(); 14 | bool isStopped(); 15 | bool hasJustStopped(); 16 | 17 | private: 18 | bool _wasRunning = false; 19 | bool _running = false; 20 | unsigned long _startTime; 21 | unsigned long _waitTime; 22 | 23 | void update(); 24 | 25 | }; 26 | 27 | #endif 28 | -------------------------------------------------------------------------------- /src/util/mathutil.h: -------------------------------------------------------------------------------- 1 | #ifndef mathutil_h 2 | #define mathutil_h 3 | 4 | #define M_PI 3.14159265358979323846f 5 | #define ONE_OVER_ROOT_TWO 0.70710678 6 | 7 | inline float smooth(float currentValue, float prevValue, float smoothing = 0.2) { 8 | return (currentValue*smoothing) + prevValue*(1-smoothing); 9 | } 10 | 11 | /** 12 | * More efficient than function than fmodf(x, 1.0f) 13 | * for calculating the decimal part of a floating point value. 14 | */ 15 | inline float fastmod1f(float x){ 16 | return x - static_cast(x); 17 | } 18 | 19 | #endif -------------------------------------------------------------------------------- /src/dsp/sample/DelayLine.cpp: -------------------------------------------------------------------------------- 1 | #include "DelayLine.h" 2 | 3 | using namespace eurorack; 4 | 5 | void DelayLine::setDelay(size_t delay) { 6 | this->delay = delay; 7 | delayFrac = 0.0f; 8 | this->delayInt = delay < bufferSize ? delay : bufferSize - 1; 9 | } 10 | 11 | void DelayLine::setDelay(float delay) { 12 | this->delay = delay; 13 | int32_t int_delay = static_cast(delay); 14 | delayFrac = delay - static_cast(int_delay); 15 | delayInt = static_cast(int_delay) < bufferSize ? int_delay : bufferSize - 1; 16 | } 17 | -------------------------------------------------------------------------------- /docs/device.md: -------------------------------------------------------------------------------- 1 | --- 2 | order: 4 3 | --- 4 | 5 | # Devices 6 | 7 | A Device is a piece of hardware that provides a number analog/digital input/output pins. 8 | The DevicePin objects that are available for the device are provided by each specific Device class. 9 | The followwing devices are included in the library. 10 | 11 | ## HC595 - Digital Outputs 12 | 13 | HC595 is a shift register with 8 output pins. 14 | 15 | TODO 16 | 17 | ## DAC8164 18 | 19 | TODO 20 | 21 | ## MCP23S17 22 | 23 | TODO 24 | 25 | ## MAX11300 26 | 27 | TODO 28 | 29 | ## IS32FL3738 30 | 31 | TODO -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | *.d 3 | 4 | # Compiled Object files 5 | *.slo 6 | *.lo 7 | *.o 8 | *.obj 9 | 10 | # Precompiled Headers 11 | *.gch 12 | *.pch 13 | 14 | # Compiled Dynamic libraries 15 | *.so 16 | *.dylib 17 | *.dll 18 | 19 | # Fortran module files 20 | *.mod 21 | *.smod 22 | 23 | # Compiled Static libraries 24 | *.lai 25 | *.la 26 | *.a 27 | *.lib 28 | 29 | # Executables 30 | *.exe 31 | *.out 32 | *.app 33 | 34 | # Platform IO IDE 35 | .pio 36 | .clang_complete 37 | .gcc-flags.json 38 | .vscode 39 | .DS_Store 40 | 41 | # Eagle files 42 | *.*#* 43 | *.zip 44 | *.pro 45 | temp -------------------------------------------------------------------------------- /library.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Arduino Eurorack", 3 | "version": "0.0.1", 4 | "description": "Utilities for making Eurorack modules from Arduino devices", 5 | "keywords": "eurorack", 6 | "repository": 7 | { 8 | "type": "git", 9 | "url": "https://github.com/pigatron-industries/arduino_eurorack.git" 10 | }, 11 | "authors": 12 | [ 13 | { 14 | "name": "Robert Ellis" 15 | } 16 | ], 17 | "license": "Unlicense", 18 | "dependencies": { 19 | "sumotoy/gpio_MCP23S17": "^0.9" 20 | }, 21 | "frameworks": "arduino", 22 | "platforms": "*" 23 | } -------------------------------------------------------------------------------- /src/dsp/clock/Clock.cpp: -------------------------------------------------------------------------------- 1 | #include "Clock.h" 2 | 3 | void Clock::init(float sampleRate) { 4 | this->sampleRate = sampleRate; 5 | sampleTime = 1/sampleRate; 6 | } 7 | 8 | void Clock::setFrequency(float frequency) { 9 | this->frequency = frequency; 10 | phaseInc = frequency * sampleTime; 11 | } 12 | 13 | void Clock::reset() { 14 | phase = 0; 15 | } 16 | 17 | bool Clock::process() { 18 | phase += phaseInc; 19 | if(phase > phaseMax) { 20 | phase -= phaseMax; 21 | return true; 22 | } else if (phase < 0.0f) { 23 | phase += phaseMax; 24 | return true; 25 | } 26 | return false; 27 | } 28 | -------------------------------------------------------------------------------- /src/io/analoginputs/PowInput.h: -------------------------------------------------------------------------------- 1 | #ifndef PowInput_h 2 | #define PowInput_h 3 | 4 | #include 5 | #include "LinearInput.h" 6 | 7 | template> 8 | class PowInput : public LinearInput { 9 | public: 10 | PowInput(T& input, float power, float realMin, float realMax) : LinearInput(input, realMin, realMax, 0, 1) { 11 | this->power = power; 12 | } 13 | 14 | float getValue() { 15 | powValue = powf(this->getValue(), power); 16 | return powValue; 17 | } 18 | 19 | private: 20 | float power; 21 | float powValue; 22 | }; 23 | 24 | #endif -------------------------------------------------------------------------------- /src/filesystem/reader/BufferFileReader.h: -------------------------------------------------------------------------------- 1 | #ifndef BufferFileReader_h 2 | #define BufferFileReader_h 3 | 4 | #include 5 | #include "FileReader.h" 6 | #include "memory/MemPool.h" 7 | 8 | class BufferFileReader : public FileReader { 9 | public: 10 | BufferFileReader(); 11 | BufferFileReader(MemPool<>& memPool); 12 | bool read(FsFile& file); 13 | 14 | unsigned char* getBuffer() { return buffer; } 15 | size_t getSize() { return size; } 16 | 17 | protected: 18 | unsigned char* buffer; 19 | size_t size; 20 | 21 | MemPool<>* memPool; 22 | 23 | void allocate(size_t size); 24 | }; 25 | 26 | #endif -------------------------------------------------------------------------------- /src/graphics/manager/FocusManager.h: -------------------------------------------------------------------------------- 1 | #ifndef FocusManager_h 2 | #define FocusManager_h 3 | 4 | #include "../Component.h" 5 | 6 | template 7 | class FocusManager { 8 | 9 | public: 10 | FocusManager() {} 11 | void setFocus(Component* component); 12 | 13 | private: 14 | Component* focus = nullptr; 15 | 16 | }; 17 | 18 | template 19 | void FocusManager::setFocus(Component* component) { 20 | if(focus != nullptr) { 21 | focus->setFocus(false); 22 | focus->render(); 23 | } 24 | focus = component; 25 | focus->setFocus(true); 26 | focus->render(); 27 | } 28 | 29 | 30 | 31 | #endif 32 | -------------------------------------------------------------------------------- /src/dsp/waveshapes/Saw.h: -------------------------------------------------------------------------------- 1 | #ifndef Saw_h 2 | #define Saw_h 3 | 4 | #include "../base/WaveShape.h" 5 | #include "math.h" 6 | 7 | namespace eurorack { 8 | 9 | class Saw : public WaveShape { 10 | public: 11 | Saw() {} 12 | virtual float get(float phase); 13 | virtual float polyblep(float phase, float phaseIncrement); 14 | private: 15 | }; 16 | 17 | inline float Saw::get(float phase) { 18 | return -1 * (((phase * 2)) - 1); 19 | } 20 | 21 | inline float Saw::polyblep(float phase, float phaseIncrement) { 22 | return polyblepTransient(phase, phaseIncrement); 23 | } 24 | 25 | } 26 | 27 | #endif -------------------------------------------------------------------------------- /src/util/SlewLimiter.h: -------------------------------------------------------------------------------- 1 | #ifndef SlewLimiter_h 2 | #define SlewLimiter_h 3 | 4 | #include "mathutil.h" 5 | 6 | class SlewLimiter { 7 | public: 8 | SlewLimiter(float speed = 0.01) { this->speed = speed; } 9 | void setTargetValue(float targetValue) { this->targetValue = targetValue; } 10 | void setSpeed(float speed) { this->speed = speed; } 11 | float getValue() { return value; } 12 | float update() { 13 | value = smooth(targetValue, value, speed); 14 | return value; 15 | } 16 | 17 | private: 18 | float targetValue; 19 | float speed = 0.01; 20 | 21 | float value; 22 | 23 | }; 24 | 25 | #endif -------------------------------------------------------------------------------- /src/filesystem/reader/BufferFileReader.cpp: -------------------------------------------------------------------------------- 1 | #include "BufferFileReader.h" 2 | #include 3 | 4 | BufferFileReader::BufferFileReader() { 5 | this->memPool = nullptr; 6 | } 7 | 8 | BufferFileReader::BufferFileReader(MemPool<>& memPool) { 9 | this->memPool = &memPool; 10 | this->size = 0; 11 | } 12 | 13 | bool BufferFileReader::read(FsFile& file) { 14 | size = file.size(); 15 | allocate(size); 16 | file.readBytes(buffer, size); 17 | return true; 18 | } 19 | 20 | void BufferFileReader::allocate(size_t size) { 21 | if(memPool == nullptr) { 22 | buffer = new unsigned char[size]; 23 | } else { 24 | buffer = memPool->allocate(size); 25 | } 26 | } -------------------------------------------------------------------------------- /src/dsp/waveshapes/interpolation/WaveInterpolator2D.h: -------------------------------------------------------------------------------- 1 | #ifndef WaveInterpolator2D_h 2 | #define WaveInterpolator2D_h 3 | 4 | #include "WaveInterpolator.h" 5 | 6 | namespace eurorack { 7 | 8 | template 9 | class WaveInterpolator2D : public WaveArrayInterpolator, X> { 10 | public: 11 | 12 | void setInterpolationX(float x) { 13 | this->setInterpolation(x); 14 | } 15 | 16 | void setInterpolationY(float y) { 17 | for(int i = 0; i < X; i++) { 18 | (*this)[i].setInterpolation(y); 19 | } 20 | } 21 | }; 22 | 23 | } 24 | 25 | #endif -------------------------------------------------------------------------------- /src/dsp/clock/Clock.h: -------------------------------------------------------------------------------- 1 | #ifndef Clock_h 2 | #define Clock_h 3 | 4 | class Clock { 5 | public: 6 | void init(float sampleRate); 7 | void setFrequency(float frequency); 8 | void reset(); 9 | float getFrequency() { return frequency; } 10 | bool process(); 11 | 12 | float getPhaseIncrement() { return phaseInc; } 13 | float getPhase() { return phase; } 14 | 15 | protected: 16 | void setPhaseMax(float phaseMax) { this->phaseMax = phaseMax; } 17 | 18 | float sampleRate, sampleTime; 19 | 20 | float frequency; 21 | float phaseInc; 22 | float phase = 1.0; 23 | float phaseMax = 1.0; 24 | }; 25 | 26 | #endif 27 | -------------------------------------------------------------------------------- /src/hardware/device/AnalogInputSumPin.h: -------------------------------------------------------------------------------- 1 | #ifndef AnalogInputSumPin_h 2 | #define AnalogInputSumPin_h 3 | 4 | #include 5 | #include "../../hardware/device/DevicePin.h" 6 | 7 | template 8 | class AnalogInputSumPin : public AnalogInputPin { 9 | public: 10 | AnalogInputSumPin(AnalogInputPin& pin1, AnalogInputPin& pin2) : 11 | DevicePin(pin1.getDevice(), -1), 12 | AnalogInputPin(pin1.getDevice(), -1), 13 | pin1(pin1), pin2(pin2) 14 | {} 15 | 16 | float analogRead() { 17 | return pin1.analogRead() + pin2.analogRead(); 18 | } 19 | 20 | private: 21 | AnalogInputPin& pin1; 22 | AnalogInputPin& pin2; 23 | 24 | }; 25 | 26 | #endif -------------------------------------------------------------------------------- /docs/util.md: -------------------------------------------------------------------------------- 1 | --- 2 | order: 7 3 | --- 4 | 5 | # Utilities 6 | 7 | ## RangeScale 8 | 9 | Scales a value from one range to another range. 10 | 11 | RangeScale scale(fromMin, fromMax, toMin, toMax); 12 | 13 | float toValue = scale.convert(fromValue); 14 | 15 | ## CycleEnum 16 | 17 | Cycles through a list of enums. Useful to make a rotary encoder cycle through an enumerated list of modes. e.g. 18 | 19 | enum Mode { 20 | FIRST_MODE, 21 | SECOND_MODE, 22 | LAST_MODE 23 | }; 24 | 25 | CycleEnum mode = CycleEnum(FIRST_MODE, LAST_MODE); //first and last modes in enum 26 | 27 | Cycle using RotaryEncoder: 28 | 29 | if(encoder.update()) { 30 | mode.cycle(encoder.getMovement()); 31 | } 32 | 33 | Get value of enum using ```mode.value``` 34 | 35 | -------------------------------------------------------------------------------- /docs/usage.md: -------------------------------------------------------------------------------- 1 | --- 2 | order: 2 3 | --- 4 | 5 | # Basic Usage 6 | 7 | ### PlatformIO 8 | 9 | Add to a platformio project by adding the following to lib_deps in platformio.ini. Other dependencies may need to be added when using specific device drivers. See full documnetation for details. 10 | 11 | lib_deps = 12 | https://github.com/pigatron-industries/arduino_eurorack.git 13 | 14 | ### Include Headers 15 | 16 | Include the following header for basic usage of input/output libraries and other utilities. Other includes may be needed for the DSP libraries and specific devices drivers. See full documentation for details. 17 | 18 | include 19 | 20 | ### Initialise 21 | 22 | Initialise the library before using with the follwoing. This usually goes in the arduino setup() function. 23 | 24 | Eurorack::init(); 25 | -------------------------------------------------------------------------------- /src/dsp/sample/DelayLine.h: -------------------------------------------------------------------------------- 1 | #ifndef DelayLine_h 2 | #define DelayLine_h 3 | 4 | #include 5 | #include 6 | #include "SampleBuffer.h" 7 | 8 | namespace eurorack { 9 | 10 | class DelayLine : public SampleBuffer 11 | { 12 | public: 13 | DelayLine() {} 14 | 15 | void setDelay(size_t delay); 16 | void setDelay(float delay); 17 | const float read() const; 18 | 19 | private: 20 | float delay; 21 | float delayFrac; 22 | size_t delayInt; 23 | }; 24 | 25 | inline const float DelayLine::read() const { 26 | float pos = writePointer - delay; 27 | if(pos < 0) { 28 | pos += bufferSize; 29 | } 30 | return SampleBuffer::read(pos); 31 | } 32 | 33 | } 34 | 35 | #endif 36 | -------------------------------------------------------------------------------- /src/graphics/containers/VerticalContainer.h: -------------------------------------------------------------------------------- 1 | #ifndef VerticalContainer_h 2 | #define VerticalContainer_h 3 | 4 | #include "Container.h" 5 | 6 | template 7 | class VerticalContainer : public Container { 8 | 9 | public: 10 | VerticalContainer() {} 11 | virtual void layout(); 12 | }; 13 | 14 | 15 | template 16 | void VerticalContainer::layout() { 17 | int childTop = this->top; 18 | for(Component* component : this->components) { 19 | component->setTop(childTop); 20 | component->setLeft(this->left); 21 | component->layout(); 22 | childTop += component->getHeight(); 23 | if(component->getWidth() > this->width) { 24 | this->setWidth(component->getWidth()); 25 | } 26 | } 27 | this->setHeight(childTop); 28 | } 29 | 30 | #endif 31 | -------------------------------------------------------------------------------- /src/dsp/waveshapes/AsymmetricalTriangle.h: -------------------------------------------------------------------------------- 1 | #ifndef AsymmetricalTriangle_h 2 | #define AsymmetricalTriangle_h 3 | 4 | #include "WaveSequence.h" 5 | #include "Line.h" 6 | 7 | namespace eurorack { 8 | 9 | class AsymmetricalTriangle : public WaveSequence<2, Line> { 10 | public: 11 | AsymmetricalTriangle(); 12 | void setPeakPosition(float peakPosition); 13 | }; 14 | 15 | inline AsymmetricalTriangle::AsymmetricalTriangle() { 16 | segment(0).setStartValue(-1); 17 | segment(0).setEndValue(1); 18 | segment(1).setStartValue(1); 19 | segment(1).setEndValue(-1); 20 | } 21 | 22 | inline void AsymmetricalTriangle::setPeakPosition(float peakPosition) { 23 | segment(0).setLength(peakPosition); 24 | segment(1).setLength(1.0f-peakPosition); 25 | } 26 | 27 | } 28 | 29 | 30 | #endif -------------------------------------------------------------------------------- /src/io/analoginputs/ExpInput.h: -------------------------------------------------------------------------------- 1 | #ifndef ExpInput_h 2 | #define ExpInput_h 3 | 4 | #include "AbstractAnalogInput.h" 5 | 6 | template> 7 | class ExpInput : public AbstractAnalogInput { 8 | public: 9 | static constexpr float MID_VALUE_C5 = 523.25; 10 | 11 | ExpInput(T& input, float midValue = MID_VALUE_C5, float base = 2) : AbstractAnalogInput(input), base(base) { 12 | this->midValue = midValue; 13 | } 14 | 15 | float getValue() { 16 | expValue = midValue*powf(base, this->getStableVoltage()); 17 | return expValue; 18 | } 19 | 20 | void setMidValue(float midValue) { this->midValue = midValue; } 21 | 22 | private: 23 | const float base; 24 | float midValue; 25 | float expValue; 26 | }; 27 | 28 | #endif -------------------------------------------------------------------------------- /src/graphics/containers/HorizontalContainer.h: -------------------------------------------------------------------------------- 1 | #ifndef HorizontalContainer_h 2 | #define HorizontalContainer_h 3 | 4 | #include "Container.h" 5 | 6 | template 7 | class HorizontalContainer : public Container { 8 | 9 | public: 10 | HorizontalContainer() {} 11 | virtual void layout(); 12 | }; 13 | 14 | 15 | template 16 | void HorizontalContainer::layout() { 17 | int childLeft = this->left; 18 | for(Component* component : this->components) { 19 | component->setLeft(childLeft); 20 | component->setTop(this->top); 21 | component->layout(); 22 | childLeft += component->getWidth(); 23 | if(component->getHeight() > this->getWidth()) { 24 | this->setHeight(component->getHeight()); 25 | } 26 | } 27 | this->setWidth(childLeft); 28 | } 29 | 30 | #endif 31 | -------------------------------------------------------------------------------- /src/util/Timer.cpp: -------------------------------------------------------------------------------- 1 | #include "Timer.h" 2 | 3 | #include 4 | 5 | Timer::Timer() { 6 | } 7 | 8 | void Timer::start(unsigned long waitTime) { 9 | _running = true; 10 | _waitTime = waitTime; 11 | _startTime = micros(); 12 | } 13 | 14 | void Timer::start() { 15 | _running = true; 16 | _startTime = micros(); 17 | } 18 | 19 | bool Timer::isRunning() { 20 | update(); 21 | return _running; 22 | } 23 | 24 | bool Timer::isStopped() { 25 | update(); 26 | return !_running; 27 | } 28 | 29 | bool Timer::hasJustStopped() { 30 | update(); 31 | return _wasRunning && !_running; 32 | } 33 | 34 | void Timer::update() { 35 | _wasRunning = _running; 36 | if(_running) { 37 | unsigned long timeDiff = micros() - _startTime; 38 | if(timeDiff > _waitTime) { 39 | _running = false; 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/dsp/waveshapes/Triangle.h: -------------------------------------------------------------------------------- 1 | #ifndef Triangle_h 2 | #define Triangle_h 3 | 4 | #include "../base/WaveShape.h" 5 | #include "../../util/mathutil.h" 6 | #include "math.h" 7 | 8 | namespace eurorack { 9 | 10 | class Triangle : public WaveShape { 11 | public: 12 | Triangle() {} 13 | virtual float get(float phase); 14 | virtual float polyblep(float phase, float phaseIncrement); 15 | }; 16 | 17 | inline float Triangle::get(float phase) { 18 | float t = -1 + (2*phase); 19 | return 2 * (fabsf(t) - 0.5); 20 | } 21 | 22 | inline float Triangle::polyblep(float phase, float phaseIncrement) { 23 | float pb = 0; 24 | pb += polyblepTransient(phase, phaseIncrement); 25 | pb -= polyblepTransient(fastmod1f(phase + 0.5f), phaseIncrement); 26 | return pb; 27 | } 28 | 29 | } 30 | 31 | #endif -------------------------------------------------------------------------------- /platformio.ini: -------------------------------------------------------------------------------- 1 | ; PlatformIO Project Configuration File 2 | ; 3 | ; Build options: build flags, source filter 4 | ; Upload options: custom upload port, speed and extra flags 5 | ; Library options: dependencies, extra library storages 6 | ; Advanced options: extra scripting 7 | ; 8 | ; Please visit documentation for the other options and examples 9 | ; https://docs.platformio.org/page/projectconf.html 10 | 11 | [platformio] 12 | default_envs = arduino_eurorack 13 | 14 | [env:arduino_eurorack] 15 | platform = teensy@5.0.0 16 | board = teensy41 17 | framework = arduino 18 | build_flags = -I./src -DTEST_COMPILE=1 19 | lib_deps = 20 | SPI 21 | sumotoy/gpio_MCP23S17@^0.9 22 | 23 | [env:arduino_eurorack_test] 24 | platform = native 25 | test_ignore = test_embedded 26 | build_unflags = -std=gnu++11 27 | build_flags = -I./test -I./test/dsp/clock -std=c++17 28 | lib_deps = 29 | fabiobatsilva/ArduinoFake@^0.4.0 -------------------------------------------------------------------------------- /src/dsp/clock/ClockGate.h: -------------------------------------------------------------------------------- 1 | #ifndef ClockGate_h 2 | #define ClockGate_h 3 | 4 | class ClockGate { 5 | public: 6 | ClockGate(int length = 1) { 7 | this->length = length; 8 | } 9 | 10 | void setLength(int length) { 11 | this->length = length; 12 | } 13 | 14 | bool tick(bool trigger) { 15 | if(trigger) { 16 | count = 0; 17 | gate = true; 18 | } else if(gate) { 19 | count++; 20 | if(count >= length) { 21 | gate = false; 22 | } 23 | } 24 | 25 | return gate; 26 | } 27 | 28 | void reset() { 29 | count = 0; 30 | gate = false; 31 | } 32 | 33 | private: 34 | int length = 1; 35 | int count = 0; 36 | bool gate; 37 | 38 | }; 39 | 40 | #endif 41 | -------------------------------------------------------------------------------- /src/eeprom/Config.h: -------------------------------------------------------------------------------- 1 | #ifndef Config_h 2 | #define Config_h 3 | 4 | #include 5 | #include 6 | 7 | template 8 | class ConfigField { 9 | public: 10 | int address = 0; 11 | int size = 0; 12 | T data; 13 | }; 14 | 15 | class Config { 16 | public: 17 | static Config config; 18 | 19 | template 20 | void load(ConfigField &field) { 21 | if(field.size == 0) { 22 | field.address = nextAddress; 23 | field.size = sizeof(T); 24 | } 25 | 26 | EEPROM.get(field.address, field.data); 27 | nextAddress += field.size; 28 | } 29 | 30 | template 31 | void save(ConfigField &field) { 32 | EEPROM.put(field.address, field.data); 33 | } 34 | 35 | private: 36 | int nextAddress = 0; 37 | }; 38 | 39 | #endif 40 | -------------------------------------------------------------------------------- /src/dsp/waveshapes/Pulse.h: -------------------------------------------------------------------------------- 1 | #ifndef Pulse_h 2 | #define Pulse_h 3 | 4 | #include "../base/WaveShape.h" 5 | #include "../../util/mathutil.h" 6 | #include "math.h" 7 | 8 | namespace eurorack { 9 | 10 | class Pulse : public WaveShape { 11 | public: 12 | Pulse() {} 13 | void setWidth(float width) { this->width = width; } 14 | virtual float get(float phase); 15 | virtual float polyblep(float phase, float phaseIncrement); 16 | protected: 17 | float width = 0.5; 18 | }; 19 | 20 | inline float Pulse::get(float phase) { 21 | return phase < width ? 1 : -1; 22 | } 23 | 24 | inline float Pulse::polyblep(float phase, float phaseIncrement) { 25 | float pb = 0; 26 | pb += polyblepTransient(phase, phaseIncrement); 27 | pb -= polyblepTransient(fastmod1f(phase + width), phaseIncrement); 28 | return pb; 29 | } 30 | 31 | } 32 | 33 | #endif -------------------------------------------------------------------------------- /src/io/analoginputs/CrossfadeInput.h: -------------------------------------------------------------------------------- 1 | #ifndef CrossfadeInput_h 2 | #define CrossfadeInput_h 3 | 4 | #include "LinearInput.h" 5 | 6 | template> 7 | class CrossfadeInput : public LinearInput { 8 | public: 9 | CrossfadeInput(T& input, float _realMin, float _realMax) : 10 | LinearInput(input, _realMin, _realMax, 0, 1) { 11 | } 12 | 13 | inline bool update() { 14 | if(LinearInput::update()) { 15 | dryLevel = (sinf(this->getValue()*M_PI - M_PI*0.5) + 1)*0.5; 16 | wetLevel = (sinf((1-this->getValue())*M_PI - M_PI*0.5) + 1)*0.5; 17 | } 18 | return this->isChanged(); 19 | } 20 | 21 | inline float getDryLevel() { return dryLevel; } 22 | inline float getWetLevel() { return wetLevel; } 23 | 24 | private: 25 | float dryLevel; 26 | float wetLevel; 27 | 28 | }; 29 | 30 | #endif -------------------------------------------------------------------------------- /src/io/analoginputs/LinearInput.h: -------------------------------------------------------------------------------- 1 | #ifndef LinearInput_h 2 | #define LinearInput_h 3 | 4 | #include 5 | #include "AbstractAnalogInput.h" 6 | #include "../../util/RangeScale.h" 7 | 8 | template> 9 | class LinearInput : public AbstractAnalogInput { 10 | public: 11 | LinearInput(T& input, float _realMin, float _realMax, float _virtualMin, float _virtualMax) : 12 | AbstractAnalogInput(input), 13 | scale(_realMin, _realMax, _virtualMin, _virtualMax) { 14 | } 15 | 16 | void setRange(float _virtualMin, float _virtualMax) { 17 | scale.setOutputRange(_virtualMin, _virtualMax); 18 | } 19 | 20 | inline float getValue() { 21 | float voltage = this->getStableVoltage(); 22 | return scale.convert(voltage); 23 | } 24 | 25 | private: 26 | float virtualValue; 27 | 28 | RangeScale scale; 29 | }; 30 | 31 | #endif -------------------------------------------------------------------------------- /src/dsp/waveshapes/interpolation/WaveInterpolator3D.h: -------------------------------------------------------------------------------- 1 | #ifndef WaveInterpolator3D_h 2 | #define WaveInterpolator3D_h 3 | 4 | #include "WaveInterpolator.h" 5 | 6 | namespace eurorack { 7 | 8 | template 9 | class WaveInterpolator3D : public WaveArrayInterpolator, Y>, X> { 10 | public: 11 | 12 | void setInterpolationX(float x) { 13 | this->setInterpolation(x); 14 | } 15 | 16 | void setInterpolationY(float y) { 17 | for(int i = 0; i < X; i++) { 18 | (*this)[i].setInterpolation(y); 19 | } 20 | } 21 | 22 | void setInterpolationZ(float z) { 23 | for(int i = 0; i < X; i++) { 24 | for(int j = 0; j < Y; j++) { 25 | (*this)[i][j].setInterpolation(z); 26 | } 27 | } 28 | } 29 | }; 30 | 31 | } 32 | 33 | #endif -------------------------------------------------------------------------------- /src/dsp/distortionfunctions/TwoLineFunction.h: -------------------------------------------------------------------------------- 1 | #ifndef TwoLineFunction_h 2 | #define TwoLineFunction_h 3 | 4 | #include "../waveshapes/WaveSequence.h" 5 | #include "../waveshapes/Line.h" 6 | 7 | namespace eurorack { 8 | 9 | class TwoLineFunction : public WaveSequence<2, Line> { 10 | public: 11 | TwoLineFunction(); 12 | void setMidPoint(float x, float y); 13 | }; 14 | 15 | inline TwoLineFunction::TwoLineFunction() { 16 | segment(0).setStartValue(0); 17 | segment(0).setLength(0.5); 18 | segment(0).setEndValue(0.5); 19 | segment(1).setStartValue(0.5); 20 | segment(1).setLength(0.5); 21 | segment(1).setEndValue(1); 22 | } 23 | 24 | inline void TwoLineFunction::setMidPoint(float x, float y) { 25 | segment(0).setStartValue(0); 26 | segment(0).setEndValue(x); 27 | segment(0).setLength(y); 28 | segment(1).setStartValue(x); 29 | segment(1).setEndValue(1); 30 | segment(1).setLength(1.0-y); 31 | } 32 | 33 | } 34 | 35 | #endif -------------------------------------------------------------------------------- /src/filesystem/FileSystem.h: -------------------------------------------------------------------------------- 1 | #ifndef FileSystem_h 2 | #define FileSystem_h 3 | 4 | #include 5 | #include "FileInfo.h" 6 | #include "reader/FileReader.h" 7 | #include "util/Array.h" 8 | 9 | #define MAX_FILE_COUNT 255 10 | 11 | 12 | typedef Array FileList; 13 | 14 | 15 | class FileSystem { 16 | public: 17 | FileSystem(const char* rootDirectory = "/"); // Use SDIO 18 | FileSystem(uint8_t csPin, const char* rootDirectory = "/"); // Use SPI 19 | void init(); 20 | 21 | bool cd(const char* directoryPath); 22 | FileList& ls() { return fileList; } 23 | FileList& ls(const char* extension); 24 | bool read(const char* filePath, FileReader* fileReader); 25 | 26 | private: 27 | static SdFat sd; 28 | 29 | bool sdio = false; 30 | uint8_t csPin; 31 | const char* rootDirectory; 32 | const char* currentDirectory; 33 | 34 | FileList fileList; 35 | FileList filteredFileList; 36 | 37 | bool begin(); 38 | }; 39 | 40 | #endif -------------------------------------------------------------------------------- /src/io/analogoutputs/AnalogGateOutput.h: -------------------------------------------------------------------------------- 1 | #ifndef AnalogGateOutput_h 2 | #define AnalogGateOutput_h 3 | 4 | #include "../../hardware/device/DevicePin.h" 5 | #include "../../hardware/native/NativeDevice.h" 6 | 7 | template 8 | class AnalogGateOutput { 9 | 10 | public: 11 | AnalogGateOutput(AnalogOutputPin& output, float gateVoltage = 5, float zeroVoltage = 0) : output(output) { 12 | this->gateVoltage = gateVoltage; 13 | this->zeroVoltage = zeroVoltage; 14 | } 15 | 16 | void gate(bool value) { 17 | this->value = value; 18 | output.analogWrite(value ? gateVoltage : zeroVoltage); 19 | } 20 | 21 | bool getGate() { return value; } 22 | 23 | void setZeroVoltage(float zeroVoltage) { 24 | this->zeroVoltage = zeroVoltage; 25 | gate(value); 26 | } 27 | 28 | protected: 29 | AnalogOutputPin& output; 30 | bool value; 31 | float gateVoltage; 32 | float zeroVoltage; 33 | 34 | }; 35 | 36 | #endif 37 | -------------------------------------------------------------------------------- /src/dsp/distortionfunctions/PowerFunction.h: -------------------------------------------------------------------------------- 1 | #ifndef PowerFunction_h 2 | #define PowerFunction_h 3 | 4 | #include 5 | 6 | namespace eurorack { 7 | 8 | // https://www.desmos.com/calculator/g8anfrfrxa 9 | class PowerFunction { 10 | public: 11 | PowerFunction(float gradient = 0.5) { this->gradient = gradient; calcCoefficients(); } 12 | void setGradient(float gradient) { this->gradient = gradient; calcCoefficients(); } 13 | float get(float phase); 14 | 15 | private: 16 | float gradient; // -1 - 1 17 | float c; 18 | 19 | void calcCoefficients(); 20 | }; 21 | 22 | 23 | inline void PowerFunction::calcCoefficients() { 24 | c = (2.0/(1-gradient))-1; 25 | } 26 | 27 | inline float PowerFunction::get(float x) { 28 | if(x < 0) { 29 | return 0; 30 | } else if (x > 1) { 31 | return 1; 32 | } else { 33 | return 1 - powf(1-x, c); 34 | } 35 | } 36 | 37 | } 38 | 39 | #endif -------------------------------------------------------------------------------- /src/graphics/GraphicsContext.h: -------------------------------------------------------------------------------- 1 | #ifndef GraphicsContext_h 2 | #define GraphicsContext_h 3 | 4 | #include 5 | 6 | class GraphicsContext { 7 | public: 8 | GraphicsContext() {} 9 | virtual void init() = 0; 10 | 11 | virtual void clear() = 0; 12 | virtual void update() = 0; 13 | 14 | // display info 15 | virtual uint16_t getWidth() = 0; 16 | virtual uint16_t getHeight() = 0; 17 | 18 | // fonts 19 | virtual void setFont(uint8_t font) = 0; 20 | virtual uint16_t getFontHeight(uint8_t font) = 0; 21 | virtual uint16_t getFontWidth(uint8_t font) = 0; 22 | 23 | // colours 24 | virtual void setTextColour(uint16_t colour) = 0; 25 | 26 | // text 27 | virtual void text(const char* text, uint8_t x = 0, uint8_t y = 0) = 0; 28 | 29 | // shapes 30 | virtual void fillRectangle(int x, int y, int w, int h, int color) = 0; 31 | virtual void drawRectangle(int x, int y, int w, int h, int color) = 0; 32 | 33 | protected: 34 | }; 35 | 36 | #endif 37 | -------------------------------------------------------------------------------- /src/graphics/containers/Container.h: -------------------------------------------------------------------------------- 1 | #ifndef Container_h 2 | #define Container_h 3 | 4 | #include "../Component.h" 5 | #include "../../util/Array.h" 6 | 7 | template 8 | class Container : public Component { 9 | 10 | public: 11 | Container() {} 12 | virtual void setContext(G* graphicsContext); 13 | virtual void render(); 14 | void addComponent(Component* component); 15 | 16 | protected: 17 | Array*, N> components; 18 | 19 | }; 20 | 21 | 22 | template 23 | void Container::setContext(G* graphicsContext) { 24 | this->graphicsContext = graphicsContext; 25 | for(Component* component : components) { 26 | component->setContext(graphicsContext); 27 | } 28 | } 29 | 30 | 31 | template 32 | void Container::render() { 33 | for(Component* component : components) { 34 | component->render(); 35 | } 36 | } 37 | 38 | template 39 | void Container::addComponent(Component* component) { 40 | components.add(component); 41 | } 42 | 43 | #endif 44 | -------------------------------------------------------------------------------- /src/hardware/hc595/HC595Device.h: -------------------------------------------------------------------------------- 1 | #ifndef HC595Device_h 2 | #define HC595Device_h 3 | 4 | #include "../device/DevicePin.h" 5 | 6 | #define HC595_PINCOUNT 8 7 | 8 | class HC595Device: public Device, public DigitalOutputDevice { 9 | public: 10 | HC595Device(const uint8_t clockPin, const uint8_t latchPin, const uint8_t dataPin); 11 | void digitalWrite(uint8_t pin, bool value); 12 | void send(); 13 | 14 | DigitalOutputPin pins[HC595_PINCOUNT] = { 15 | DigitalOutputPin(*this, 0), 16 | DigitalOutputPin(*this, 1), 17 | DigitalOutputPin(*this, 2), 18 | DigitalOutputPin(*this, 3), 19 | DigitalOutputPin(*this, 4), 20 | DigitalOutputPin(*this, 5), 21 | DigitalOutputPin(*this, 6), 22 | DigitalOutputPin(*this, 7) 23 | }; 24 | 25 | private: 26 | uint8_t dataPin; 27 | uint8_t latchPin; 28 | uint8_t clockPin; 29 | }; 30 | 31 | #endif 32 | -------------------------------------------------------------------------------- /src/io/digitalinputs/GateInput.h: -------------------------------------------------------------------------------- 1 | #ifndef GateInput_h 2 | #define GateInput_h 3 | 4 | #include "../../hardware/device/DevicePin.h" 5 | #include "../../hardware/native/NativeDevice.h" 6 | #include "../../util/Debounce.h" 7 | 8 | template 9 | class GateInput { 10 | 11 | public: 12 | GateInput(DigitalInputPin& input, bool inverted = true) : input(input), inverted(inverted) { 13 | input.setPinType(PinType::DIGITAL_INPUT_PULLUP); 14 | debounce.begin(false); 15 | } 16 | 17 | bool getValue() { return inverted ? !debounce.read() : debounce.read(); } 18 | bool update() { return debounce.update(input.digitalRead()); } 19 | bool rose() { return inverted ? debounce.fell() : debounce.rose(); } 20 | bool fell() { return inverted ? debounce.rose() : debounce.fell(); } 21 | unsigned long duration() { return debounce.duration(); } 22 | unsigned long previousDuration() { return debounce.previousDuration(); } 23 | 24 | protected: 25 | DigitalInputPin& input; 26 | Debounce debounce; 27 | bool inverted; 28 | 29 | }; 30 | 31 | #endif 32 | -------------------------------------------------------------------------------- /test/dsp/test_clock/ClockTest.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "ClockTest.h" 3 | #include "../src/dsp/clock/Clock.cpp" 4 | 5 | void test_process2Hz() { 6 | Clock clock = Clock(); 7 | clock.init(4); // 4Hz sample rate 8 | clock.setFrequency(2); // 2Hz frequency 9 | 10 | TEST_ASSERT_TRUE(clock.process()); 11 | TEST_ASSERT_FALSE(clock.process()); 12 | TEST_ASSERT_TRUE(clock.process()); 13 | TEST_ASSERT_FALSE(clock.process()); 14 | TEST_ASSERT_TRUE(clock.process()); 15 | } 16 | 17 | void test_process1Hz() { 18 | Clock clock = Clock(); 19 | clock.init(4); // 4Hz sample rate 20 | clock.setFrequency(1); // 1Hz frequency 21 | 22 | TEST_ASSERT_TRUE(clock.process()); 23 | TEST_ASSERT_FALSE(clock.process()); 24 | TEST_ASSERT_FALSE(clock.process()); 25 | TEST_ASSERT_FALSE(clock.process()); 26 | TEST_ASSERT_TRUE(clock.process()); 27 | TEST_ASSERT_FALSE(clock.process()); 28 | TEST_ASSERT_FALSE(clock.process()); 29 | TEST_ASSERT_FALSE(clock.process()); 30 | TEST_ASSERT_TRUE(clock.process()); 31 | } 32 | 33 | void test_Clock() { 34 | RUN_TEST(test_process2Hz); 35 | RUN_TEST(test_process1Hz); 36 | } 37 | -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | order: 1 3 | --- 4 | 5 | # Arduino Eurorack 6 | 7 | A library for creating Eurorack modules using Arduino devices. 8 | 9 | - Provides APIs for analog/digital input/output handling for CVs and Potentiometers. 10 | - DSP and library for processing and generation of audio and CV. 11 | - Extendable device library with some common devices. 12 | 13 | It is recommended to use [PlatformIO](https://platformio.org/) to write module code. 14 | 15 | See documentation links on the left for usage information. 16 | 17 | ## Status 18 | 19 | This library is still in an alpha stage. Breaking changes could be made at any time. 20 | 21 | The following things are currently untested and may or may not work ok: 22 | - Using the Arduino IDE (recommend using [PlatformIO](https://platformio.org/)) 23 | - Not tested with 5V logic devices. 24 | 25 | The following devices have been tested and work, it is likely to work with other Teensy microcontrollers and other 3.3V logic level devices. 26 | - Teensy LC / 4.0 / 4.1 27 | - Electrosmith Daisy 28 | 29 | If you use with an untested configuration, then feel free to report problems and anything fixes you made, or let me know if it works ok! 30 | -------------------------------------------------------------------------------- /src/dsp/waveshapes/wavetable/WaveTableFactory.h: -------------------------------------------------------------------------------- 1 | #ifndef WaveTableGenerator_h 2 | #define WaveTableGenerator_h 3 | 4 | #include "RollOffFunction.h" 5 | #include 6 | 7 | namespace eurorack { 8 | 9 | class WaveTableFactory { 10 | public: 11 | static bool addSine(BaseWaveTable* wavetable, float amplitude = 0.5, int mult = 1, float phaseShift = 0); 12 | static void addHarmonics(BaseWaveTable* wavetable, RollOffFunction* rolloff, float amplitude = 0.5, int mult = 1); 13 | static void addSquare(BaseWaveTable* wavetable, float amplitude = 0.5, int mult = 1); 14 | static void addTriangle(BaseWaveTable* wavetable, float amplitude = 0.5, int mult = 1); 15 | static void addRamp(BaseWaveTable* wavetable, float amplitude = 0.5, int mult = 1); 16 | static void addPulse(BaseWaveTable* wavetable, float amplitude = 0.5, int mult = 1); 17 | static void addImpulse(BaseWaveTable* wavetable, float amplitude = 0.5, int mult = 1); 18 | static void addViolin(BaseWaveTable* wavetable, float amplitude = 0.5, int mult = 1); 19 | 20 | static float SAMPLERATE; 21 | }; 22 | 23 | } 24 | 25 | #endif 26 | -------------------------------------------------------------------------------- /src/hardware/RotaryEncoderButton.h: -------------------------------------------------------------------------------- 1 | #ifndef RotaryEncoderButton_h 2 | #define RotaryEncoderButton_h 3 | 4 | #include "RotaryEncoder.h" 5 | #include "../io/digitalinputs/PushButton.h" 6 | 7 | class RotaryEncoderButton { 8 | 9 | public: 10 | 11 | enum EncoderEvent { 12 | EVENT_NONE, 13 | EVENT_PRESSED, 14 | EVENT_CLOCKWISE, 15 | EVENT_ANTICLOCKWISE, 16 | EVENT_SHORT_PRESS, 17 | EVENT_LONG_HOLD, 18 | EVENT_HELD_CLOCKWISE, 19 | EVENT_HELD_ANTICLOCKWISE 20 | }; 21 | 22 | RotaryEncoderButton(uint8_t encoderPin1, uint8_t encoderPin2, uint8_t buttonPin, bool useInterrupt = true) : 23 | encoder(encoderPin1, encoderPin2, useInterrupt), 24 | encoderButtonPin(NativeDevice::instance, buttonPin), 25 | encoderButton(encoderButtonPin) {} 26 | 27 | RotaryEncoder& getEncoder() { return encoder; } 28 | PushButton<>& getEncoderButton() { return encoderButton; } 29 | 30 | EncoderEvent getEncoderEvent(); 31 | 32 | private: 33 | RotaryEncoder encoder; 34 | DigitalInputPin<> encoderButtonPin; 35 | PushButton<> encoderButton; 36 | 37 | bool rotatedWhilePressed = false; 38 | 39 | }; 40 | 41 | #endif 42 | -------------------------------------------------------------------------------- /src/dsp/sample/SamplePlayer.cpp: -------------------------------------------------------------------------------- 1 | #include "SamplePlayer.h" 2 | 3 | using namespace eurorack; 4 | 5 | void SamplePlayer::init(float sampleRate) { 6 | this->sampleRate = sampleRate; 7 | if (sample != nullptr) { 8 | sample->setPlaybackSampleRate(sampleRate); 9 | } 10 | } 11 | 12 | void SamplePlayer::setSample(SampleBuffer* sample) { 13 | this->sample = sample; 14 | sample->setPlaybackSampleRate(sampleRate); 15 | calcReadIncrement(); 16 | } 17 | 18 | void SamplePlayer::setFrequency(float frequency) { 19 | this->frequency = frequency; 20 | calcReadIncrement(); 21 | } 22 | 23 | void SamplePlayer::calcReadIncrement() { 24 | if (sample != nullptr) { 25 | float originalFrequency = sample->getSampleFrequency(); 26 | float originalSampleRate = sample->getSampleRate(); 27 | readIncrement = (frequency / originalFrequency) * (originalSampleRate / sampleRate); 28 | } 29 | } 30 | 31 | void SamplePlayer::play() { 32 | if (sample != nullptr) { 33 | playing = true; 34 | 35 | } 36 | } 37 | 38 | void SamplePlayer::pause() { 39 | playing = false; 40 | } 41 | 42 | void SamplePlayer::stop() { 43 | playing = false; 44 | readPointer = 0; 45 | } -------------------------------------------------------------------------------- /lib/README: -------------------------------------------------------------------------------- 1 | 2 | This directory is intended for project specific (private) libraries. 3 | PlatformIO will compile them to static libraries and link into executable file. 4 | 5 | The source code of each library should be placed in a an own separate directory 6 | ("lib/your_library_name/[here are source files]"). 7 | 8 | For example, see a structure of the following two libraries `Foo` and `Bar`: 9 | 10 | |--lib 11 | | | 12 | | |--Bar 13 | | | |--docs 14 | | | |--examples 15 | | | |--src 16 | | | |- Bar.c 17 | | | |- Bar.h 18 | | | |- library.json (optional, custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html 19 | | | 20 | | |--Foo 21 | | | |- Foo.c 22 | | | |- Foo.h 23 | | | 24 | | |- README --> THIS FILE 25 | | 26 | |- platformio.ini 27 | |--src 28 | |- main.c 29 | 30 | and a contents of `src/main.c`: 31 | ``` 32 | #include 33 | #include 34 | 35 | int main (void) 36 | { 37 | ... 38 | } 39 | 40 | ``` 41 | 42 | PlatformIO Library Dependency Finder will find automatically dependent 43 | libraries scanning project source files. 44 | 45 | More information about PlatformIO Library Dependency Finder 46 | - https://docs.platformio.org/page/librarymanager/ldf.html 47 | -------------------------------------------------------------------------------- /src/dsp/waveshapes/WaveSelector.h: -------------------------------------------------------------------------------- 1 | #ifndef WaveSelector_h 2 | #define WaveSelector_h 3 | 4 | #include "../../util/TypeSelector.h" 5 | 6 | namespace eurorack { 7 | 8 | template 9 | class WaveSelector : public WaveShape, public TypeSelector { 10 | public: 11 | WaveSelector() {} 12 | WaveSelector(Ts&&... args) : TypeSelector(args...) {} 13 | virtual float get(float phase); 14 | virtual float polyblep(float phase, float phaseIncrement); 15 | virtual void setFrequency(float frequency); 16 | }; 17 | 18 | template 19 | inline float WaveSelector::get(float phase) { 20 | return TypeSelector::getSelected()->get(phase); 21 | } 22 | 23 | template 24 | inline float WaveSelector::polyblep(float phase, float phaseIncrement) { 25 | return TypeSelector::getSelected()->polyblep(phase, phaseIncrement); 26 | } 27 | 28 | template 29 | inline void WaveSelector::setFrequency(float frequency) { 30 | TypeSelector::getSelected()->setFrequency(frequency); 31 | } 32 | 33 | } 34 | 35 | #endif -------------------------------------------------------------------------------- /src/dsp/clock/InternalExternalClock.h: -------------------------------------------------------------------------------- 1 | #ifndef InternalExternalClock_h 2 | #define InternalExternalClock_h 3 | 4 | #include "Clock.h" 5 | #include "ClockDivider.h" 6 | 7 | class InternalExternalClock : public Clock { 8 | public: 9 | enum State { 10 | CLK_INTERNAL, CLK_EXTERNAL_WAITING, CLK_EXTERNAL 11 | }; 12 | enum MultiplierDivider { 13 | CLK_NONE, CLK_MULTIPLIER, CLK_DIVIDER 14 | }; 15 | 16 | void externalTick(); 17 | void setFrequency(float frequency); 18 | void setFrequencyMultiplierDivider(MultiplierDivider type, int value); 19 | 20 | State getState() { return state; } 21 | bool process(); 22 | 23 | protected: 24 | virtual bool processExternal(); 25 | virtual bool processInternal(); 26 | 27 | private: 28 | State state = CLK_INTERNAL; 29 | bool externalTicked; 30 | 31 | float externalTimeCounter = 0; 32 | float externalTime = 0; 33 | float externalWaitTime; 34 | 35 | MultiplierDivider frequencyMultiplierDividerType = CLK_NONE; 36 | int frequencyMultiplierDivider = 1; 37 | ClockDivider clockDivider; 38 | int multiplierCounter = 0; 39 | }; 40 | 41 | #endif 42 | -------------------------------------------------------------------------------- /src/io/digitaloutputs/TriggerOutput.h: -------------------------------------------------------------------------------- 1 | #ifndef TriggerOutput_h 2 | #define TriggerOutput_h 3 | 4 | #include "../../hardware/device/DevicePin.h" 5 | #include "../../hardware/native/NativeDevice.h" 6 | #include "../../util/Timer.h" 7 | 8 | template 9 | class TriggerOutput { 10 | 11 | public: 12 | TriggerOutput(DigitalOutputPin& output, unsigned long durationMicros = 1000) : output(output) { 13 | duration = durationMicros; 14 | } 15 | 16 | void update() { 17 | if(triggered && timer.hasJustStopped()) { 18 | triggered = false; 19 | output.digitalWrite(false); 20 | } 21 | } 22 | 23 | void trigger() { 24 | triggered = true; 25 | output.digitalWrite(true); 26 | timer.start(duration); 27 | } 28 | 29 | void setValue(bool value) { 30 | output.digitalWrite(value); 31 | } 32 | 33 | void setTriggerDurationMicros(unsigned long duration) { this->duration = duration; } 34 | 35 | protected: 36 | DigitalOutputPin& output; 37 | bool triggered; 38 | unsigned long duration; 39 | Timer timer; 40 | 41 | }; 42 | 43 | #endif 44 | -------------------------------------------------------------------------------- /src/dsp/filter/AnalogInputFilter.h: -------------------------------------------------------------------------------- 1 | #ifndef AnalogInputFilter_h 2 | #define AnalogInputFilter_h 3 | 4 | #include "../distortionfunctions/PowerFunction.h" 5 | 6 | using namespace eurorack; 7 | 8 | class AnalogInputFilter { 9 | public: 10 | AnalogInputFilter(float threshold = 1.0, float gradient = 0.75) : smoothnessFunction(gradient) { setThreshold(threshold); } 11 | void setThreshold(float threshold) { this->threshold = threshold; thresholdRecip = 1.0/threshold; } 12 | void setGradient(float gradient) { smoothnessFunction.setGradient(gradient); } 13 | float process(float value); 14 | 15 | private: 16 | PowerFunction smoothnessFunction; 17 | float threshold; 18 | float thresholdRecip; 19 | 20 | float prevValue = 0; 21 | float filteredValue = 0; 22 | }; 23 | 24 | 25 | inline float AnalogInputFilter::process(float value) { 26 | float diff = fabsf(value-prevValue); 27 | if(diff < threshold) { 28 | float smoothing = smoothnessFunction.get(diff*thresholdRecip); 29 | filteredValue = (value*smoothing) + filteredValue*(1-smoothing); 30 | } else { 31 | filteredValue = value; 32 | } 33 | prevValue = value; 34 | return filteredValue; 35 | } 36 | 37 | #endif -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /src/hardware/RotaryEncoderButton.cpp: -------------------------------------------------------------------------------- 1 | #include "RotaryEncoderButton.h" 2 | 3 | RotaryEncoderButton::EncoderEvent RotaryEncoderButton::getEncoderEvent() { 4 | encoderButton.update(); 5 | encoder.update(); 6 | long movement = encoder.getMovement(); 7 | 8 | if(encoderButton.held()) { 9 | if(movement > 0) { 10 | rotatedWhilePressed = true; 11 | return EncoderEvent::EVENT_HELD_CLOCKWISE; 12 | } 13 | if(movement < 0) { 14 | rotatedWhilePressed = true; 15 | return EncoderEvent::EVENT_HELD_ANTICLOCKWISE; 16 | } 17 | if(!rotatedWhilePressed && encoderButton.duration() >= 3000) { 18 | return EncoderEvent::EVENT_LONG_HOLD; 19 | } 20 | } 21 | if(movement > 0) { 22 | return EncoderEvent::EVENT_CLOCKWISE; 23 | } 24 | if(movement < 0) { 25 | return EncoderEvent::EVENT_ANTICLOCKWISE; 26 | } 27 | if(encoderButton.released()) { 28 | if(!rotatedWhilePressed && encoderButton.previousDuration() < 1000) { 29 | return EncoderEvent::EVENT_SHORT_PRESS; 30 | } 31 | rotatedWhilePressed = false; 32 | } 33 | if(encoderButton.pressed()) { 34 | return EncoderEvent::EVENT_PRESSED; 35 | } 36 | return EncoderEvent::EVENT_NONE; 37 | } 38 | -------------------------------------------------------------------------------- /src/dsp/base/WaveShape.h: -------------------------------------------------------------------------------- 1 | #ifndef WaveShape_h 2 | #define WaveShape_h 3 | 4 | #include "Function.h" 5 | 6 | namespace eurorack { 7 | 8 | // Curve that represents a single cycle of a waveform 9 | class WaveShape : public Function { 10 | public: 11 | WaveShape() {} 12 | virtual void setLength(float length); 13 | float getLength() { return length; } 14 | virtual float polyblep(float phase, float phaseIncrement) { return 0; } 15 | virtual void setFrequency(float frequency) {} 16 | protected: 17 | float length = 1; 18 | float polyblepTransient(float phase, float phaseIncrement); 19 | }; 20 | 21 | inline void WaveShape::setLength(float length) { 22 | this->length = length; 23 | } 24 | 25 | inline float WaveShape::polyblepTransient(float phase, float phaseIncrement) { 26 | if(phase < phaseIncrement) { 27 | phase /= phaseIncrement; 28 | return phase + phase - phase * phase - 1.0; 29 | } 30 | else if(phase > 1.0f - phaseIncrement) { 31 | phase = (phase - 1.0) / phaseIncrement; 32 | return phase * phase + phase + phase + 1.0; 33 | } 34 | else { 35 | return 0.0f; 36 | } 37 | } 38 | 39 | } 40 | 41 | #endif -------------------------------------------------------------------------------- /src/hardware/hc595/HC595Device.cpp: -------------------------------------------------------------------------------- 1 | #include "HC595Device.h" 2 | 3 | #define CLOCKINTERVAL 1 //us 4 | 5 | HC595Device::HC595Device(const uint8_t clockPin, const uint8_t latchPin, const uint8_t dataPin) { 6 | this->dataPin = dataPin; 7 | this->latchPin = latchPin; 8 | this->clockPin = clockPin; 9 | pinMode(dataPin, OUTPUT); 10 | pinMode(latchPin, OUTPUT); 11 | pinMode(clockPin, OUTPUT); 12 | ::digitalWrite(clockPin, LOW); 13 | ::digitalWrite(latchPin, LOW); 14 | ::digitalWrite(dataPin, LOW); 15 | Device::setDeferredOutput(true); 16 | } 17 | 18 | void HC595Device::digitalWrite(uint8_t pin, bool value) { 19 | pins[pin].digitalWrite(value); 20 | send(); 21 | } 22 | 23 | void HC595Device::send() { 24 | ::digitalWrite(latchPin, LOW); 25 | ::digitalWrite(clockPin, LOW); 26 | 27 | for(int8_t i=HC595_PINCOUNT-1; i>=0; i--) { 28 | ::digitalWrite(dataPin, pins[i].getDigitalValue()); 29 | delayMicroseconds(CLOCKINTERVAL); 30 | ::digitalWrite(clockPin, HIGH); 31 | delayMicroseconds(CLOCKINTERVAL); 32 | ::digitalWrite(clockPin, LOW); 33 | } 34 | 35 | ::digitalWrite(dataPin, LOW); 36 | ::digitalWrite(clockPin, LOW); 37 | ::digitalWrite(latchPin, HIGH); 38 | delayMicroseconds(CLOCKINTERVAL); 39 | ::digitalWrite(latchPin, LOW); 40 | } -------------------------------------------------------------------------------- /src/io/analoginputs/IntegerInput.h: -------------------------------------------------------------------------------- 1 | #ifndef IntegerInput_h 2 | #define IntegerInput_h 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | template> 9 | class IntegerInput : public LinearInput { 10 | public: 11 | IntegerInput(T& input, float _realMin, float _realMax, int minValue, int maxValue) : 12 | LinearInput(input, _realMin, _realMax, float(minValue)-0.49, float(maxValue)+0.49) { 13 | this->minValue = minValue; 14 | this->maxValue = maxValue; 15 | } 16 | 17 | void setRange(int min, int max) { 18 | LinearInput::setRange(float(min)-0.49, float(max)+0.49); 19 | } 20 | 21 | inline bool update() { 22 | bool changed = LinearInput::update(); 23 | if(changed) { 24 | int prevIntValue = intValue; 25 | intValue = int(roundf(this->getValue())); 26 | if(prevIntValue != intValue) { 27 | return true; 28 | } 29 | } 30 | return false; 31 | } 32 | 33 | inline int getIntValue() { 34 | return intValue; 35 | } 36 | 37 | private: 38 | int minValue; 39 | int maxValue; 40 | int intValue; 41 | }; 42 | 43 | #endif -------------------------------------------------------------------------------- /src/eurorack_dsp.h: -------------------------------------------------------------------------------- 1 | #ifndef EurorackDsp_h 2 | #define EurorackDsp_h 3 | 4 | #include "dsp/clock/Clock.h" 5 | #include "dsp/clock/InternalExternalClock.h" 6 | #include "dsp/clock/ClockDivider.h" 7 | #include "dsp/clock/ClockGate.h" 8 | #include "dsp/envelope/Envelope.h" 9 | #include "dsp/envelope/EnvelopePlayer.h" 10 | #include "dsp/oscillator/WaveOscillator.h" 11 | #include "dsp/oscillator/PhaseDistortionOscillator.h" 12 | #include "dsp/filter/StateVariableFilter.h" 13 | #include "dsp/sample/SampleBuffer.h" 14 | #include "dsp/sample/SamplePlayer.h" 15 | #include "dsp/sample/DelayLine.h" 16 | 17 | #include "dsp/distortionfunctions/TwoLineFunction.h" 18 | #include "dsp/distortionfunctions/SmoothStepFunction.h" 19 | 20 | #include "dsp/waveshapes/Line.h" 21 | #include "dsp/waveshapes/Pulse.h" 22 | #include "dsp/waveshapes/Saw.h" 23 | #include "dsp/waveshapes/Sine.h" 24 | #include "dsp/waveshapes/Triangle.h" 25 | #include "dsp/waveshapes/AsymmetricalTriangle.h" 26 | #include "dsp/waveshapes/WaveSelector.h" 27 | #include "dsp/waveshapes/WaveSequence.h" 28 | 29 | #include "dsp/waveshapes/interpolation/WaveInterpolator.h" 30 | #include "dsp/waveshapes/interpolation/WaveInterpolator2D.h" 31 | #include "dsp/waveshapes/interpolation/WaveInterpolator3D.h" 32 | 33 | #include "dsp/waveshapes/wavetable/WaveTable.h" 34 | #include "dsp/waveshapes/wavetable/WaveTableFactory.h" 35 | 36 | #endif -------------------------------------------------------------------------------- /src/io/analoginputs/AnalogGateInput.h: -------------------------------------------------------------------------------- 1 | #ifndef AnalogGateInput_h 2 | #define AnalogGateInput_h 3 | 4 | #include "LinearInput.h" 5 | 6 | template> 7 | class AnalogGateInput : public AbstractAnalogInput { 8 | public: 9 | AnalogGateInput(T& input, float triggerVoltage = 2) : 10 | AbstractAnalogInput(input) { 11 | this->triggerVoltage = triggerVoltage; 12 | } 13 | 14 | inline bool update() { 15 | if(AbstractAnalogInput::update()) { 16 | gate = this->getStableVoltage() > triggerVoltage; 17 | if(debounce.update(gate)) { 18 | triggeredOn = debounce.rose(); 19 | triggeredOff = debounce.fell(); 20 | return true; 21 | } 22 | } 23 | return false; 24 | } 25 | 26 | inline bool isTriggeredOn() { 27 | return triggeredOn; 28 | } 29 | 30 | inline bool isTriggeredOff() { 31 | return triggeredOff; 32 | } 33 | 34 | inline bool isGateOn() { 35 | return gate; 36 | } 37 | 38 | private: 39 | float triggerVoltage; 40 | bool gate; 41 | bool triggeredOn; 42 | bool triggeredOff; 43 | Debounce debounce; 44 | }; 45 | 46 | #endif -------------------------------------------------------------------------------- /src/util/Debounce.h: -------------------------------------------------------------------------------- 1 | #ifndef Debounce_h 2 | #define Debounce_h 3 | 4 | #include 5 | 6 | #define BOUNCE_LOCK_OUT 1 7 | 8 | class Debounce { 9 | 10 | public: 11 | Debounce(); 12 | void setInterval(uint16_t intervalMillis); 13 | void begin(bool value); 14 | bool update(bool value); 15 | bool read() const; 16 | bool fell() const; 17 | bool rose() const; 18 | 19 | bool changed( ) const { return getStateFlag(CHANGED_STATE); } 20 | unsigned long duration() const; 21 | unsigned long previousDuration() const; 22 | 23 | private: 24 | static const uint8_t DEBOUNCED_STATE = 0b00000001; 25 | static const uint8_t UNSTABLE_STATE = 0b00000010; 26 | static const uint8_t CHANGED_STATE = 0b00000100; 27 | 28 | unsigned long previousMillis; 29 | uint16_t intervalMillis; 30 | uint8_t state; 31 | unsigned long stateChangeLastTime; 32 | unsigned long durationOfPreviousState; 33 | 34 | inline void changeState(); 35 | inline void setStateFlag(const uint8_t flag) {state |= flag;} 36 | inline void unsetStateFlag(const uint8_t flag) {state &= ~flag;} 37 | inline void toggleStateFlag(const uint8_t flag) {state ^= flag;} 38 | inline bool getStateFlag(const uint8_t flag) const {return((state & flag) != 0);} 39 | 40 | }; 41 | 42 | #endif 43 | -------------------------------------------------------------------------------- /src/dsp/clock/ClockDivider.h: -------------------------------------------------------------------------------- 1 | #ifndef ClockDivider_h 2 | #define ClockDivider_h 3 | 4 | class ClockDivider { 5 | public: 6 | ClockDivider(int divisor = 1, int offset = 0); 7 | bool tick(); 8 | void reset(); 9 | 10 | void setDivisor(int divisor) { this->divisor = divisor; } 11 | void setOffset(int offset) { this->offset = offset; } 12 | 13 | bool getTrigger(); 14 | bool getAndResetTrigger(); 15 | 16 | private: 17 | int counter = -1; 18 | int divisor = 1; 19 | int offset = 0; 20 | bool triggered; 21 | 22 | }; 23 | 24 | inline ClockDivider::ClockDivider(int divisor, int offset) { 25 | this->divisor = divisor; 26 | this->offset = offset; 27 | reset(); 28 | } 29 | 30 | inline bool ClockDivider::tick() { 31 | counter++; 32 | if(counter >= divisor) { 33 | counter = 0; 34 | } 35 | 36 | if(counter == 0) { 37 | triggered = true; 38 | return true; 39 | } else { 40 | return false; 41 | } 42 | } 43 | 44 | inline void ClockDivider::reset() { 45 | counter = -1 + offset; 46 | } 47 | 48 | inline bool ClockDivider::getTrigger() { 49 | return triggered; 50 | } 51 | 52 | inline bool ClockDivider::getAndResetTrigger() { 53 | if(triggered) { 54 | triggered = false; 55 | return true; 56 | } else { 57 | return false; 58 | } 59 | } 60 | 61 | #endif 62 | -------------------------------------------------------------------------------- /src/hardware/RotaryEncoder.h: -------------------------------------------------------------------------------- 1 | #ifndef RotaryEncoder_h 2 | #define RotaryEncoder_h 3 | 4 | #include 5 | #include "util/Array.h" 6 | 7 | // Enable this to emit codes twice per step. 8 | //#define HALF_STEP 9 | 10 | // Enable weak pullups 11 | #define ENABLE_PULLUPS 12 | 13 | // Values returned by 'process' 14 | // No complete step yet. 15 | #define DIR_NONE 0x0 16 | // Clockwise step. 17 | #define DIR_CW 0x10 18 | // Anti-clockwise step. 19 | #define DIR_CCW 0x20 20 | 21 | #define MAX_ENCODERS 2 22 | 23 | class RotaryEncoder { 24 | 25 | public: 26 | RotaryEncoder(uint8_t pin1, uint8_t pin2, bool useInterrupt = true); 27 | 28 | bool update() { 29 | if (!useInterrupt) { 30 | poll(); 31 | } 32 | movement = position; 33 | position = 0; 34 | return movement != 0; 35 | } 36 | 37 | long getMovement() { 38 | return movement; 39 | } 40 | 41 | private: 42 | static RotaryEncoder* encoderPtrs[MAX_ENCODERS]; 43 | static int encoderCount; 44 | 45 | bool useInterrupt; 46 | 47 | long position = 0; 48 | long movement = 0; 49 | 50 | unsigned char state; 51 | unsigned char pin1; 52 | unsigned char pin2; 53 | 54 | static void interrupt(); 55 | void poll(); 56 | unsigned char process(); 57 | 58 | }; 59 | 60 | #endif 61 | -------------------------------------------------------------------------------- /src/hardware/device/Device.h: -------------------------------------------------------------------------------- 1 | #ifndef Device_h 2 | #define Device_h 3 | 4 | #include 5 | 6 | enum PinType { 7 | ANALOG_OUTPUT, 8 | ANALOG_INPUT, 9 | DIGITAL_OUTPUT, 10 | DIGITAL_INPUT, 11 | DIGITAL_INPUT_PULLUP 12 | }; 13 | 14 | class Device { 15 | public: 16 | bool isDeferredInit() { return deferredInit; } 17 | void setDeferredInit(bool deferredInit) { this->deferredInit = deferredInit; } 18 | bool isDeferredOutput() { return deferredOutput; } 19 | void setDeferredOutput(bool deferredOutput) { this->deferredOutput = deferredOutput; } 20 | bool isDeferredInput() { return deferredInput; } 21 | void setDeferredInput(bool deferredInput) { this->deferredInput = deferredInput; } 22 | void setPinType(uint8_t pin, PinType pinType) {} 23 | protected: 24 | bool deferredInit = true; 25 | bool deferredOutput = false; 26 | bool deferredInput = false; 27 | }; 28 | 29 | class DigitalOutputDevice { 30 | public: 31 | virtual void digitalWrite(uint8_t pin, bool value) = 0; 32 | }; 33 | 34 | class DigitalInputDevice { 35 | public: 36 | virtual bool digitalRead(uint8_t pin) = 0; 37 | }; 38 | 39 | class AnalogOutputDevice { 40 | public: 41 | virtual void analogWrite(uint8_t pin, uint16_t value) = 0; 42 | }; 43 | 44 | class AnalogInputDevice { 45 | public: 46 | virtual uint16_t analogRead(uint8_t pin) = 0; 47 | }; 48 | 49 | #endif -------------------------------------------------------------------------------- /src/dsp/waveshapes/wavetable/RollOffFunction.h: -------------------------------------------------------------------------------- 1 | #ifndef RollOffFunction_h 2 | #define RollOffFunction_h 3 | 4 | #include 5 | 6 | class RollOffFunction { 7 | public: 8 | virtual float rolloff(int harmonic) = 0; 9 | }; 10 | 11 | class SquareRollOffFunction : public RollOffFunction { 12 | public: 13 | virtual float rolloff(int harmonic) { 14 | if(harmonic % 2 == 0) { 15 | return 0; 16 | } else { 17 | return 1.0 / float(harmonic); 18 | } 19 | } 20 | }; 21 | 22 | class TriangleRollOffFunction : public RollOffFunction { 23 | public: 24 | virtual float rolloff(int harmonic) { 25 | if(harmonic % 2 == 0) { 26 | return 0; 27 | } else { 28 | float amp = 1.0 / float(harmonic*harmonic); 29 | if(harmonic % 4 == 1) { 30 | return amp; 31 | } else { 32 | return amp * -1; 33 | } 34 | } 35 | } 36 | }; 37 | 38 | class RampRollOffFunction : public RollOffFunction { 39 | public: 40 | virtual float rolloff(int harmonic) { 41 | return 1.0 / float(harmonic); 42 | } 43 | }; 44 | 45 | class PulseRollOffFunction : public RollOffFunction { 46 | public: 47 | virtual float rolloff(int harmonic) { 48 | return sinf(float(harmonic)) / float(harmonic); 49 | } 50 | }; 51 | 52 | #endif -------------------------------------------------------------------------------- /src/util/CycleSelectEnum.h: -------------------------------------------------------------------------------- 1 | #ifndef CycleSelectEnum_h 2 | #define CycleSelectEnum_h 3 | 4 | #include "CycleEnum.h" 5 | 6 | template 7 | class CycleSelectEnum : public CycleEnum { 8 | public: 9 | bool autoSelect = true; 10 | bool selecting = false; 11 | T selectValue; 12 | 13 | CycleSelectEnum() : CycleEnum() { 14 | } 15 | 16 | CycleSelectEnum(T value, T last) : CycleEnum(value, last) { 17 | } 18 | 19 | void setAutoSelect(bool autoSelect) { 20 | this->autoSelect = autoSelect; 21 | } 22 | 23 | T cycle(int amount) { 24 | if(!selecting) { 25 | selecting = true; 26 | selectValue = this->value; 27 | } 28 | 29 | if(amount > 0) { 30 | selectValue = static_cast((selectValue + 1)%(this->last + 1)); 31 | } else if(amount < 0) { 32 | selectValue = static_cast(selectValue > 0 ? selectValue - 1 : this->last); 33 | } 34 | 35 | if (autoSelect) { 36 | return select(); 37 | } 38 | 39 | return selectValue; 40 | } 41 | 42 | T getSelectValue() { 43 | return selectValue; 44 | } 45 | 46 | T select() { 47 | selecting = false; 48 | this->value = selectValue; 49 | return this->value; 50 | } 51 | 52 | }; 53 | 54 | #endif -------------------------------------------------------------------------------- /src/dsp/envelope/Envelope.h: -------------------------------------------------------------------------------- 1 | #ifndef Envelope_h 2 | #define Envelope_h 3 | 4 | #include "../base/WaveShape.h" 5 | 6 | namespace eurorack { 7 | 8 | template 9 | class Envelope : public T { 10 | public: 11 | Envelope() {} 12 | void setSustainPhase(float sustainPhase) { this->sustainPhase = sustainPhase; } 13 | float getSustainPhase() { return sustainPhase; } 14 | float getAttackPhase(float value); 15 | float getDecayPhase(float value); 16 | 17 | private: 18 | float sustainPhase; 19 | }; 20 | 21 | template 22 | float Envelope::getAttackPhase(float targetValue) { 23 | float phaseIncrement = 0.01; 24 | float phase = 0; 25 | float value = this->get(phase); 26 | 27 | while(value < targetValue && phase < this->getSustainPhase()) { 28 | value = this->get(phase); 29 | phase += phaseIncrement; 30 | } 31 | 32 | return phase; 33 | } 34 | 35 | template 36 | float Envelope::getDecayPhase(float targetValue) { 37 | float phaseIncrement = 0.01; 38 | float phase = sustainPhase; 39 | float value = this->get(phase); 40 | 41 | while(value > targetValue && phase < this->getLength()) { 42 | value = this->get(phase); 43 | phase += phaseIncrement; 44 | } 45 | 46 | return phase; 47 | } 48 | 49 | } 50 | 51 | #endif -------------------------------------------------------------------------------- /src/dsp/oscillator/PhaseDistortionOscillator.h: -------------------------------------------------------------------------------- 1 | #ifndef PhaseDistortionOscillator_h 2 | #define PhaseDistortionOscillator_h 3 | 4 | #include "WaveOscillator.h" 5 | 6 | namespace eurorack { 7 | 8 | template 9 | class PhaseDistortionOscillator : public WaveOscillator { 10 | public: 11 | PhaseDistortionOscillator() {} 12 | PhaseDistortionOscillator(T waveShape) : WaveOscillator(waveShape) {} 13 | PhaseDistortionOscillator(T waveShape, F distortionFunction) : WaveOscillator(waveShape), distortionFunction(distortionFunction) {} 14 | virtual float process(); 15 | 16 | protected: 17 | F distortionFunction; 18 | }; 19 | 20 | template 21 | float PhaseDistortionOscillator::process() { 22 | if(!WaveOscillator::playing) { 23 | return 0; 24 | } 25 | 26 | float distortedPhase = distortionFunction.get(WaveOscillator::phase); // + phaseOffset; 27 | //TODO mod distortedPhase so value is between 0 and 1 28 | float value = WaveOscillator::waveShape.get(distortedPhase); 29 | if(WaveOscillator::polyblepEnabled) { 30 | value += WaveOscillator::waveShape.polyblep(WaveOscillator::phase, WaveOscillator::increment); 31 | } 32 | WaveOscillator::incrementPhase(); 33 | return value * WaveOscillator::amplitude; 34 | } 35 | 36 | } 37 | 38 | #endif -------------------------------------------------------------------------------- /src/dsp/sample/SampleBuffer.cpp: -------------------------------------------------------------------------------- 1 | #include "SampleBuffer.h" 2 | #include 3 | 4 | using namespace eurorack; 5 | 6 | void SampleBuffer::init(size_t bufferSize, MemPool<>& memPool) { 7 | init(0, 0, bufferSize, memPool); 8 | } 9 | 10 | void SampleBuffer::init(float sampleRate, size_t bufferSize, MemPool<>& memPool) { 11 | init(sampleRate, 0, bufferSize, memPool); 12 | } 13 | 14 | void SampleBuffer::init(float sampleRate, float sampleFrequency, size_t bufferSize, MemPool<>& memPool) { 15 | this->bufferSize = bufferSize; 16 | this->sampleSize = bufferSize; 17 | this->sampleRate = sampleRate; 18 | this->sampleFrequency = sampleFrequency; 19 | this->playbackSampleRate = sampleRate; 20 | buffer = allocateBuffer(bufferSize, &memPool); 21 | reset(); 22 | clear(); 23 | inited = true; 24 | } 25 | 26 | void SampleBuffer::reset() { 27 | writePointer = 0; 28 | sampleFull = false; 29 | bufferFull = false; 30 | } 31 | 32 | void SampleBuffer::clear() { 33 | for(size_t i = 0; i < bufferSize; i++) { 34 | buffer[i] = 0.0; 35 | } 36 | } 37 | 38 | void SampleBuffer::setSampleSize(size_t sampleSize) { 39 | if(sampleSize <= bufferSize) { 40 | this->sampleSize = sampleSize; 41 | } else { 42 | this->sampleSize = bufferSize; 43 | } 44 | } 45 | 46 | float SampleBuffer::calculateReadIncrement(float playbackFrequency) { 47 | return (sampleRate / playbackSampleRate) * (playbackFrequency / sampleFrequency); 48 | } 49 | -------------------------------------------------------------------------------- /src/dsp/sample/SamplePlayer.h: -------------------------------------------------------------------------------- 1 | #ifndef SamplePlayer_h 2 | #define SamplePlayer_h 3 | 4 | #include "SampleBuffer.h" 5 | 6 | namespace eurorack { 7 | 8 | class SamplePlayer 9 | { 10 | public: 11 | SamplePlayer() {} 12 | void init(float sampleRate); 13 | 14 | void setSample(SampleBuffer* sample); 15 | void setFrequency(float frequency); 16 | void setLoop(boolean loop) { this->loop = loop; } 17 | 18 | void play(); 19 | void pause(); 20 | void stop(); 21 | 22 | float process(); 23 | 24 | private: 25 | SampleBuffer* sample = nullptr; 26 | float sampleRate; 27 | 28 | boolean playing = false; 29 | boolean loop = false; 30 | float readIncrement = 1.0; 31 | float frequency = 440; 32 | float readPointer = 0; 33 | 34 | void calcReadIncrement(); 35 | }; 36 | 37 | 38 | inline float SamplePlayer::process() { 39 | if(playing) { 40 | float output = sample->read(readPointer); 41 | readPointer += readIncrement; 42 | if (readPointer >= sample->getSampleSize()) { 43 | readPointer = 0; 44 | if(!loop) { 45 | playing = false; 46 | } 47 | } 48 | return output; 49 | } 50 | return 0; 51 | } 52 | 53 | 54 | } 55 | 56 | #endif 57 | -------------------------------------------------------------------------------- /src/graphics/components/TextComponent.h: -------------------------------------------------------------------------------- 1 | #ifndef TextComponent_h 2 | #define TextComponent_h 3 | 4 | #include 5 | #include "../Component.h" 6 | 7 | 8 | template 9 | class TextComponent : public Component { 10 | 11 | public: 12 | static const int MAX_LENGTH = 32; 13 | static const int DEFAULT_HEIGHT = 10; 14 | 15 | TextComponent(uint16_t width, const char* text = "", uint8_t font = 1, uint16_t colour = 0xFFFF); 16 | virtual void layout(); 17 | virtual void render(); 18 | 19 | void setText(const char* text); 20 | 21 | protected: 22 | char text[MAX_LENGTH]; 23 | uint8_t font; 24 | uint16_t colour; 25 | 26 | }; 27 | 28 | 29 | template 30 | TextComponent::TextComponent(uint16_t width, const char* text, uint8_t font, uint16_t colour) { 31 | this->setHeight(DEFAULT_HEIGHT); 32 | this->setWidth(width); 33 | this->setText(text); 34 | this->font = font; 35 | this->colour = colour; 36 | } 37 | 38 | template 39 | void TextComponent::layout() { 40 | this->setHeight(this->graphicsContext->getFontHeight(font)); 41 | } 42 | 43 | template 44 | void TextComponent::render() { 45 | this->graphicsContext->setFont(font); 46 | this->graphicsContext->setTextColour(colour); 47 | this->graphicsContext->text(&text[0], this->left, this->top); 48 | } 49 | 50 | template 51 | void TextComponent::setText(const char* text) { 52 | strncpy(this->text, text, MAX_LENGTH); 53 | } 54 | 55 | #endif 56 | -------------------------------------------------------------------------------- /src/graphics/Component.h: -------------------------------------------------------------------------------- 1 | #ifndef Component_h 2 | #define Component_h 3 | 4 | #include 5 | #include "GraphicsContext.h" 6 | 7 | template 8 | class Component { 9 | 10 | public: 11 | Component() {} 12 | virtual void setContext(G* graphicsContext); 13 | virtual void layout() {}; 14 | virtual void render() = 0; 15 | 16 | uint16_t getTop() { return top; } 17 | uint16_t getLeft() { return left; } 18 | uint16_t getHeight() { return height; } 19 | uint16_t getWidth() { return width; } 20 | bool isVisibile() { return visible; } 21 | bool isFocusable() { return focusable; } 22 | 23 | void setTop(uint16_t top) { this->top = top; } 24 | void setLeft(uint16_t left) { this->left = left; } 25 | void setHeight(uint16_t height) { this->height = height; } 26 | void setWidth(uint16_t width) { this->width = width; } 27 | void setVisibility(bool visible) { this->visible = visible; } 28 | void setFocusable(bool focusable) { this->focusable = focusable; } 29 | 30 | void setFocus(bool focus) { this->focus = focus; } 31 | 32 | protected: 33 | G* graphicsContext = nullptr; 34 | 35 | uint16_t top = 0; 36 | uint16_t left = 0; 37 | uint16_t height = 0; 38 | uint16_t width = 0; 39 | bool visible = true; 40 | bool focusable = false; 41 | bool focus = false; 42 | 43 | }; 44 | 45 | template 46 | void Component::setContext(G* graphicsContext) { 47 | this->graphicsContext = graphicsContext; 48 | } 49 | 50 | 51 | #endif 52 | -------------------------------------------------------------------------------- /src/io/digitalinputs/PushButton.h: -------------------------------------------------------------------------------- 1 | #ifndef PushButton_h 2 | #define PushButton_h 3 | 4 | #include "../../hardware/device/DevicePin.h" 5 | #include "../../hardware/native/NativeDevice.h" 6 | #include "../../util/Debounce.h" 7 | 8 | template 9 | class PushButton { 10 | 11 | public: 12 | PushButton(DigitalInputPin& input) : input(input) { 13 | input.setPinType(PinType::DIGITAL_INPUT_PULLUP); 14 | } 15 | bool update() { return debounce.update(input.digitalRead()); } 16 | bool released() { return debounce.rose(); } 17 | bool pressed() { return debounce.fell(); } 18 | bool held() { return !debounce.read(); } 19 | unsigned long duration() { return debounce.duration(); } 20 | unsigned long previousDuration() { return debounce.previousDuration(); } 21 | 22 | bool heldFor(int duration) { 23 | if (!held()) { 24 | latch = false; 25 | return false; 26 | } 27 | if (held() && debounce.duration() >= duration && !latch) { 28 | latch = true; 29 | return true; 30 | } 31 | return false; 32 | } 33 | 34 | void waitForPressAndRelease() { 35 | while(!held()) { update(); } 36 | while(held()) { update(); } 37 | } 38 | 39 | protected: 40 | DigitalInputPin& input; 41 | Debounce debounce; 42 | bool latch = false; 43 | 44 | }; 45 | 46 | #endif 47 | -------------------------------------------------------------------------------- /src/test_dsp.cpp: -------------------------------------------------------------------------------- 1 | #ifdef TEST_COMPILE 2 | #include "eurorack.h" 3 | #include "eurorack_dsp.h" 4 | 5 | using namespace eurorack; 6 | 7 | Sine sine; 8 | Pulse pulse; 9 | WaveTable<10, 128> waveTable; 10 | 11 | WaveSelector waveSelector; 12 | 13 | void testWaveSelector() { 14 | waveSelector.select(0); 15 | } 16 | 17 | 18 | WaveInterpolator waveInterpolator; 19 | WaveInterpolator waveInterpolatorRef = WaveInterpolator(sine, pulse); 20 | WaveArrayInterpolator, 5> waveArrayInterpolator; 21 | WaveInterpolator2D, 5, 3> waveInterpolator2D; 22 | WaveInterpolator3D, 5, 3, 2> waveInterpolator3D; 23 | 24 | void testWaveInterpolator() { 25 | waveInterpolator.setInterpolation(0); 26 | waveInterpolator.get(0.5); 27 | waveInterpolatorRef.setInterpolation(0); 28 | waveInterpolatorRef.get(0.5); 29 | waveArrayInterpolator.setInterpolation(0); 30 | waveArrayInterpolator.get(0.5); 31 | waveInterpolator2D.setInterpolationX(0); 32 | waveInterpolator2D.setInterpolationY(0); 33 | waveInterpolator2D.get(0.5); 34 | waveInterpolator3D.setInterpolationX(0); 35 | waveInterpolator3D.setInterpolationY(0); 36 | waveInterpolator3D.setInterpolationZ(0); 37 | waveInterpolator3D.get(0.5); 38 | } 39 | 40 | EnvelopePlayer>> envelopePlayer; 41 | 42 | #endif 43 | -------------------------------------------------------------------------------- /include/README: -------------------------------------------------------------------------------- 1 | 2 | This directory is intended for project header files. 3 | 4 | A header file is a file containing C declarations and macro definitions 5 | to be shared between several project source files. You request the use of a 6 | header file in your project source file (C, C++, etc) located in `src` folder 7 | by including it, with the C preprocessing directive `#include'. 8 | 9 | ```src/main.c 10 | 11 | #include "header.h" 12 | 13 | int main (void) 14 | { 15 | ... 16 | } 17 | ``` 18 | 19 | Including a header file produces the same results as copying the header file 20 | into each source file that needs it. Such copying would be time-consuming 21 | and error-prone. With a header file, the related declarations appear 22 | in only one place. If they need to be changed, they can be changed in one 23 | place, and programs that include the header file will automatically use the 24 | new version when next recompiled. The header file eliminates the labor of 25 | finding and changing all the copies as well as the risk that a failure to 26 | find one copy will result in inconsistencies within a program. 27 | 28 | In C, the usual convention is to give header files names that end with `.h'. 29 | It is most portable to use only letters, digits, dashes, and underscores in 30 | header file names, and at most one dot. 31 | 32 | Read more about using header files in official GCC documentation: 33 | 34 | * Include Syntax 35 | * Include Operation 36 | * Once-Only Headers 37 | * Computed Includes 38 | 39 | https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html 40 | -------------------------------------------------------------------------------- /src/hardware/dac8164/DAC8164Device.h: -------------------------------------------------------------------------------- 1 | #ifndef DAC8164Device_h 2 | #define DAC8164Device_h 3 | 4 | #include 5 | #include "DAC8164.h" 6 | 7 | #define DAC8164_PINCOUNT 4 8 | 9 | #define DAC8164_PINDEF(PIN) AnalogOutputPin(*this, PIN, 14, 5, -5) 10 | 11 | 12 | class DAC8164Device : public DAC8164, public Device, public DigitalOutputDevice, public AnalogOutputDevice { 13 | public: 14 | DAC8164Device(uint8_t enablePin=-1, uint8_t syncPin=-1, uint8_t ldacPin=-1) : 15 | DAC8164(enablePin, syncPin, ldacPin) { 16 | } 17 | 18 | inline void digitalWrite(uint8_t pin, bool value); 19 | inline void analogWrite(uint8_t pin, uint16_t value); 20 | inline void send(); 21 | 22 | AnalogOutputPin pins[DAC8164_PINCOUNT] = { 23 | DAC8164_PINDEF(0), DAC8164_PINDEF(1), DAC8164_PINDEF(2), DAC8164_PINDEF(3) 24 | }; 25 | }; 26 | 27 | void DAC8164Device::digitalWrite(uint8_t pin, bool value) { 28 | writeChannel(pin, 0x3FFF); 29 | } 30 | 31 | void DAC8164Device::analogWrite(uint8_t pin, uint16_t value) { 32 | writeChannel(pin, value); 33 | } 34 | 35 | void DAC8164Device::send() { 36 | for(int i = 0; i < DAC8164_PINCOUNT; i++) { 37 | AnalogOutputPin& pin = pins[i]; 38 | if(pin.getPinType() == PinType::ANALOG_OUTPUT) { 39 | analogWrite(pin.getPin(), pin.getBinaryValue()); 40 | } else if(pin.getPinType() == PinType::DIGITAL_OUTPUT) { 41 | digitalWrite(pin.getPin(), pin.getDigitalValue()); 42 | } 43 | } 44 | } 45 | 46 | #endif 47 | -------------------------------------------------------------------------------- /src/memory/MemPool.h: -------------------------------------------------------------------------------- 1 | #ifndef MemPool_h 2 | #define MemPool_h 3 | 4 | #include 5 | #include 6 | 7 | template 8 | class MemPool { 9 | public: 10 | MemPool(T* poolMem, size_t poolSize) : poolMem(poolMem), poolSize(poolSize) {} 11 | MemPool(size_t poolSize) : poolSize(poolSize) { poolMem = new T[poolSize]; } 12 | T* allocate(size_t size); 13 | void reset(); 14 | private: 15 | T* poolMem; 16 | size_t poolSize; 17 | size_t poolIndex; 18 | }; 19 | 20 | template 21 | T* MemPool::allocate(size_t size) { 22 | if (poolIndex + size >= poolSize) { 23 | Serial.println("Pool memory space exceeded"); 24 | return 0; 25 | } 26 | T* ptr = &poolMem[poolIndex]; 27 | poolIndex += size; 28 | return ptr; 29 | } 30 | 31 | template 32 | void MemPool::reset() { 33 | poolIndex = 0; 34 | } 35 | 36 | 37 | template 38 | T* allocateObject(MemPool<>* memPool = nullptr) { 39 | T* obj = nullptr; 40 | if(memPool == nullptr) { 41 | obj = new T(); 42 | } else { 43 | T* mem = (T*)memPool->allocate(sizeof(T)); 44 | obj = new(mem) T(); 45 | } 46 | return obj; 47 | } 48 | 49 | template 50 | T* allocateBuffer(size_t size, MemPool<>* memPool = nullptr) { 51 | T* buffer = nullptr; 52 | if(memPool == nullptr) { 53 | buffer = new T[size](); 54 | } else { 55 | T* mem = (T*)memPool->allocate(size * sizeof(T)); 56 | buffer = mem; 57 | } 58 | return buffer; 59 | } 60 | 61 | 62 | #endif 63 | -------------------------------------------------------------------------------- /src/eurorack.h: -------------------------------------------------------------------------------- 1 | #ifndef Eurorack_h 2 | #define Eurorack_h 3 | 4 | #include 5 | #include 6 | 7 | #include "debug/Profiler.h" 8 | 9 | #include "util/util.h" 10 | #include "util/stringutil.h" 11 | #include "util/pitchutil.h" 12 | #include "util/Array.h" 13 | #include "util/CycleEnum.h" 14 | #include "util/SlewLimiter.h" 15 | #include "util/TypeSelector.h" 16 | #include "util/LinkedList.h" 17 | 18 | #include "memory/MemPool.h" 19 | 20 | #include "hardware/device/Device.h" 21 | #include "hardware/device/DevicePin.h" 22 | #include "hardware/device/AnalogInputSumPin.h" 23 | #include "hardware/device/AnalogPinCalibration.h" 24 | #include "hardware/native/NativeDevice.h" 25 | 26 | #include "hardware/RotaryEncoder.h" 27 | #include "hardware/RotaryEncoderButton.h" 28 | 29 | #include "io/analoginputs/LinearInput.h" 30 | #include "io/analoginputs/IntegerInput.h" 31 | #include "io/analoginputs/ExpInput.h" 32 | #include "io/analoginputs/PowInput.h" 33 | #include "io/analoginputs/AnalogGateInput.h" 34 | #include "io/analoginputs/FilterInput.h" 35 | #include "io/analoginputs/CrossfadeInput.h" 36 | #include "io/analogoutputs/AnalogTriggerOutput.h" 37 | #include "io/analogoutputs/AnalogGateOutput.h" 38 | #include "io/digitalinputs/GateInput.h" 39 | #include "io/digitalinputs/PushButton.h" 40 | #include "io/digitalinputs/TriggerInput.h" 41 | #include "io/digitaloutputs/TriggerOutput.h" 42 | 43 | #include "controller/SingleEncoderController.h" 44 | #include "controller/DoubleEncoderController.h" 45 | #include "controller/AbstractController.h" 46 | #include "controller/AbstractParameterizedController.h" 47 | 48 | #include "eeprom/Config.h" 49 | 50 | #endif -------------------------------------------------------------------------------- /src/dsp/distortionfunctions/SmoothStepFunction.h: -------------------------------------------------------------------------------- 1 | #ifndef SmoothStepFunction_h 2 | #define SmoothStepFunction_h 3 | 4 | #include "../waveshapes/WaveSequence.h" 5 | #include "../waveshapes/Line.h" 6 | 7 | namespace eurorack { 8 | 9 | //https://www.desmos.com/calculator/a09w9zntai 10 | class SmoothStepFunction : public WaveShape { 11 | public: 12 | SmoothStepFunction(float midGradient = 0, float midPoint = 0.5) { this->midGradient = midGradient; this->midPoint = midPoint; calcCoefficients(); } 13 | void setMidPoint(float midPoint) { this->midPoint = midPoint; calcCoefficients(); } 14 | void setMidGradient(float midGradient) { this->midGradient = midGradient; calcCoefficients(); } 15 | void setParams(float midPoint, float midGradient) { this->midPoint = midPoint; this->midGradient = midGradient; calcCoefficients(); } 16 | virtual float get(float phase); 17 | 18 | private: 19 | float midPoint = 0.5; // 0 - 1 20 | float midGradient = 0; // -1 - 1 21 | float c, d, e; 22 | 23 | void calcCoefficients(); 24 | }; 25 | 26 | inline void SmoothStepFunction::calcCoefficients() { 27 | c = (2.0/(1-midGradient))-1; 28 | d = 1.0/powf(midPoint, c-1); 29 | e = 1.0/powf(1-midPoint, c-1); 30 | } 31 | 32 | inline float SmoothStepFunction::get(float x) { 33 | if(x < 0) { 34 | return 0; 35 | } else if (x > 1) { 36 | return 1; 37 | } else if (x < midPoint) { 38 | return powf(x, c) * d; 39 | } else { 40 | return 1 - (powf(1-x, c) * e); 41 | } 42 | } 43 | 44 | } 45 | 46 | #endif -------------------------------------------------------------------------------- /src/io/analoginputs/FilterInput.h: -------------------------------------------------------------------------------- 1 | #ifndef FilterInput_h 2 | #define FilterInput_h 3 | 4 | #include 5 | #include "AbstractAnalogInput.h" 6 | 7 | template> 8 | class FilterInput : public AbstractAnalogInput { 9 | public: 10 | FilterInput(T& input) : AbstractAnalogInput(input) { 11 | } 12 | 13 | FilterInput(T& input, float zeroFrequency) : AbstractAnalogInput(input) { 14 | this->zeroFrequency = zeroFrequency; 15 | } 16 | 17 | inline bool update() { 18 | if(AbstractAnalogInput::update()) { 19 | if(this->getStableVoltage() > 0.5) { 20 | frequency = zeroFrequency*powf(2, (this->getStableVoltage()*2)-6); 21 | highPass = true; 22 | lowPass = false; 23 | } else if (this->getStableVoltage() < -0.5) { 24 | frequency = zeroFrequency*powf(2, (this->getStableVoltage()*2)+7); 25 | lowPass = true; 26 | highPass = false; 27 | } else { 28 | lowPass = false; 29 | highPass = false; 30 | } 31 | } 32 | return this->isChanged(); 33 | } 34 | 35 | inline float getFrequency() { 36 | return frequency; 37 | } 38 | 39 | inline void setZeroFrequency(float _zeroFrequency) { 40 | zeroFrequency = _zeroFrequency; 41 | } 42 | 43 | inline bool getLowPass() { return lowPass; } 44 | inline bool getHighPass() { return highPass; } 45 | 46 | private: 47 | float zeroFrequency = 523.25; //C5 48 | float frequency; 49 | bool lowPass; 50 | bool highPass; 51 | }; 52 | 53 | #endif -------------------------------------------------------------------------------- /src/dsp/waveshapes/Line.h: -------------------------------------------------------------------------------- 1 | #ifndef Line_h 2 | #define Line_h 3 | 4 | #include "../base/WaveShape.h" 5 | #include "math.h" 6 | 7 | namespace eurorack { 8 | 9 | class Line : public WaveShape { 10 | public: 11 | Line() { setLength(1); } 12 | virtual void setLength(float length); 13 | void setStartValue(float startValue); 14 | void setEndValue(float endValue); 15 | void setStartEndValue(float startValue, float endValue); 16 | virtual float get(float phase); 17 | 18 | protected: 19 | float startValue; 20 | float endValue; 21 | float lengthRecip; 22 | 23 | float gradient; 24 | 25 | void calculateGradient(); 26 | }; 27 | 28 | inline void Line::setLength(float length) { 29 | WaveShape::setLength(length); 30 | lengthRecip = 1 / length; 31 | calculateGradient(); 32 | } 33 | 34 | inline void Line::setStartValue(float startValue) { 35 | this->startValue = startValue; 36 | calculateGradient(); 37 | } 38 | 39 | inline void Line::setEndValue(float endValue) { 40 | this->endValue = endValue; 41 | calculateGradient(); 42 | } 43 | 44 | inline void Line::setStartEndValue(float startValue, float endValue) { 45 | this->startValue = startValue; 46 | this->endValue = endValue; 47 | calculateGradient(); 48 | } 49 | 50 | inline float Line::get(float phase) { 51 | if(isinf(gradient)) { 52 | return endValue; 53 | } else { 54 | return startValue + phase * gradient; 55 | } 56 | } 57 | 58 | inline void Line::calculateGradient() { 59 | gradient = (endValue - startValue) * lengthRecip; 60 | } 61 | 62 | } 63 | 64 | #endif -------------------------------------------------------------------------------- /src/hardware/native/NativeDevice.cpp: -------------------------------------------------------------------------------- 1 | #include "NativeDevice.h" 2 | 3 | #define SERIAL_BAUD 115200 4 | 5 | NativeDevice NativeDevice::instance; 6 | 7 | NativeDevice::NativeDevice() { 8 | setDeferredInit(false); 9 | } 10 | 11 | void NativeDevice::init() { 12 | analogReadResolution(12); 13 | Serial.begin(SERIAL_BAUD); 14 | #if defined(TEENSYDUINO) 15 | for(uint32_t i = 0; i < (sizeof(adc.adc)/sizeof(adc.adc[0])); i++ ) { 16 | adc.adc[i]->setAveraging(4); 17 | adc.adc[i]->setSamplingSpeed(ADC_SAMPLING_SPEED::HIGH_SPEED); 18 | adc.adc[i]->setConversionSpeed(ADC_CONVERSION_SPEED::HIGH_SPEED); 19 | } 20 | #endif 21 | } 22 | 23 | void NativeDevice::setPinType(uint8_t pin, PinType pinType) { 24 | switch(pinType) { 25 | case PinType::ANALOG_OUTPUT: 26 | pinMode(pin, OUTPUT); 27 | break; 28 | case PinType::ANALOG_INPUT: 29 | pinMode(pin, INPUT); 30 | break; 31 | case PinType::DIGITAL_OUTPUT: 32 | pinMode(pin, OUTPUT); 33 | break; 34 | case PinType::DIGITAL_INPUT: 35 | pinMode(pin, INPUT); 36 | break; 37 | case PinType::DIGITAL_INPUT_PULLUP: 38 | pinMode(pin, INPUT_PULLUP); 39 | break; 40 | } 41 | } 42 | 43 | void NativeDevice::digitalWrite(uint8_t pin, bool value) { 44 | ::digitalWrite(pin, value); 45 | } 46 | 47 | bool NativeDevice::digitalRead(uint8_t pin) { 48 | return ::digitalRead(pin); 49 | } 50 | 51 | void NativeDevice::analogWrite(uint8_t pin, uint16_t value) { 52 | ::analogWrite(pin, value); 53 | } 54 | 55 | uint16_t NativeDevice::analogRead(uint8_t pin) { 56 | #if defined(TEENSYDUINO) 57 | return adc.analogRead(pin); 58 | #else 59 | return ::analogRead(pin); 60 | #endif 61 | } 62 | -------------------------------------------------------------------------------- /src/hardware/max11300/MAX11300Device.cpp: -------------------------------------------------------------------------------- 1 | #include "MAX11300Device.h" 2 | 3 | MAX11300Device::MAX11300Device(SPIClass* spi, uint8_t convertPin, uint8_t selectPin) : 4 | MAX11300(spi, convertPin, selectPin) { 5 | } 6 | 7 | void MAX11300Device::init() { 8 | for(int i = 0; i < MAX11300_PINCOUNT; i++) { 9 | initPinType(pins[i].getPin(), pins[i].getPinType()); 10 | } 11 | } 12 | 13 | void MAX11300Device::initPinType(uint8_t pin, PinType pinType) { 14 | switch(pinType) { 15 | case PinType::ANALOG_OUTPUT: 16 | setPinModeAnalogOut(pin, DACNegative5to5); 17 | break; 18 | case PinType::ANALOG_INPUT: 19 | setPinModeAnalogIn(pin, ADCNegative5to5); 20 | break; 21 | case PinType::DIGITAL_OUTPUT: 22 | //TODO 23 | break; 24 | case PinType::DIGITAL_INPUT: 25 | case PinType::DIGITAL_INPUT_PULLUP: 26 | //TODO 27 | break; 28 | } 29 | } 30 | 31 | void MAX11300Device::digitalWrite(uint8_t pin, bool value) { 32 | writeDigitalPin(pin, value); 33 | } 34 | 35 | bool MAX11300Device::digitalRead(uint8_t pin) { 36 | return readDigitalPin(pin); 37 | } 38 | 39 | void MAX11300Device::analogWrite(uint8_t pin, uint16_t value) { 40 | writeAnalogPin(pin, value); 41 | } 42 | 43 | uint16_t MAX11300Device::analogRead(uint8_t pin) { 44 | return readAnalogPin(pin); 45 | } 46 | 47 | void MAX11300Device::send() { 48 | for(int i = 0; i < MAX11300_PINCOUNT; i++) { 49 | AnalogInputOutputPin& pin = pins[i]; 50 | if(pin.getPinType() == PinType::ANALOG_OUTPUT) { 51 | analogWrite(pin.getPin(), pin.getBinaryValue()); 52 | } else if(pin.getPinType() == PinType::DIGITAL_OUTPUT) { 53 | digitalWrite(pin.getPin(), pin.getDigitalValue()); 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/dsp/filter/StateVariableFilter.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "StateVariableFilter.h" 3 | 4 | #define MIN(x, y) (((x) < (y)) ? (x) : (y)) 5 | 6 | void StateVariableFilter::init(float sample_rate) 7 | { 8 | sr_ = sample_rate; 9 | fc_ = 200.0f; 10 | res_ = 0.5f; 11 | freq_ = 0.25f; 12 | damp_ = 0.0f; 13 | notch_ = 0.0f; 14 | low_ = 0.0f; 15 | high_ = 0.0f; 16 | band_ = 0.0f; 17 | peak_ = 0.0f; 18 | input_ = 0.0f; 19 | out_notch_ = 0.0f; 20 | out_low_ = 0.0f; 21 | out_high_ = 0.0f; 22 | out_peak_ = 0.0f; 23 | out_band_ = 0.0f; 24 | } 25 | 26 | void StateVariableFilter::setFrequency(float f) 27 | { 28 | if(f < 0.000001f) 29 | { 30 | fc_ = 0.000001f; 31 | } 32 | else if(f > sr_ / 2.0f) 33 | { 34 | fc_ = (sr_ / 2.0f) - 1.0f; 35 | } 36 | else 37 | { 38 | fc_ = f; 39 | } 40 | // Set Internal Frequency for fc_ 41 | freq_ = 2.0f 42 | * sinf((float)M_PI 43 | * MIN(0.25f, 44 | fc_ / (sr_ * 2.0f))); // fs*2 because double sampled 45 | // recalculate damp 46 | //damp = (MIN(2.0f * powf(res_, 0.25f), MIN(2.0f, 2.0f / freq - freq * 0.5f))); 47 | damp_ = MIN(2.0f * (1.0f - powf(res_, 0.25f)), 48 | MIN(2.0f, 2.0f / freq_ - freq_ * 0.5f)); 49 | } 50 | 51 | void StateVariableFilter::setResonance(float r) 52 | { 53 | if(r < 0.0f) 54 | { 55 | r = 0.0f; 56 | } 57 | else if(r > 1.0f) 58 | { 59 | r = 1.0f; 60 | } 61 | res_ = r; 62 | // recalculate damp 63 | //damp = (MIN(2.0f * powf(res_, 0.25f), MIN(2.0f, 2.0f / freq - freq * 0.5f))); 64 | damp_ = MIN(2.0f * (1.0f - powf(res_, 0.25f)), 65 | MIN(2.0f, 2.0f / freq_ - freq_ * 0.5f)); 66 | } 67 | -------------------------------------------------------------------------------- /src/io/analoginputs/AbstractAnalogInput.h: -------------------------------------------------------------------------------- 1 | #ifndef AbstractAnalogInput_h 2 | #define AbstractAnalogInput_h 3 | 4 | #include 5 | #include 6 | #include "../../hardware/device/DevicePin.h" 7 | #include "../../hardware/native/NativeDevice.h" 8 | #include "../../util/util.h" 9 | #include "../../util/RangeScale.h" 10 | #include "../../dsp/filter/AnalogInputFilter.h" 11 | 12 | #define STABILISE_THRESHOLD 0.005 13 | 14 | template> 15 | class AbstractAnalogInput { 16 | public: 17 | AbstractAnalogInput(T& input) : 18 | input(input) { 19 | input.setPinType(PinType::ANALOG_INPUT); 20 | } 21 | 22 | bool update() { return readVoltage(); } 23 | bool isChanged() { return changed; } 24 | float getVoltage() { return measuredVoltage; } 25 | uint32_t getRawValue() { return value; } 26 | 27 | float getStableVoltage() { 28 | return stableVoltage; 29 | } 30 | 31 | protected: 32 | T& input; 33 | 34 | uint32_t value; 35 | float measuredVoltage; // actual voltage as measured 36 | float smoothedVoltage; // after smoothing (averaging) function 37 | float stableVoltage; // after hysteresis function 38 | 39 | AnalogInputFilter inputFilter = AnalogInputFilter(); 40 | 41 | bool changed; 42 | 43 | bool readVoltage() { 44 | measuredVoltage = input.analogRead(); 45 | smoothedVoltage = inputFilter.process(measuredVoltage); 46 | 47 | float diff = fabsf(smoothedVoltage-stableVoltage); 48 | if(diff > STABILISE_THRESHOLD) { 49 | changed = true; 50 | stableVoltage = smoothedVoltage; 51 | } else { 52 | changed = false; 53 | } 54 | return changed; 55 | } 56 | }; 57 | 58 | #endif -------------------------------------------------------------------------------- /src/util/CycleEnum.h: -------------------------------------------------------------------------------- 1 | #ifndef CycleEnum_h 2 | #define CycleEnum_h 3 | 4 | template 5 | class CycleEnum { 6 | public: 7 | T value; 8 | T last; 9 | 10 | CycleEnum() { 11 | this->value = 0; 12 | this->last = 0; 13 | } 14 | 15 | CycleEnum(T value, T last) { 16 | this->value = value; 17 | this->last = last; 18 | } 19 | 20 | T cycle(int amount) { 21 | if(amount > 0) { 22 | value = static_cast((value + 1)%(last + 1)); 23 | } else if(amount < 0) { 24 | value = static_cast(value > 0 ? value - 1 : last); 25 | } 26 | return value; 27 | } 28 | 29 | T setValue(T value) { 30 | if(value > last) { 31 | this->value = value % (last + 1); 32 | } else if (value < 0) { 33 | this->value = value + (last + 1); 34 | } else { 35 | this->value = value; 36 | } 37 | 38 | return value; 39 | } 40 | 41 | T getValue() { 42 | return value; 43 | } 44 | 45 | const T operator=(const T& other) { 46 | return setValue(other); 47 | } 48 | 49 | const T operator-(const T& other) const { 50 | T returnValue = value - other; 51 | if(returnValue < 0) { 52 | return returnValue + (last + 1); 53 | } else { 54 | return returnValue; 55 | } 56 | } 57 | 58 | const T operator+(const T& other) const { 59 | T returnValue = value + other; 60 | if(returnValue > last) { 61 | return returnValue % (last + 1); 62 | } else { 63 | return returnValue; 64 | } 65 | } 66 | }; 67 | 68 | #endif -------------------------------------------------------------------------------- /src/dsp/waveshapes/WaveSequence.h: -------------------------------------------------------------------------------- 1 | #ifndef WaveSequence_h 2 | #define WaveSequence_h 3 | 4 | #include "../base/WaveShape.h" 5 | #include "math.h" 6 | 7 | namespace eurorack { 8 | 9 | template 10 | class WaveSequence : public WaveShape { 11 | public: 12 | WaveSequence(); 13 | T& segment(int i); 14 | void setSegmentLength(int i, float length); 15 | virtual float get(float phase); 16 | 17 | protected: 18 | T segments[N]; 19 | 20 | void calculateTotalLength(); 21 | }; 22 | 23 | template 24 | WaveSequence::WaveSequence() { 25 | for(int i = 0; i< N; i++) { 26 | segment(i).setLength(1.0f/N); 27 | } 28 | } 29 | 30 | template 31 | inline T& WaveSequence::segment(int i) { 32 | return segments[i]; 33 | } 34 | 35 | template 36 | inline void WaveSequence::setSegmentLength(int i, float length) { 37 | segments[i].setLength(length); 38 | calculateTotalLength(); 39 | } 40 | 41 | template 42 | inline void WaveSequence::calculateTotalLength() { 43 | float totalLength = 0; 44 | for(T& segment : segments) { 45 | totalLength += segment.getLength(); 46 | } 47 | setLength(totalLength); 48 | } 49 | 50 | template 51 | inline float WaveSequence::get(float phase) { 52 | float segmentStart = 0; 53 | float segmentEnd = 0; 54 | for(T& segment : segments) { 55 | segmentEnd += segment.getLength(); 56 | if(phase < segmentEnd) { 57 | return segment.get(phase - segmentStart); 58 | } 59 | segmentStart = segmentEnd; 60 | } 61 | return segments[N-1].get(segments[N-1].getLength()); 62 | } 63 | 64 | } 65 | 66 | #endif -------------------------------------------------------------------------------- /src/io/analogoutputs/AnalogTriggerOutput.h: -------------------------------------------------------------------------------- 1 | #ifndef AnalogTriggerOutput_h 2 | #define AnalogTriggerOutput_h 3 | 4 | #include "../../hardware/device/DevicePin.h" 5 | #include "../../hardware/native/NativeDevice.h" 6 | #include "../../util/Timer.h" 7 | 8 | template 9 | class AnalogTriggerOutput { 10 | 11 | public: 12 | AnalogTriggerOutput(AnalogOutputPin& output, unsigned long durationMicros = 20000, float triggerVoltage = 5, float zeroVoltage = 0) : output(output) { 13 | this->duration = durationMicros; 14 | this->triggerVoltage = triggerVoltage; 15 | this->zeroVoltage = zeroVoltage; 16 | } 17 | 18 | void setDuration(unsigned long durationMillis) { 19 | duration = durationMillis; 20 | } 21 | 22 | void update() { 23 | if(triggered && timer.hasJustStopped()) { 24 | triggered = false; 25 | output.analogWrite(zeroVoltage); 26 | } 27 | } 28 | 29 | void trigger() { 30 | triggered = true; 31 | output.analogWrite(triggerVoltage); 32 | timer.start(duration); 33 | } 34 | 35 | void setVoltage(float voltage) { 36 | output.analogWrite(voltage); 37 | } 38 | 39 | void setTriggerDurationMicros(unsigned long duration) { this->duration = duration; } 40 | void setTriggerVoltage(float triggerVoltage) { this->triggerVoltage = triggerVoltage; } 41 | 42 | void setZeroVoltage(float zeroVoltage) { 43 | this->zeroVoltage = zeroVoltage; 44 | if(timer.isStopped()) { 45 | output.analogWrite(zeroVoltage); 46 | } 47 | } 48 | 49 | protected: 50 | AnalogOutputPin& output; 51 | bool triggered; 52 | unsigned long duration; 53 | float triggerVoltage; 54 | float zeroVoltage; 55 | Timer timer; 56 | 57 | }; 58 | 59 | #endif 60 | -------------------------------------------------------------------------------- /src/graphics/components/MessageBoxComponent.h: -------------------------------------------------------------------------------- 1 | #ifndef MessageBoxComponent_h 2 | #define MessageBoxFieldComponent_h 3 | 4 | #include 5 | #include "TextComponent.h" 6 | #include "../Component.h" 7 | 8 | 9 | template 10 | class MessageBoxComponent : public Component { 11 | 12 | public: 13 | MessageBoxComponent(uint16_t width, uint16_t height, uint8_t font = 1, uint16_t colourText = 0xFFFF); 14 | virtual void layout(); 15 | virtual void render(); 16 | void showMessage(const char* message); 17 | 18 | protected: 19 | uint8_t font; 20 | uint16_t colourText; 21 | 22 | const char* message; 23 | uint16_t textLeft; 24 | uint16_t textTop; 25 | }; 26 | 27 | 28 | template 29 | MessageBoxComponent::MessageBoxComponent(uint16_t width, uint16_t height, uint8_t font, uint16_t colourText) { 30 | this->setHeight(height); 31 | this->setWidth(width); 32 | this->font = font; 33 | this->colourText = colourText; 34 | } 35 | 36 | template 37 | void MessageBoxComponent::layout() { 38 | this->setLeft((this->graphicsContext->getWidth() - this->getWidth()) * 0.5); 39 | this->setTop((this->graphicsContext->getHeight() - this->getHeight()) * 0.5); 40 | textTop = (this->graphicsContext->getHeight() - this->graphicsContext->getFontHeight(font)) * 0.5; 41 | } 42 | 43 | template 44 | void MessageBoxComponent::render() { 45 | this->graphicsContext->fillRectangle(this->left, this->top, this->width, this->height, 0); 46 | this->graphicsContext->drawRectangle(this->left, this->top, this->width, this->height, colourText); 47 | this->graphicsContext->setFont(font); 48 | this->graphicsContext->setTextColour(colourText); 49 | this->graphicsContext->text(message, textLeft, textTop); 50 | } 51 | 52 | template 53 | void MessageBoxComponent::showMessage(const char* message) { 54 | this->message = message; 55 | textLeft = (this->graphicsContext->getWidth() - (strlen(message)*this->graphicsContext->getFontWidth(font))) * 0.5; 56 | render(); 57 | } 58 | 59 | #endif -------------------------------------------------------------------------------- /src/debug/Profiler.h: -------------------------------------------------------------------------------- 1 | #ifndef Profiler_h 2 | #define Profiler_h 3 | 4 | #include 5 | 6 | #ifdef PROFILE_PROCESS 7 | #define PROFILE_START Profiler::profiler.start(); 8 | #define PROFILE_END Profiler::profiler.end(); 9 | #define PROFILE_PRINT if(Profiler::profiler.isFull()) { Profiler::profiler.printAverageTime(); } 10 | #else 11 | #define PROFILE_START 12 | #define PROFILE_END 13 | #define PROFILE_PRINT 14 | #endif 15 | 16 | #define PROFILER_SAMPLES 1000 17 | 18 | #ifdef PROFLE_CYCLES 19 | #define TIME ARM_DWT_CYCCNT 20 | #else 21 | #define TIME micros() 22 | #endif 23 | 24 | class Profiler { 25 | public: 26 | static Profiler profiler; 27 | 28 | Profiler() {} 29 | 30 | inline void start() { 31 | startTime = TIME; 32 | } 33 | 34 | inline void end() { 35 | if(!full) { 36 | unsigned long endTime = TIME; 37 | if(endTime > startTime) { 38 | times[position++] = endTime - startTime; 39 | if(position >= PROFILER_SAMPLES) { 40 | position = 0; 41 | full = true; 42 | } 43 | } 44 | } 45 | } 46 | 47 | inline bool isFull() { 48 | return full; 49 | } 50 | 51 | inline void printAverageTime() { 52 | unsigned long average = 0; 53 | for(int i = 0; i < PROFILER_SAMPLES; i++) { 54 | average += times[i]; 55 | } 56 | average = average / PROFILER_SAMPLES; 57 | Serial.print("Average time: "); 58 | Serial.println(average); 59 | full = false; 60 | position = 0; 61 | } 62 | 63 | private: 64 | unsigned long startTime; 65 | unsigned long times[PROFILER_SAMPLES]; 66 | bool full; 67 | int position = 0; 68 | 69 | }; 70 | 71 | #endif -------------------------------------------------------------------------------- /src/controller/AbstractParameterizedController.h: -------------------------------------------------------------------------------- 1 | #ifndef AbstractParameterizedController_h 2 | #define AbstractParameterizedController_h 3 | 4 | #include 5 | #include "AbstractController.h" 6 | #include "../util/CycleSelectEnum.h" 7 | 8 | template 9 | class AbstractParameterizedController { 10 | public: 11 | AbstractParameterizedController() {} 12 | void configParam(int param, int defaultval, int maxval, bool autoSelect = true); 13 | int getParameterValue(int index) { return parameters[index].value; } 14 | void load(); 15 | void save(); 16 | int cycleParameter(int amount); 17 | void cycleValue(int amount); 18 | 19 | protected: 20 | ArraySelector, N> parameters; 21 | 22 | struct SaveParameters { 23 | uint8_t check = 0; 24 | int parameters[N]; 25 | }; 26 | ConfigField config; 27 | }; 28 | 29 | 30 | template 31 | void AbstractParameterizedController::configParam(int index, int defaultval, int maxval, bool autoSelect) { 32 | parameters[index].setAutoSelect(autoSelect); 33 | parameters[index].last = maxval; 34 | if(config.data.check == 0 && config.data.parameters[index] <= maxval) { 35 | parameters[index].value = config.data.parameters[index]; 36 | } else { 37 | parameters[index].value = defaultval; 38 | } 39 | } 40 | 41 | template 42 | void AbstractParameterizedController::load() { 43 | Config::config.load(config); 44 | } 45 | 46 | template 47 | void AbstractParameterizedController::save() { 48 | config.data.check = 0; 49 | for(int i = 0; i < N; i++) { 50 | config.data.parameters[i] = parameters[i].value; 51 | } 52 | Config::config.save(config); 53 | } 54 | 55 | template 56 | int AbstractParameterizedController::cycleParameter(int amount) { 57 | parameters.cycle(amount); 58 | return parameters.getSelectedIndex(); 59 | } 60 | 61 | template 62 | void AbstractParameterizedController::cycleValue(int amount) { 63 | parameters.getSelected().cycle(amount); 64 | save(); 65 | } 66 | 67 | #endif -------------------------------------------------------------------------------- /src/hardware/is32fl3738/IS32FL3738Device.h: -------------------------------------------------------------------------------- 1 | #ifndef IS32FL3738Device_h 2 | #define IS32FL3738Device_h 3 | 4 | #include "../device/DevicePin.h" 5 | #include 6 | 7 | #define IS32_ADDRESS_A 0b1010000 8 | #define IS32_ADDRESS_B 0b1010101 9 | #define IS32_ADDRESS_C 0b1011010 10 | #define IS32_ADDRESS_D 0b1011111 11 | 12 | #define IS32FL3738_PINCOUNT 48 13 | #define IS32FL3738_PINDEF(PIN) AnalogOutputPin(*this, PIN, 8, 0, 1) 14 | 15 | 16 | class IS32FL3738Device : public Device, public DigitalOutputDevice, public AnalogOutputDevice { 17 | public: 18 | IS32FL3738Device(TwoWire& wire, uint8_t sdbPin, uint8_t address = IS32_ADDRESS_A); 19 | void init(); 20 | void digitalWrite(uint8_t pin, bool value); 21 | void analogWrite(uint8_t pin, uint16_t value); 22 | 23 | AnalogOutputPin pins[IS32FL3738_PINCOUNT] = { 24 | IS32FL3738_PINDEF(0), IS32FL3738_PINDEF(1), IS32FL3738_PINDEF(2), IS32FL3738_PINDEF(3), IS32FL3738_PINDEF(4), IS32FL3738_PINDEF(5), 25 | IS32FL3738_PINDEF(6), IS32FL3738_PINDEF(7), IS32FL3738_PINDEF(8), IS32FL3738_PINDEF(9), IS32FL3738_PINDEF(10),IS32FL3738_PINDEF(11), 26 | IS32FL3738_PINDEF(12),IS32FL3738_PINDEF(13),IS32FL3738_PINDEF(14),IS32FL3738_PINDEF(15),IS32FL3738_PINDEF(16),IS32FL3738_PINDEF(17), 27 | IS32FL3738_PINDEF(18),IS32FL3738_PINDEF(19),IS32FL3738_PINDEF(20),IS32FL3738_PINDEF(21),IS32FL3738_PINDEF(22),IS32FL3738_PINDEF(23), 28 | IS32FL3738_PINDEF(24),IS32FL3738_PINDEF(25),IS32FL3738_PINDEF(26),IS32FL3738_PINDEF(27),IS32FL3738_PINDEF(28),IS32FL3738_PINDEF(29), 29 | IS32FL3738_PINDEF(30),IS32FL3738_PINDEF(31),IS32FL3738_PINDEF(32),IS32FL3738_PINDEF(33),IS32FL3738_PINDEF(34),IS32FL3738_PINDEF(35), 30 | IS32FL3738_PINDEF(36),IS32FL3738_PINDEF(37),IS32FL3738_PINDEF(38),IS32FL3738_PINDEF(39),IS32FL3738_PINDEF(40),IS32FL3738_PINDEF(41), 31 | IS32FL3738_PINDEF(42),IS32FL3738_PINDEF(43),IS32FL3738_PINDEF(44),IS32FL3738_PINDEF(45),IS32FL3738_PINDEF(46),IS32FL3738_PINDEF(47) 32 | }; 33 | 34 | private: 35 | TwoWire& wire; 36 | uint8_t address; 37 | uint8_t sdbPin; 38 | 39 | void setOnOffRegisters(); 40 | void writeRegister(uint8_t reg, uint8_t data); 41 | void selectPage(uint8_t page); 42 | }; 43 | 44 | #endif 45 | -------------------------------------------------------------------------------- /src/util/stringutil.h: -------------------------------------------------------------------------------- 1 | #ifndef stringutil_h 2 | #define stringutil_h 3 | 4 | #include 5 | 6 | inline bool beginsWith(const char* str, const char* beginning) { 7 | return strncmp(str, beginning, strlen(beginning)) == 0; 8 | } 9 | 10 | inline bool endsWith(const char* str, const char* ending) { 11 | size_t strLength = strlen(str); 12 | size_t endingLength = strlen(ending); 13 | if (endingLength > strLength) { 14 | return false; 15 | } 16 | const char* strEnd = str + strLength - endingLength; 17 | return strcmp(strEnd, ending) == 0; 18 | } 19 | 20 | inline bool wildcardMatch(const char *wild, const char *string) { 21 | const char *cp = NULL, *mp = NULL; 22 | 23 | while ((*string) && (*wild != '*')) { 24 | if ((*wild != *string) && (*wild != '?')) { 25 | return false; 26 | } 27 | wild++; 28 | string++; 29 | } 30 | 31 | while (*string) { 32 | if (*wild == '*') { 33 | if (!*++wild) { 34 | return true; 35 | } 36 | mp = wild; 37 | cp = string+1; 38 | } else if ((*wild == *string) || (*wild == '?')) { 39 | wild++; 40 | string++; 41 | } else { 42 | wild = mp; 43 | string = cp++; 44 | } 45 | } 46 | 47 | while (*wild == '*') { 48 | wild++; 49 | } 50 | return !*wild; 51 | } 52 | 53 | 54 | /* 55 | * The memmem() function finds the start of the first occurrence of the 56 | * substring 'needle' of length 'nlen' in the memory area 'haystack' of 57 | * length 'hlen'. 58 | * 59 | * The return value is a pointer to the beginning of the sub-string, or 60 | * NULL if the substring is not found. 61 | */ 62 | inline const char* memmem(const char *haystack, size_t hlen, const char *needle, size_t nlen) { 63 | int needle_first; 64 | const char *p = haystack; 65 | size_t plen = hlen; 66 | 67 | if (!nlen) 68 | return nullptr; 69 | 70 | needle_first = *(unsigned char *)needle; 71 | 72 | while (plen >= nlen && (p = (const char*)memchr(p, needle_first, plen - nlen + 1))) 73 | { 74 | if (!memcmp(p, needle, nlen)) 75 | return p; 76 | 77 | p++; 78 | plen = hlen - (p - haystack); 79 | } 80 | 81 | return nullptr; 82 | } 83 | 84 | 85 | #endif -------------------------------------------------------------------------------- /src/hardware/native/NativeDevice.h: -------------------------------------------------------------------------------- 1 | #ifndef NativeDevice_h 2 | #define NativeDevice_h 3 | 4 | #include "../device/DevicePin.h" 5 | 6 | #if defined(TEENSYDUINO) 7 | #include 8 | #endif 9 | 10 | #define NATIVE NativeDevice::instance 11 | #define GET_MACRO(_1, _2, NAME,...) NAME 12 | #define GET_MACRO3(_1, _2, _3, NAME,...) NAME 13 | 14 | #define AnalogInput1(PIN) AnalogInputPin<> PIN = AnalogInputPin<>(NativeDevice::instance, ::PIN); 15 | #define AnalogInput2(NAME, PIN) AnalogInputPin<> NAME = AnalogInputPin<>(NativeDevice::instance, ::PIN); 16 | #define AnalogInput(...) GET_MACRO(__VA_ARGS__, AnalogInput2, AnalogInput1)(__VA_ARGS__) 17 | 18 | #define AnalogOutput1(PIN) AnalogOutputPin<> PIN = AnalogOutputPin<>(NativeDevice::instance, ::PIN); 19 | #define AnalogOutput2(NAME, PIN) AnalogOutputPin<> NAME = AnalogOutputPin<>(NativeDevice::instance, ::PIN); 20 | #define AnalogOutput(...) GET_MACRO(__VA_ARGS__, AnalogOutput2, AnalogOutput1)(__VA_ARGS__) 21 | 22 | #define DigitalInput1(PIN) DigitalInputPin<> D ## PIN = DigitalInputPin<>(NativeDevice::instance, PIN); 23 | #define DigitalInput2(NAME, PIN) DigitalInputPin<> NAME = DigitalInputPin<>(NativeDevice::instance, PIN); 24 | #define DigitalInput3(NAME, PIN, INVERT) DigitalInputPin<> NAME = DigitalInputPin<>(NativeDevice::instance, PIN, INVERT); 25 | #define DigitalInput(...) GET_MACRO3(__VA_ARGS__, DigitalInput3, DigitalInput2, DigitalInput1)(__VA_ARGS__) 26 | 27 | #define DigitalOutput1(PIN) DigitalOutputPin<> D ## PIN = DigitalOutputPin<>(NativeDevice::instance, PIN); 28 | #define DigitalOutput2(NAME, PIN) DigitalOutputPin<> NAME = DigitalOutputPin<>(NativeDevice::instance, PIN); 29 | #define DigitalOutput(...) GET_MACRO(__VA_ARGS__, DigitalOutput2, DigitalOutput1)(__VA_ARGS__) 30 | 31 | class NativeDevice: public Device, public DigitalOutputDevice, public DigitalInputDevice, public AnalogOutputDevice, public AnalogInputDevice { 32 | public: 33 | static NativeDevice instance; 34 | NativeDevice(); 35 | void init(); 36 | void setPinType(uint8_t pin, PinType pinType); 37 | void digitalWrite(uint8_t pin, bool value); 38 | bool digitalRead(uint8_t pin); 39 | void analogWrite(uint8_t pin, uint16_t value); 40 | uint16_t analogRead(uint8_t pin); 41 | 42 | private: 43 | #if defined(TEENSYDUINO) 44 | ADC adc; 45 | #endif 46 | }; 47 | 48 | #endif 49 | -------------------------------------------------------------------------------- /src/util/RangeScale.h: -------------------------------------------------------------------------------- 1 | #ifndef RangeScale_h 2 | #define RangeScale_h 3 | 4 | #include 5 | 6 | class RangeScale { 7 | public: 8 | RangeScale(float fromMin, float fromMax, float toMin, float toMax) { 9 | this->fromMin = fromMin; 10 | this->fromMax = fromMax; 11 | this->toMin = toMin; 12 | this->toMax = toMax; 13 | calcFactor(); 14 | } 15 | 16 | float convert(float fromValue) { 17 | if(fromValue < fromMin) { 18 | fromValue = fromMin; 19 | } else if (fromValue > fromMax) { 20 | fromValue = fromMax; 21 | } 22 | return ((fromValue - fromMin) * factor) + toMin; 23 | } 24 | 25 | float convertReverse(float toValue) { 26 | float fromValue = ((toValue - toMin) * reverseFactor) + fromMin; 27 | if(fromValue < fromMin) { 28 | fromValue = fromMin; 29 | } else if (fromValue > fromMax) { 30 | fromValue = fromMax; 31 | } 32 | return fromValue; 33 | } 34 | 35 | void setInputRange(float fromMin, float fromMax) { 36 | this->fromMin = fromMin; 37 | this->fromMax = fromMax; 38 | calcFactor(); 39 | } 40 | 41 | void setOutputRange(float toMin, float toMax) { 42 | this->toMin = toMin; 43 | this->toMax = toMax; 44 | calcFactor(); 45 | } 46 | 47 | float getFromMin() { return fromMin; } 48 | float getFromMax() { return fromMax; } 49 | float getToMin() { return toMin; } 50 | float getToMax() { return toMax; } 51 | 52 | void offset(float amount) { 53 | toMin += amount; 54 | toMax += amount; 55 | calcFactor(); 56 | } 57 | 58 | void scale(float amount) { 59 | toMin += amount; 60 | toMax -= amount; 61 | calcFactor(); 62 | } 63 | 64 | private: 65 | float fromMin, fromMax, toMin, toMax; 66 | float fromRange, toRange, factor, reverseFactor; 67 | 68 | void calcFactor() { 69 | fromRange = fromMax - fromMin; 70 | toRange = toMax - toMin; 71 | factor = toRange / fromRange; 72 | reverseFactor = fromRange / toRange; 73 | } 74 | }; 75 | 76 | #endif -------------------------------------------------------------------------------- /src/hardware/max11300/MAX11300Device.h: -------------------------------------------------------------------------------- 1 | #ifndef MAX11300Device_h 2 | #define MAX11300Device_h 3 | 4 | #include "../device/DevicePin.h" 5 | #include "MAX11300.h" 6 | 7 | #define MAX11300_PINCOUNT 20 8 | 9 | class MAX11300Device: public MAX11300, public Device, public DigitalOutputDevice, public DigitalInputDevice, public AnalogOutputDevice, public AnalogInputDevice { 10 | public: 11 | MAX11300Device(SPIClass* spi, uint8_t convertPin, uint8_t selectPin); 12 | void init(); 13 | void digitalWrite(uint8_t pin, bool value); 14 | bool digitalRead(uint8_t pin); 15 | void analogWrite(uint8_t pin, uint16_t value); 16 | uint16_t analogRead(uint8_t pin); 17 | void send(); 18 | 19 | AnalogInputOutputPin pins[MAX11300_PINCOUNT] = { 20 | AnalogInputOutputPin(*this, 0, 12, -5, 5), 21 | AnalogInputOutputPin(*this, 1, 12, -5, 5), 22 | AnalogInputOutputPin(*this, 2, 12, -5, 5), 23 | AnalogInputOutputPin(*this, 3, 12, -5, 5), 24 | AnalogInputOutputPin(*this, 4, 12, -5, 5), 25 | AnalogInputOutputPin(*this, 5, 12, -5, 5), 26 | AnalogInputOutputPin(*this, 6, 12, -5, 5), 27 | AnalogInputOutputPin(*this, 7, 12, -5, 5), 28 | AnalogInputOutputPin(*this, 8, 12, -5, 5), 29 | AnalogInputOutputPin(*this, 9, 12, -5, 5), 30 | AnalogInputOutputPin(*this, 10, 12, -5, 5), 31 | AnalogInputOutputPin(*this, 11, 12, -5, 5), 32 | AnalogInputOutputPin(*this, 12, 12, -5, 5), 33 | AnalogInputOutputPin(*this, 13, 12, -5, 5), 34 | AnalogInputOutputPin(*this, 14, 12, -5, 5), 35 | AnalogInputOutputPin(*this, 15, 12, -5, 5), 36 | AnalogInputOutputPin(*this, 16, 12, -5, 5), 37 | AnalogInputOutputPin(*this, 17, 12, -5, 5), 38 | AnalogInputOutputPin(*this, 18, 12, -5, 5), 39 | AnalogInputOutputPin(*this, 19, 12, -5, 5) 40 | }; 41 | 42 | private: 43 | void initPinType(uint8_t pin, PinType pinType); 44 | }; 45 | 46 | #endif 47 | -------------------------------------------------------------------------------- /src/hardware/device/AnalogPinCalibration.h: -------------------------------------------------------------------------------- 1 | #ifndef AnalogPinCalibration_h 2 | #define AnalogPinCalibration_h 3 | 4 | #include 5 | 6 | class AnalogPinCalibration { 7 | public: 8 | AnalogPinCalibration() { 9 | } 10 | 11 | void calibratePin(AbstractAnalogPin* pin) { 12 | this->analogPin = pin; 13 | increment = analogPin->getScale().convert(0) - analogPin->getScale().convert(1); 14 | } 15 | 16 | AbstractAnalogPin* getPin() { 17 | return analogPin; 18 | } 19 | 20 | void offset(int amount) { 21 | analogPin->getCalibratedScale().offset(increment * amount); 22 | } 23 | 24 | void scale(int amount) { 25 | analogPin->getCalibratedScale().scale(increment * amount); 26 | } 27 | 28 | void reset() { 29 | analogPin->getCalibratedScale().setInputRange(analogPin->getScale().getFromMin(), analogPin->getScale().getFromMax()); 30 | analogPin->getCalibratedScale().setOutputRange(analogPin->getScale().getToMin(), analogPin->getScale().getToMax()); 31 | } 32 | 33 | float convertReverse(float value) { 34 | return analogPin->getCalibratedScale().convertReverse(value); 35 | } 36 | 37 | float convert(float value) { 38 | return analogPin->getCalibratedScale().convert(value); 39 | } 40 | 41 | int16_t getDigitalOffset() { 42 | int16_t zeroPoint = analogPin->getScale().convertReverse(0); 43 | int16_t calibratedZeroPoint = analogPin->getCalibratedScale().convertReverse(0); 44 | return calibratedZeroPoint - zeroPoint; 45 | } 46 | 47 | int16_t getDigitalScale() { 48 | int16_t positivePoint = analogPin->getScale().convertReverse(1); 49 | int16_t negativePoint = analogPin->getScale().convertReverse(-1); 50 | int16_t calibratedPositivePoint = analogPin->getCalibratedScale().convertReverse(1); 51 | int16_t calibratedNegativePoint = analogPin->getCalibratedScale().convertReverse(-1); 52 | int16_t scale = negativePoint - positivePoint; 53 | int16_t calibratedScale = calibratedNegativePoint - calibratedPositivePoint; 54 | return calibratedScale - scale; 55 | } 56 | 57 | private: 58 | AbstractAnalogPin* analogPin = nullptr; 59 | float increment = 0; 60 | }; 61 | 62 | #endif -------------------------------------------------------------------------------- /src/dsp/envelope/EnvelopePlayer.h: -------------------------------------------------------------------------------- 1 | #ifndef EnvelopePlayer_h 2 | #define EnvelopePlayer_h 3 | 4 | #include 5 | #include "Envelope.h" 6 | #include "../oscillator/WaveOscillator.h" 7 | 8 | namespace eurorack { 9 | 10 | template> 11 | class EnvelopePlayer : public WaveOscillator { 12 | public: 13 | EnvelopePlayer() {} 14 | EnvelopePlayer(T waveShape): WaveOscillator(waveShape) {} 15 | void init(float sampleRate, bool repeat = false); 16 | void setGate(bool value); 17 | virtual float process(); 18 | 19 | private: 20 | enum State { OFF, ATTACK, SUSTAIN, DECAY }; 21 | State state = State::OFF; 22 | bool gate; 23 | float value; 24 | }; 25 | 26 | template 27 | void EnvelopePlayer::init(float sampleRate, bool repeat) { 28 | WaveOscillator::init(sampleRate, repeat); 29 | WaveOscillator::increment = WaveOscillator::sampleTime; 30 | } 31 | 32 | template 33 | void EnvelopePlayer::setGate(bool gate) { 34 | if(gate == this->gate) { 35 | return; 36 | } 37 | this->gate = gate; 38 | if(gate) { 39 | state = State::ATTACK; 40 | WaveOscillator::phase = WaveOscillator::waveShape.getAttackPhase(value); 41 | WaveOscillator::playing = true; 42 | } else { 43 | state = State::DECAY; 44 | WaveOscillator::phase = WaveOscillator::waveShape.getDecayPhase(value); 45 | WaveOscillator::playing = true; 46 | } 47 | } 48 | 49 | template 50 | float EnvelopePlayer::process() { 51 | if(state == State::SUSTAIN) { 52 | WaveOscillator::phase = WaveOscillator::waveShape.getSustainPhase(); 53 | } 54 | 55 | value = WaveOscillator::waveShape.get(WaveOscillator::phase); 56 | 57 | if(WaveOscillator::playing) { 58 | WaveOscillator::incrementPhase(); 59 | if(gate && WaveOscillator::phase >= WaveOscillator::waveShape.getSustainPhase()) { 60 | state = State::SUSTAIN; 61 | WaveOscillator::playing = false; 62 | } else if(!gate && WaveOscillator::phase > WaveOscillator::waveShape.getLength()) { 63 | state = State::OFF; 64 | WaveOscillator::playing = false; 65 | } 66 | } 67 | 68 | return value; 69 | } 70 | 71 | } 72 | 73 | #endif -------------------------------------------------------------------------------- /src/hardware/is32fl3738/IS32FL3738Device.cpp: -------------------------------------------------------------------------------- 1 | #include "IS32FL3738Device.h" 2 | 3 | #define IS32_I2C_SPEED 3400000 4 | 5 | // Global Registers 6 | #define IS32_REG_GLOBAL_PAGE 0xFD 7 | #define IS32_REG_GLOBAL_UNLOCK 0xFE 8 | 9 | // Page 3 registers 10 | #define IS32_REG_GLOBAL_CONFIG 0x00 11 | #define IS32_REG_GLOBAL_CURRENT_CONTROL 0x01 12 | 13 | // Page 0 Registers 14 | #define IS32_REG_LED_ON_OFF_START 0x00 15 | 16 | 17 | // IS32_REG_GLOBAL_CONFIG flags 18 | #define IS32_REG_GLOBAL_CONFIG_SSD_RUN 0x01 19 | 20 | // IS32_REG_GLOBAL_UNLOCK values 21 | #define IS32_REG_GLOBAL_UNLOCK_PAGE_UNLOCK 0xC5 //Unlock page selection 22 | 23 | 24 | IS32FL3738Device::IS32FL3738Device(TwoWire& wire, uint8_t sdbPin, uint8_t address) : 25 | wire(wire), address(address) { 26 | this->sdbPin = sdbPin; 27 | } 28 | 29 | void IS32FL3738Device::init() { 30 | wire.begin(); 31 | wire.setClock(IS32_I2C_SPEED); 32 | 33 | selectPage(3); 34 | writeRegister(IS32_REG_GLOBAL_CONFIG, IS32_REG_GLOBAL_CONFIG_SSD_RUN); 35 | writeRegister(IS32_REG_GLOBAL_CURRENT_CONTROL, 0xFF); 36 | setOnOffRegisters(); 37 | 38 | pinMode(sdbPin, OUTPUT); 39 | ::digitalWrite(sdbPin, true); 40 | } 41 | 42 | void IS32FL3738Device::digitalWrite(uint8_t pin, bool value) { 43 | analogWrite(pin, value ? 0xFF : 0x00); 44 | } 45 | 46 | void IS32FL3738Device::analogWrite(uint8_t pin, uint16_t value) { 47 | uint8_t sw = pin/8; 48 | uint8_t cs = pin%8; 49 | uint8_t reg = sw*0x20 + cs*0x02; 50 | selectPage(1); 51 | writeRegister(reg, value & 0xFF); 52 | } 53 | 54 | void IS32FL3738Device::setOnOffRegisters() { 55 | selectPage(0); 56 | for(int sw = 0; sw < 6; sw++) { 57 | for(int cs = 0; cs < 2; cs++) { 58 | uint8_t reg = sw*4 + cs; 59 | uint8_t data = 0; 60 | data |= pins[sw*8+cs*4].isEnabled() ? 0b00000011 : 0x00; 61 | data |= pins[sw*8+cs*4+1].isEnabled() ? 0b00001100 : 0x00; 62 | data |= pins[sw*8+cs*4+2].isEnabled() ? 0b00110000 : 0x00; 63 | data |= pins[sw*8+cs*4+3].isEnabled() ? 0b11000000 : 0x00; 64 | writeRegister(reg, data); 65 | writeRegister(reg+0x02, data); 66 | } 67 | } 68 | } 69 | 70 | void IS32FL3738Device::selectPage(uint8_t page) { 71 | writeRegister(IS32_REG_GLOBAL_UNLOCK, IS32_REG_GLOBAL_UNLOCK_PAGE_UNLOCK); 72 | writeRegister(IS32_REG_GLOBAL_PAGE, page); 73 | } 74 | 75 | void IS32FL3738Device::writeRegister(uint8_t reg, uint8_t data) { 76 | wire.beginTransmission(address); 77 | wire.write(reg); 78 | wire.write(data); 79 | wire.endTransmission(); 80 | } -------------------------------------------------------------------------------- /src/util/Array.h: -------------------------------------------------------------------------------- 1 | #ifndef Array_h 2 | #define Array_h 3 | 4 | #include 5 | 6 | // use ArrayPtr for pasing around arrays without needing to know the size at compile time. 7 | template 8 | class ArrayPtr { 9 | 10 | public: 11 | ArrayPtr(T* ptr, int size) : ptr(ptr), _size(size) {} 12 | T& operator[](int i) { return ptr[i]; } 13 | const T& operator[](int i) const { return ptr[i]; } 14 | int size() { return _size; } 15 | 16 | private: 17 | T* ptr; 18 | int _size = 0; 19 | }; 20 | 21 | 22 | template 23 | class Array { 24 | 25 | public: 26 | 27 | Array(int size = 0) { 28 | _size = size; 29 | } 30 | Array(T& initItem, int size) { 31 | init(initItem, size); 32 | } 33 | Array(std::initializer_list list) { 34 | for(auto& item : list) { 35 | add(item); 36 | } 37 | } 38 | 39 | void init(T& initItem, int size) { 40 | clear(); 41 | for(int i = 0; i < size; i++) { 42 | add(initItem); 43 | } 44 | } 45 | 46 | T& get(int i) { 47 | if (i >= 0) { 48 | return items[i]; 49 | } else { 50 | return items[_size + i]; 51 | } 52 | } 53 | T& operator[](int i) { return items[i]; } 54 | const T& operator[](int i) const { return items[i]; } 55 | 56 | int size() { return _size; } 57 | bool isFull() { return _size == MAXSIZE; } 58 | 59 | int add(const T& item) { 60 | if(_size == MAXSIZE) { 61 | Serial.println("ERROR: Array overflow!"); 62 | return -1; 63 | } 64 | items[_size] = item; 65 | return _size++; 66 | } 67 | 68 | int remove(int index) { 69 | for(int i = index; i < _size-1; i++) { 70 | items[i] = items[i+1]; 71 | } 72 | _size--; 73 | return _size; 74 | } 75 | 76 | void clear() { 77 | _size = 0; 78 | } 79 | 80 | bool contains(T& findItem) { 81 | for(int i = 0; i < _size; i++) { 82 | if(items[i] == findItem) { 83 | return true; 84 | } 85 | } 86 | return false; 87 | } 88 | 89 | T* begin() { return &items[0]; } 90 | T* end() { return &items[_size]; } 91 | 92 | ArrayPtr ptr() { return ArrayPtr(&items[0], _size); } 93 | 94 | private: 95 | int _size = 0; 96 | T items[MAXSIZE]; 97 | 98 | }; 99 | 100 | #endif -------------------------------------------------------------------------------- /docs/hardware.md: -------------------------------------------------------------------------------- 1 | --- 2 | order: 8 3 | --- 4 | 5 | # Hardware 6 | 7 | ## Teensy Motherboard 8CV 8 | 9 | Motherboard for teensy containing circuits to enable the following: 10 | 11 | - 8 CV Inputs 12 | - 8 Potentiometer Inputs 13 | - 8 CV Outputs using DAC8164 14 | - 16 Digital Inputs or Outputs using MCP23S17 IO expander 15 | 16 | Works with Teensy LC/4.0/4.1 17 | 18 | ### Soldering 19 | 20 | Depending on requirements not all of the components need to be soldered. 21 | 22 | Do not solder both section A and B at the same time. 23 | - **A.** Solder these resistors if there are 4 or less potentiometers. They connect the potentiometers directy to analog input on the Teensy. 24 | - **B.** Solder the CD4051 if there are more than 4 potentiometers. They will be multiplexed to a sinlge analog input on the Teensy. 25 | - **C.** 8 control voltage inputs with range -5V to 5V. 26 | - **D.** IO expander providing 16 digital input or output pins. 27 | - **E.** 8 control voltage outputs with a range from -5V to 5V. If only 4 outputs are required then only one of DAC8164 need to soldered. 28 | 29 | ![Teensy Motherboard](images/hardware_teensy_motherboard_8cv_mki.drawio.png) 30 | 31 | ### Board Pins 32 | 33 | - **POT 1-8**: Potentiometer connections. Potentiometers should output a voltage from 0V to 3.3V. Values higher will damage the Teensy. 34 | - **CVIN 1-8**: 8 Control Voltage inputs which accept a range from -5V to 5V. Each input has 2 pins which are summed together, but won't go outside the range. e.g. summing 5V+5V will still read 5V on the Teensy. Input is tolerant to -12V/+12V. 35 | - **DOUT/DIN 1-16**: Digital input/output. Pins need to be configured in softare for either input or output. Outputs 5V when turned on. Input is tolerant to -12V/+12V. 36 | - **CVOUT 1-8**: 8 Control voltage outputs with a default range from -5V to 5V. 37 | - **OFFSET**: Applying a voltage will apply an offset to the CV outputs. Apply -5V to produce a 0V to 10V range. 38 | - **28-41**: Numbered Teensy pins, from Teensy 4.1 only. 39 | - **0-13**: Numbered Teensy pins, can be used if the alternative function is not in use. 40 | - **SDA/SCL**: I2C Communication. 41 | - **ENC1/ENC2/ENCBT**: Suggested to use for rotary encoder. 42 | - **TX/RX**: Serial communication. 43 | - **INTB/INTA**: If digital inputs are in use, then these are connected to interrupt pins on the MCP23S17. 44 | - **SCK/MISO/MOSI**: SPI Communication. 45 | - **IOCS**: SPI select pin for MCP23S17 digital inputs/outputs. Do not use if digital inputs/outputs are in use. 46 | - **DAC1CS/DAC2CS**: SPI select pin for DAC8164 analog outputs. Do not use if anaolog outputs are in use. 47 | 48 | ### Teensy Connections 49 | 50 | #### Teensy LC 51 | ![Teensy LC](images/hardware_teensy_motherboard_LC.png) 52 | 53 | #### Teensy 4.0 54 | ![Teensy 4.0](images/hardware_teensy_motherboard_4.0.png) 55 | 56 | #### Teensy 4.1 57 | ![Teensy 4.1](images/hardware_teensy_motherboard_4.1.png) 58 | -------------------------------------------------------------------------------- /src/hardware/dac8164/DAC8164.h: -------------------------------------------------------------------------------- 1 | // ********************************************************************************** 2 | // Driver definition for TI DAC7565, DAC7564, DAC8164 and DAC8564 Library 3 | // ********************************************************************************** 4 | // Creative Commons Attrib Share-Alike License 5 | // You are free to use/extend this library but please abide with the CC-BY-SA license: 6 | // http://creativecommons.org/licenses/by-sa/4.0/ 7 | // 8 | // For any explanation see DAC7565 information at 9 | // http://www.ti.com/product/dac7565 10 | // 11 | // Code based on following datasheet 12 | // http://www.ti.com/lit/gpn/dac7565 13 | // 14 | // Written by Charles-Henri Hallard (http://hallard.me) 15 | // 16 | // History : V1.00 2015-04-14 - First release 17 | // 18 | // All text above must be included in any redistribution. 19 | // 20 | // ********************************************************************************** 21 | #ifndef DAC_H 22 | #define DAC_H 23 | 24 | #include 25 | 26 | // 24 bits code definition 27 | #define DAC_REFERENCE_ALWAYS_POWERED_DOWN 0x2000 28 | #define DAC_REFERENCE_POWERED_TO_DEFAULT 0x0000 29 | #define DAC_REFERENCE_ALWAYS_POWERED_UP 0x1000 30 | 31 | #define DAC_DATA_INPUT_REGISTER 0x011000 32 | 33 | #define DAC_MASK_LD1 0x200000 34 | #define DAC_MASK_LD0 0x100000 35 | #define DAC_MASK_DACSEL1 0x040000 36 | #define DAC_MASK_DACSEL0 0x020000 37 | #define DAC_MASK_PD0 0x010000 38 | #define DAC_MASK_PD1 0x008000 39 | #define DAC_MASK_PD2 0x004000 40 | #define DAC_MASK_DATA 0x00FFF0 41 | 42 | #define DAC_SINGLE_CHANNEL_STORE 0 /* LD1=0,LD0=0 */ 43 | #define DAC_SINGLE_CHANNEL_UPDATE DAC_MASK_LD0 /* LD1=0,LD0=1 */ 44 | #define DAC_SIMULTANEOUS_UPDATE DAC_MASK_LD1 /* LD1=1,LD0=0 */ 45 | #define DAC_BROADCAST_UPDATE DAC_MASK_LD1 | DAC_MASK_LD0 /* LD1=1,LD0=1 */ 46 | 47 | #define DAC_POWER_DOWN_1K DAC_MASK_PD2 48 | #define DAC_POWER_DOWN_100K DAC_MASK_PD1 49 | #define DAC_POWER_DOWN_HIZ DAC_MASK_PD2 | DAC_MASK_PD1 50 | 51 | // 8 bit constant to pass only 8 bits paramaeters 52 | #define DAC_CHANNEL_A 0 53 | #define DAC_CHANNEL_B 1 54 | #define DAC_CHANNEL_C 2 55 | #define DAC_CHANNEL_D 3 56 | #define DAC_CHANNEL_ALL 4 57 | 58 | #define DAC_MAX_SCALE 4096 // Max Scale points (DAC 14 bits) 59 | 60 | #include 61 | 62 | 63 | class DAC8164 { 64 | public: 65 | DAC8164(uint8_t enablePin=-1, uint8_t syncPin=-1, uint8_t ldacPin=-1); 66 | void init(void); 67 | void setReference(uint16_t reference); 68 | void writeChannel(uint8_t channel, uint16_t value); 69 | void setChannelPower(uint8_t channel, uint16_t power); 70 | 71 | private: 72 | void write(uint32_t data); 73 | uint8_t enablePin; 74 | uint8_t syncPin; 75 | uint8_t ldacPin; 76 | 77 | SPISettings spiMode; 78 | 79 | }; 80 | 81 | #endif 82 | -------------------------------------------------------------------------------- /src/dsp/filter/StateVariableFilter.h: -------------------------------------------------------------------------------- 1 | #ifndef StateVariableFilter_h 2 | #define StateVariableFilter_h 3 | 4 | 5 | /** Double Sampled, Stable State Variable Filter 6 | 7 | Credit to Andrew Simper from musicdsp.org 8 | 9 | This is his "State Variable Filter (Double Sampled, Stable)" 10 | 11 | Additional thanks to Laurent de Soras for stability limit, and 12 | Stefan Diedrichsen for the correct notch output 13 | 14 | Ported by: Stephen Hensley 15 | */ 16 | class StateVariableFilter 17 | { 18 | public: 19 | StateVariableFilter() {} 20 | /** Initializes the filter 21 | float sample_rate - sample rate of the audio engine being run, and the frequency that the Process function will be called. 22 | */ 23 | void init(float sample_rate); 24 | 25 | /** 26 | Process the input signal, updating all of the outputs. 27 | */ 28 | void process(float in); 29 | 30 | /** sets the frequency of the cutoff frequency. 31 | f must be between 0.0 and sample_rate / 2 32 | */ 33 | void setFrequency(float f); 34 | 35 | /** sets the resonance of the filter. 36 | Must be between 0.0 and 1.0 to ensure stability. 37 | */ 38 | void setResonance(float r); 39 | 40 | /** lowpass output 41 | \return low pass output of the filter 42 | */ 43 | inline float low() { return 0.5f * out_low_; } 44 | /** highpass output 45 | \return high pass output of the filter 46 | */ 47 | inline float high() { return 0.5f * out_high_; } 48 | /** bandpass output 49 | \return band pass output of the filter 50 | */ 51 | inline float band() { return 0.5f * out_band_; } 52 | /** notchpass output 53 | \return notch pass output of the filter 54 | */ 55 | inline float notch() { return 0.5f * out_notch_; } 56 | /** peak output 57 | \return peak output of the filter 58 | */ 59 | inline float peak() { return 0.5f * out_peak_; } 60 | 61 | private: 62 | float sr_, fc_, res_, freq_, damp_; 63 | float notch_, low_, high_, band_, peak_; 64 | float input_; 65 | float out_low_, out_high_, out_band_, out_peak_, out_notch_; 66 | }; 67 | 68 | 69 | inline void StateVariableFilter::process(float in) 70 | { 71 | input_ = in; 72 | // first pass 73 | notch_ = input_ - damp_ * band_; 74 | low_ = low_ + freq_ * band_; 75 | high_ = notch_ - low_; 76 | band_ = freq_ * high_ + band_; 77 | out_low_ = low_; 78 | out_high_ = high_; 79 | out_band_ = band_; 80 | out_peak_ = low_ - high_; 81 | out_notch_ = notch_; 82 | // second pass 83 | notch_ = input_ - damp_ * band_; 84 | low_ = low_ + freq_ * band_; 85 | high_ = notch_ - low_; 86 | band_ = freq_ * high_ + band_; 87 | out_low_ += low_; 88 | out_high_ += high_; 89 | out_band_ += band_; 90 | out_peak_ += low_ - high_; 91 | out_notch_ += notch_; 92 | } 93 | 94 | #endif 95 | -------------------------------------------------------------------------------- /src/dsp/clock/InternalExternalClock.cpp: -------------------------------------------------------------------------------- 1 | #include "InternalExternalClock.h" 2 | #include 3 | 4 | #define WAIT_TIME_MIN 0.2 5 | 6 | void InternalExternalClock::setFrequency(float frequency) { 7 | this->frequency = frequency; 8 | if (frequencyMultiplierDividerType == MultiplierDivider::CLK_MULTIPLIER) { 9 | phaseInc = frequency * sampleTime * float(frequencyMultiplierDivider); 10 | } else { 11 | phaseInc = frequency * sampleTime; 12 | } 13 | } 14 | 15 | void InternalExternalClock::setFrequencyMultiplierDivider(MultiplierDivider type, int value) { 16 | frequencyMultiplierDividerType = type; 17 | frequencyMultiplierDivider = value; 18 | clockDivider.setDivisor(value); 19 | if (type == MultiplierDivider::CLK_MULTIPLIER) { 20 | phaseInc = frequency * sampleTime * float(value); 21 | } 22 | } 23 | 24 | void InternalExternalClock::externalTick() { 25 | externalTicked = true; 26 | switch(state) { 27 | case CLK_INTERNAL: 28 | state = CLK_EXTERNAL_WAITING; 29 | break; 30 | case CLK_EXTERNAL_WAITING: 31 | state = CLK_EXTERNAL; 32 | case CLK_EXTERNAL: 33 | externalTime = externalTimeCounter; 34 | setFrequency(1/externalTime); 35 | phase = 0; 36 | externalWaitTime = fmaxf(externalTime*2, WAIT_TIME_MIN); 37 | } 38 | externalTimeCounter = 0; 39 | multiplierCounter = 0; 40 | } 41 | 42 | bool InternalExternalClock::process() { 43 | bool tick = false; 44 | switch(state) { 45 | case CLK_EXTERNAL_WAITING: 46 | externalTimeCounter += sampleTime; 47 | tick = externalTicked; 48 | if(externalTicked) { 49 | externalTicked = false; 50 | } 51 | processInternal(); 52 | break; 53 | case CLK_INTERNAL: 54 | tick = processInternal(); 55 | break; 56 | case CLK_EXTERNAL: 57 | tick = processExternal(); 58 | } 59 | 60 | if (frequencyMultiplierDividerType == CLK_DIVIDER && tick == true) { 61 | return clockDivider.tick(); 62 | } 63 | 64 | return tick; 65 | } 66 | 67 | bool InternalExternalClock::processInternal() { 68 | return Clock::process(); 69 | } 70 | 71 | bool InternalExternalClock::processExternal() { 72 | bool tick = Clock::process(); 73 | externalTimeCounter += sampleTime; 74 | 75 | if(tick) { 76 | multiplierCounter++; 77 | if(frequencyMultiplierDividerType == MultiplierDivider::CLK_MULTIPLIER) { 78 | tick = multiplierCounter < frequencyMultiplierDivider; 79 | } 80 | } 81 | 82 | if(externalTicked) { 83 | externalTicked = false; 84 | tick = true; 85 | } else { 86 | // Switch back to internal after no external tick for time 87 | if(externalTimeCounter > externalWaitTime) { 88 | state = CLK_INTERNAL; 89 | } 90 | } 91 | 92 | return tick; 93 | } -------------------------------------------------------------------------------- /src/dsp/oscillator/WaveOscillator.h: -------------------------------------------------------------------------------- 1 | #ifndef WaveOscillator_h 2 | #define WaveOscillator_h 3 | 4 | #include "BaseOscillator.h" 5 | #include "../base/WaveShape.h" 6 | 7 | namespace eurorack { 8 | 9 | // increments through a waveshape over time 10 | template 11 | class WaveOscillator : public BaseOscillator { 12 | public: 13 | WaveOscillator() {} 14 | WaveOscillator(T waveShape): waveShape(waveShape) {} 15 | void init(float sampleRate, bool repeat = true); 16 | void setFrequency(float frequency); 17 | void setAmplitude(float amplitude); 18 | void setPeriod(float period); 19 | void setPhase(float phase); 20 | void setPolyblep(bool polyblepEnabled) { this->polyblepEnabled = polyblepEnabled; } 21 | T& getShape() { return waveShape; } 22 | virtual float process(); 23 | 24 | protected: 25 | T waveShape; 26 | 27 | float sampleRate; 28 | float sampleTime; 29 | 30 | float amplitude = 0.8; 31 | float increment; 32 | float phase; 33 | bool repeat; 34 | bool playing; 35 | bool polyblepEnabled; 36 | 37 | void incrementPhase(); 38 | }; 39 | 40 | template 41 | void WaveOscillator::init(float sampleRate, bool repeat) { 42 | this->sampleRate = sampleRate; 43 | this->sampleTime = 1.0f/sampleRate; 44 | this->increment = sampleTime; 45 | this->repeat = repeat; 46 | phase = 0; 47 | playing = repeat; 48 | } 49 | 50 | template 51 | void WaveOscillator::setFrequency(float frequency) { 52 | increment = sampleTime * frequency; 53 | waveShape.setFrequency(frequency); 54 | } 55 | 56 | template 57 | void WaveOscillator::setAmplitude(float amplitude) { 58 | this->amplitude = amplitude; 59 | } 60 | 61 | template 62 | void WaveOscillator::setPeriod(float period) { 63 | increment = sampleTime / period; 64 | } 65 | 66 | template 67 | void WaveOscillator::setPhase(float phase) { 68 | this->phase = phase; 69 | } 70 | 71 | template 72 | float WaveOscillator::process() { 73 | if(!playing) { 74 | return 0; 75 | } 76 | 77 | float value = waveShape.get(phase); 78 | if(polyblepEnabled) { 79 | value += waveShape.polyblep(phase, increment); 80 | } 81 | 82 | incrementPhase(); 83 | return value * amplitude; 84 | } 85 | 86 | template 87 | void WaveOscillator::incrementPhase() { 88 | phase += increment; 89 | if(phase > waveShape.getLength()) { 90 | if(repeat) { 91 | phase -= waveShape.getLength(); 92 | } else { 93 | playing = false; 94 | phase = 0; 95 | } 96 | } else if(phase < 0) { 97 | phase += waveShape.getLength(); 98 | } 99 | } 100 | 101 | } 102 | 103 | #endif -------------------------------------------------------------------------------- /src/io/digitalinputs/TriggerInput.h: -------------------------------------------------------------------------------- 1 | #ifndef eur_TriggerInput_h 2 | #define eur_TriggerInput_h 3 | 4 | #include "../../hardware/device/DevicePin.h" 5 | #include 6 | 7 | #define MAX_INTERRUPT_TRIGGERS 4 8 | 9 | namespace eurorack { 10 | 11 | template 12 | class TriggerInput { 13 | 14 | public: 15 | TriggerInput(DigitalInputPin& inputPin, bool inverted = true, uint8_t interruptPin = -1); 16 | bool getValue() { return value; } 17 | bool rose() { return inverted ? didFallInternal() : didRiseInternal(); } 18 | bool fell() { return inverted ? didRiseInternal() : didFallInternal(); } 19 | bool update(); // need to call update if pin doesn't use interrupt 20 | 21 | private: 22 | DigitalInputPin& inputPin; 23 | bool value; 24 | bool inverted; 25 | uint8_t interruptPin; 26 | bool interruptFired; 27 | 28 | bool didRise; 29 | bool didFall; 30 | 31 | static TriggerInput* interruptInputs[MAX_INTERRUPT_TRIGGERS]; 32 | static uint8_t interruptInputCount; 33 | static void interrupt(); 34 | 35 | void setValue(bool value) { 36 | this->value = value; 37 | this->didRise = didRise || value; 38 | this->didFall = didFall || !value; 39 | } 40 | 41 | bool didRiseInternal() { 42 | bool returnValue = didRise; 43 | didRise = false; 44 | return returnValue; 45 | } 46 | 47 | bool didFallInternal() { 48 | bool returnValue = didFall; 49 | didFall = false; 50 | return returnValue; 51 | } 52 | }; 53 | 54 | 55 | template 56 | uint8_t TriggerInput::interruptInputCount = 0; 57 | 58 | template 59 | TriggerInput* TriggerInput::interruptInputs[MAX_INTERRUPT_TRIGGERS]; 60 | 61 | template 62 | TriggerInput::TriggerInput(DigitalInputPin& inputPin, bool inverted, uint8_t interruptPin) : inputPin(inputPin) { 63 | this->inverted = inverted; 64 | this->interruptPin = interruptPin; 65 | if(interruptPin >= 0) { 66 | pinMode(interruptPin, INPUT); 67 | inputPin.enableInterrupt(true); 68 | interruptInputs[interruptInputCount] = this; 69 | interruptInputCount++; 70 | attachInterrupt(digitalPinToInterrupt(interruptPin), interrupt, CHANGE); 71 | } 72 | } 73 | 74 | template 75 | void TriggerInput::interrupt() { 76 | for(uint8_t i = 0; i < interruptInputCount; i++) { 77 | TriggerInput* triggerInput = interruptInputs[i]; 78 | bool value = triggerInput->inputPin.digitalRead(); 79 | if(value != triggerInput->getValue()) { 80 | triggerInput->setValue(value); 81 | triggerInput->interruptFired = true; 82 | } 83 | } 84 | } 85 | 86 | template 87 | bool TriggerInput::update() { 88 | bool updatedValue = inputPin.digitalRead(); 89 | if(updatedValue != value || interruptFired) { 90 | setValue(updatedValue); 91 | interruptFired = false; 92 | return true; 93 | } 94 | return false; 95 | } 96 | } 97 | 98 | #endif 99 | -------------------------------------------------------------------------------- /src/filesystem/FileSystem.cpp: -------------------------------------------------------------------------------- 1 | #include "FileSystem.h" 2 | #include "util/stringutil.h" 3 | 4 | SdFat FileSystem::sd; 5 | 6 | FileSystem::FileSystem(const char* rootDirectory) : 7 | rootDirectory(rootDirectory) { 8 | sdio = true; 9 | } 10 | 11 | FileSystem::FileSystem(uint8_t csPin, const char* rootDirectory) : 12 | csPin(csPin), rootDirectory(rootDirectory) { 13 | sdio = false; 14 | } 15 | 16 | void FileSystem::init() { 17 | begin(); 18 | 19 | if(!sd.exists(rootDirectory)) { 20 | Serial.println("Creating root directory"); 21 | sd.mkdir(rootDirectory, true); 22 | } 23 | } 24 | 25 | bool FileSystem::begin() { 26 | if(!sdio) { 27 | if (!sd.begin(csPin)) { 28 | Serial.println("SPI SD card init failed"); 29 | Serial.print(F("SdError: 0X")); 30 | Serial.print(sd.sdErrorCode(), HEX); 31 | Serial.print(F(",0X")); 32 | Serial.println(sd.sdErrorData(), HEX); 33 | sdio = true; 34 | } else { 35 | Serial.println("SPI SD card init success"); 36 | } 37 | } 38 | 39 | if(sdio) { 40 | if (!sd.begin(SdioConfig(FIFO_SDIO))) { 41 | Serial.println("SDIO card init failed"); 42 | Serial.print(F("SdError: 0X")); 43 | Serial.print(sd.sdErrorCode(), HEX); 44 | Serial.print(F(",0X")); 45 | Serial.println(sd.sdErrorData(), HEX); 46 | return false; 47 | } else { 48 | Serial.println("SDIO card init success"); 49 | } 50 | } 51 | 52 | return true; 53 | } 54 | 55 | bool FileSystem::cd(const char* directoryPath) { 56 | if(!begin()) { 57 | return false; 58 | } 59 | 60 | // TODO if directoryPath == currentDirectory do nothing 61 | 62 | FsFile dir = sd.open(String(rootDirectory) + directoryPath); 63 | if(dir.isDirectory()) { 64 | fileList.clear(); 65 | FsFile entry = dir.openNextFile(); 66 | while(entry) { 67 | FileInfo file; 68 | entry.getName(file.filename, MAX_FILENAME_SIZE); 69 | entry.close(); 70 | if(!beginsWith(file.filename, "._")) { 71 | String filepath = directoryPath + String(file.filename); 72 | strcpy(file.filepath, filepath.c_str()); 73 | fileList.add(file); 74 | } 75 | entry = dir.openNextFile(); 76 | } 77 | dir.close(); 78 | currentDirectory = directoryPath; 79 | return true; 80 | } else { 81 | dir.close(); 82 | return false; 83 | } 84 | } 85 | 86 | FileList& FileSystem::ls(const char* extension) { 87 | filteredFileList.clear(); 88 | for(int i = 0; i < fileList.size(); i++) { 89 | FileInfo& file = fileList.get(i); 90 | if(endsWith(file.filename, extension)) { 91 | filteredFileList.add(file); 92 | } 93 | } 94 | return filteredFileList; 95 | } 96 | 97 | 98 | bool FileSystem::read(const char* filePath, FileReader* fileReader) { 99 | if(!begin()) { 100 | return false; 101 | } 102 | 103 | FsFile file = sd.open(String(rootDirectory) + filePath); 104 | fileReader->read(file); 105 | 106 | file.close(); 107 | return true; 108 | } -------------------------------------------------------------------------------- /src/util/TypeSelector.h: -------------------------------------------------------------------------------- 1 | #ifndef TypeSelector_h 2 | #define TypeSelector_h 3 | 4 | #include 5 | #include "Array.h" 6 | 7 | template 8 | inline typename std::enable_if::type 9 | for_each(std::tuple &, Func) {} 10 | 11 | template 12 | inline typename std::enable_if::type 13 | for_each(std::tuple& t, Func func) { 14 | func(&std::get(t)); 15 | for_each(t, func); 16 | } 17 | 18 | /** 19 | * Select an object from an array of objects. 20 | * T is the class of all the objects. 21 | * N is the number of objects. 22 | * Objects are automatically instantated using default constructor. 23 | */ 24 | template 25 | class ArraySelector { 26 | public: 27 | ArraySelector() {} 28 | ArraySelector(std::initializer_list list) : items(list) {} 29 | T& operator[](int i) { return items[i]; } 30 | const T& operator[](int i) const { return items[i]; } 31 | T& getSelected() { return items[selectedIndex]; } 32 | size_t getSelectedIndex() { return selectedIndex; } 33 | Array& getItems() { return items; } 34 | 35 | T& select(size_t i) { 36 | if(i < N) { 37 | selectedIndex = i; 38 | } 39 | return getSelected(); 40 | } 41 | 42 | T& increment() { 43 | selectedIndex = (selectedIndex + 1) % N; 44 | return getSelected(); 45 | } 46 | 47 | T& decrement() { 48 | selectedIndex = selectedIndex > 0 ? selectedIndex - 1 : N - 1; 49 | return getSelected(); 50 | } 51 | 52 | T& cycle(int direction) { 53 | if(N <= 1) { 54 | return getSelected(); 55 | } 56 | if(direction > 0) { 57 | return increment(); 58 | } else if(direction < 0) { 59 | return decrement(); 60 | } 61 | return getSelected(); 62 | } 63 | 64 | unsigned int getSize() { 65 | return N; 66 | } 67 | 68 | protected: 69 | Array items; 70 | size_t selectedIndex; 71 | }; 72 | 73 | 74 | /** 75 | * Select a type instance from a list of types. 76 | * B is the base class of all the objects. 77 | * Ts are the classes of each item in the list. 78 | * Default constructor automatically instantiates the types using their default constructors. 79 | * Instances can be passed into the constructor to use pre-constructed objects. 80 | */ 81 | template 82 | class TypeSelector : public ArraySelector { 83 | public: 84 | TypeSelector() { 85 | buildList(); 86 | } 87 | 88 | TypeSelector(Ts&&... args) : objects(args...) { 89 | buildList(); 90 | } 91 | 92 | void buildList() { 93 | for_each(objects, [this](B* object) { 94 | this->items.add(object); 95 | }); 96 | ArraySelector::select(0); 97 | } 98 | 99 | std::tuple& getObjects() { return objects; } 100 | 101 | protected: 102 | std::tuple objects; 103 | }; 104 | 105 | #endif -------------------------------------------------------------------------------- /src/hardware/RotaryEncoder.cpp: -------------------------------------------------------------------------------- 1 | #include "RotaryEncoder.h" 2 | 3 | /* 4 | * The below state table has, for each state (row), the new state 5 | * to set based on the next encoder output. From left to right in, 6 | * the table, the encoder outputs are 00, 01, 10, 11, and the value 7 | * in that position is the new state to set. 8 | */ 9 | 10 | #define R_START 0x0 11 | 12 | #ifdef HALF_STEP 13 | // Use the half-step state table (emits a code at 00 and 11) 14 | #define R_CCW_BEGIN 0x1 15 | #define R_CW_BEGIN 0x2 16 | #define R_START_M 0x3 17 | #define R_CW_BEGIN_M 0x4 18 | #define R_CCW_BEGIN_M 0x5 19 | const unsigned char ttable[6][4] = { 20 | // R_START (00) 21 | {R_START_M, R_CW_BEGIN, R_CCW_BEGIN, R_START}, 22 | // R_CCW_BEGIN 23 | {R_START_M | DIR_CCW, R_START, R_CCW_BEGIN, R_START}, 24 | // R_CW_BEGIN 25 | {R_START_M | DIR_CW, R_CW_BEGIN, R_START, R_START}, 26 | // R_START_M (11) 27 | {R_START_M, R_CCW_BEGIN_M, R_CW_BEGIN_M, R_START}, 28 | // R_CW_BEGIN_M 29 | {R_START_M, R_START_M, R_CW_BEGIN_M, R_START | DIR_CW}, 30 | // R_CCW_BEGIN_M 31 | {R_START_M, R_CCW_BEGIN_M, R_START_M, R_START | DIR_CCW}, 32 | }; 33 | #else 34 | // Use the full-step state table (emits a code at 00 only) 35 | #define R_CW_FINAL 0x1 36 | #define R_CW_BEGIN 0x2 37 | #define R_CW_NEXT 0x3 38 | #define R_CCW_BEGIN 0x4 39 | #define R_CCW_FINAL 0x5 40 | #define R_CCW_NEXT 0x6 41 | 42 | const unsigned char ttable[7][4] = { 43 | // R_START 44 | {R_START, R_CW_BEGIN, R_CCW_BEGIN, R_START}, 45 | // R_CW_FINAL 46 | {R_CW_NEXT, R_START, R_CW_FINAL, R_START | DIR_CW}, 47 | // R_CW_BEGIN 48 | {R_CW_NEXT, R_CW_BEGIN, R_START, R_START}, 49 | // R_CW_NEXT 50 | {R_CW_NEXT, R_CW_BEGIN, R_CW_FINAL, R_START}, 51 | // R_CCW_BEGIN 52 | {R_CCW_NEXT, R_START, R_CCW_BEGIN, R_START}, 53 | // R_CCW_FINAL 54 | {R_CCW_NEXT, R_CCW_FINAL, R_START, R_START | DIR_CCW}, 55 | // R_CCW_NEXT 56 | {R_CCW_NEXT, R_CCW_FINAL, R_CCW_BEGIN, R_START}, 57 | }; 58 | #endif 59 | 60 | RotaryEncoder* RotaryEncoder::encoderPtrs[MAX_ENCODERS]; 61 | int RotaryEncoder::encoderCount = 0; 62 | 63 | RotaryEncoder::RotaryEncoder(uint8_t pin1, uint8_t pin2, bool useInterrupt) { 64 | encoderPtrs[encoderCount] = this; 65 | encoderCount++; 66 | this->pin1 = pin1; 67 | this->pin2 = pin2; 68 | 69 | pinMode(pin1, INPUT_PULLUP); 70 | pinMode(pin2, INPUT_PULLUP); 71 | #ifdef ENABLE_PULLUPS 72 | digitalWrite(pin1, HIGH); 73 | digitalWrite(pin2, HIGH); 74 | #endif 75 | 76 | state = R_START; 77 | 78 | this->useInterrupt = useInterrupt; 79 | if(useInterrupt) { 80 | attachInterrupt(pin1, RotaryEncoder::interrupt, CHANGE); 81 | attachInterrupt(pin2, RotaryEncoder::interrupt, CHANGE); 82 | } 83 | } 84 | 85 | void RotaryEncoder::interrupt() { 86 | for(int i = 0; ipoll(); 89 | } 90 | } 91 | 92 | void RotaryEncoder::poll() { 93 | unsigned char result = this->process(); 94 | if (result == DIR_CW) { 95 | this->position++; 96 | } else if (result == DIR_CCW) { 97 | this->position--; 98 | } 99 | } 100 | 101 | unsigned char RotaryEncoder::process() { 102 | unsigned char pinstate = (digitalRead(pin2) << 1) | digitalRead(pin1); 103 | state = ttable[state & 0xf][pinstate]; 104 | return state & 0x30; 105 | } -------------------------------------------------------------------------------- /src/hardware/mcp23s17/MCP23S17Device.h: -------------------------------------------------------------------------------- 1 | #ifndef MCP23S17Device_h 2 | #define MCP23S17Device_h 3 | 4 | #include 5 | #include 6 | #include "../device/DevicePin.h" 7 | 8 | #define MCP23S17_PINCOUNT 16 9 | 10 | class MCP23S17Device: public gpio_MCP23S17, public Device, public DigitalOutputDevice, public DigitalInputDevice { 11 | public: 12 | MCP23S17Device(const uint8_t csPin,const uint8_t haenAddress) : gpio_MCP23S17(csPin, haenAddress) { 13 | Device::setDeferredOutput(true); 14 | Device::setDeferredInput(true); 15 | begin(); 16 | }; 17 | inline void init(); 18 | inline void send(); 19 | inline void receive(); 20 | inline void digitalWrite(uint8_t pin, bool value); 21 | inline bool digitalRead(uint8_t pin); 22 | 23 | DigitalInputOutputPin pins[MCP23S17_PINCOUNT] = { 24 | DigitalInputOutputPin(*this, 0), 25 | DigitalInputOutputPin(*this, 1), 26 | DigitalInputOutputPin(*this, 2), 27 | DigitalInputOutputPin(*this, 3), 28 | DigitalInputOutputPin(*this, 4), 29 | DigitalInputOutputPin(*this, 5), 30 | DigitalInputOutputPin(*this, 6), 31 | DigitalInputOutputPin(*this, 7), 32 | DigitalInputOutputPin(*this, 8), 33 | DigitalInputOutputPin(*this, 9), 34 | DigitalInputOutputPin(*this, 10), 35 | DigitalInputOutputPin(*this, 11), 36 | DigitalInputOutputPin(*this, 12), 37 | DigitalInputOutputPin(*this, 13), 38 | DigitalInputOutputPin(*this, 14), 39 | DigitalInputOutputPin(*this, 15) 40 | }; 41 | }; 42 | 43 | void MCP23S17Device::init() { 44 | uint16_t interruptPins = 0; 45 | uint16_t pinModes = 0; 46 | uint16_t pinPullups = 0; 47 | for(int i = 0; i < MCP23S17_PINCOUNT; i++) { 48 | if(pins[i].getPinType() == PinType::DIGITAL_OUTPUT) { 49 | pinModes |= (1 << pins[i].getPin()); 50 | } 51 | if(pins[i].getPinType() == PinType::DIGITAL_INPUT_PULLUP) { 52 | pinPullups |= (1 << pins[i].getPin()); 53 | } 54 | if(pins[i].getInterruptEnabled()) { 55 | interruptPins |= (1 << pins[i].getPin()); 56 | } 57 | } 58 | gpioPinMode(pinModes); 59 | portPullup(pinPullups); 60 | gpioRegisterWriteWord(MCP23S17_GPINTEN, interruptPins); 61 | } 62 | 63 | void MCP23S17Device::send() { 64 | for(int i = 0; i < MCP23S17_PINCOUNT; i++) { 65 | if(pins[i].getPinType() == PinType::DIGITAL_OUTPUT) { 66 | gpioDigitalWriteFast(pins[i].getPin(), pins[i].getDigitalValue()); 67 | } 68 | } 69 | gpioPortUpdate(); 70 | } 71 | 72 | void MCP23S17Device::receive() { 73 | uint16_t gpio = readGpioPort(); 74 | for(int i = 0; i < MCP23S17_PINCOUNT; i++) { 75 | if(pins[i].getPinType() == PinType::DIGITAL_INPUT || pins[i].getPinType() == PinType::DIGITAL_INPUT_PULLUP) { 76 | bool value = bitRead(gpio, pins[i].getPin()); 77 | pins[i].setDigitalValue(value); 78 | } 79 | } 80 | } 81 | 82 | void MCP23S17Device::digitalWrite(uint8_t pin, bool value) { 83 | gpioDigitalWrite(pin, value); 84 | } 85 | 86 | bool MCP23S17Device::digitalRead(uint8_t pin) { 87 | return gpioDigitalRead(pin); 88 | } 89 | 90 | #endif 91 | -------------------------------------------------------------------------------- /src/graphics/components/FieldComponent.h: -------------------------------------------------------------------------------- 1 | #ifndef FieldComponent_h 2 | #define FieldComponent_h 3 | 4 | #include 5 | #include "TextComponent.h" 6 | #include "../Component.h" 7 | 8 | #ifndef EU_FOCUS_COLOUR 9 | #define EU_FOCUS_COLOUR 1 10 | #endif 11 | 12 | template 13 | class FieldComponent : public Component { 14 | 15 | public: 16 | static const int MAX_LABEL_LENGTH = 32; 17 | static const int MAX_VALUE_LENGTH = 32; 18 | 19 | FieldComponent(uint16_t width, uint16_t labelWidth, const char* label = "", uint8_t font = 1, uint16_t colourText = G::DEF_COLOUR_TEXT); 20 | virtual void layout(); 21 | virtual void render(); 22 | 23 | void setLabel(const char* text); 24 | void setFormat(const char* format); 25 | void setValue(const char* value); 26 | void setValue(int value); 27 | 28 | protected: 29 | char label[MAX_LABEL_LENGTH]; 30 | char value[MAX_VALUE_LENGTH]; 31 | char* format = "%d"; 32 | 33 | uint16_t labelWidth; 34 | uint8_t font; 35 | uint16_t colourText; 36 | uint16_t colourBack = G::DEF_COLOUR_BACK; 37 | uint16_t colourFocusText = G::DEF_COLOUR_FOCUS_TEXT; 38 | uint16_t colourFocusBack = G::DEF_COLOUR_FOCUS_BACK; 39 | uint8_t paddingTop = 1; 40 | uint8_t paddingBottom = 1; 41 | 42 | uint16_t valueLeft; 43 | uint16_t valueWidth; 44 | uint16_t textTop; 45 | 46 | void renderLabel(); 47 | void renderValue(); 48 | }; 49 | 50 | 51 | template 52 | FieldComponent::FieldComponent(uint16_t width, uint16_t labelWidth, const char* label, uint8_t font, uint16_t colourText) { 53 | this->setHeight(TextComponent::DEFAULT_HEIGHT); 54 | this->setLabel(label); 55 | this->setWidth(width); 56 | this->labelWidth = labelWidth; 57 | this->font = font; 58 | this->colourText = colourText; 59 | } 60 | 61 | template 62 | void FieldComponent::layout() { 63 | valueLeft = this->left + labelWidth; 64 | valueWidth = this->width - labelWidth; 65 | textTop = this->top + paddingTop; 66 | this->setHeight(this->graphicsContext->getFontHeight(font) + paddingTop + paddingBottom); 67 | } 68 | 69 | template 70 | void FieldComponent::render() { 71 | renderLabel(); 72 | renderValue(); 73 | } 74 | 75 | template 76 | void FieldComponent::renderLabel() { 77 | this->graphicsContext->setFont(font); 78 | this->graphicsContext->setTextColour(colourText); 79 | this->graphicsContext->text(&label[0], this->left, textTop); 80 | } 81 | 82 | template 83 | void FieldComponent::setFormat(const char* format) { 84 | this->format = format; 85 | } 86 | 87 | template 88 | void FieldComponent::renderValue() { 89 | this->graphicsContext->fillRectangle(this->valueLeft, this->top, this->valueWidth, this->height, this->focus ? colourFocusBack : colourBack); 90 | this->graphicsContext->setFont(font); 91 | this->graphicsContext->setTextColour(this->focus ? colourFocusText : colourText); 92 | this->graphicsContext->text(&value[0], this->valueLeft, textTop); 93 | } 94 | 95 | template 96 | void FieldComponent::setLabel(const char* label) { 97 | strncpy(this->label, label, MAX_LABEL_LENGTH); 98 | } 99 | 100 | template 101 | void FieldComponent::setValue(const char* value) { 102 | strncpy(this->value, value, MAX_VALUE_LENGTH); 103 | renderValue(); 104 | } 105 | 106 | template 107 | void FieldComponent::setValue(int value) { 108 | snprintf(this->value, MAX_VALUE_LENGTH, format, value); 109 | renderValue(); 110 | } 111 | 112 | 113 | #endif -------------------------------------------------------------------------------- /src/util/Debounce.cpp: -------------------------------------------------------------------------------- 1 | #include "Debounce.h" 2 | 3 | #include 4 | 5 | Debounce::Debounce(): 6 | previousMillis(0), 7 | intervalMillis(10), 8 | state(0) { 9 | } 10 | 11 | void Debounce::setInterval(uint16_t intervalMillis) { 12 | this->intervalMillis = intervalMillis; 13 | } 14 | 15 | void Debounce::begin(bool value) { 16 | state = 0; 17 | if (value) { 18 | setStateFlag(DEBOUNCED_STATE | UNSTABLE_STATE); 19 | } 20 | 21 | #ifdef BOUNCE_LOCK_OUT 22 | previousMillis = 0; 23 | #else 24 | previousMillis = millis(); 25 | #endif 26 | } 27 | 28 | bool Debounce::update(bool value) { 29 | unsetStateFlag(CHANGED_STATE); 30 | #ifdef BOUNCE_LOCK_OUT 31 | 32 | // Ignore everything if we are locked out 33 | if (millis() - previousMillis >= intervalMillis) { 34 | if (value != getStateFlag(DEBOUNCED_STATE) ) { 35 | previousMillis = millis(); 36 | changeState(); 37 | } 38 | } 39 | 40 | #elif defined BOUNCE_WITH_PROMPT_DETECTION 41 | 42 | if (value != getStateFlag(DEBOUNCED_STATE)) { 43 | // We have seen a change from the current button state. 44 | if (millis() - previousMillis >= intervalMillis) { 45 | // We have passed the time threshold, so a new change of state is allowed. 46 | // set the STATE_CHANGED flag and the new DEBOUNCED_STATE. 47 | // This will be prompt as long as there has been greater than interval_misllis ms since last change of input. 48 | // Otherwise debounced state will not change again until bouncing is stable for the timeout period. 49 | changeState(); 50 | } 51 | } 52 | 53 | // If the readState is different from previous readState, reset the debounce timer - as input is still unstable 54 | // and we want to prevent new button state changes until the previous one has remained stable for the timeout. 55 | if (value != getStateFlag(UNSTABLE_STATE)) { 56 | // Update Unstable Bit to macth readState 57 | toggleStateFlag(UNSTABLE_STATE); 58 | previousMillis = millis(); 59 | } 60 | 61 | #else 62 | 63 | // If the reading is different from last reading, reset the debounce counter 64 | if (value != getStateFlag(UNSTABLE_STATE)) { 65 | previousMillis = millis(); 66 | toggleStateFlag(UNSTABLE_STATE); 67 | } else 68 | if ( millis() - previousMillis >= intervalMillis ) { 69 | // We have passed the threshold time, so the input is now stable 70 | // If it is different from last state, set the STATE_CHANGED flag 71 | if (value != getStateFlag(DEBOUNCED_STATE) ) { 72 | previousMillis = millis(); 73 | changeState(); 74 | } 75 | } 76 | 77 | #endif 78 | 79 | return changed(); 80 | } 81 | 82 | unsigned long Debounce::previousDuration() const { 83 | return durationOfPreviousState; 84 | } 85 | 86 | unsigned long Debounce::duration() const { 87 | return (millis() - stateChangeLastTime); 88 | } 89 | 90 | inline void Debounce::changeState() { 91 | toggleStateFlag(DEBOUNCED_STATE); 92 | setStateFlag(CHANGED_STATE) ; 93 | durationOfPreviousState = millis() - stateChangeLastTime; 94 | stateChangeLastTime = millis(); 95 | } 96 | 97 | bool Debounce::read() const{ 98 | return getStateFlag(DEBOUNCED_STATE); 99 | } 100 | 101 | bool Debounce::rose() const { 102 | return getStateFlag(DEBOUNCED_STATE) && getStateFlag(CHANGED_STATE); 103 | } 104 | 105 | bool Debounce::fell() const { 106 | return !getStateFlag(DEBOUNCED_STATE) && getStateFlag(CHANGED_STATE); 107 | } 108 | -------------------------------------------------------------------------------- /src/dsp/waveshapes/wavetable/WaveTable.h: -------------------------------------------------------------------------------- 1 | #ifndef WaveTable_h 2 | #define WaveTable_h 3 | 4 | #include 5 | #include "../../base/WaveShape.h" 6 | #include "../../sample/SampleBuffer.h" 7 | 8 | #define RANGE_OCTAVES 10.0 9 | 10 | namespace eurorack { 11 | 12 | class BaseWaveTable : public WaveShape { 13 | public: 14 | virtual size_t getTableSize() = 0; 15 | virtual size_t getSampleSize() = 0; 16 | virtual SampleBuffer& getTempBuffer() = 0; 17 | virtual void copyTempBufferToTable(int tableIndex) = 0; 18 | virtual float getTableFrequency(int tableIndex) = 0; 19 | }; 20 | 21 | template 22 | class WaveTable : public BaseWaveTable { 23 | public: 24 | WaveTable() {} 25 | void init(MemPool<>& memPool); 26 | virtual float get(float phase); 27 | 28 | virtual size_t getTableSize() { return TABLES; } 29 | virtual size_t getSampleSize() { return SAMPLES; } 30 | 31 | virtual void setFrequency(float frequency); 32 | virtual float getTableFrequency(int tableIndex); 33 | 34 | virtual SampleBuffer& getTempBuffer() { return tempBuffer; } 35 | virtual void copyTempBufferToTable(int tableIndex); 36 | 37 | private: 38 | SampleBuffer buffer[TABLES]; 39 | float tableFrequency[TABLES]; 40 | int tableIndex; 41 | 42 | // when initialising wavetable, temporary buffer can be written to first then copied to each table. 43 | static SampleBuffer tempBuffer; 44 | 45 | void calcTableFrequencies(); 46 | }; 47 | 48 | 49 | template 50 | SampleBuffer WaveTable::tempBuffer; 51 | 52 | template 53 | void WaveTable::init(MemPool<>& memPool) { 54 | if(!tempBuffer.isInited()) { 55 | tempBuffer.init(SAMPLES, memPool); 56 | } 57 | for(int i = 0; i < TABLES; i++) { 58 | buffer[i].init(SAMPLES, memPool); 59 | } 60 | calcTableFrequencies(); 61 | } 62 | 63 | template 64 | void WaveTable::copyTempBufferToTable(int tableIndex) { 65 | for(size_t i = 0; i < SAMPLES; i++) { 66 | buffer[tableIndex][i] += tempBuffer[i]; 67 | } 68 | } 69 | 70 | template 71 | float WaveTable::get(float phase) { 72 | return buffer[tableIndex].read(phase * SAMPLES); 73 | } 74 | 75 | template 76 | void WaveTable::setFrequency(float frequency) { 77 | for(int i = 0; i < TABLES; i++) { 78 | if(frequency < tableFrequency[i]) { 79 | tableIndex = i; 80 | return; 81 | } 82 | } 83 | tableIndex = TABLES-1; 84 | } 85 | 86 | template 87 | float WaveTable::getTableFrequency(int tableIndex) { 88 | return tableFrequency[tableIndex]; 89 | } 90 | 91 | template 92 | void WaveTable::calcTableFrequencies() { 93 | // Split 10 octave range equally between number of tables 94 | float baseFrequency = 46.875; 95 | float octaveInc = RANGE_OCTAVES / float(TABLES); 96 | float octave = 0; 97 | for(int i = 0; i < TABLES; i++) { 98 | tableFrequency[i] = baseFrequency * powf(2, octave); 99 | // if(tableFrequency[i] > sampleRate*0.5) { 100 | // tableFrequency[i] = sampleRate*0.5; 101 | // } 102 | octave += octaveInc; 103 | } 104 | } 105 | 106 | } 107 | 108 | #endif 109 | -------------------------------------------------------------------------------- /src/dsp/waveshapes/wavetable/WaveTableFactory.cpp: -------------------------------------------------------------------------------- 1 | #include "WaveTableFactory.h" 2 | #include 3 | 4 | using namespace eurorack; 5 | 6 | float WaveTableFactory::SAMPLERATE = 48000; 7 | 8 | bool WaveTableFactory::addSine(BaseWaveTable* wavetable, float amplitude, int mult, float phaseShift) { 9 | float nyquist = SAMPLERATE * 0.5; 10 | size_t bufferSize = wavetable->getSampleSize(); 11 | SampleBuffer& tempBuffer = wavetable->getTempBuffer(); 12 | bool added = false; 13 | 14 | // create sine in temp buffer 15 | float phaseIncrement = (1 / float(bufferSize)) * M_PI*2 * mult; 16 | float phase = phaseShift; 17 | for(size_t i = 0; i < bufferSize; i++) { 18 | tempBuffer[i] = sinf(phase) * amplitude; 19 | phase += phaseIncrement; 20 | } 21 | 22 | // copy temp buffer to each table if it's max frequency is less than nyquist 23 | for(size_t tableIndex = 0; tableIndex < wavetable->getTableSize(); tableIndex++) { 24 | float maxFrequency = wavetable->getTableFrequency(tableIndex) * mult; 25 | if(maxFrequency <= nyquist) { 26 | added = true; 27 | wavetable->copyTempBufferToTable(tableIndex); 28 | } 29 | } 30 | 31 | return added; 32 | } 33 | 34 | void WaveTableFactory::addHarmonics(BaseWaveTable* wavetable, RollOffFunction* rolloff, float amplitude, int mult) { 35 | int harmonic = 1; 36 | bool added = true; 37 | while(added) { 38 | float harmonicAmplitude = rolloff->rolloff(harmonic) * amplitude; 39 | if(harmonicAmplitude > 0) { 40 | int harmonicMult = harmonic * mult; 41 | added = addSine(wavetable, harmonicAmplitude, harmonicMult); 42 | } 43 | harmonic++; 44 | } 45 | } 46 | 47 | void WaveTableFactory::addSquare(BaseWaveTable* wavetable, float amplitude, int mult) { 48 | SquareRollOffFunction rolloff = SquareRollOffFunction(); 49 | addHarmonics(wavetable, &rolloff, amplitude, mult); 50 | } 51 | 52 | void WaveTableFactory::addTriangle(BaseWaveTable* wavetable, float amplitude, int mult) { 53 | TriangleRollOffFunction rolloff = TriangleRollOffFunction(); 54 | addHarmonics(wavetable, &rolloff, amplitude, mult); 55 | } 56 | 57 | void WaveTableFactory::addRamp(BaseWaveTable* wavetable, float amplitude, int mult) { 58 | RampRollOffFunction rolloff = RampRollOffFunction(); 59 | addHarmonics(wavetable, &rolloff, amplitude, mult); 60 | } 61 | 62 | void WaveTableFactory::addPulse(BaseWaveTable* wavetable, float amplitude, int mult) { 63 | PulseRollOffFunction rolloff = PulseRollOffFunction(); 64 | addHarmonics(wavetable, &rolloff, amplitude, mult); 65 | } 66 | 67 | void WaveTableFactory::addImpulse(BaseWaveTable* wavetable, float amplitude, int mult) { 68 | float amp = amplitude * -0.18; 69 | addSine(wavetable, amp * 1.0, mult * 1, 0); 70 | addSine(wavetable, amp * -0.9, mult * 2, M_PI*0.5); 71 | addSine(wavetable, amp * -0.8, mult * 3, 0); 72 | addSine(wavetable, amp * 0.7, mult * 4, M_PI*0.5); 73 | addSine(wavetable, amp * 0.6, mult * 5, 0); 74 | addSine(wavetable, amp * -0.5, mult * 6, M_PI*0.5); 75 | addSine(wavetable, amp * -0.4, mult * 7, 0); 76 | addSine(wavetable, amp * 0.3, mult * 8, M_PI*0.5); 77 | addSine(wavetable, amp * 0.2, mult * 9, 0); 78 | addSine(wavetable, amp * -0.1, mult * 10, M_PI*0.5); 79 | } 80 | 81 | void WaveTableFactory::addViolin(BaseWaveTable* wavetable, float amplitude, int mult) { 82 | float amp = amplitude * 0.490; 83 | addSine(wavetable, amp * 0.995, mult * 1, 0); 84 | addSine(wavetable, amp * 0.940, mult * 2, M_PI*0.5); 85 | addSine(wavetable, amp * 0.425, mult * 3, 0); 86 | addSine(wavetable, amp * 0.480, mult * 4, M_PI*0.5); 87 | addSine(wavetable, amp * 0.365, mult * 6, M_PI*0.5); 88 | addSine(wavetable, amp * 0.040, mult * 7, 0); 89 | addSine(wavetable, amp * 0.085, mult * 8, M_PI*0.5); 90 | addSine(wavetable, amp * 0.090, mult * 10, M_PI*0.5); 91 | } 92 | -------------------------------------------------------------------------------- /src/hardware/dac8164/DAC8164.cpp: -------------------------------------------------------------------------------- 1 | // ********************************************************************************** 2 | // Driver definition for TI DAC7565, DAC7564, DAC8164 and DAC8564 Library 3 | // ********************************************************************************** 4 | // Creative Commons Attrib Share-Alike License 5 | // You are free to use/extend this library but please abide with the CC-BY-SA license: 6 | // http://creativecommons.org/licenses/by-sa/4.0/ 7 | // 8 | // For any explanation see DAC7565 information at 9 | // http://www.ti.com/product/dac7565 10 | // 11 | // Code based on following datasheet 12 | // http://www.ti.com/lit/gpn/dac7565 13 | // 14 | // Written by Charles-Henri Hallard (http://hallard.me) 15 | // 16 | // History : V1.00 2015-04-14 - First release 17 | // 18 | // All text above must be included in any redistribution. 19 | // 20 | // ********************************************************************************** 21 | #include "DAC8164.h" 22 | #include 23 | 24 | // Class Constructor 25 | DAC8164::DAC8164(uint8_t enablePin, uint8_t syncPin, uint8_t ldacPin) : 26 | enablePin(enablePin), syncPin(syncPin), ldacPin(ldacPin), 27 | spiMode(20000000L, MSBFIRST, SPI_MODE0) { 28 | } 29 | 30 | void DAC8164::init(void) { 31 | // Sync HIGH 32 | if (syncPin != -1) { 33 | pinMode(syncPin, OUTPUT); 34 | digitalWrite(syncPin, HIGH); 35 | } 36 | 37 | // LDAC to low 38 | if (ldacPin != -1) { 39 | pinMode(ldacPin, OUTPUT); 40 | digitalWrite(ldacPin, LOW); 41 | } 42 | 43 | SPI.begin(); 44 | } 45 | 46 | void DAC8164::write(uint32_t data) { 47 | if (enablePin != -1) { 48 | digitalWrite(enablePin, LOW); 49 | } 50 | if (syncPin != -1) { 51 | digitalWrite(syncPin, LOW); 52 | } 53 | 54 | uint8_t datahigh = (uint8_t) ((data >> 16) & 0xFF); 55 | uint8_t datamid = (uint8_t) ((data >> 8) & 0xFF); 56 | uint8_t datalow = (uint8_t) ((data >> 0) & 0xFF); 57 | 58 | SPI.beginTransaction(spiMode); 59 | SPI.transfer(datahigh); 60 | SPI.transfer(datamid); 61 | SPI.transfer(datalow); 62 | SPI.endTransaction(); 63 | 64 | if (syncPin != -1) { 65 | digitalWrite(syncPin, HIGH); 66 | } 67 | if (enablePin != -1) { 68 | digitalWrite(enablePin, HIGH); 69 | } 70 | } 71 | 72 | void DAC8164::setReference(uint16_t reference) { 73 | uint32_t data = DAC_MASK_PD0; 74 | data |= reference; 75 | write(data); 76 | } 77 | 78 | void DAC8164::writeChannel(uint8_t channel, uint16_t value) 79 | { 80 | uint32_t data ; 81 | if (channel == DAC_CHANNEL_A) { 82 | data = DAC_SINGLE_CHANNEL_UPDATE; 83 | } else if (channel == DAC_CHANNEL_B) { 84 | data = DAC_SINGLE_CHANNEL_UPDATE | DAC_MASK_DACSEL0 ; 85 | } else if (channel == DAC_CHANNEL_C) { 86 | data = DAC_SINGLE_CHANNEL_UPDATE| DAC_MASK_DACSEL1 ; 87 | } else if (channel == DAC_CHANNEL_D) { 88 | data = DAC_SINGLE_CHANNEL_UPDATE | DAC_MASK_DACSEL1 | DAC_MASK_DACSEL0 ; 89 | } else if (channel == DAC_CHANNEL_ALL) { 90 | data = DAC_BROADCAST_UPDATE | DAC_MASK_DACSEL1 ; 91 | } else { 92 | return; 93 | } 94 | 95 | // value is 14 MSB bits (last 2 bits set to 0) 96 | data |= value << 2; 97 | write(data); 98 | } 99 | 100 | void DAC8164::setChannelPower(uint8_t channel, uint16_t power) 101 | { 102 | uint32_t data = power | DAC_MASK_PD0 ; 103 | if (channel == DAC_CHANNEL_A) { 104 | data |= DAC_SINGLE_CHANNEL_UPDATE; 105 | } else if (channel == DAC_CHANNEL_B) { 106 | data |= DAC_SINGLE_CHANNEL_UPDATE | DAC_MASK_DACSEL0 ; 107 | } else if (channel == DAC_CHANNEL_C) { 108 | data |= DAC_SINGLE_CHANNEL_UPDATE| DAC_MASK_DACSEL1 ; 109 | } else if (channel == DAC_CHANNEL_D) { 110 | data |= DAC_SINGLE_CHANNEL_UPDATE | DAC_MASK_DACSEL1 | DAC_MASK_DACSEL0 ; 111 | } else if (channel == DAC_CHANNEL_ALL) { 112 | data |= DAC_BROADCAST_UPDATE | DAC_MASK_DACSEL1 ; 113 | } 114 | 115 | write(data); 116 | } 117 | 118 | -------------------------------------------------------------------------------- /src/controller/SingleEncoderController.h: -------------------------------------------------------------------------------- 1 | #ifndef SingleEncoderController_h 2 | #define SingleEncoderController_h 3 | 4 | #include "eeprom/Config.h" 5 | #include "hardware/RotaryEncoderButton.h" 6 | 7 | #define AbstractMainController SingleEncoderController 8 | 9 | 10 | class ControllerConfig { 11 | public: 12 | uint8_t controllerIndex; 13 | uint8_t parameterIndex; 14 | }; 15 | 16 | template 17 | class SingleEncoderController { 18 | public: 19 | SingleEncoderController(RotaryEncoderButton& rotaryEncoderButton) : 20 | encoder(rotaryEncoderButton) {} 21 | 22 | void init(); 23 | void update(); 24 | 25 | protected: 26 | TypeSelector controllers; 27 | 28 | RotaryEncoderButton& encoder; 29 | 30 | ConfigField controllerConfig; 31 | 32 | void (SingleEncoderController::*encoderHeldTurn)(int8_t direction) = &SingleEncoderController::changeController; 33 | void (SingleEncoderController::*encoderShortPress)() = &SingleEncoderController::changeParameter; 34 | void (SingleEncoderController::*encoderTurn)(int8_t direction) = &SingleEncoderController::changeValue; 35 | bool initOnModeSelect = true; 36 | 37 | virtual void controllerInit(); 38 | void doEncoderEvent(RotaryEncoderButton::EncoderEvent event); 39 | void saveState(); 40 | 41 | void changeController(int8_t direction); 42 | void changeParameter(); 43 | void changeValue(int8_t direction); 44 | }; 45 | 46 | 47 | template 48 | void SingleEncoderController::init() { 49 | Config::config.load(controllerConfig); 50 | controllers.select(controllerConfig.data.controllerIndex); 51 | for(B* controller : controllers.getItems()) { 52 | controller->load(); 53 | } 54 | controllerInit(); 55 | } 56 | 57 | template 58 | void SingleEncoderController::update() { 59 | doEncoderEvent(encoder.getEncoderEvent()); 60 | controllers.getSelected()->update(); 61 | } 62 | 63 | template 64 | void SingleEncoderController::controllerInit() { 65 | saveState(); 66 | controllers.getSelected()->init(); 67 | } 68 | 69 | template 70 | void SingleEncoderController::saveState() { 71 | controllerConfig.data.controllerIndex = controllers.getSelectedIndex(); 72 | Config::config.save(controllerConfig); 73 | } 74 | 75 | template 76 | void SingleEncoderController::changeController(int8_t direction) { 77 | controllers.cycle(direction); 78 | controllerInit(); 79 | } 80 | 81 | template 82 | void SingleEncoderController::changeParameter() { 83 | controllerConfig.data.parameterIndex = controllers.getSelected()->cycleParameter(1); 84 | if(initOnModeSelect) { 85 | controllers.getSelected()->init(); 86 | } 87 | 88 | Config::config.save(controllerConfig); 89 | } 90 | 91 | template 92 | void SingleEncoderController::changeValue(int8_t direction) { 93 | controllers.getSelected()->cycleValue(direction); 94 | } 95 | 96 | template 97 | void SingleEncoderController::doEncoderEvent(RotaryEncoderButton::EncoderEvent event) { 98 | switch(event) { 99 | case RotaryEncoderButton::EncoderEvent::EVENT_CLOCKWISE: 100 | (this->*encoderTurn)(1); 101 | break; 102 | case RotaryEncoderButton::EncoderEvent::EVENT_ANTICLOCKWISE: 103 | (this->*encoderTurn)(-1); 104 | break; 105 | case RotaryEncoderButton::EncoderEvent::EVENT_HELD_CLOCKWISE: 106 | (this->*encoderHeldTurn)(1); 107 | break; 108 | case RotaryEncoderButton::EncoderEvent::EVENT_HELD_ANTICLOCKWISE: 109 | (this->*encoderHeldTurn)(-1); 110 | break; 111 | case RotaryEncoderButton::EncoderEvent::EVENT_SHORT_PRESS: 112 | (this->*encoderShortPress)(); 113 | break; 114 | case RotaryEncoderButton::EncoderEvent::EVENT_NONE: 115 | break; 116 | } 117 | } 118 | 119 | #endif -------------------------------------------------------------------------------- /src/test_io.cpp: -------------------------------------------------------------------------------- 1 | #ifdef TEST_COMPILE 2 | #include "eurorack.h" 3 | #include "eurorack_dsp.h" 4 | #include "eurorack_max11300.h" 5 | #include "eurorack_mcp23s17.h" 6 | #include "eurorack_hc595.h" 7 | #include "eurorack_is32fl3738.h" 8 | #include "eurorack_dac8164.h" 9 | 10 | void setup() {} 11 | void loop() {} 12 | 13 | //Native 14 | 15 | class Hardware { 16 | public: 17 | static Hardware hw; 18 | AnalogInput(A0) 19 | AnalogInput(test1, A0) 20 | AnalogOutput(A1) 21 | AnalogOutput(test2, A1) 22 | DigitalInput(10) 23 | DigitalInput(test3, 10) 24 | DigitalOutput(11) 25 | DigitalOutput(test4, 11) 26 | 27 | AnalogInputSumPin<> sum = AnalogInputSumPin<>(Hardware::hw.A0, Hardware::hw.test1); 28 | }; 29 | 30 | Hardware Hardware::hw; 31 | 32 | void testNativePins() { 33 | Hardware::hw.A0.analogRead(); 34 | Hardware::hw.A1.analogWrite(5); 35 | Hardware::hw.D10.digitalRead(); 36 | Hardware::hw.D11.digitalWrite(true); 37 | 38 | Hardware::hw.test1.analogRead(); 39 | Hardware::hw.test2.analogWrite(5); 40 | Hardware::hw.test3.digitalRead(); 41 | Hardware::hw.test4.digitalWrite(true); 42 | } 43 | 44 | // HC595 45 | 46 | HC595Device hc595 = HC595Device(10, 11, 12); 47 | 48 | void testHC595Pins() { 49 | hc595.setDeferredOutput(false); 50 | hc595.pins[0].digitalWrite(true); 51 | } 52 | 53 | // MCP23S17 54 | 55 | MCP23S17Device mcp23s17 = MCP23S17Device(10, 0x20); 56 | 57 | void testMCP23S17Pins() { 58 | mcp23s17.pins[0].setPinType(DIGITAL_OUTPUT); 59 | mcp23s17.pins[0].digitalWrite(true); 60 | mcp23s17.pins[0].digitalRead(); 61 | } 62 | 63 | // MAX11300 64 | 65 | MAX11300Device max11300 = MAX11300Device(&SPI, 10, 11); 66 | LinearInput> max11300Input = LinearInput>(max11300.pins[0], -5.0, 5.0, 0.0, 1.0); 67 | 68 | void testMAX11300Pins() { 69 | max11300.pins[0].setPinType(ANALOG_OUTPUT); 70 | max11300.pins[0].digitalWrite(true); 71 | max11300.pins[0].analogWrite(5); 72 | max11300.pins[0].digitalRead(); 73 | max11300.pins[0].analogRead(); 74 | } 75 | 76 | // IS32FL3738 77 | 78 | IS32FL3738Device is32fl3738 = IS32FL3738Device(Wire, IS32_ADDRESS_A); 79 | 80 | void testIS32FL3738Pins() { 81 | is32fl3738.pins[0].analogWrite(1); 82 | } 83 | 84 | // DAC8164 85 | 86 | DAC8164Device dac8164 = DAC8164Device(); 87 | 88 | void testDAC8164Pins() { 89 | dac8164.pins[0].analogWrite(1); 90 | } 91 | 92 | // Analog Inputs 93 | 94 | LinearInput<> linearInput = LinearInput<>(Hardware::hw.A0, -5.0, 5.0, 0.0, 5.0); 95 | LinearInput<> linearSumInput = LinearInput<>(Hardware::hw.sum, -5.0, 5.0, 0.0, 5.0); 96 | AnalogGateInput<> analogGateInput = AnalogGateInput<>(Hardware::hw.A0, 3.0); 97 | CrossfadeInput<> crossfadeInput = CrossfadeInput<>(Hardware::hw.A0, -5.0, 5.0); 98 | ExpInput<> expInput = ExpInput<>(Hardware::hw.A0); 99 | PowInput<> powInput = PowInput<>(Hardware::hw.A0, 2, -5.0, 5.0); 100 | 101 | void testAnalogInputs() { 102 | linearInput.update(); 103 | linearInput.getValue(); 104 | analogGateInput.update(); 105 | analogGateInput.isTriggeredOn(); 106 | analogGateInput.isGateOn(); 107 | } 108 | 109 | // Digital inputs 110 | 111 | GateInput<> gateInput = GateInput<>(Hardware::hw.D10); 112 | PushButton<> pushButton = PushButton<>(Hardware::hw.D10); 113 | eurorack::TriggerInput<> triggerInput = eurorack::TriggerInput<>(Hardware::hw.D10, true, 10); 114 | 115 | void testDigitalInputs() { 116 | gateInput.update(); 117 | pushButton.update(); 118 | } 119 | 120 | // Digital Outputs 121 | 122 | TriggerOutput<> triggerOutput = TriggerOutput<>(Hardware::hw.D11); 123 | 124 | void testDigitalOutputs() { 125 | triggerOutput.trigger(); 126 | } 127 | 128 | // Analog Outputs 129 | 130 | AnalogTriggerOutput<> analogTriggerOutput = AnalogTriggerOutput<>(Hardware::hw.A1); 131 | 132 | void testAnalogOutputs() { 133 | analogTriggerOutput.trigger(); 134 | } 135 | 136 | #endif 137 | -------------------------------------------------------------------------------- /src/dsp/sample/SampleBuffer.h: -------------------------------------------------------------------------------- 1 | #ifndef SampleBuffer_h 2 | #define SampleBuffer_h 3 | #include 4 | #include 5 | #include "memory/MemPool.h" 6 | 7 | namespace eurorack { 8 | 9 | /** 10 | * Represents a buffer of stored samples. 11 | * The buffer has a write head that moves through the buffer as samples are written. 12 | * The sample size represents the point at which the buffer is considered full and the write head wraps around to the beginning. 13 | * The buffer size can be larger than the sample size to allow the sample to be resized up to the buffer size. 14 | * The sample can be read at any position but does not keep a track of the read head position. 15 | * SamplePlayer can be used for this and allows for multplie read heads to be used on the same sample buffer. 16 | */ 17 | class SampleBuffer 18 | { 19 | public: 20 | SampleBuffer() {} 21 | 22 | void init(size_t bufferSize, MemPool<>& memPool); 23 | void init(float sampleRate, size_t bufferSize, MemPool<>& memPool); 24 | void init(float sampleRate, float sampleFrequency, size_t bufferSize, MemPool<>& memPool); 25 | bool isInited() { return inited; } 26 | void setPlaybackSampleRate(float playbackSampleRate) { this->playbackSampleRate = playbackSampleRate; } 27 | void reset(); 28 | void clear(); 29 | 30 | size_t getSampleSize() { return sampleSize; } 31 | size_t getBufferSize() { return bufferSize; } 32 | float getSampleRate() { return sampleRate; } 33 | float getSampleFrequency() { return sampleFrequency; } 34 | void setSampleSize(size_t sampleSize); 35 | 36 | float& operator [] (size_t i) { return buffer[i]; } 37 | float operator [] (float i) { return read(i); } 38 | 39 | bool write(const float sample); 40 | bool mix(const float sample); 41 | const float read(int position) const; 42 | const float read(float position) const; 43 | float calculateReadIncrement(float playbackFrequency); 44 | 45 | bool isSampleFull() { return sampleFull; } 46 | bool isBufferFull() { return bufferFull; } 47 | 48 | protected: 49 | bool inited; 50 | size_t bufferSize; // size of buffer 51 | size_t sampleSize; // size of the sample within the buffer 52 | float* buffer; 53 | 54 | size_t writePointer; 55 | bool sampleFull; 56 | bool bufferFull; 57 | 58 | float sampleRate; // sample rate that the sample was recorded at 59 | float sampleFrequency; // actual frequency of waveform when played back at recorded sample rate 60 | float playbackSampleRate; // sample rate of playback 61 | 62 | const float read(size_t position, float fraction) const; 63 | }; 64 | 65 | 66 | inline bool SampleBuffer::write(const float sample) { 67 | buffer[writePointer] = sample; 68 | writePointer++; 69 | if(!sampleFull && writePointer >= sampleSize) { 70 | sampleFull = true; 71 | } 72 | if(writePointer >= bufferSize) { 73 | bufferFull = true; 74 | writePointer = 0; 75 | } 76 | return sampleFull; 77 | } 78 | 79 | inline bool SampleBuffer::mix(const float sample) { 80 | buffer[writePointer] += sample; 81 | writePointer++; 82 | if(!sampleFull && writePointer >= sampleSize) { 83 | sampleFull = true; 84 | } 85 | if(writePointer >= bufferSize) { 86 | bufferFull = true; 87 | writePointer = 0; 88 | } 89 | return sampleFull; 90 | } 91 | 92 | inline const float SampleBuffer::read(int position) const { 93 | return buffer[position]; 94 | } 95 | 96 | inline const float SampleBuffer::read(float position) const { 97 | int32_t intPosition = static_cast(position); 98 | float fracPosition = position - static_cast(intPosition); 99 | intPosition = static_cast(intPosition) < bufferSize ? intPosition : bufferSize - 1; 100 | return read(intPosition, fracPosition); 101 | } 102 | 103 | inline const float SampleBuffer::read(size_t intPosition, float fracPosition) const { 104 | float a = buffer[intPosition % bufferSize]; 105 | float b = buffer[(intPosition + 1) % bufferSize]; 106 | return a + (b - a) * fracPosition; 107 | } 108 | 109 | } 110 | 111 | #endif 112 | -------------------------------------------------------------------------------- /src/dsp/waveshapes/interpolation/WaveInterpolator.h: -------------------------------------------------------------------------------- 1 | #ifndef WaveInterpolator_h 2 | #define WaveInterpolator_h 3 | 4 | #include "../../base/WaveShape.h" 5 | #include "../../../util/TypeSelector.h" 6 | 7 | namespace eurorack { 8 | 9 | class Interpolator : public WaveShape { 10 | public: 11 | Interpolator(int size) { lastIndex = size-1; } 12 | void setInterpolation(float interpolation); 13 | virtual float get(float phase); 14 | virtual float polyblep(float phase, float phaseIncrement); 15 | virtual void setFrequency(float frequency); 16 | protected: 17 | float interpolation; 18 | float interpolationFraction; 19 | size_t interpolationIndex1; 20 | size_t interpolationIndex2; 21 | size_t lastIndex; 22 | 23 | virtual float get(int index, float phase) = 0; 24 | virtual float polyblep(int index, float phase, float phaseIncrement) = 0; 25 | virtual void setFrequency(int index, float frequency) = 0; 26 | }; 27 | 28 | inline void Interpolator::setInterpolation(float interpolation) { 29 | this->interpolation = interpolation; 30 | if(interpolation >= lastIndex) { 31 | interpolationIndex1 = lastIndex; 32 | interpolationIndex2 = lastIndex; 33 | interpolationFraction = 0; 34 | } else if (interpolation <= 0) { 35 | interpolationIndex1 = 0; 36 | interpolationIndex2 = 0; 37 | interpolationFraction = 0; 38 | } else { 39 | interpolationIndex1 = static_cast(interpolation); 40 | interpolationIndex2 = interpolationIndex1 + 1; 41 | interpolationFraction = interpolation - static_cast(interpolationIndex1); 42 | } 43 | } 44 | 45 | inline float Interpolator::get(float phase) { 46 | if(interpolationIndex1 == interpolationIndex2) { 47 | return get(interpolationIndex1, phase); 48 | } else { 49 | float a = get(interpolationIndex1, phase); 50 | float b = get(interpolationIndex2, phase); 51 | return a + (b - a) * interpolationFraction; 52 | } 53 | } 54 | 55 | inline float Interpolator::polyblep(float phase, float phaseIncrement) { 56 | if(interpolationIndex1 == interpolationIndex2) { 57 | return polyblep(interpolationIndex1, phase, phaseIncrement); 58 | } else { 59 | float a = polyblep(interpolationIndex1, phase, phaseIncrement); 60 | float b = polyblep(interpolationIndex2, phase, phaseIncrement); 61 | return a + (b - a) * interpolationFraction; 62 | } 63 | } 64 | 65 | inline void Interpolator::setFrequency(float frequency) { 66 | if(interpolationIndex1 == interpolationIndex2) { 67 | setFrequency(interpolationIndex1, frequency); 68 | } else { 69 | setFrequency(interpolationIndex1, frequency); 70 | setFrequency(interpolationIndex2, frequency); 71 | } 72 | } 73 | 74 | 75 | 76 | template 77 | class WaveInterpolator : public Interpolator, private TypeSelector { 78 | public: 79 | WaveInterpolator() : Interpolator(sizeof...(Ts)) {} 80 | WaveInterpolator(Ts&&... args) : Interpolator(sizeof...(Ts)), TypeSelector(args...) {} 81 | using Interpolator::get; 82 | using Interpolator::polyblep; 83 | using Interpolator::setFrequency; 84 | using TypeSelector::operator[]; 85 | 86 | protected: 87 | float get(int index, float phase) { return (*this)[index]->get(phase); } 88 | float polyblep(int index, float phase, float phaseIncrement) { return (*this)[index]->polyblep(phase, phaseIncrement); } 89 | void setFrequency(int index, float frequency) { (*this)[index]->setFrequency(frequency); } 90 | }; 91 | 92 | 93 | 94 | template 95 | class WaveArrayInterpolator : public Interpolator, private ArraySelector { 96 | public: 97 | WaveArrayInterpolator() : Interpolator(N) {} 98 | void select(float interpolation); 99 | using Interpolator::get; 100 | using Interpolator::polyblep; 101 | using Interpolator::setFrequency; 102 | using ArraySelector::operator[]; 103 | 104 | protected: 105 | float get(int index, float phase) { return (*this)[index].get(phase); } 106 | float polyblep(int index, float phase, float phaseIncrement) { return (*this)[index].polyblep(phase, phaseIncrement); } 107 | void setFrequency(int index, float frequency) { (*this)[index].setFrequency(frequency); } 108 | }; 109 | 110 | } 111 | 112 | #endif -------------------------------------------------------------------------------- /src/controller/DoubleEncoderController.h: -------------------------------------------------------------------------------- 1 | #ifndef DoubleEncoderController_h 2 | #define DoubleEncoderController_h 3 | 4 | #include "eeprom/Config.h" 5 | #include "hardware/RotaryEncoderButton.h" 6 | #include "SingleEncoderController.h" 7 | 8 | 9 | template 10 | class DoubleEncoderController { 11 | public: 12 | DoubleEncoderController(RotaryEncoderButton& rotaryEncoderButton1, RotaryEncoderButton& rotaryEncoderButton2) : 13 | encoder1(rotaryEncoderButton1), 14 | encoder2(rotaryEncoderButton2) 15 | {} 16 | 17 | void init(); 18 | void update(); 19 | 20 | protected: 21 | TypeSelector controllers; 22 | 23 | RotaryEncoderButton& encoder1; 24 | RotaryEncoderButton& encoder2; 25 | 26 | ConfigField controllerConfig; 27 | 28 | void (DoubleEncoderController::*encoder1HeldTurn)(int8_t direction) = &DoubleEncoderController::changeController; 29 | void (DoubleEncoderController::*encoder1Turn)(int8_t direction) = &DoubleEncoderController::changeParameter; 30 | void (DoubleEncoderController::*encoder1ShortPress)() = &DoubleEncoderController::controllerInit; 31 | void (DoubleEncoderController::*encoder2Turn)(int8_t direction) = &DoubleEncoderController::changeValue; 32 | void (DoubleEncoderController::*encoder2ShortPress)() = &DoubleEncoderController::selectValue; 33 | 34 | bool initOnModeSelect = true; 35 | 36 | virtual void controllerInit(); 37 | void doEncoder1Event(RotaryEncoderButton::EncoderEvent event); 38 | void doEncoder2Event(RotaryEncoderButton::EncoderEvent event); 39 | virtual void saveState(); 40 | void changeController(int8_t direction); 41 | void changeParameter(int8_t direction); 42 | void changeValue(int8_t direction); 43 | void selectValue(); 44 | }; 45 | 46 | 47 | template 48 | void DoubleEncoderController::init() { 49 | Config::config.load(controllerConfig); 50 | controllers.select(controllerConfig.data.controllerIndex); 51 | for(B* controller : controllers.getItems()) { 52 | controller->load(); 53 | } 54 | controllerInit(); 55 | } 56 | 57 | template 58 | void DoubleEncoderController::update() { 59 | doEncoder1Event(encoder1.getEncoderEvent()); 60 | doEncoder2Event(encoder2.getEncoderEvent()); 61 | controllers.getSelected()->update(); 62 | } 63 | 64 | template 65 | void DoubleEncoderController::controllerInit() { 66 | saveState(); 67 | controllers.getSelected()->init(); 68 | } 69 | 70 | template 71 | void DoubleEncoderController::saveState() { 72 | controllerConfig.data.controllerIndex = controllers.getSelectedIndex(); 73 | Config::config.save(controllerConfig); 74 | } 75 | 76 | template 77 | void DoubleEncoderController::changeController(int8_t direction) { 78 | controllers.cycle(direction); 79 | controllerInit(); 80 | } 81 | 82 | template 83 | void DoubleEncoderController::changeParameter(int8_t direction) { 84 | controllerConfig.data.parameterIndex = controllers.getSelected()->cycleParameter(direction); 85 | if(initOnModeSelect) { 86 | controllers.getSelected()->init(); 87 | } 88 | 89 | this->saveState(); 90 | } 91 | 92 | template 93 | void DoubleEncoderController::changeValue(int8_t direction) { 94 | controllers.getSelected()->cycleValue(direction); 95 | } 96 | 97 | template 98 | void DoubleEncoderController::selectValue() { 99 | controllers.getSelected()->selectValue(); 100 | } 101 | 102 | template 103 | void DoubleEncoderController::doEncoder1Event(RotaryEncoderButton::EncoderEvent event) { 104 | switch(event) { 105 | case RotaryEncoderButton::EncoderEvent::EVENT_CLOCKWISE: 106 | (this->*encoder1Turn)(1); 107 | break; 108 | case RotaryEncoderButton::EncoderEvent::EVENT_ANTICLOCKWISE: 109 | (this->*encoder1Turn)(-1); 110 | break; 111 | case RotaryEncoderButton::EncoderEvent::EVENT_HELD_CLOCKWISE: 112 | (this->*encoder1HeldTurn)(1); 113 | break; 114 | case RotaryEncoderButton::EncoderEvent::EVENT_HELD_ANTICLOCKWISE: 115 | (this->*encoder1HeldTurn)(-1); 116 | break; 117 | case RotaryEncoderButton::EncoderEvent::EVENT_SHORT_PRESS: 118 | (this->*encoder1ShortPress)(); 119 | break; 120 | case RotaryEncoderButton::EncoderEvent::EVENT_NONE: 121 | break; 122 | } 123 | } 124 | 125 | template 126 | void DoubleEncoderController::doEncoder2Event(RotaryEncoderButton::EncoderEvent event) { 127 | switch(event) { 128 | case RotaryEncoderButton::EncoderEvent::EVENT_CLOCKWISE: 129 | (this->*encoder2Turn)(1); 130 | break; 131 | case RotaryEncoderButton::EncoderEvent::EVENT_ANTICLOCKWISE: 132 | (this->*encoder2Turn)(-1); 133 | break; 134 | case RotaryEncoderButton::EncoderEvent::EVENT_SHORT_PRESS: 135 | (this->*encoder2ShortPress)(); 136 | break; 137 | case RotaryEncoderButton::EncoderEvent::EVENT_NONE: 138 | break; 139 | } 140 | } 141 | 142 | #endif --------------------------------------------------------------------------------