diff --git a/algorithms.h b/algorithms.h index e4cf919..f15084c 100644 --- a/algorithms.h +++ b/algorithms.h @@ -50,7 +50,7 @@ #define ALG_DX9_2(feedback) \ { \ {0, 1, 0, 0, 1}, \ - {0, 0, 1, 1, 0}, \ + {0, 0, 1, 1, 0}, \ {0, 0, 0, 0, 0}, \ {0, 0, 0, feedback, 0}, \ } diff --git a/fingering.h b/fingering.h index 6658aa5..1e2de78 100644 --- a/fingering.h +++ b/fingering.h @@ -1,3 +1,6 @@ +#pragma once +#include "notes.h" + #define CCCC NOTE_CS5, NOTE_CS5, NOTE_CS5, NOTE_CS5 #define CCDD NOTE_CS5, NOTE_CS5, NOTE_D5, NOTE_D5 #define CDCD NOTE_CS5, NOTE_D5, NOTE_CS5, NOTE_D5 diff --git a/patches.h b/patches.h index 6ab0470..ed1c8f9 100644 --- a/patches.h +++ b/patches.h @@ -2,6 +2,7 @@ #pragma once #include "algorithms.h" +#include "synth.h" // Waveform, offset, multiplier, delay, attack, holdAmp, hold, decay, sustainAmp, release FMPatch Bank[] = { diff --git a/pipe.cpp b/pipe.cpp new file mode 100644 index 0000000..12dc309 --- /dev/null +++ b/pipe.cpp @@ -0,0 +1,84 @@ +#include "pipe.h" +#include "fingering.h" + +// Kludge time: this is something I did just to make my breadboard look nicer. +#define KEY_OFFSET 2 + +#define CLOSEDVAL 0x30 +#define OPENVAL 0x70 +#define GLISSANDO_STEPS (OPENVAL - CLOSEDVAL) + +Pipe::Pipe() { +} + +bool Pipe::Init() { + // Capacative touch sensor + if (!capSensor.begin(0x5A)) { + return false; + } + + // Proximity sensor + if (paj7620Init()) { + return false; + } + + // Bag button + bagSensor.begin(); + // This library takes the entire program out if you poll it 5-40 times without anything connected + bag_enabled = bagSensor.isConnected(); + + return true; +} + +void Pipe::Update() { + uint8_t glissandoKeys = 0; + + // Read the bag state, if there's a bag. + // if there isn't a bag, don't try, or this library will crash the program. + if (bag_enabled) { + bag = bagSensor.isPressed(); + } else { + bag = false; + } + + // 0x6c is actually 8 bytes, but all 8 are always the same... + paj7620ReadReg(0x6c, 1, &kneeClosedness); + + keys = 0; + glissandoKeys = 0; + for (int i=0; i<8; i++) { + uint16_t val = max(capSensor.filteredData(i+KEY_OFFSET), CLOSEDVAL); + float openness = ((val - CLOSEDVAL) / float(GLISSANDO_STEPS)); + + // keys = all keys which are at least touched + // glissandoKeys = all keys which are fully closed + // The glissando operation computes the difference. + if (openness < 1.0) { + glissandoOpenness = max(glissandoOpenness, openness); + bitSet(keys, i); + } + if (openness == 0.0) { + bitSet(glissandoKeys, i); + } + } + + // Look up notes in the big table + note = uilleann_matrix[keys]; + glissandoNote = uilleann_matrix[glissandoKeys]; + + // Was the high bit set? That indicates "alternate fingering", which sounds different. + altFingering = (note & 0x80); + + note &= 0x7f; + glissandoNote &= 0x7f; + + // If the bag is squished, jump up an octave + // But only if the left thumb is down! + if (bag && (keys & bit(7))) { + note += 12; + glissandoNote += 12; + } + + // All keys closed + knee = no sound + silent = ((kneeClosedness > 240) && (keys == 0xff)); +} diff --git a/pipe.h b/pipe.h new file mode 100644 index 0000000..4b1d3c7 --- /dev/null +++ b/pipe.h @@ -0,0 +1,51 @@ +#pragma once + +#include +#include +#include +#include + +class Pipe { + public: + // kneeClosedness indicates how "closed" the knee sensor is. 0 = wide open. + uint8_t kneeClosedness; + + // keys are which keys are being pressed. + uint8_t keys; + + // note holds the note being played, according to the fingering chart. + uint8_t note; + + // silent is true if all keys and the knee are closed. + bool silent; + + // bag is true if the bag is being squished. + bool bag; + + // altFingering is true if the "alternate fingering" is being played. + // This should sound different than the standard fingering. + bool altFingering; + + // glissandoNote is the note that would be played if partially open keys were fully open. + uint8_t glissandoNote; + + // glissandoOpenness is how "open" the holes are in the direction of the glissandoNote. + float glissandoOpenness; + + Pipe(); + + // Init initializes everything. + // + // Returns true if it all worked. You can run it again if it didn't. + bool Init(); + + // Update reads sensors and updates pipe state. + // + // It should be run once per loop. + void Update(); + + private: + Adafruit_MPR121 capSensor; + QwiicButton bagSensor; + bool bag_enabled; +}; diff --git a/synth.h b/synth.h index fc0d08f..6db692f 100644 --- a/synth.h +++ b/synth.h @@ -73,7 +73,7 @@ typedef struct FMOperator { * can be accomplished by patching an operator into itself. */ typedef struct FMPatch { - char *name; + const char *name; float gains[NUM_OPERATORS][NUM_OPERATORS+1]; FMOperator operators[NUM_OPERATORS]; } FMPatch; diff --git a/uilleann.ino b/uilleann.ino index 18e39e7..625108b 100644 --- a/uilleann.ino +++ b/uilleann.ino @@ -8,11 +8,14 @@ #include "synth.h" #include "patches.h" #include "notes.h" -#include "fingering.h" +#include "pipe.h" #define DRONES #define DEBUG -#define KEY_OFFSET 2 + +Pipe pipe; +Adafruit_SSD1306 display(128, 32, &Wire, -1); +int currentPatch = 0; FMVoice Chanter; FMVoice Drones[3]; @@ -61,13 +64,6 @@ AudioConnection FMVoicePatchCords[] = { FMVoiceWiring(Regulators[2]), }; -int currentPatch = 0; - -Adafruit_MPR121 cap = Adafruit_MPR121(); -Adafruit_SSD1306 display(128, 32, &Wire, -1); -QwiicButton bag; -bool use_bag; - void blink(bool forever) { for (;;) { digitalWrite(LED_BUILTIN, true); @@ -88,12 +84,7 @@ void setup() { delay(100); Wire.begin(); - // Initialize gesture/proximity sensor - if (paj7620Init()) { - // XXX: Error handling - } - - // Initialize display display + // Initialize display if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3c)) { blink(true); } @@ -103,13 +94,8 @@ void setup() { display.print("Starting"); display.display(); - // Initialize bag - bag.begin(); - use_bag = bag.isConnected(); - - // Initialize touch sensor - while (!cap.begin(0x5A)) { - display.clearDisplay(); + while (!pipe.Init()) { + display.clearDisplay(); display.setCursor(0, 0); display.print("Pipe?"); display.display(); @@ -117,7 +103,7 @@ void setup() { } // Set aside some memory for the audio library - AudioMemory(120); + AudioMemory(20); // initialize tunables updateTunables(3, 0); @@ -143,6 +129,11 @@ void setup() { mixL.gain(3, 0.1); mixR.gain(3, 0.1); #endif + + display.clearDisplay(); + display.setCursor(0, 0); + display.print("Done!"); + display.display(); } #define INIT_PITCH_ADJUST 0 @@ -202,88 +193,17 @@ void updateTunables(uint8_t buttons, int note) { FMPatch *p = &Bank[patch]; Chanter.LoadPatch(p); - - display.clearDisplay(); - display.setCursor(0, 0); - display.print(p->name); - display.setCursor(0, 10); - display.print("Patch "); - display.print(patch); - display.display(); } } -const uint8_t CLOSEDVAL = 0x30; -const uint8_t OPENVAL = 0x70; -const uint8_t GLISSANDO_STEPS = OPENVAL - CLOSEDVAL; - -uint8_t loopno = 0; -uint8_t last_note = 0; void loop() { - uint8_t keys = 0; - uint8_t note; - uint8_t glissandoKeys = 0; - uint8_t glissandoNote; - float glissandoOpenness = 0; - bool silent = false; - uint8_t paj_knee = 127; - bool knee = false; + static uint8_t last_note = 0; + bool updateDisplay = false; + bool setupMode = false; - loopno++; - - paj7620ReadReg(0x6c, 1, &paj_knee); - if (paj_knee > 240) { - knee = true; - } - - for (int i = 0; i < 8; i++) { - uint16_t val = max(cap.filteredData(i+KEY_OFFSET), CLOSEDVAL); - float openness = ((val - CLOSEDVAL) / float(GLISSANDO_STEPS)); - - // keys = all keys which are at least touched - // glissandoKeys = all keys which are fully closed - // The glissando operation computes the difference. - if (openness < 1.0) { - glissandoOpenness = max(glissandoOpenness, openness); - bitSet(keys, i); - - if (openness == 0.0) { - bitSet(glissandoKeys, i); - } - } - - } + pipe.Update(); - note = uilleann_matrix[keys]; - glissandoNote = uilleann_matrix[glissandoKeys]; - - bool alt = note & 0x80; - bool galt = glissandoNote & 0x80; - note = note & 0x7f; - glissandoNote = glissandoNote & 0x7f; - - // All keys closed + knee = no sound - if (knee) { - if (keys == 0xff) { - silent = true; - } - } - // Look up the note name - char *note_name = NoteNames[note % 12]; - if (silent) { - note_name = "-"; - } - - // Jump octave if the bag is squished - //bag = !digitalRead(BAG); - if (use_bag && bag.isPressed()) { - if (keys & bit(7)) { - note += 12; - glissandoNote += 12; - } - } - #if 0 display.clearDisplay(); display.setCursor(0, 0); @@ -300,26 +220,34 @@ void loop() { return; #endif +// If we're infinitely (for the sensor) off the knee, +// go into setup mode! +if (pipe.kneeClosedness == 0) { + setupMode = true; + updateDisplay = true; +} - if (silent) { + if (pipe.silent) { Chanter.NoteOff(); } else { // Calculate pitch, and glissando pitch - uint16_t pitch = JustPitches[note]; - uint16_t glissandoPitch = JustPitches[glissandoNote]; + uint16_t pitch = JustPitches[pipe.note]; + uint16_t glissandoPitch = JustPitches[pipe.glissandoNote]; - if (alt) { + // Bend pitch if fewer than 3 half steps away + if (abs(pipe.glissandoNote - pipe.note) < 3) { + float diff = glissandoPitch - pitch; + pitch += diff * pipe.glissandoOpenness; + } + + // Apply a low shelf filter if this is the alternate fingering + if (pipe.altFingering) { biquad1.setLowShelf(0, 2000, 0.2, 1); } else { biquad1.setHighShelf(0, 1000, 1.0, 1); } - // Bend pitch if fewer than 3 half steps away - if (abs(glissandoNote - note) < 3) { - float diff = glissandoPitch - pitch; - pitch += diff * glissandoOpenness; - } - + // We've figured out what pitch to play, now we can play it. if (Chanter.playing) { Chanter.SetPitch(pitch); } else { @@ -327,11 +255,33 @@ void loop() { } } - if (note != last_note) { + // Look up the note name + const char *note_name = NoteNames[pipe.note % 12]; + if (pipe.silent) { + note_name = "--"; + updateDisplay = true; + } + + if (pipe.note != last_note) { + updateDisplay = true; + } + + if (updateDisplay) { display.clearDisplay(); + display.setTextSize(2); display.setCursor(0, 0); - display.print(note_name); + display.print(Chanter.patch->name); + + if (setupMode) { + // THE SETUP DONUT + display.fillCircle(128-8, 16+8, 4, SSD1306_WHITE); + display.fillCircle(128-8, 16+8, 2, SSD1306_BLACK); + } else { + display.setCursor(0, 16); + display.print(note_name); + } + display.display(); - last_note = note; + last_note = pipe.note; } }