diff --git a/fingering.h b/fingering.h index ac85cef..3e0900f 100644 --- a/fingering.h +++ b/fingering.h @@ -11,14 +11,14 @@ struct Fingering { #define P(note) \ { note, true } -#define CCCC n(NOTE_CS5), n(NOTE_CS5), n(NOTE_CS5), n(NOTE_CS5) -#define CCDD n(NOTE_CS5), n(NOTE_CS5), n(NOTE_D5), n(NOTE_D5) -#define CDCD n(NOTE_CS5), n(NOTE_D5), n(NOTE_CS5), n(NOTE_D5) +#define CCCC n(NOTE_Cs5), n(NOTE_Cs5), n(NOTE_Cs5), n(NOTE_Cs5) +#define CCDD n(NOTE_Cs5), n(NOTE_Cs5), n(NOTE_D5), n(NOTE_D5) +#define CDCD n(NOTE_Cs5), n(NOTE_D5), n(NOTE_Cs5), n(NOTE_D5) #define DDDD n(NOTE_D5), n(NOTE_D5), n(NOTE_D5), n(NOTE_D5) struct Fingering uilleann_matrix[] = { // Open Back D - n(NOTE_CS5), n(NOTE_CS5), n(NOTE_CS5), n(NOTE_D5), // OOO OO.. + n(NOTE_Cs5), n(NOTE_Cs5), n(NOTE_Cs5), n(NOTE_D5), // OOO OO.. CCDD, // OOO OX.. CDCD, // OOO XO.. DDDD, // OOO XX.. @@ -53,24 +53,24 @@ struct Fingering uilleann_matrix[] = { // Closed Back D CCCC, // OOO OO... - n(NOTE_CS5), n(NOTE_CS5), n(NOTE_CS5), P(NOTE_CS5), // OOO OX.. + n(NOTE_Cs5), n(NOTE_Cs5), n(NOTE_Cs5), P(NOTE_Cs5), // OOO OX.. CCCC, // OOO XO.. CCCC, // OOO XX.. CCCC, // OOX OO.. - n(NOTE_CS5), P(NOTE_CS5), n(NOTE_CS5), P(NOTE_CS5), // OOX OX.. + n(NOTE_Cs5), P(NOTE_Cs5), n(NOTE_Cs5), P(NOTE_Cs5), // OOX OX.. CCCC, // OOX XO.. CCCC, // OOX XX.. CCCC, // OXO OO.. - n(NOTE_CS5), P(NOTE_CS5), n(NOTE_CS5), P(NOTE_CS5), // OXO OX.. + n(NOTE_Cs5), P(NOTE_Cs5), n(NOTE_Cs5), P(NOTE_Cs5), // OXO OX.. CCCC, // OXO XO.. CCCC, // OXO XX.. P(NOTE_C5), P(NOTE_C5), P(NOTE_C5), P(NOTE_C5), // OXX OO.. n(NOTE_C5), n(NOTE_C5), n(NOTE_C5), n(NOTE_C5), // OXX OX.. n(NOTE_C5), n(NOTE_C5), n(NOTE_C5), P(NOTE_C5), // OXX XO.. - n(NOTE_C5), n(NOTE_C5), n(NOTE_C5), n(NOTE_CS5), // OXX XX.. + n(NOTE_C5), n(NOTE_C5), n(NOTE_C5), n(NOTE_Cs5), // OXX XX.. n(NOTE_B4), n(NOTE_B4), n(NOTE_B4), n(NOTE_B4), // XOO OO.. P(NOTE_B4), P(NOTE_B4), n(NOTE_B4), P(NOTE_B4), // XOO OX.. - n(NOTE_AS4), n(NOTE_B4), n(NOTE_AS4), n(NOTE_B4), // XOO XO.. + n(NOTE_As4), n(NOTE_B4), n(NOTE_As4), n(NOTE_B4), // XOO XO.. n(NOTE_B4), n(NOTE_B4), n(NOTE_B4), n(NOTE_B4), // XOO XX.. P(NOTE_B4), P(NOTE_B4), P(NOTE_B4), P(NOTE_B4), // XOX OO.. P(NOTE_B4), P(NOTE_B4), P(NOTE_B4), P(NOTE_B4), // XOX OX.. @@ -78,10 +78,14 @@ struct Fingering uilleann_matrix[] = { P(NOTE_B4), P(NOTE_B4), P(NOTE_B4), P(NOTE_B4), // XOX XX.. n(NOTE_A4), n(NOTE_A4), P(NOTE_A4), n(NOTE_A4), // XXO OO.. P(NOTE_A4), P(NOTE_A4), P(NOTE_A4), P(NOTE_A4), // XXO OX.. - n(NOTE_GS4), P(NOTE_GS4), n(NOTE_A4), n(NOTE_A4), // XXO XO.. + n(NOTE_Gs4), P(NOTE_Gs4), n(NOTE_A4), n(NOTE_A4), // XXO XO.. P(NOTE_A4), P(NOTE_A4), P(NOTE_A4), n(NOTE_A4), // XXO XX.. n(NOTE_G4), n(NOTE_G4), P(NOTE_G4), n(NOTE_G4), // XXX OO.. P(NOTE_G4), P(NOTE_G4), P(NOTE_G4), P(NOTE_G4), // XXX OX.. - n(NOTE_FS4), n(NOTE_FS4), n(NOTE_F4), P(NOTE_FS4), // XXX XO.. - n(NOTE_E4), P(NOTE_E4), n(NOTE_DS4), n(NOTE_D4), // XXX XX.. + n(NOTE_Fs4), n(NOTE_Fs4), n(NOTE_F4), P(NOTE_Fs4), // XXX XO.. + n(NOTE_E4), P(NOTE_E4), n(NOTE_Ds4), n(NOTE_D4), // XXX XX.. }; + +inline Fingering FingeredNote(uint16_t keys) { + return uilleann_matrix[keys & 0xff]; +} diff --git a/main-play.h b/main-play.h new file mode 100644 index 0000000..8a957f2 --- /dev/null +++ b/main-play.h @@ -0,0 +1,71 @@ +#pragma once + +void playDrones() { + for (int i = 0; i < NUM_DRONES; ++i) { + float pitch = tuning.GetPitch(NOTE_D3); + pitch /= 1 << i; // Take down the appropriate number of octaves + pitch *= (i - 1) / 1000; // Detune ever so AudioProcessorUsageMaxReset(); + Drones[i].NoteOn(pitch); + } +} + +void doPlay(Pipe pipe, Adafruit_SSD1306 display, bool forceDisplayUpdate) { + static Note last_note = NOTE_ZERO; + bool updateDisplay = forceDisplayUpdate; + + diag("silent?"); + if (pipe.silent) { + Chanter.NoteOff(); + } else { + // Calculate pitch, and glissando pitch + uint16_t pitch = tuning.GetPitch(pipe.note); + uint16_t glissandoPitch = tuning.GetPitch(pipe.glissandoNote); + + diag("bend"); + // Bend pitch if fewer than 3 half steps away + if (abs(pipe.glissandoNote - pipe.note) < 3) { + float diff = glissandoPitch - pitch; + pitch += diff * pipe.glissandoOpenness; + } + + diag("shelf"); + // 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); + } + + // We've figured out what pitch to play, now we can play it. + if (Chanter.playing) { + Chanter.SetPitch(pitch); + } else { + Chanter.NoteOn(pitch); + } + } + + diag("check last note"); + if (pipe.note != last_note) { + updateDisplay = true; + } + + diag("update display"); + if (updateDisplay) { + // Look up the note name + const char *note_name = NoteName(pipe.note); + if (pipe.silent) { + note_name = "--"; + updateDisplay = true; + } + + display.clearDisplay(); + display.setFont(&FreeSans9pt7b); + + display.setCursor(0, 16); + display.print(note_name); + + display.display(); + //last_note = pipe.note; + } + diag("play done"); +} diff --git a/main-setup.h b/main-setup.h new file mode 100644 index 0000000..62f0e5c --- /dev/null +++ b/main-setup.h @@ -0,0 +1,188 @@ +#pragma once + +#define QUELL_DURATION 200 +#define ADJ_TYPEMATIC_DELAY 500 +#define ADJ_TYPEMATIC_REPEAT 33 + +#define VOLUME_INITIAL 0.8 + +const char *settingNames[4] = {"c", "r", "d", "*"}; + +// quellUntil can be set to give the user time to get their fingers off the continuous adjustment buttons. +unsigned long quellUntil = 0; +void quell(unsigned long ms) { + quellUntil = millis() + ms; +} +void quell() { + quell(QUELL_DURATION); +} + +void setupVolume() { + Adjust volAdjust = pipe.ReadAdjust(2, 3, 0, ADJ_TYPEMATIC_REPEAT); + Adjust patchAdjust = pipe.ReadAdjust(0, 1, 0, 500); + + for (int i = 0; i < 3; i++) { + int16_t x = 1; + int16_t y = i * 8; + + display.setCursor(x, y); + if (pipe.Pressed(6 - i)) { + display.fillRect(x - 1, y, 8, 8, SSD1306_WHITE); + display.setTextColor(SSD1306_BLACK); + switch (volAdjust) { + case ADJUST_BOTH: + volume[i] = VOLUME_INITIAL; + quell(); + break; + case ADJUST_UP: + case ADJUST_DOWN: + volume[i] += float(volAdjust)*0.02; + break; + default: + break; + } + switch (patchAdjust) { + case ADJUST_BOTH: + patch[i] = 0; + quell(); + loadPatch(i); + break; + case ADJUST_UP: + case ADJUST_DOWN: + patch[i] = (patch[i] + PATCH_MAX + int(patchAdjust)) % PATCH_MAX; + loadPatch(i); + break; + default: + break; + } + mixL.gain(i, volume[i]); + mixR.gain(i, volume[i]); + } else { + display.setTextColor(SSD1306_WHITE); + } + display.print(settingNames[i]); + x += 7; + + display.drawRect(x, y + 2, 32, 4, SSD1306_WHITE); + display.fillRect(x, y + 2, 32 * volume[i], 4, SSD1306_WHITE); + + x += 34; + display.setTextColor(SSD1306_WHITE); + display.setCursor(x, y); + display.print(patch[i]); + display.print(" "); + display.print(Bank[patch[i]].name); + } +} + +void setupTuning() { + Adjust noteAdjust = pipe.ReadAdjust(2, 3, ADJ_TYPEMATIC_DELAY, ADJ_TYPEMATIC_REPEAT); + Adjust pitchAdjust = pipe.ReadAdjust(0, 1, 0, ADJ_TYPEMATIC_REPEAT); + TuningSystem system = tuning.GetTuningSystem(); + float freq = tuning.GetPitch(NOTE_D4); + Note note = NearestNote(freq); + + if (noteAdjust != ADJUST_NONE) { + // Set up even temperament to pick a concert pitch + tuning.Setup(NOTE_A4, PITCH_CONCERT_A4, TUNINGSYSTEM_EQUAL); + switch (noteAdjust) { + case ADJUST_BOTH: + ++system; + break; + case ADJUST_UP: + case ADJUST_DOWN: + note += int(noteAdjust); + freq = tuning.GetPitch(note); + break; + default: + break; + } + // Now retune + tuning.Setup(NOTE_D4, freq, system); + } + + if (pitchAdjust != ADJUST_NONE) { + switch (pitchAdjust) { + case ADJUST_BOTH: + freq = PITCH_CONCERT_D4; + quell(); + break; + case ADJUST_UP: + freq *= 1.001; + break; + case ADJUST_DOWN: + freq /= 1.001; + break; + default: + break; + } + tuning.Setup(NOTE_D4, freq); + note = NearestNote(freq); + } + + display.setFont(&FreeSans9pt7b); + display.setCursor(0, 12); + display.print(NoteName(note)); + display.setCursor(24, 12); + display.print(NoteOctave(note)); + display.setCursor(48, 12); + display.print(freq); + + display.setCursor(0, 27); + display.print(TuningSystemName(system)); +} + +void setupInfo() { + display.setFont(&FreeSans9pt7b); + display.setCursor(64, 18); + display.print("Setup"); + + display.setFont(); + display.setTextSize(1); + display.setCursor(0, 16); + display.print("build"); + display.setCursor(0, 24); + display.print(buildDate); +} + +/** doSetup performs "setup mode" behavior for the pipe. + * + * Setup mode sets the following new meanings to the buttons: + * + * key: function [alternate] + * C♯: Alt + * B♮: Chanter + * A♮: Regulators + * G♮: Drones + * F♯: Up [+ coarse] + * E♮: Down [- coarse] + * E♭: + [+ fine] + * D♮: - [- fine] + * + */ +void doSetup() { + if (millis() < quellUntil) { + return; + } + + // Draw setup indicator bar + display.clearDisplay(); + display.fillRect(126, 0, 2, 32, SSD1306_WHITE); + display.setFont(0); + display.setTextSize(1); + display.setCursor(0, 0); + + if (pipe.Pressed(7)) { // Volume + setupVolume(); + } else if (pipe.Pressed(4)) { + display.print("fn3"); + } else if (pipe.Pressed(5)) { + display.print("fn2"); + } else if (pipe.Pressed(6)) { // Tuning + setupTuning(); + } else { + setupInfo(); + } + + display.display(); +} diff --git a/patches.h b/patches.h index ed1c8f9..82b8c15 100644 --- a/patches.h +++ b/patches.h @@ -61,3 +61,5 @@ FMPatch Bank[] = { }, }, }; + +const int PATCH_MAX = sizeof(Bank) / sizeof(Bank[0]); diff --git a/pipe.cpp b/pipe.cpp index 0ded6b1..e322b9e 100644 --- a/pipe.cpp +++ b/pipe.cpp @@ -2,8 +2,6 @@ #include "fingering.h" #include "tuning.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 @@ -35,6 +33,9 @@ bool Pipe::Init() { void Pipe::Update() { uint8_t glissandoKeys = 0; + keysLast = keys; + keys = 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) { @@ -46,11 +47,8 @@ void Pipe::Update() { // 0x6c is actually 8 bytes, but all 8 are always the same... paj7620ReadReg(0x6c, 1, &kneeClosedness); - keysLast = keys; - keys = 0; - glissandoKeys = 0; - for (int i = 0; i < 8; i++) { - uint16_t val = max(capSensor.filteredData(i + KEY_OFFSET), CLOSEDVAL); + for (int i = 0; i < NUM_KEYS; i++) { + uint16_t val = max(capSensor.filteredData(i), CLOSEDVAL); float openness = ((val - CLOSEDVAL) / float(GLISSANDO_STEPS)); // keys = all keys which are at least touched @@ -66,8 +64,8 @@ void Pipe::Update() { } // Look up notes in the big table - struct Fingering f = uilleann_matrix[keys]; - struct Fingering gf = uilleann_matrix[glissandoKeys]; + struct Fingering f = FingeredNote(keys); + struct Fingering gf = FingeredNote(glissandoKeys); note = f.note; glissandoNote = gf.note; @@ -96,3 +94,40 @@ bool Pipe::JustPressed(uint8_t key) { } return false; } + +bool Pipe::typematicEvent(uint8_t key, uint16_t delay, uint16_t repeat) { + if (Pressed(key)) { + unsigned long now = millis(); + + if (JustPressed(key)) { + nextRepeat[key] = now + max(delay, repeat); + return true; + } + if (now >= nextRepeat[key]) { + nextRepeat[key] = now + repeat; + return true; + } + } + return false; +} + +Adjust Pipe::ReadAdjust(uint8_t keyUp, uint8_t keyDown, uint16_t delay, uint16_t repeat) { + bool eventUp = typematicEvent(keyUp, delay, repeat); + bool eventDown = typematicEvent(keyDown, delay, repeat); + + if (Pressed(keyUp) && Pressed(keyDown)) { + unsigned long nr = max(nextRepeat[keyUp], nextRepeat[keyDown]); + + nextRepeat[keyUp] = nr; + nextRepeat[keyDown] = nr; + } + + if (eventUp && eventDown) { + return ADJUST_BOTH; + } else if (eventUp) { + return ADJUST_UP; + } else if (eventDown) { + return ADJUST_DOWN; + } + return ADJUST_NONE; +} \ No newline at end of file diff --git a/pipe.h b/pipe.h index 44c4862..0f2ea0f 100644 --- a/pipe.h +++ b/pipe.h @@ -6,14 +6,23 @@ #include #include "tuning.h" +#define NUM_KEYS 12 + +enum Adjust { + ADJUST_DOWN = -1, + ADJUST_NONE = 0, + ADJUST_UP = 1, + ADJUST_BOTH, +}; + 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; - uint8_t keysLast; + uint16_t keys; + uint16_t keysLast; // note holds the note being played, according to the fingering chart. Note note; @@ -52,8 +61,16 @@ class Pipe { // JustPressed returns whether the given key was just pressed. bool JustPressed(uint8_t key); + // ReadAdjust returns the input for two keys paired as up/down. + // + // delay is the number of milliseconds to wait before repeating a key + // repeat is the number of milliseconds to wait between repeated keystrokes + Adjust ReadAdjust(uint8_t upKey, uint8_t downKey, uint16_t delay, uint16_t repeat); + private: Adafruit_MPR121 capSensor; QwiicButton bagSensor; bool bag_enabled; + unsigned long nextRepeat[NUM_KEYS]; + bool typematicEvent(uint8_t key, uint16_t delay, uint16_t repeat); }; diff --git a/synth.cpp b/synth.cpp index 1b85228..64ca9e4 100644 --- a/synth.cpp +++ b/synth.cpp @@ -2,6 +2,10 @@ #include "synth_waveform.h" void FMVoice::LoadPatch(FMPatch *p) { + bool playing = this->playing; + float pitch = this->pitch; + + NoteOff(); for (int i=0; ioperators[i]; @@ -22,6 +26,9 @@ void FMVoice::LoadPatch(FMPatch *p) { this->outputMixer.gain(i, p->gains[i][NUM_OPERATORS]); } this->patch = p; + if (playing) { + NoteOn(pitch); + } } void FMVoice::SetPitch(float freq) { @@ -29,6 +36,7 @@ void FMVoice::SetPitch(float freq) { FMOperator op = this->patch->operators[i]; this->oscillators[i].frequency(op.offset + (freq * op.multiplier)); } + this->pitch = freq; } void FMVoice::NoteOn(float freq) { diff --git a/synth.h b/synth.h index 6db692f..e14cfb0 100644 --- a/synth.h +++ b/synth.h @@ -94,6 +94,10 @@ class FMVoice { */ void SetPitch(float pitch); + /** GetPitch returns the pitch (Hz) of a voice. + */ + float GetPitch(); + /** SetModulation sets the modulation amount of a voice. * * What this means depends on the loaded patch. @@ -126,6 +130,7 @@ class FMVoice { AudioEffectEnvelope envelopes[NUM_OPERATORS]; AudioMixer4 outputMixer; FMPatch *patch; + float pitch; bool playing; }; diff --git a/tuning.cpp b/tuning.cpp index a605f41..be9826c 100644 --- a/tuning.cpp +++ b/tuning.cpp @@ -125,6 +125,10 @@ const char *NoteName(Note note) { return noteNames[note % 12]; } +int NoteOctave(Note note) { + return int(note / NOTE_OCTAVE); +} + const char *TuningSystemName(TuningSystem system) { switch (system) { case TUNINGSYSTEM_EQUAL: diff --git a/tuning.h b/tuning.h index a2f6375..6a08a23 100644 --- a/tuning.h +++ b/tuning.h @@ -72,6 +72,9 @@ class Tuning { // NearestNote returns the note nearest to pitch. Note NearestNote(float pitch); +// NoteOctave returns which octave the note is in +int NoteOctave(Note note); + // NoteName returns the name of a note (without octave). const char *NoteName(Note note); @@ -88,8 +91,14 @@ inline Note toNote(int a) { return Note(a); } } +inline Note operator+(const Note &a, const int b) { + return toNote(int(a) + b); +} inline Note operator+(const Note &a, const Note b) { - return toNote(int(a) + int(b)); + return a + int(b); +} +inline Note &operator+=(Note &a, const int b) { + return a = a + b; } inline Note &operator+=(Note &a, const Note b) { return a = a + b; diff --git a/uilleann.ino b/uilleann.ino index 89616fa..2dceacc 100644 --- a/uilleann.ino +++ b/uilleann.ino @@ -12,7 +12,9 @@ #include "synth.h" #include "tuning.h" -#if defined(ADAFRUIT_TRELLIS_M4_EXPRESS) +const char *buildDate = __DATE__; + +#if defined(ADAFRUIT_TRELLIS_MAdafruit_SSD1306EXPRESS) #include Adafruit_NeoTrellisM4 trellis; // = Adafruit_NeoTrellisM4(); #endif @@ -25,13 +27,13 @@ Adafruit_SSD1306 display(128, 32, &Wire, -1); // Settings uint8_t patch[4] = {0}; float volume[4] = {0}; -const char *settingNames[4] = {"c", "r", "d", "*"}; -const char *buildDate = __DATE__; // Pipes +#define NUM_DRONES 3 +#define NUM_REGULATORS 3 FMVoice Chanter; -FMVoice Drones[3]; -FMVoice Regulators[3]; +FMVoice Drones[NUM_DRONES]; +FMVoice Regulators[NUM_REGULATORS]; AudioFilterBiquad biquad1; AudioMixer4 mixDrones; @@ -103,6 +105,7 @@ void diag(const char *fmt, ...) { display.clearDisplay(); display.drawRect(124, 16, 4, 16, SSD1306_WHITE); display.setTextColor(SSD1306_WHITE); + display.setFont(); display.setTextSize(1); display.setCursor(56, 24); @@ -121,6 +124,13 @@ void diag(const char *fmt, ...) { display.display(); } +// The right way to do this would be to make a Uilleann object, +// and pass that around. +// The Auido library makes this sort of a pain, +// and honestly, is anybody other than me going to use this? +#include "main-play.h" +#include "main-setup.h" + void setup() { // Initialize settings // XXX: Read these from persistent storage later @@ -129,16 +139,18 @@ void setup() { volume[i] = 0.75; } - Wire.begin(); - // PREPARE TO BLINK pinMode(LED_BUILTIN, OUTPUT); digitalWrite(LED_BUILTIN, true); + // Set up I2C. Apparently this needs a bit of startup delay. + Wire.begin(); + // Initialize display if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3c)) { blink(true); } + digitalWrite(LED_BUILTIN, false); diag("Hello!"); #if defined(ADAFRUIT_TRELLIS_M4_EXPRESS) @@ -153,37 +165,62 @@ void setup() { } diag("Audio..."); - // Set aside some memory for the audio library AudioMemory(20); - - // Set up the SGTL5000 using I2C + AudioProcessorUsageMaxReset(); + AudioMemoryUsageMaxReset(); sgtl5000.enable(); sgtl5000.volume(0.3); - // Initialize processor and memory measurements - AudioProcessorUsageMaxReset(); - AudioMemoryUsageMaxReset(); - - diag("Drones..."); - // Turn on drones - for (int i = 0; i < 3; i++) { - Note note = NOTE_D4 - (NOTE_OCTAVE * i); - float pitch = tuning.GetPitch(note) * (0.01 * (i - 1)); // Detune just a touch - Drones[i].LoadPatch(&Bank[0]); - Drones[i].NoteOn(pitch); - } + diag("Synth..."); + loadPatch(0); + loadPatch(1); + loadPatch(2); + diag("Mixer..."); // Turn on all mixer channels for (int i = 0; i < 4; i++) { - mixL.gain(i, 0.5); - mixR.gain(i, 0.6); + mixL.gain(i, volume[i]); + mixR.gain(i, volume[i]); } + for (int i = 0; i < NUM_REGULATORS; ++i) { + mixRegulators.gain(i, 1); + } + for (int i = 0; i < NUM_DRONES; ++i) { + mixDrones.gain(i, 1); + } + biquad1.setBandpass(0, PITCH_CONCERT_A4, 1.0); + + diag("Drones..."); + playDrones(); diag("Done!"); } +void loadPatch(uint8_t where) { + FMPatch *p = &Bank[where]; + + switch (where) { + case 0: + Chanter.LoadPatch(p); + break; + case 1: + for (int i = 0; i < NUM_REGULATORS; ++i) { + Regulators[i].LoadPatch(p); + } + break; + case 2: + for (int i = 0; i < NUM_DRONES; ++i) { + Drones[i].LoadPatch(p); + } + break; + default: + break; + } +} + + void loop() { - static bool forceDisplayUpdate = true; + static bool upSetting = false; // GET IT? pipe.Update(); @@ -192,199 +229,21 @@ void loop() { #endif // If we're infinitely (for the sensor) off the knee, - // go into setup mode! + // we might be in setup mode. if (pipe.kneeClosedness == 0) { - doSetup(); - forceDisplayUpdate = true; - } else { - doPlay(forceDisplayUpdate); - forceDisplayUpdate = false; - } -} - -/** doSetup performs "setup mode" behavior for the pipe. - * - * Setup mode sets the following new meanings to the buttons: - * - * key: function [alternate] - * C♯: Alt - * B♮: Chanter - * A♮: Regulators - * G♮: Drones - * F♯: Up [+ coarse] - * E♮: Down [- coarse] - * E♭: + [+ fine] - * D♮: - [- fine] - * - */ -void doSetup() { - static unsigned long quietUntil = 0; - - // Stuff can set quietUntil to stop responding to keys for a bit -#define quiet() quietUntil = millis() + 200 - if (millis() < quietUntil) { - return; - } - - display.clearDisplay(); - - bool alt = pipe.Pressed(7); - - // Draw indicator bar - display.fillRect(126, 0, 2, 32, SSD1306_WHITE); - display.setFont(0); - display.setTextSize(1); - display.setCursor(0, 0); - - if (alt) { - // Show settings for each of Chanter, Regulators, Drones - for (int i = 0; i < 3; i++) { - int p = patch[i]; - int16_t x = 1; - int16_t y = i * 8; - - display.setCursor(x, y); - if (pipe.Pressed(6 - i)) { - display.fillRect(x - 1, y, 8, 8, SSD1306_WHITE); - display.setTextColor(SSD1306_BLACK); - } else { - display.setTextColor(SSD1306_WHITE); - } - display.print(settingNames[i]); - x += 7; - - display.drawRect(x, y + 2, 32, 4, SSD1306_WHITE); - display.fillRect(x, y + 2, 32 * volume[i], 4, SSD1306_WHITE); - - x += 34; - display.setTextColor(SSD1306_WHITE); - display.setCursor(x, y); - if (p < 10) { - display.print(" "); - } - display.print(p); - display.print(" "); - display.print(Bank[p].name); - } - } else { - - if (pipe.Pressed(6)) { - TuningSystem system = tuning.GetTuningSystem(); - float freq = tuning.GetPitch(NOTE_D4); - Note note = NearestNote(freq); - int octave = int(note) / 12; - - if (pipe.JustPressed(3) || pipe.JustPressed(2)) { - tuning.Setup(NOTE_A4, PITCH_CONCERT_A4, TUNINGSYSTEM_EQUAL); - if (pipe.Pressed(3) && pipe.Pressed(2)) { - ++system; - } else if (pipe.Pressed(3)) { - freq = tuning.GetPitch(++note); - } else if (pipe.Pressed(2)) { - freq = tuning.GetPitch(--note); - } - tuning.Setup(NOTE_D4, freq, system); - } - - if (pipe.Pressed(1) || pipe.Pressed(0)) { - if (pipe.Pressed(1) && pipe.Pressed(0)) { - freq = PITCH_CONCERT_D4; - quiet(); - } else if (pipe.Pressed(1)) { - freq *= 1.001; - } else if (pipe.Pressed(0)) { - freq /= 1.001; - } - tuning.Setup(NOTE_D4, freq); - note = NearestNote(freq); - } - - display.setFont(&FreeSans9pt7b); - display.setCursor(0, 12); - display.print(NoteName(note)); - display.print(octave); - display.setCursor(48, 12); - display.print(freq); - - display.setCursor(0, 27); - display.print(TuningSystemName(system)); - } else if (pipe.Pressed(5)) { - display.print("fn2"); - } else if (pipe.Pressed(4)) { - display.print("fn3"); - } else { - display.setFont(&FreeSans9pt7b); - display.setCursor(64, 18); - display.print("Setup"); - - display.setFont(); - display.setTextSize(1); - display.setCursor(0, 16); - display.print("build"); - display.setCursor(0, 24); - display.print(buildDate); - } - } - - display.display(); -} - -void doPlay(bool forceUpdate) { - static uint8_t last_note = 0; - bool updateDisplay = forceUpdate; - - if (pipe.silent) { - Chanter.NoteOff(); - } else { - // Calculate pitch, and glissando pitch - uint16_t pitch = tuning.GetPitch(pipe.note); - uint16_t glissandoPitch = tuning.GetPitch(pipe.glissandoNote); - - // 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); - } - - // We've figured out what pitch to play, now we can play it. - if (Chanter.playing) { - Chanter.SetPitch(pitch); - } else { - Chanter.NoteOn(pitch); - } - } - - // Look up the note name - const char *note_name = NoteName(pipe.note); - if (pipe.silent) { - note_name = "--"; - updateDisplay = true; - } - - if (pipe.note != last_note) { - updateDisplay = true; - } - - if (updateDisplay) { - display.clearDisplay(); - display.setFont(&FreeSans9pt7b); - - if (Chanter.patch) { - display.setCursor(0, 32); - display.print(Chanter.patch->name); - } - - display.setCursor(0, 16); - display.print(note_name); - - display.display(); - last_note = pipe.note; + // We only enter into setup mode if no keys are pressed. + // This hopefully avoids accidentally entering setup while playing. + // Like say you're playing a jaunty tune and suddenly there's an earthquake. + // You ought to be able to finish the tune off before your pipe goes into setup mode. + if (upSetting || (pipe.keys == 0)) { + doSetup(); + upSetting = true; + return; + } } + + diag("Play!"); + doPlay(pipe, display, upSetting); + upSetting = false; + diag("Done!"); }