diff --git a/CMakeLists.txt b/CMakeLists.txt index 16978cd..afecd97 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,19 +3,53 @@ set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_EXTENSIONS OFF) project(bleepbloopmachine CXX) -add_executable(bleepbloopmachine src/main.cc) -add_library(controls src/controls.h src/controls.cc) + add_library(AudioSampleC src/kit/AudioSampleC.h src/kit/AudioSampleC.cpp) add_library(AudioSampleK src/kit/AudioSampleK.h src/kit/AudioSampleK.cpp) add_library(AudioSampleS src/kit/AudioSampleS.h src/kit/AudioSampleS.cpp) add_library(AudioSampleH src/kit/AudioSampleH.h src/kit/AudioSampleH.cpp) add_library(AudioSampleO src/kit/AudioSampleO.h src/kit/AudioSampleO.cpp) -target_link_arduino_libraries(bleepbloopmachine AUTO_PUBLIC) -target_link_arduino_libraries(controls AUTO_PUBLIC) + target_link_arduino_libraries(AudioSampleK AUTO_PUBLIC) target_link_arduino_libraries(AudioSampleC AUTO_PUBLIC) target_link_arduino_libraries(AudioSampleS AUTO_PUBLIC) target_link_arduino_libraries(AudioSampleH AUTO_PUBLIC) target_link_arduino_libraries(AudioSampleO AUTO_PUBLIC) -target_link_libraries(bleepbloopmachine PRIVATE controls AudioSampleK AudioSampleC AudioSampleS AudioSampleH AudioSampleO) + +add_executable(bleepbloopmachine src/main.h src/main.cc) +target_link_arduino_libraries(bleepbloopmachine AUTO_PUBLIC) + +add_library(controls src/controls.h src/controls.cc) +target_link_arduino_libraries(controls AUTO_PUBLIC) + +add_library(display src/display.h src/display.cc) +target_link_arduino_libraries(display AUTO_PUBLIC) + +add_library(track src/track.h src/track.cc) +target_link_arduino_libraries(track AUTO_PUBLIC) + +add_library(block src/block.h src/block.cc) +target_link_arduino_libraries(block AUTO_PUBLIC) + +add_library(weirdmath src/weirdmath.h src/weirdmath.cc) +target_link_arduino_libraries(weirdmath AUTO_PUBLIC) + +add_library(freq src/freq.h src/freq.cc) +target_link_arduino_libraries(freq AUTO_PUBLIC) + +target_link_libraries(track PUBLIC + AudioSampleK + AudioSampleC + AudioSampleS + AudioSampleH + AudioSampleO) + +target_link_libraries(bleepbloopmachine PUBLIC + controls + track + block + weirdmath + freq +) + target_enable_arduino_upload(bleepbloopmachine) diff --git a/src/block.cc b/src/block.cc new file mode 100644 index 0000000..82bd301 --- /dev/null +++ b/src/block.cc @@ -0,0 +1,140 @@ +#include "block.h" + +float freq[12][9] = { + { + 16.3516, + 32.7032, + 65.40639, + 130.8128, + 261.6256, + 523.2511, + 1046.502, + 2093.005, + 4186.009, + }, + { + 17.32391, + 34.64783, + 69.29566, + 138.5913, + 277.1826, + 554.3653, + 1108.731, + 2217.461, + 4434.922, + }, + { + 18.35405, + 36.7081, + 73.41619, + 146.8324, + 293.6648, + 587.3295, + 1174.659, + 2349.318, + 4698.636, + }, + { + 19.44544, + 38.89087, + 77.78175, + 155.5635, + 311.127, + 622.254, + 1244.508, + 2489.016, + 4978.032, + }, + { + 20.60172, + 41.20344, + 82.40689, + 164.8138, + 329.6276, + 659.2551, + 1318.51, + 2637.02, + 5274.041, + }, + { + 21.82676, + 43.65353, + 87.30706, + 174.6141, + 349.2282, + 698.4565, + 1396.913, + 2793.826, + 5587.652, + }, + { + 23.12465, + 46.2493, + 92.49861, + 184.9972, + 369.9944, + 739.9888, + 1479.978, + 2959.955, + 5919.911, + }, + { + 24.49971, + 48.99943, + 97.99886, + 195.9977, + 391.9954, + 783.9909, + 1567.982, + 3135.963, + 6271.927, + }, + { + 25.95654, + 51.91309, + 103.8262, + 207.6523, + 415.3047, + 830.6094, + 1661.219, + 3322.438, + 6644.875, + }, + { + 27.5, + 55.0, + 110.0, + 220.0, + 440.0, + 880.0, + 1760.0, + 3520.0, + 7040.0, + }, + { + 29.13524, + 58.27047, + 116.5409, + 233.0819, + 466.1638, + 932.3275, + 1864.655, + 3729.31, + 7458.62, + }, + { + 30.86771, + 61.73541, + 123.4708, + 246.9417, + 493.8833, + 987.7666, + 1975.533, + 3951.066, + 7902.133, + }, +}; + +float SoundBlock::frequency() { + return freq[note][octave]; +} diff --git a/src/block.h b/src/block.h new file mode 100644 index 0000000..55cf2a6 --- /dev/null +++ b/src/block.h @@ -0,0 +1,91 @@ +#pragma once +#include "weirdmath.h" + +class Block { +public: + bool active = false; + void activate() { active = true; } + void deactivate() { active = false; } +}; + +class KitBlock : public Block { +public: + int sample; + void sampleUp() { sample = wrapping_add(sample, 4); } + void sampleDown() { sample = wrapping_sub(sample, 4); } +}; + +#define MAX_ATTACK 800.0 +#define MAX_DECAY 800.0 + +class SoundBlock : public Block { +public: + enum Filter { low }; + int note = 0; + int octave = 3; + float attack = 10.0; + float decay = 40.0; + float delay = 0.0; + float pan = 0.0; + float amp = 1.0; + float sustain = 0.0; + float filterType = Filter::low; + float filterQ = 0.618; + float filterFreq = 8000.0; + int filterStage = 0; + float pulseWidth = 0.5; + float frequency(); + void activate(int n, int o) { + note = n; + octave = o; + active = true; + } + void filterDown() { + filterFreq = saturating_sub(filterFreq, 10000.0, 100.0, 40.0); + } + void filterUp() { + filterFreq = saturating_add(filterFreq, 10000.0, 100.0, 40.0); + } + void filterQDown() { + filterFreq = saturating_sub(filterFreq, 1.0, 0.0234, 0); + } + void filterQUp() { filterFreq = saturating_add(filterFreq, 1.0, 0.0234, 0); } + void noteUp() { note = wrapping_add(note, 11); } + void noteDown() { note = wrapping_sub(note, 11); } + void octaveUp() { octave = wrapping_add(octave, 8); } + void octaveDown() { octave = wrapping_sub(octave, 8); } + void decayUp() { decay = saturating_add_curve(decay, MAX_DECAY, 10, 0); } + void decayDown() { decay = saturating_sub_curve(decay, MAX_DECAY, 10, 0); } + void delayUp() { delay = saturating_add(delay, 100.0, 10.0, 0); } + void delayDown() { delay = saturating_sub(delay, 100.0, 10.0, 0); } + void ampUp() { amp = saturating_add(amp, 1.0, 0.1, 0); } + void ampDown() { amp = saturating_sub(amp, 1.0, 0.1, 0); } + void pulseWidthUp() { pulseWidth = saturating_add(pulseWidth, 1.0, 0.1, 0); } + void pulseWidthDown() { + pulseWidth = saturating_sub(pulseWidth, 1.0, 0.1, 0); + } + void panLeft() { pan = saturating_sub(pan, 1.0); } + void panRight() { pan = saturating_add(pan, 1.0); } + void attackUp() { attack = saturating_add_curve(attack, MAX_ATTACK, 10, 0); } + void attackDown() { attack = saturating_sub_curve(attack, MAX_ATTACK, 10, 0); } + void sustainUp() { sustain = saturating_add(sustain, 1.0, 0.137, 0); } + void sustainDown() { sustain = saturating_sub(sustain, 1.0, 0.137, 0); } + SoundBlock cut() { + active = false; + SoundBlock s; + s.note = note; + s.octave = octave; + s.active = true; + s.attack = attack; + s.decay = decay; + s.delay = delay; + s.amp = amp; + s.pan = pan; + s.sustain = sustain; + s.filterFreq = filterFreq; + s.filterQ = filterQ; + s.filterType = filterType; + s.pulseWidth = pulseWidth; + return s; + } +}; diff --git a/src/controls.cc b/src/controls.cc index f82addf..9ff430c 100644 --- a/src/controls.cc +++ b/src/controls.cc @@ -94,3 +94,20 @@ uint32_t Controls::readButtons(void) { uint32_t Controls::justPressed() { return justpressed_buttons; } uint32_t Controls::justReleased() { return justreleased_buttons; } + +Menu1Control &operator++(Menu1Control &s) { + return s = Menu1Control{wrapping_add((int)s, 3)}; +} + +Menu1Control &operator--(Menu1Control &s) { + return s = Menu1Control{wrapping_sub((int)s, 3)}; +} + + +Menu2Control &operator++(Menu2Control &s) { + return s = Menu2Control{wrapping_add((int)s, 3)}; +} + +Menu2Control &operator--(Menu2Control &s) { + return s = Menu2Control{wrapping_sub((int)s, 3)}; +} diff --git a/src/controls.h b/src/controls.h index 3cd2065..0aee729 100644 --- a/src/controls.h +++ b/src/controls.h @@ -1,5 +1,7 @@ #pragma once #include +#include "weirdmath.h" + #define BUTTON_CLOCK 48 #define BUTTON_DATA 49 #define BUTTON_LATCH 50 @@ -23,6 +25,15 @@ #define PAD_LEFT 0x40 #define PAD_RIGHT 0x80 +#define KEYLAYER_0 0 +#define KEYLAYER_1 1 +#define KEYLAYER_2 2 +#define KEYLAYER_3 3 +#define KEYLAYER_4 4 +#define KEYLAYER_5 5 + +enum class Keymod { a, b, ab, select, start, none }; + class Controls { public: void begin(); @@ -38,3 +49,17 @@ class Controls { uint32_t justpressed_buttons; uint32_t justreleased_buttons; }; + +enum class MenuMode { live, menu1, menu2 }; + +enum class Menu1Control { env, mod, filter, delay }; + +enum class Menu2Control { bpm, chain, wave, file }; + +Menu1Control &operator++(Menu1Control &s); + +Menu1Control &operator--(Menu1Control &s); + +Menu2Control &operator++(Menu2Control &s); + +Menu2Control &operator--(Menu2Control &s); diff --git a/src/display.cc b/src/display.cc new file mode 100644 index 0000000..0d9c452 --- /dev/null +++ b/src/display.cc @@ -0,0 +1,10 @@ +#include "display.h" + +Point getCoord(int idx) { + int x = idx % 4; + int y = idx / 4; + return Point { + x : GRID_OFFSET_X + (x * (BLOCK_SIZE + GRID_GAP)), + y : GRID_OFFSET_Y + (y * (BLOCK_SIZE + GRID_GAP)) + }; +} diff --git a/src/display.h b/src/display.h new file mode 100644 index 0000000..adffba8 --- /dev/null +++ b/src/display.h @@ -0,0 +1,28 @@ +#pragma once + +#include +#include + +#define NEOPIXEL_PIN 8 +#define NEOPIXEL_LENGTH 5 + +#define DISPLAY_CS 44 +#define DISPLAY_RST 46 +#define DISPLAY_DC 45 +#define DISPLAY_BACKLIGHT 47 +#define DISPLAY_HEIGHT 128 +#define DISPLAY_WIDTH 160 + +#define BLOCK_SIZE 20 +#define GRID_OFFSET_X 36 +#define GRID_OFFSET_Y 20 +#define GRID_GAP 2 +#define STANDARD_BLOCK_COLOR 0xfebb +#define TICK_BLOCK_COLOR 0xfc53 + +struct Point { + int x; + int y; +}; + +Point getCoord(int idx); diff --git a/src/freq.cc b/src/freq.cc new file mode 100644 index 0000000..e69de29 diff --git a/src/freq.h b/src/freq.h index a234edf..6f70f09 100644 --- a/src/freq.h +++ b/src/freq.h @@ -1,134 +1 @@ -float freq[12][9] = { - { - 16.3516, - 32.7032, - 65.40639, - 130.8128, - 261.6256, - 523.2511, - 1046.502, - 2093.005, - 4186.009, - }, - { - 17.32391, - 34.64783, - 69.29566, - 138.5913, - 277.1826, - 554.3653, - 1108.731, - 2217.461, - 4434.922, - }, - { - 18.35405, - 36.7081, - 73.41619, - 146.8324, - 293.6648, - 587.3295, - 1174.659, - 2349.318, - 4698.636, - }, - { - 19.44544, - 38.89087, - 77.78175, - 155.5635, - 311.127, - 622.254, - 1244.508, - 2489.016, - 4978.032, - }, - { - 20.60172, - 41.20344, - 82.40689, - 164.8138, - 329.6276, - 659.2551, - 1318.51, - 2637.02, - 5274.041, - }, - { - 21.82676, - 43.65353, - 87.30706, - 174.6141, - 349.2282, - 698.4565, - 1396.913, - 2793.826, - 5587.652, - }, - { - 23.12465, - 46.2493, - 92.49861, - 184.9972, - 369.9944, - 739.9888, - 1479.978, - 2959.955, - 5919.911, - }, - { - 24.49971, - 48.99943, - 97.99886, - 195.9977, - 391.9954, - 783.9909, - 1567.982, - 3135.963, - 6271.927, - }, - { - 25.95654, - 51.91309, - 103.8262, - 207.6523, - 415.3047, - 830.6094, - 1661.219, - 3322.438, - 6644.875, - }, - { - 27.5, - 55.0, - 110.0, - 220.0, - 440.0, - 880.0, - 1760.0, - 3520.0, - 7040.0, - }, - { - 29.13524, - 58.27047, - 116.5409, - 233.0819, - 466.1638, - 932.3275, - 1864.655, - 3729.31, - 7458.62, - }, - { - 30.86771, - 61.73541, - 123.4708, - 246.9417, - 493.8833, - 987.7666, - 1975.533, - 3951.066, - 7902.133, - }, -}; +#pragma once diff --git a/src/main.cc b/src/main.cc index 1336bf2..1d5aabe 100644 --- a/src/main.cc +++ b/src/main.cc @@ -1,5 +1,9 @@ #include "main.h" #include "controls.h" +#include "display.h" +#include "block.h" +#include "track.h" +#include "weirdmath.h" #include @@ -9,9 +13,6 @@ #define SPEAKER_ENABLE 51 -#define NEOPIXEL_PIN 8 -#define NEOPIXEL_LENGTH 5 - #define LIGHT_SENSOR A7 #define BATTERY_SENSOR A6 @@ -24,218 +25,9 @@ #include "AudioStream.h" #include "output_dacs.h" -#include "freq.h" #include #include -#define DISPLAY_CS 44 -#define DISPLAY_RST 46 -#define DISPLAY_DC 45 -#define DISPLAY_BACKLIGHT 47 -#define DISPLAY_HEIGHT 128 -#define DISPLAY_WIDTH 160 - -Adafruit_ST7735 display = - Adafruit_ST7735(&SPI1, DISPLAY_CS, DISPLAY_DC, DISPLAY_RST); - -#include "kit/AudioSampleC.h" -#include "kit/AudioSampleH.h" -#include "kit/AudioSampleK.h" -#include "kit/AudioSampleO.h" -#include "kit/AudioSampleS.h" - -enum class Waveform { - sine, - sawtooth, - square, - triangle, - arbitrary, - pulse, - sawtoothReverse, - sampleHold -}; - -#define KEYLAYER_0 0 -#define KEYLAYER_1 1 -#define KEYLAYER_2 2 -#define KEYLAYER_3 3 -#define KEYLAYER_4 4 -#define KEYLAYER_5 5 - -enum class Keymod { a, b, ab, select, start, none }; - -int wrapping_add(int cur, int max, int inc = 1, int min = 0) { - auto nxt = cur + inc; - return nxt <= max ? nxt : min; -} -int wrapping_sub(int cur, int max, int inc = 1, int min = 0) { - auto nxt = cur - inc; - return nxt >= min ? nxt : max; -} - -float wrapping_add(float cur, float max = 1.0, float inc = 0.1, - int min = -1.0) { - auto nxt = cur + inc; - return nxt <= max ? nxt : min; -} - -float wrapping_sub(float cur, float max = 1.0, float inc = 0.1, - int min = -1.0) { - auto nxt = cur - inc; - return nxt >= min ? nxt : max; -} - -float saturating_add(float cur, float max = 1.0, float inc = 0.1, - int min = -1.0) { - auto nxt = cur + inc; - return nxt <= max ? nxt : max; -} - -float saturating_sub(float cur, float max = 1.0, float inc = 0.1, - int min = -1.0) { - auto nxt = cur - inc; - return nxt >= min ? nxt : min; -} - -enum class MenuMode { live, menu1, menu2 }; - -enum class Menu1Control { env, mod, filter, delay }; - -Menu1Control &operator++(Menu1Control &s) { - return s = Menu1Control{wrapping_add((int)s, 3)}; -} - -Menu1Control &operator--(Menu1Control &s) { - return s = Menu1Control{wrapping_sub((int)s, 3)}; -} - -enum class Menu2Control { bpm, chain, wave, file }; - -Menu2Control &operator++(Menu2Control &s) { - return s = Menu2Control{wrapping_add((int)s, 3)}; -} - -Menu2Control &operator--(Menu2Control &s) { - return s = Menu2Control{wrapping_sub((int)s, 3)}; -} - -String note_names[] = {"c", "c#", "d", "d#", "e", "f", - "f#", "g", "g#", "a", "a#", "b"}; -String octave_names[] = {"0", "1", "2", "3", "4", "5", "6", "7", "8"}; - -#define BLOCK_SIZE 20 -#define GRID_OFFSET_X 36 -#define GRID_OFFSET_Y 20 -#define GRID_GAP 2 -#define STANDARD_BLOCK_COLOR 0xfebb -#define TICK_BLOCK_COLOR 0xfc53 - -struct Point { - int x; - int y; -}; - -Point getCoord(int idx) { - int x = idx % 4; - int y = idx / 4; - return Point{ - x : GRID_OFFSET_X + (x * (BLOCK_SIZE + GRID_GAP)), - y : GRID_OFFSET_Y + (y * (BLOCK_SIZE + GRID_GAP)) - }; -} - -#define MAX_ATTACK 400.0 -#define MAX_DECAY 400.0 - -class Block { -public: - bool active = false; - void activate() { active = true; } - void deactivate() { active = false; } -}; - -class KitBlock : public Block { -public: - int sample; - void sampleUp() { sample = wrapping_add(sample, 4); } - void sampleDown() { sample = wrapping_sub(sample, 4); } -}; - -class SoundBlock : public Block { -public: - enum Filter { low }; - int note = 0; - int octave = 3; - float attack = 10.0; - float decay = 40.0; - float delay = 0.0; - float pan = 0.0; - float amp = 1.0; - float sustain = 0.0; - float filterType = Filter::low; - float filterQ = 0.618; - float filterFreq = 8000.0; - int filterStage = 0; - float pulseWidth = 0.5; - float frequency() { return freq[note][octave]; } - void activate(int n, int o) { - note = n; - octave = o; - active = true; - } - void filterDown() { - filterFreq = saturating_sub(filterFreq, 10000.0, 100.0, 40.0); - } - void filterUp() { - filterFreq = saturating_add(filterFreq, 10000.0, 100.0, 40.0); - } - void filterQDown() { - filterFreq = saturating_sub(filterFreq, 1.0, 0.0234, 0); - } - void filterQUp() { filterFreq = saturating_add(filterFreq, 1.0, 0.0234, 0); } - void noteUp() { note = wrapping_add(note, 11); } - void noteDown() { note = wrapping_sub(note, 11); } - void octaveUp() { octave = wrapping_add(octave, 8); } - void octaveDown() { octave = wrapping_sub(octave, 8); } - void decayUp() { decay = saturating_add(decay, MAX_DECAY, 20.0, 0); } - void decayDown() { decay = saturating_sub(decay, MAX_DECAY, 20.0, 0); } - void delayUp() { delay = saturating_add(delay, 100.0, 10.0, 0); } - void delayDown() { delay = saturating_sub(delay, 100.0, 10.0, 0); } - void ampUp() { amp = saturating_add(amp, 1.0, 0.1, 0); } - void ampDown() { amp = saturating_sub(amp, 1.0, 0.1, 0); } - void pulseWidthUp() { pulseWidth = saturating_add(pulseWidth, 1.0, 0.1, 0); } - void pulseWidthDown() { - pulseWidth = saturating_sub(pulseWidth, 1.0, 0.1, 0); - } - void panLeft() { pan = saturating_sub(pan, 1.0); } - void panRight() { pan = saturating_add(pan, 1.0); } - void attackUp() { attack = saturating_add(attack, MAX_ATTACK, 20.0, 0); } - void attackDown() { attack = saturating_sub(attack, MAX_ATTACK, 20.0, 0); } - void sustainUp() { sustain = saturating_add(sustain, 1.0, 0.137, 0); } - void sustainDown() { sustain = saturating_sub(sustain, 1.0, 0.137, 0); } - SoundBlock cut() { - active = false; - SoundBlock s; - s.note = note; - s.octave = octave; - s.active = true; - s.attack = attack; - s.decay = decay; - s.delay = delay; - s.amp = amp; - s.pan = pan; - s.sustain = sustain; - s.filterFreq = filterFreq; - s.filterQ = filterQ; - s.filterType = filterType; - s.pulseWidth = pulseWidth; - return s; - } -}; - -SoundBlock clipboard; -bool hasClipboard = false; - AudioMixer4 mainmixer0L; AudioMixer4 mainmixer0R; AudioMixer4 mainmixer1L; @@ -250,393 +42,20 @@ AudioOutputAnalogStereo headphones; AudioConnection left_ear_patch(mainmixerL, 0, headphones, 0); AudioConnection right_ear_patch(mainmixerR, 0, headphones, 1); -class Track { -protected: - int lastStepBlockIndex = 0; - -public: - int bpmDivider = 1; - unsigned char name; - Block blocks[16]; - int stepBlockIndex(int currentStep) { return currentStep / bpmDivider % 16; } - void bpmDividerUp() { - switch (bpmDivider) { - case 4: - bpmDivider = 2; - break; - case 2: - bpmDivider = 1; - break; - } - } - void bpmDividerDown() { - switch (bpmDivider) { - case 2: - bpmDivider = 4; - break; - case 1: - bpmDivider = 2; - break; - } - } - virtual void updateDisplay(Adafruit_GFX *display, MenuMode mode, - Menu1Control menu1control, - Menu2Control menu2control) = 0; - virtual void handle(Menu1Control control, Keymod modifier, uint32_t pressed, - int blockIndex) = 0; - virtual void handle(Menu2Control control, Keymod modifier, uint32_t pressed, - int blockIndex) = 0; - virtual void handle(Keymod modifier, uint32_t pressed, int blockIndex) = 0; - void step(int currentStep) { - auto idx = stepBlockIndex(currentStep); - if (idx != lastStepBlockIndex) { - lastStepBlockIndex = idx; - play(idx); - } - } - virtual void play(int blockIndex) = 0; -}; - -class KitTrack : public Track { - AudioPlayMemory *player = new AudioPlayMemory(); - AudioConnection *patches[2]; - -public: - KitBlock *blocks = new KitBlock[16]; - // TODO update display should be the same every time, it should grab info it - // needs to display from the blocks? - void updateDisplay(Adafruit_GFX *display, MenuMode mode, - Menu1Control menu1control, Menu2Control menu2control) {} - void handle(Menu1Control control, Keymod modifier, uint32_t pressed, - int blockIndex){}; - void handle(Menu2Control control, Keymod modifier, uint32_t pressed, - int blockIndex){}; - void handle(Keymod modifier, uint32_t pressed, int blockIndex) { - auto block = &blocks[blockIndex]; - if (modifier == Keymod::b) { - // if (pressed & PAD_UP) { - // block->sampleRateUp(); - // } - // if (pressed & PAD_DOWN) { - // block->sampleRateDown(); - // } - if (pressed & PAD_LEFT) { - block->sampleDown(); - } - if (pressed & PAD_RIGHT) { - block->sampleUp(); - } - } else if (modifier == Keymod::none) { - if (pressed == PAD_B) { - block->activate(); - Serial.println("activated"); - } else if (pressed == PAD_A) { - block->deactivate(); - // TODO local clipboard - // if (block->active) { - // //clipboard = sound->cut(); - // //hasClipboard = true; - // } else if (hasClipboard) { - // //blocks[blockIndex] = clipboard; - // } - } - } - } - void play(int blockIndex) { - auto block = &blocks[blockIndex]; - - if (!block->active) { - return; - } - - switch (block->sample) { - case 0: - player->play(AudioSampleK); - break; - case 1: - player->play(AudioSampleS); - break; - case 2: - player->play(AudioSampleH); - break; - case 3: - player->play(AudioSampleO); - break; - case 4: - player->play(AudioSampleC); - break; - default: - break; - } - } - KitTrack(unsigned char n, AudioMixer4 &lMixer, AudioMixer4 &rMixer, - int mixerIndex) { - name = n; - patches[0] = new AudioConnection(*player, 0, lMixer, mixerIndex); - patches[1] = new AudioConnection(*player, 0, rMixer, mixerIndex); - } -}; +Adafruit_NeoPixel neopixels(NEOPIXEL_LENGTH, NEOPIXEL_PIN, NEO_GRB); -class Wave : public Track { - AudioConnection *patches[7]; +Adafruit_ST7735 display = + Adafruit_ST7735(&SPI1, DISPLAY_CS, DISPLAY_DC, DISPLAY_RST); -public: - Waveform form; - AudioSynthWaveform *synthL = new AudioSynthWaveform(); - AudioSynthWaveform *synthR = new AudioSynthWaveform(); - AudioEffectEnvelope *envL = new AudioEffectEnvelope(); - AudioEffectEnvelope *envR = new AudioEffectEnvelope(); - AudioFilterBiquad *filterL = new AudioFilterBiquad(); - AudioFilterBiquad *filterR = new AudioFilterBiquad(); - AudioMixer4 *mixerL = new AudioMixer4(); - AudioMixer4 *mixerR = new AudioMixer4(); - SoundBlock *blocks = new SoundBlock[16]; - Wave(unsigned char n, Waveform f, AudioMixer4 &lmixer, AudioMixer4 &rmixer, - int mixerIndex) { - form = f; - name = n; - patches[0] = new AudioConnection(*synthL, *envL); - patches[1] = new AudioConnection(*envL, *filterL); - patches[2] = new AudioConnection(*filterL, 0, *mixerL, 0); - patches[3] = new AudioConnection(*synthR, *envR); - patches[4] = new AudioConnection(*envR, *filterR); - patches[5] = new AudioConnection(*filterR, 0, *mixerR, 0); - patches[6] = new AudioConnection(*mixerL, 0, lmixer, mixerIndex); - patches[7] = new AudioConnection(*mixerR, 0, rmixer, mixerIndex); - filterL->setLowpass(0, 12000); - filterR->setLowpass(0, 12000); - synthL->begin((int)f); - synthR->begin((int)f); - synthL->amplitude(1.0); - synthR->amplitude(1.0); - envL->sustain(1.0); - envR->sustain(1.0); - if (f == Waveform::pulse) { - synthL->pulseWidth(0.5); - synthR->pulseWidth(0.5); - } - } - ~Wave() { - // TODO delete pointers - delete[] patches; - } - void play(int blockIndex) { - auto sound = &blocks[blockIndex]; - if (!sound->active) { - return; - } - synthL->frequency(sound->frequency()); - synthR->frequency(sound->frequency()); - envL->attack(sound->attack); - envL->decay(sound->decay); - envL->delay(sound->delay); - envL->sustain(sound->sustain); - envR->attack(sound->attack); - envR->decay(sound->decay); - envR->delay(sound->delay); - envR->sustain(sound->sustain); - mixerL->gain(0, sound->amp / 2); - mixerR->gain(0, sound->amp / 2); - if (sound->filterType == SoundBlock::Filter::low) { - filterL->setLowpass(sound->filterStage, sound->filterFreq, - sound->filterQ); - filterR->setLowpass(sound->filterStage, sound->filterFreq, - sound->filterQ); - } - if (sound->pan < 0) { - mixerL->gain(0, (sound->amp / 2) + (abs(sound->pan) / 2)); - mixerR->gain(0, (sound->amp / 2) - (abs(sound->pan) / 2)); - } else if (sound->pan > 0) { - mixerR->gain(0, (sound->amp / 2) + (abs(sound->pan) / 2)); - mixerL->gain(0, (sound->amp / 2) - (abs(sound->pan) / 2)); - } - if (form == Waveform::pulse) { - synthL->pulseWidth(sound->pulseWidth); - synthR->pulseWidth(sound->pulseWidth); - } - envL->noteOn(); - envR->noteOn(); - } - void updateDisplay(Adafruit_GFX *display, MenuMode mode, - Menu1Control menu1control, Menu2Control menu2control) { - for (int i = 0; i <= 0xf; i++) { - auto sound = &blocks[i]; - Point point = getCoord(i); - if (!sound->active) - continue; - String note = note_names[sound->note]; - display->fillRect(point.x + (sound->octave * 2), point.y, 2, 5, - sound->octave * 360); - display->drawChar(point.x + 2, point.y + 5, note[0], ST7735_BLACK, - ST7735_BLACK, 1); - if (note.length() == 2) { - display->drawChar(point.x + 9, point.y + 5, '#', ST7735_BLACK, - ST7735_BLACK, 1); - } - if (mode == MenuMode::live) { - // only note name and octave - } else if (mode == MenuMode::menu1) { - if (menu1control == Menu1Control::env) { - // draw panning - auto panX = ((BLOCK_SIZE / 2) * sound->pan) + BLOCK_SIZE / 2; - display->fillRect(point.x + panX, point.y + BLOCK_SIZE - 2, 2, 2, - ST7735_CYAN); - - // draw attack/decay/amp - /// perhaps unexpected is that amplitude is represented by the height - /// of the envelope, sustain being the bar on the right - - auto envColor = ST7735_GREEN; - float attackX = ((float)BLOCK_SIZE / 2 / MAX_ATTACK) * sound->attack; - float decayW = ((float)BLOCK_SIZE / 2 / MAX_DECAY) * sound->decay; - - /// attack/amp - display->drawLine(point.x, point.y + BLOCK_SIZE, point.x + attackX, - (point.y + BLOCK_SIZE) - BLOCK_SIZE * sound->amp, - envColor); - /// decay/amp - display->drawLine(point.x + attackX, - (point.y + BLOCK_SIZE) - BLOCK_SIZE * sound->amp, - point.x + attackX + decayW, (point.y + BLOCK_SIZE), - envColor); - - // draw sustain - auto susY = BLOCK_SIZE * sound->sustain; - display->fillRect(point.x + BLOCK_SIZE - 4, - point.y + (BLOCK_SIZE - susY), 4, susY, - ST7735_CYAN); - } - } else if (mode == MenuMode::menu2) { - display->drawPixel(point.x + 5, point.y + 5, ST7735_BLACK); - } - } - display->drawChar(10, 10, name, ST77XX_BLACK, ST7735_BLACK, 1); - } - void handle(Menu1Control control, Keymod modifier, uint32_t pressed, - int blockIndex) { - auto sound = &blocks[blockIndex]; - if (control == Menu1Control::env) { - if (modifier == Keymod::a) { - if (pressed & PAD_RIGHT) { - sound->decayUp(); - } - if (pressed & PAD_LEFT) { - sound->decayDown(); - } - if (pressed & PAD_UP) { - sound->sustainUp(); - } - if (pressed & PAD_DOWN) { - sound->sustainDown(); - } - } - if (modifier == Keymod::b) { - if (pressed & PAD_LEFT) { - sound->attackDown(); - } - if (pressed & PAD_RIGHT) { - sound->attackUp(); - } - if (pressed & PAD_UP) { - sound->sustainUp(); - } - if (pressed & PAD_DOWN) { - sound->sustainDown(); - } - } - if (modifier == Keymod::ab) { - if (pressed & PAD_LEFT) { - sound->panLeft(); - } - if (pressed & PAD_RIGHT) { - sound->panRight(); - } - if (pressed & PAD_UP) { - sound->ampUp(); - } - if (pressed & PAD_DOWN) { - sound->ampDown(); - } - } - } else if (control == Menu1Control::filter) { - if (modifier == Keymod::a) { - } else if (modifier == Keymod::b) { - if (pressed & PAD_LEFT) { - sound->filterDown(); - } - if (pressed & PAD_RIGHT) { - sound->filterUp(); - } - if (pressed & PAD_UP) { - sound->filterQUp(); - } - if (pressed & PAD_DOWN) { - sound->filterQDown(); - } - } - } else if (control == Menu1Control::mod) { - if (modifier == Keymod::a) { - } else if (modifier == Keymod::b) { - if (pressed & PAD_UP) { - if (form == Waveform::pulse) { - sound->pulseWidthUp(); - } - } - if (pressed & PAD_DOWN) { - if (form == Waveform::pulse) { - sound->pulseWidthDown(); - } - } - } - } else if (control == Menu1Control::delay) { - if (modifier == Keymod::a) { - } else if (modifier == Keymod::b) { - if (pressed & PAD_UP) { - sound->delayUp(); - } - if (pressed & PAD_DOWN) { - sound->delayDown(); - } - } - } - } - void handle(Menu2Control control, Keymod modifier, uint32_t pressed, - int blockIndex) {} - int lastNote = 0; - int lastOctave = 3; - void handle(Keymod modifier, uint32_t pressed, int blockIndex) { - auto sound = &blocks[blockIndex]; - if (modifier == Keymod::b) { - if (pressed & PAD_UP) { - sound->noteUp(); - } - if (pressed & PAD_DOWN) { - sound->noteDown(); - } - if (pressed & PAD_LEFT) { - sound->octaveDown(); - } - if (pressed & PAD_RIGHT) { - sound->octaveUp(); - } - lastNote = sound->note; - lastOctave = sound->octave; - } else if (modifier == Keymod::none) { - if (pressed == PAD_B) { - sound->activate(lastNote, lastOctave); - } else if (pressed == PAD_A) { - if (sound->active) { - clipboard = sound->cut(); - hasClipboard = true; - } else if (hasClipboard) { - blocks[blockIndex] = clipboard; - } - } - } - } -}; +Point getCoord(int idx) { + int x = idx % 4; + int y = idx / 4; + return Point { + x : GRID_OFFSET_X + (x * (BLOCK_SIZE + GRID_GAP)), + y : GRID_OFFSET_Y + (y * (BLOCK_SIZE + GRID_GAP)) + }; +} -Adafruit_NeoPixel neopixels(NEOPIXEL_LENGTH, NEOPIXEL_PIN, NEO_GRB); // TODO sizeof()/sizeof[0] #define NUMBER_OF_TRACKS 5 @@ -650,22 +69,19 @@ class BleepBloopMachine { int selectedSoundIndex = 0; Menu1Control selectedMenu1Control{0}; Menu2Control selectedMenu2Control{0}; - int selectedWaveIndex = 0; + int selectedTrackIndex = 0; int selectedBlockIndex = 0; int lastSelectedBlockIndex = 0; int currentStep = 0; - - SoundBlock clipboard; - bool hasClipboard = false; int ignoreRelease = 0; Track *tracks[NUMBER_OF_TRACKS]; void begin() { // TODO refactor?, something like track.connect(l, r, idx) - tracks[0] = new Wave('q', Waveform::square, mainmixer0L, mainmixer0R, 0); - tracks[1] = new Wave('p', Waveform::pulse, mainmixer0L, mainmixer0R, 1); - tracks[2] = new Wave('s', Waveform::sawtooth, mainmixer0L, mainmixer0R, 2); + tracks[0] = new WaveformTrack('q', Waveform::square, mainmixer0L, mainmixer0R, 0); + tracks[1] = new WaveformTrack('p', Waveform::pulse, mainmixer0L, mainmixer0R, 1); + tracks[2] = new WaveformTrack('s', Waveform::sawtooth, mainmixer0L, mainmixer0R, 2); tracks[3] = - new Wave('n', Waveform::sampleHold, mainmixer0L, mainmixer0R, 3); + new WaveformTrack('n', Waveform::sampleHold, mainmixer0L, mainmixer0R, 3); tracks[4] = new KitTrack('k', mainmixer1L, mainmixer1R, 0); } void step() { @@ -688,14 +104,14 @@ class BleepBloopMachine { neopixels.show(); } } - Track *selectedTrack() { return tracks[selectedWaveIndex]; } + Track *selectedTrack() { return tracks[selectedTrackIndex]; } void trackUp() { - selectedWaveIndex = wrapping_sub(selectedWaveIndex, NUMBER_OF_TRACKS - 1); + selectedTrackIndex = wrapping_sub(selectedTrackIndex, NUMBER_OF_TRACKS - 1); display.fillScreen(ST7735_WHITE); } void trackDown() { // TODO only blank out the location of the track char - selectedWaveIndex = wrapping_add(selectedWaveIndex, NUMBER_OF_TRACKS - 1); + selectedTrackIndex = wrapping_add(selectedTrackIndex, NUMBER_OF_TRACKS - 1); display.fillScreen(ST7735_WHITE); } void drawMenu(String choices, int selectedControl, int background) { @@ -727,11 +143,15 @@ class BleepBloopMachine { void drawMenu(String choices, Menu2Control selectedControl) { drawMenu(choices, (int)selectedControl, 0xeeff); } + uint16_t readLightSensor() { return analogRead(LIGHT_SENSOR); } + float readBatterySensor() { + return ((float)analogRead(BATTERY_SENSOR) / 1023.0) * 2.0 * 3.3; + } void updateDisplay() { auto stepBlockIndex = selectedTrack()->stepBlockIndex(currentStep); // first comes blocks for (auto i = 0; i <= 0xf; i++) { - auto coord = getCoord(i); + Point coord = getCoord(i); auto color = stepBlockIndex == i ? TICK_BLOCK_COLOR : STANDARD_BLOCK_COLOR; display.fillRect(coord.x, coord.y, BLOCK_SIZE, BLOCK_SIZE, color); @@ -872,7 +292,7 @@ class BleepBloopMachine { if (pressed == PAD_START) { menu(MenuMode::menu2); } - } else if (menu == MenuMode::menu2) { + } else if (mode == MenuMode::menu2) { if (released & PAD_RIGHT) { selectedMenu2Control = ++selectedMenu2Control; } @@ -938,17 +358,13 @@ class BleepBloopMachine { BleepBloopMachine machine; -uint16_t readLightSensor() { return analogRead(LIGHT_SENSOR); } - -float readBatterySensor() { - return ((float)analogRead(BATTERY_SENSOR) / 1023.0) * 2.0 * 3.3; -} - Controls controls; void setup() { Serial.begin(115200); machine.begin(); + pinMode(SPEAKER_ENABLE, OUTPUT); + digitalWrite(SPEAKER_ENABLE, HIGH); AudioMemory(18); controls.begin(); display.initR(INITR_BLACKTAB); @@ -967,7 +383,7 @@ void loop() { uint32_t justReleased = controls.justReleased(); float joyX = controls.readJoystickX(); float joyY = controls.readJoystickY(); - + Serial.println(machine.readBatterySensor()); machine.handleButtons(buttons, justReleased, joyX, joyX); machine.step(); } diff --git a/src/track.cc b/src/track.cc new file mode 100644 index 0000000..10b9e27 --- /dev/null +++ b/src/track.cc @@ -0,0 +1,131 @@ +#include "display.h" + +#include "track.h" +#include "controls.h" + +#include "kit/AudioSampleC.h" +#include "kit/AudioSampleH.h" +#include "kit/AudioSampleK.h" +#include "kit/AudioSampleO.h" +#include "kit/AudioSampleS.h" + +String note_names[] = {"c", "c#", "d", "d#", "e", "f", + "f#", "g", "g#", "a", "a#", "b"}; + +void KitTrack::handle(Menu1Control control, Keymod modifier, uint32_t pressed, + int blockIndex) {} + +void KitTrack::handle(Menu2Control control, Keymod modifier, uint32_t pressed, + int blockIndex) {} + +void KitTrack::handle(Keymod modifier, uint32_t pressed, int blockIndex) { + auto block = &blocks[blockIndex]; + if (modifier == Keymod::b) { + // if (pressed & PAD_UP) { + // block->sampleRateUp(); + // } + // if (pressed & PAD_DOWN) { + // block->sampleRateDown(); + // } + if (pressed & PAD_LEFT) { + block->sampleDown(); + } + if (pressed & PAD_RIGHT) { + block->sampleUp(); + } + } else if (modifier == Keymod::none) { + if (pressed == PAD_B) { + block->activate(); + } else if (pressed == PAD_A) { + block->deactivate(); + // TODO local clipboard + // if (block->active) { + // clipboard = sound->cut(); + // hasClipboard = true; + // } else if (hasClipboard) { + // blocks[blockIndex] = clipboard; + // } + } + } +} + +void KitTrack::play(int blockIndex) { + auto block = &blocks[blockIndex]; + if (!block->active) { + return; + } + switch (block->sample) { + case 0: + player->play(AudioSampleK); + break; + case 1: + player->play(AudioSampleS); + break; + case 2: + player->play(AudioSampleH); + break; + case 3: + player->play(AudioSampleO); + break; + case 4: + player->play(AudioSampleC); + break; + default: + break; + } +} + +void WaveformTrack::updateDisplay(Adafruit_GFX *display, MenuMode mode, + Menu1Control menu1control, + Menu2Control menu2control) { + for (int i = 0; i <= 0xf; i++) { + auto sound = &blocks[i]; + Point point = getCoord(i); + if (!sound->active) + continue; + String note = note_names[sound->note]; + display->fillRect(point.x + (sound->octave * 2), point.y, 2, 5, + sound->octave * 360); + display->drawChar(point.x + 2, point.y + 5, note[0], ST7735_BLACK, + ST7735_BLACK, 1); + if (note.length() == 2) { + display->drawChar(point.x + 9, point.y + 5, '#', ST7735_BLACK, + ST7735_BLACK, 1); + } + if (mode == MenuMode::live) { + // only note name and octave + } else if (mode == MenuMode::menu1) { + if (menu1control == Menu1Control::env) { + // draw panning + auto panX = ((BLOCK_SIZE / 2) * sound->pan) + BLOCK_SIZE / 2; + display->fillRect(point.x + panX, point.y + BLOCK_SIZE - 2, 2, 2, + ST7735_CYAN); + + // draw attack/decay/amp + /// perhaps unexpected is that amplitude is represented by the height + /// of the envelope, sustain being the bar on the right + + auto envColor = ST7735_GREEN; + float attackX = ((float)BLOCK_SIZE / 2 / MAX_ATTACK) * sound->attack; + float decayW = ((float)BLOCK_SIZE / 2 / MAX_DECAY) * sound->decay; + + /// attack/amp + display->drawLine(point.x, point.y + BLOCK_SIZE, point.x + attackX, + (point.y + BLOCK_SIZE) - BLOCK_SIZE * sound->amp, + envColor); + /// decay/amp + display->drawLine( + point.x + attackX, (point.y + BLOCK_SIZE) - BLOCK_SIZE * sound->amp, + point.x + attackX + decayW, (point.y + BLOCK_SIZE), envColor); + + // draw sustain + auto susY = BLOCK_SIZE * sound->sustain; + display->fillRect(point.x + BLOCK_SIZE - 4, + point.y + (BLOCK_SIZE - susY), 4, susY, ST7735_CYAN); + } + } else if (mode == MenuMode::menu2) { + display->drawPixel(point.x + 5, point.y + 5, ST7735_BLACK); + } + } + display->drawChar(10, 10, name, ST77XX_BLACK, ST7735_BLACK, 1); +} diff --git a/src/track.h b/src/track.h new file mode 100644 index 0000000..5cb2fcf --- /dev/null +++ b/src/track.h @@ -0,0 +1,301 @@ +#pragma once +#include "block.h" +#include "controls.h" + +#include +#include +#include + +enum class Waveform { + sine, + sawtooth, + square, + triangle, + arbitrary, + pulse, + sawtoothReverse, + sampleHold +}; + +class Track { +protected: + int lastStepBlockIndex = 0; + SoundBlock clipboard; + bool hasClipboard = false; + +public: + int bpmDivider = 1; + unsigned char name; + Block blocks[16]; + int stepBlockIndex(int currentStep) { return currentStep / bpmDivider % 16; } + void bpmDividerUp() { + switch (bpmDivider) { + case 4: + bpmDivider = 2; + break; + case 2: + bpmDivider = 1; + break; + } + } + void bpmDividerDown() { + switch (bpmDivider) { + case 2: + bpmDivider = 4; + break; + case 1: + bpmDivider = 2; + break; + } + } + virtual void updateDisplay(Adafruit_GFX *display, MenuMode mode, + Menu1Control menu1control, + Menu2Control menu2control) = 0; + virtual void handle(Menu1Control control, Keymod modifier, uint32_t pressed, + int blockIndex) = 0; + virtual void handle(Menu2Control control, Keymod modifier, uint32_t pressed, + int blockIndex) = 0; + virtual void handle(Keymod modifier, uint32_t pressed, int blockIndex) = 0; + void step(int currentStep) { + auto idx = stepBlockIndex(currentStep); + if (idx != lastStepBlockIndex) { + lastStepBlockIndex = idx; + play(idx); + } + } + virtual void play(int blockIndex) = 0; +}; + +class KitTrack : public Track { + AudioPlayMemory *player = new AudioPlayMemory(); + AudioConnection *patches[2]; + +public: + KitBlock *blocks = new KitBlock[16]; + // TODO update display should be the same every time, it should grab info it + // needs to display from the blocks? + void updateDisplay(Adafruit_GFX *display, MenuMode mode, + Menu1Control menu1control, Menu2Control menu2control) {} + void handle(Menu1Control control, Keymod modifier, uint32_t pressed, + int blockIndex); + void handle(Menu2Control control, Keymod modifier, uint32_t pressed, + int blockIndex); + void handle(Keymod modifier, uint32_t pressed, int blockIndex); + void play(int blockIndex); + KitTrack(unsigned char n, AudioMixer4 &lMixer, AudioMixer4 &rMixer, + int mixerIndex) { + name = n; + patches[0] = new AudioConnection(*player, 0, lMixer, mixerIndex); + patches[1] = new AudioConnection(*player, 0, rMixer, mixerIndex); + } + ~KitTrack() { + // TODO delete pointers + delete[] patches; + } +}; + +class WaveformTrack : public Track { + AudioConnection *patches[7]; + +public: + Waveform form; + AudioSynthWaveform *synthL = new AudioSynthWaveform(); + AudioSynthWaveform *synthR = new AudioSynthWaveform(); + AudioEffectEnvelope *envL = new AudioEffectEnvelope(); + AudioEffectEnvelope *envR = new AudioEffectEnvelope(); + AudioFilterBiquad *filterL = new AudioFilterBiquad(); + AudioFilterBiquad *filterR = new AudioFilterBiquad(); + AudioMixer4 *mixerL = new AudioMixer4(); + AudioMixer4 *mixerR = new AudioMixer4(); + SoundBlock *blocks = new SoundBlock[16]; + WaveformTrack(unsigned char n, Waveform f, AudioMixer4 &lmixer, + AudioMixer4 &rmixer, int mixerIndex) { + form = f; + name = n; + patches[0] = new AudioConnection(*synthL, *envL); + patches[1] = new AudioConnection(*envL, *filterL); + patches[2] = new AudioConnection(*filterL, 0, *mixerL, 0); + patches[3] = new AudioConnection(*synthR, *envR); + patches[4] = new AudioConnection(*envR, *filterR); + patches[5] = new AudioConnection(*filterR, 0, *mixerR, 0); + patches[6] = new AudioConnection(*mixerL, 0, lmixer, mixerIndex); + patches[7] = new AudioConnection(*mixerR, 0, rmixer, mixerIndex); + filterL->setLowpass(0, 12000); + filterR->setLowpass(0, 12000); + synthL->begin((int)f); + synthR->begin((int)f); + synthL->amplitude(1.0); + synthR->amplitude(1.0); + envL->sustain(1.0); + envR->sustain(1.0); + if (f == Waveform::pulse) { + synthL->pulseWidth(0.5); + synthR->pulseWidth(0.5); + } + } + ~WaveformTrack() { + // TODO delete pointers + delete[] patches; + } + void play(int blockIndex) { + auto sound = &blocks[blockIndex]; + if (!sound->active) { + return; + } + synthL->frequency(sound->frequency()); + synthR->frequency(sound->frequency()); + envL->attack(sound->attack); + envL->decay(sound->decay); + envL->delay(sound->delay); + envL->sustain(sound->sustain); + envR->attack(sound->attack); + envR->decay(sound->decay); + envR->delay(sound->delay); + envR->sustain(sound->sustain); + mixerL->gain(0, sound->amp / 2); + mixerR->gain(0, sound->amp / 2); + if (sound->filterType == SoundBlock::Filter::low) { + filterL->setLowpass(sound->filterStage, sound->filterFreq, + sound->filterQ); + filterR->setLowpass(sound->filterStage, sound->filterFreq, + sound->filterQ); + } + if (sound->pan < 0) { + mixerL->gain(0, (sound->amp / 2) + (abs(sound->pan) / 2)); + mixerR->gain(0, (sound->amp / 2) - (abs(sound->pan) / 2)); + } else if (sound->pan > 0) { + mixerR->gain(0, (sound->amp / 2) + (abs(sound->pan) / 2)); + mixerL->gain(0, (sound->amp / 2) - (abs(sound->pan) / 2)); + } + if (form == Waveform::pulse) { + synthL->pulseWidth(sound->pulseWidth); + synthR->pulseWidth(sound->pulseWidth); + } + envL->noteOn(); + envR->noteOn(); + } + void updateDisplay(Adafruit_GFX *display, MenuMode mode, + Menu1Control menu1control, Menu2Control menu2control); + void handle(Menu1Control control, Keymod modifier, uint32_t pressed, + int blockIndex) { + auto sound = &blocks[blockIndex]; + if (control == Menu1Control::env) { + if (modifier == Keymod::a) { + if (pressed & PAD_RIGHT) { + sound->decayUp(); + } + if (pressed & PAD_LEFT) { + sound->decayDown(); + } + if (pressed & PAD_UP) { + sound->sustainUp(); + } + if (pressed & PAD_DOWN) { + sound->sustainDown(); + } + } + if (modifier == Keymod::b) { + if (pressed & PAD_LEFT) { + sound->attackDown(); + } + if (pressed & PAD_RIGHT) { + sound->attackUp(); + } + if (pressed & PAD_UP) { + sound->sustainUp(); + } + if (pressed & PAD_DOWN) { + sound->sustainDown(); + } + } + if (modifier == Keymod::ab) { + if (pressed & PAD_LEFT) { + sound->panLeft(); + } + if (pressed & PAD_RIGHT) { + sound->panRight(); + } + if (pressed & PAD_UP) { + sound->ampUp(); + } + if (pressed & PAD_DOWN) { + sound->ampDown(); + } + } + } else if (control == Menu1Control::filter) { + if (modifier == Keymod::a) { + } else if (modifier == Keymod::b) { + if (pressed & PAD_LEFT) { + sound->filterDown(); + } + if (pressed & PAD_RIGHT) { + sound->filterUp(); + } + if (pressed & PAD_UP) { + sound->filterQUp(); + } + if (pressed & PAD_DOWN) { + sound->filterQDown(); + } + } + } else if (control == Menu1Control::mod) { + if (modifier == Keymod::a) { + } else if (modifier == Keymod::b) { + if (pressed & PAD_UP) { + if (form == Waveform::pulse) { + sound->pulseWidthUp(); + } + } + if (pressed & PAD_DOWN) { + if (form == Waveform::pulse) { + sound->pulseWidthDown(); + } + } + } + } else if (control == Menu1Control::delay) { + if (modifier == Keymod::a) { + } else if (modifier == Keymod::b) { + if (pressed & PAD_UP) { + sound->delayUp(); + } + if (pressed & PAD_DOWN) { + sound->delayDown(); + } + } + } + } + void handle(Menu2Control control, Keymod modifier, uint32_t pressed, + int blockIndex) {} + int lastNote = 0; + int lastOctave = 3; + void handle(Keymod modifier, uint32_t pressed, int blockIndex) { + auto sound = &blocks[blockIndex]; + if (modifier == Keymod::b) { + if (pressed & PAD_UP) { + sound->noteUp(); + } + if (pressed & PAD_DOWN) { + sound->noteDown(); + } + if (pressed & PAD_LEFT) { + sound->octaveDown(); + } + if (pressed & PAD_RIGHT) { + sound->octaveUp(); + } + lastNote = sound->note; + lastOctave = sound->octave; + } else if (modifier == Keymod::none) { + if (pressed == PAD_B) { + sound->activate(lastNote, lastOctave); + } else if (pressed == PAD_A) { + if (sound->active) { + clipboard = sound->cut(); + hasClipboard = true; + } else if (hasClipboard) { + blocks[blockIndex] = clipboard; + } + } + } + } +}; diff --git a/src/weirdmath.cc b/src/weirdmath.cc new file mode 100644 index 0000000..4642c6f --- /dev/null +++ b/src/weirdmath.cc @@ -0,0 +1,44 @@ +#pragma once +#include "weirdmath.h" + +int wrapping_add(int cur, int max, int inc, int min) { + auto nxt = cur + inc; + return nxt <= max ? nxt : min; +} + +int wrapping_sub(int cur, int max, int inc, int min) { + auto nxt = cur - inc; + return nxt >= min ? nxt : max; +} + +float wrapping_add(float cur, float max, float inc, int min) { + auto nxt = cur + inc; + return nxt <= max ? nxt : min; +} + +float wrapping_sub(float cur, float max, float inc, int min) { + auto nxt = cur - inc; + return nxt >= min ? nxt : max; +} + +float saturating_add(float cur, float max, float inc, float min) { + auto nxt = cur + inc; + return nxt <= max ? nxt : max; +} + +float saturating_sub(float cur, float max, float inc, float min) { + auto nxt = cur - inc; + return nxt >= min ? nxt : min; +} + +float saturating_add_curve(float cur, float max, int step, float min) { + auto inc = (max - min) / step; + auto nxt = cur + inc; + return nxt <= max ? nxt : max; +} + +float saturating_sub_curve(float cur, float max, int step, float min) { + auto inc = (max - min) / step; + auto nxt = cur - inc; + return nxt >= min ? nxt : min; +} diff --git a/src/weirdmath.h b/src/weirdmath.h new file mode 100644 index 0000000..cbf5ac0 --- /dev/null +++ b/src/weirdmath.h @@ -0,0 +1,13 @@ +#pragma once +int wrapping_add(int cur, int max, int inc = 1, int min = 0); +int wrapping_sub(int cur, int max, int inc = 1, int min = 0); +float wrapping_add(float cur, float max = 1.0, float inc = 0.1, int min = -1.0); +float wrapping_sub(float cur, float max = 1.0, float inc = 0.1, int min = -1.0); +float saturating_add(float cur, float max = 1.0, float inc = 0.1, + float min = -1.0); +float saturating_sub(float cur, float max = 1.0, float inc = 0.1, + float min = -1.0); + +float saturating_add_curve(float cur, float max = 1.0, int step = 10, float min = -1.0); + +float saturating_sub_curve(float cur, float max = 1.0, int step = 10, float min = -1.0);