From 458b9eb891f6be4be869fb84fe25ccc133de50a0 Mon Sep 17 00:00:00 2001 From: Neale Pickett Date: Wed, 11 Nov 2020 16:29:09 -0700 Subject: [PATCH 01/16] Working on teensy 4.0 + PAJ7620 --- synth_waveform.cpp | 249 --------------------------------------------- synth_waveform.h | 121 ---------------------- uilleann.ino | 173 ++++++++++++++++++------------- 3 files changed, 101 insertions(+), 442 deletions(-) delete mode 100644 synth_waveform.cpp delete mode 100644 synth_waveform.h diff --git a/synth_waveform.cpp b/synth_waveform.cpp deleted file mode 100644 index c0cf6dc..0000000 --- a/synth_waveform.cpp +++ /dev/null @@ -1,249 +0,0 @@ -/* Audio Library for Teensy 3.X - * Copyright (c) 2018, Paul Stoffregen, paul@pjrc.com - * - * Development of this audio library was funded by PJRC.COM, LLC by sales of - * Teensy and Audio Adaptor boards. Please support PJRC's efforts to develop - * open source software by purchasing Teensy or other PJRC products. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice, development funding notice, and this permission - * notice shall be included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -#if defined(ADAFRUIT_TRELLIS_M4_EXPRESS) - -#include -#include "synth_waveform.h" -#include "arm_math.h" -#include "utility/dspinst.h" - -void AudioSynthWaveformModulated::update(void) -{ - audio_block_t *block, *moddata, *shapedata; - int16_t *bp, *end; - int32_t val1, val2; - int16_t magnitude15; - uint32_t i, ph, index, index2, scale, priorphase; - const uint32_t inc = phase_increment; - - moddata = receiveReadOnly(0); - shapedata = receiveReadOnly(1); - - // Pre-compute the phase angle for every output sample of this update - ph = phase_accumulator; - priorphase = phasedata[AUDIO_BLOCK_SAMPLES-1]; - if (moddata && modulation_type == 0) { - // Frequency Modulation - bp = moddata->data; - for (i=0; i < AUDIO_BLOCK_SAMPLES; i++) { - int32_t n = (*bp++) * modulation_factor; // n is # of octaves to mod - int32_t ipart = n >> 27; // 4 integer bits - n &= 0x7FFFFFF; // 27 fractional bits - #ifdef IMPROVE_EXPONENTIAL_ACCURACY - // exp2 polynomial suggested by Stefan Stenzel on "music-dsp" - // mail list, Wed, 3 Sep 2014 10:08:55 +0200 - int32_t x = n << 3; - n = multiply_accumulate_32x32_rshift32_rounded(536870912, x, 1494202713); - int32_t sq = multiply_32x32_rshift32_rounded(x, x); - n = multiply_accumulate_32x32_rshift32_rounded(n, sq, 1934101615); - n = n + (multiply_32x32_rshift32_rounded(sq, - multiply_32x32_rshift32_rounded(x, 1358044250)) << 1); - n = n << 1; - #else - // exp2 algorithm by Laurent de Soras - // https://www.musicdsp.org/en/latest/Other/106-fast-exp2-approximation.html - n = (n + 134217728) << 3; - n = multiply_32x32_rshift32_rounded(n, n); - n = multiply_32x32_rshift32_rounded(n, 715827883) << 3; - n = n + 715827882; - #endif - uint32_t scale = n >> (14 - ipart); - uint64_t phstep = (uint64_t)inc * scale; - uint32_t phstep_msw = phstep >> 32; - if (phstep_msw < 0x7FFE) { - ph += phstep >> 16; - } else { - ph += 0x7FFE0000; - } - phasedata[i] = ph; - } - release(moddata); - } else if (moddata) { - // Phase Modulation - bp = moddata->data; - for (i=0; i < AUDIO_BLOCK_SAMPLES; i++) { - // more than +/- 180 deg shift by 32 bit overflow of "n" - uint32_t n = (uint16_t)(*bp++) * modulation_factor; - phasedata[i] = ph + n; - ph += inc; - } - release(moddata); - } else { - // No Modulation Input - for (i=0; i < AUDIO_BLOCK_SAMPLES; i++) { - phasedata[i] = ph; - ph += inc; - } - } - phase_accumulator = ph; - - // If the amplitude is zero, no output, but phase still increments properly - if (magnitude == 0) { - if (shapedata) release(shapedata); - return; - } - block = allocate(); - if (!block) { - if (shapedata) release(shapedata); - return; - } - bp = block->data; - - // Now generate the output samples using the pre-computed phase angles - switch(tone_type) { - case WAVEFORM_SINE: - for (i=0; i < AUDIO_BLOCK_SAMPLES; i++) { - ph = phasedata[i]; - index = ph >> 24; - val1 = AudioWaveformSine[index]; - val2 = AudioWaveformSine[index+1]; - scale = (ph >> 8) & 0xFFFF; - val2 *= scale; - val1 *= 0x10000 - scale; - *bp++ = multiply_32x32_rshift32(val1 + val2, magnitude); - } - break; - - case WAVEFORM_ARBITRARY: - if (!arbdata) { - release(block); - if (shapedata) release(shapedata); - return; - } - // len = 256 - for (i=0; i < AUDIO_BLOCK_SAMPLES; i++) { - ph = phasedata[i]; - index = ph >> 24; - index2 = index + 1; - if (index2 >= 256) index2 = 0; - val1 = *(arbdata + index); - val2 = *(arbdata + index2); - scale = (ph >> 8) & 0xFFFF; - val2 *= scale; - val1 *= 0x10000 - scale; - *bp++ = multiply_32x32_rshift32(val1 + val2, magnitude); - } - break; - - case WAVEFORM_PULSE: - if (shapedata) { - magnitude15 = signed_saturate_rshift(magnitude, 16, 1); - for (i=0; i < AUDIO_BLOCK_SAMPLES; i++) { - uint32_t width = ((shapedata->data[i] + 0x8000) & 0xFFFF) << 16; - if (phasedata[i] < width) { - *bp++ = magnitude15; - } else { - *bp++ = -magnitude15; - } - } - break; - } // else fall through to orginary square without shape modulation - - case WAVEFORM_SQUARE: - magnitude15 = signed_saturate_rshift(magnitude, 16, 1); - for (i=0; i < AUDIO_BLOCK_SAMPLES; i++) { - if (phasedata[i] & 0x80000000) { - *bp++ = -magnitude15; - } else { - *bp++ = magnitude15; - } - } - break; - - case WAVEFORM_SAWTOOTH: - for (i=0; i < AUDIO_BLOCK_SAMPLES; i++) { - *bp++ = signed_multiply_32x16t(magnitude, phasedata[i]); - } - break; - - case WAVEFORM_SAWTOOTH_REVERSE: - for (i=0; i < AUDIO_BLOCK_SAMPLES; i++) { - *bp++ = signed_multiply_32x16t(0xFFFFFFFFu - magnitude, phasedata[i]); - } - break; - - case WAVEFORM_TRIANGLE_VARIABLE: - if (shapedata) { - for (i=0; i < AUDIO_BLOCK_SAMPLES; i++) { - uint32_t width = (shapedata->data[i] + 0x8000) & 0xFFFF; - uint32_t rise = 0xFFFFFFFF / width; - uint32_t fall = 0xFFFFFFFF / (0xFFFF - width); - uint32_t halfwidth = width << 15; - uint32_t n; - ph = phasedata[i]; - if (ph < halfwidth) { - n = (ph >> 16) * rise; - *bp++ = ((n >> 16) * magnitude) >> 16; - } else if (ph < 0xFFFFFFFF - halfwidth) { - n = 0x7FFFFFFF - (((ph - halfwidth) >> 16) * fall); - *bp++ = (((int32_t)n >> 16) * magnitude) >> 16; - } else { - n = ((ph + halfwidth) >> 16) * rise + 0x80000000; - *bp++ = (((int32_t)n >> 16) * magnitude) >> 16; - } - ph += inc; - } - break; - } // else fall through to orginary triangle without shape modulation - - case WAVEFORM_TRIANGLE: - for (i=0; i < AUDIO_BLOCK_SAMPLES; i++) { - ph = phasedata[i]; - uint32_t phtop = ph >> 30; - if (phtop == 1 || phtop == 2) { - *bp++ = ((0xFFFF - (ph >> 15)) * magnitude) >> 16; - } else { - *bp++ = (((int32_t)ph >> 15) * magnitude) >> 16; - } - } - break; - case WAVEFORM_SAMPLE_HOLD: - for (i=0; i < AUDIO_BLOCK_SAMPLES; i++) { - ph = phasedata[i]; - if (ph < priorphase) { // does not work for phase modulation - sample = random(magnitude) - (magnitude >> 1); - } - priorphase = ph; - *bp++ = sample; - } - break; - } - - if (tone_offset) { - bp = block->data; - end = bp + AUDIO_BLOCK_SAMPLES; - do { - val1 = *bp; - *bp++ = signed_saturate_rshift(val1 + tone_offset, 16, 0); - } while (bp < end); - } - if (shapedata) release(shapedata); - transmit(block, 0); - release(block); -} - -#endif diff --git a/synth_waveform.h b/synth_waveform.h deleted file mode 100644 index e206807..0000000 --- a/synth_waveform.h +++ /dev/null @@ -1,121 +0,0 @@ -/** Backport modulated waveform to Adafruit fork - */ - - #pragma once - -/* Audio Library for Teensy 3.X - * Copyright (c) 2014, Paul Stoffregen, paul@pjrc.com - * - * Development of this audio library was funded by PJRC.COM, LLC by sales of - * Teensy and Audio Adaptor boards. Please support PJRC's efforts to develop - * open source software by purchasing Teensy or other PJRC products. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice, development funding notice, and this permission - * notice shall be included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -#if defined(ADAFRUIT_TRELLIS_M4_EXPRESS) - -#include -#include -#include "AudioStream.h" -#include "arm_math.h" - -#define WAVEFORM_TRIANGLE_VARIABLE 8 - -class AudioSynthWaveformModulated : public AudioStream -{ -public: - AudioSynthWaveformModulated(void) : AudioStream(2, inputQueueArray), - phase_accumulator(0), phase_increment(0), modulation_factor(32768), - magnitude(0), arbdata(NULL), sample(0), tone_offset(0), - tone_type(WAVEFORM_SINE), modulation_type(0) { - } - - void frequency(float freq) { - if (freq < 0.0) { - freq = 0.0; - } else if (freq > AUDIO_SAMPLE_RATE_EXACT / 2) { - freq = AUDIO_SAMPLE_RATE_EXACT / 2; - } - phase_increment = freq * (4294967296.0 / AUDIO_SAMPLE_RATE_EXACT); - if (phase_increment > 0x7FFE0000u) phase_increment = 0x7FFE0000; - } - void amplitude(float n) { // 0 to 1.0 - if (n < 0) { - n = 0; - } else if (n > 1.0) { - n = 1.0; - } - magnitude = n * 65536.0; - } - void offset(float n) { - if (n < -1.0) { - n = -1.0; - } else if (n > 1.0) { - n = 1.0; - } - tone_offset = n * 32767.0; - } - void begin(short t_type) { - tone_type = t_type; - } - void begin(float t_amp, float t_freq, short t_type) { - amplitude(t_amp); - frequency(t_freq); - tone_type = t_type; - } - void arbitraryWaveform(const int16_t *data, float maxFreq) { - arbdata = data; - } - void frequencyModulation(float octaves) { - if (octaves > 12.0) { - octaves = 12.0; - } else if (octaves < 0.1) { - octaves = 0.1; - } - modulation_factor = octaves * 4096.0; - modulation_type = 0; - } - void phaseModulation(float degrees) { - if (degrees > 9000.0) { - degrees = 9000.0; - } else if (degrees < 30.0) { - degrees = 30.0; - } - modulation_factor = degrees * (65536.0 / 180.0); - modulation_type = 1; - } - virtual void update(void); - -private: - audio_block_t *inputQueueArray[2]; - uint32_t phase_accumulator; - uint32_t phase_increment; - uint32_t modulation_factor; - int32_t magnitude; - const int16_t *arbdata; - uint32_t phasedata[AUDIO_BLOCK_SAMPLES]; - int16_t sample; // for WAVEFORM_SAMPLE_HOLD - int16_t tone_offset; - uint8_t tone_type; - uint8_t modulation_type; -}; - - -#endif diff --git a/uilleann.ino b/uilleann.ino index b3557f3..1fd2fe1 100644 --- a/uilleann.ino +++ b/uilleann.ino @@ -1,38 +1,37 @@ #include #include -#include -#include +#include +#include #include #include +#include #include "synth.h" #include "patches.h" #include "notes.h" #include "fingering.h" +#define DRONES #define DEBUG -#define KNEE_OFFSET 0 #define KEY_OFFSET 2 FMVoice Chanter; FMVoice Drones[3]; FMVoice Regulators[3]; -AudioFilterBiquad biquad1; -AudioMixer4 mixDrones; -AudioMixer4 mixRegulators; -AudioMixer4 mixL; -AudioMixer4 mixR; -AudioOutputAnalogStereo dacs1; - -#ifdef DEBUG -AudioSynthNoiseWhite debug; -#endif +AudioFilterBiquad biquad1; +AudioMixer4 mixDrones; +AudioMixer4 mixRegulators; +AudioMixer4 mixL; +AudioMixer4 mixR; +AudioOutputI2S out1; +AudioSynthNoiseWhite noise; AudioConnection FMVoicePatchCords[] = { -#ifdef DEBUG - {debug, 0, mixL, 3}, - {debug, 0, mixR, 3}, -#endif + //{0, 0, 0, 0}, // For some reason, the first one is ignored + + {noise, 0, mixDrones, 3}, + {noise, 0, mixL, 3}, + {noise, 0, mixR, 3}, {Chanter.outputMixer, 0, biquad1, 0}, {biquad1, 0, mixL, 0}, @@ -50,56 +49,71 @@ AudioConnection FMVoicePatchCords[] = { {mixRegulators, 0, mixL, 2}, {mixRegulators, 0, mixR, 2}, - {mixL, 0, dacs1, 0}, - {mixR, 0, dacs1, 1}, + {mixL, 0, out1, 0}, + {mixR, 0, out1, 1}, FMVoiceWiring(Chanter), FMVoiceWiring(Drones[0]), FMVoiceWiring(Drones[1]), FMVoiceWiring(Drones[2]), - FMVoiceWiring(Drones[3]), FMVoiceWiring(Regulators[0]), FMVoiceWiring(Regulators[1]), FMVoiceWiring(Regulators[2]), - FMVoiceWiring(Regulators[3]), }; int currentPatch = 0; Adafruit_MPR121 cap = Adafruit_MPR121(); -Adafruit_NeoTrellisM4 trellis = Adafruit_NeoTrellisM4(); -MicroOLED oled(9, 1); +Adafruit_SSD1306 display(128, 32, &Wire, -1); QwiicButton bag; +bool use_bag; + +void blink(bool forever) { + for (;;) { + digitalWrite(LED_BUILTIN, true); + delay(200); + digitalWrite(LED_BUILTIN, false); + delay(200); + if (! forever) break; + } +} void setup() { - setupJustPitches(NOTE_D4, PITCH_D4); pinMode(LED_BUILTIN, OUTPUT); + digitalWrite(LED_BUILTIN, true); + + setupJustPitches(NOTE_D4, PITCH_D4); // Wire.begin needs a moment delay(100); Wire.begin(); - // Initialize OLED display - oled.begin(); - oled.clear(ALL); + // Initialize gesture/proximity sensor + if (paj7620Init()) { + // XXX: Error handling + } + + // Initialize display display + if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3c)) { + blink(true); + } + display.clearDisplay(); + display.setTextSize(1); + display.setTextColor(SSD1306_WHITE); + display.print("Starting"); + display.display(); // Initialize bag bag.begin(); - - // Initialize the Trellis - trellis.begin(); + use_bag = bag.isConnected(); // Initialize touch sensor - bool blink = true; while (!cap.begin(0x5A)) { - oled.clear(PAGE); - oled.setCursor(0, 0); - oled.print("No Pipe?"); - oled.display(); - - trellis.setPixelColor(0, blink?0xff6666:0); - blink = !blink; - delay(200); + display.clearDisplay(); + display.setCursor(0, 0); + display.print("Pipe?"); + display.display(); + blink(false); } // Set aside some memory for the audio library @@ -125,17 +139,12 @@ void setup() { } #ifdef DEBUG - debug.amplitude(0.1); + noise.amplitude(0.1); mixL.gain(3, 0.1); mixR.gain(3, 0.1); #endif } -#define BUTTON_UP 0 -#define BUTTON_DOWN 8 -#define BUTTON_PITCH 24 -#define BUTTON_VOLUME 25 - #define INIT_PITCH_ADJUST 0 #define INIT_GAIN 0.7 #define INIT_PATCH 0 @@ -162,7 +171,6 @@ void updateTunables(uint8_t buttons, int note) { float adj = pow(2, pitchAdjust / 32768.0); setupJustPitches(NOTE_D4, PITCH_D4*adj); - trellis.setPixelColor(BUTTON_PITCH, trellis.ColorHSV(uint16_t(pitchAdjust), 255, 80)); if (!note || (note == NOTE_G4)) { // Volume adjust if playing G @@ -178,20 +186,14 @@ void updateTunables(uint8_t buttons, int note) { break; } } - for (int i=0; i<3; i++) { mixL.gain(i, chanterGain); mixR.gain(i, chanterGain); } - trellis.setPixelColor(BUTTON_VOLUME, trellis.ColorHSV(uint16_t(chanterGain * 65535), 255, 80)); if (!note || (note == NOTE_CS5)) { if (buttons == 3) { patch = INIT_PATCH; - } else if (trellis.justPressed(BUTTON_DOWN)) { - patch -= 1; - } else if (trellis.justPressed(BUTTON_UP)) { - patch += 1; } // wrap @@ -201,14 +203,13 @@ void updateTunables(uint8_t buttons, int note) { FMPatch *p = &Bank[patch]; FMVoiceLoadPatch(&Chanter, p); - oled.clear(PAGE); - oled.setFontType(0); - oled.setCursor(0, 0); - oled.print(p->name); - oled.setCursor(0, 10); - oled.print("Patch "); - oled.print(patch); - oled.display(); + display.clearDisplay(); + display.setCursor(0, 0); + display.print(p->name); + display.setCursor(0, 10); + display.print("Patch "); + display.print(patch); + display.display(); } } @@ -216,7 +217,8 @@ const uint8_t CLOSEDVAL = 0x30; const uint8_t OPENVAL = 0x70; const uint8_t GLISSANDO_STEPS = OPENVAL - CLOSEDVAL; -bool playing = false; +uint8_t loopno = 0; +uint8_t last_note = 0; void loop() { uint8_t keys = 0; @@ -225,10 +227,15 @@ void loop() { uint8_t glissandoNote; float glissandoOpenness = 0; bool silent = false; - bool knee = cap.filteredData(KNEE_OFFSET) < CLOSEDVAL; - uint8_t buttons = trellis.isPressed(BUTTON_DOWN)?1:0 | trellis.isPressed(BUTTON_UP)?2:0; + uint8_t paj_knee = 127; + bool knee = false; - trellis.tick(); + 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); @@ -246,9 +253,6 @@ void loop() { } } - // print key states - //trellis.setPixelColor(7 - i, trellis.ColorHSV(65536/12, 255, 120*openness)); - trellis.setPixelColor(7 - i, trellis.ColorHSV(22222*openness, 255, 40)); } note = uilleann_matrix[keys]; @@ -265,20 +269,37 @@ void loop() { 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 (bag.isPressed()) { + if (use_bag && bag.isPressed()) { if (keys & bit(7)) { note += 12; glissandoNote += 12; } } - // Read some trellis button states - if (buttons) { - updateTunables(buttons, note); - } +#if 0 + display.clearDisplay(); + display.setCursor(0, 0); + display.print("mem: "); + display.print(AudioMemoryUsageMax()); + display.print(" prx: "); + display.print(paj_knee); + display.setCursor(0, 24); + display.print("Note: "); + display.print(note); + display.print(" n: "); + display.print(loopno); + display.display(); + return; +#endif + if (silent) { FMVoiceNoteOff(&Chanter); @@ -305,4 +326,12 @@ void loop() { FMVoiceNoteOn(&Chanter, pitch); } } + + if (note != last_note) { + display.clearDisplay(); + display.setCursor(0, 0); + display.print(note_name); + display.display(); + last_note = note; + } } From e565fb35b6f8d348175bc9316f8725d7641a1237 Mon Sep 17 00:00:00 2001 From: Neale Pickett Date: Wed, 11 Nov 2020 17:05:28 -0700 Subject: [PATCH 02/16] FMVoice is now a class --- synth.cpp | 46 ++++++++++++++--------------- synth.h | 82 ++++++++++++++++++++++++++++++---------------------- uilleann.ino | 12 ++++---- 3 files changed, 77 insertions(+), 63 deletions(-) diff --git a/synth.cpp b/synth.cpp index b4e2488..1b85228 100644 --- a/synth.cpp +++ b/synth.cpp @@ -1,47 +1,47 @@ #include "synth.h" #include "synth_waveform.h" -void FMVoiceLoadPatch(FMVoice *v, FMPatch *p) { +void FMVoice::LoadPatch(FMPatch *p) { for (int i=0; ioperators[i]; - v->oscillators[i].frequencyModulation(1); - v->oscillators[i].begin(op.waveform); - v->envelopes[i].delay(op.delayTime); - v->envelopes[i].attack(op.attackTime); - v->oscillators[i].amplitude(op.holdAmplitude); - v->envelopes[i].hold(op.holdTime); - v->envelopes[i].decay(op.decayTime); - v->envelopes[i].sustain(op.sustainAmplitude / op.holdAmplitude); - v->envelopes[i].release(op.releaseTime); + this->oscillators[i].frequencyModulation(1); + this->oscillators[i].begin(op.waveform); + this->envelopes[i].delay(op.delayTime); + this->envelopes[i].attack(op.attackTime); + this->oscillators[i].amplitude(op.holdAmplitude); + this->envelopes[i].hold(op.holdTime); + this->envelopes[i].decay(op.decayTime); + this->envelopes[i].sustain(op.sustainAmplitude / op.holdAmplitude); + this->envelopes[i].release(op.releaseTime); // This feels wasteful 🙁 for (int j=0; jmixers[i].gain(j, p->gains[i][j]); + this->mixers[i].gain(j, p->gains[i][j]); } - v->outputMixer.gain(i, p->gains[i][NUM_OPERATORS]); + this->outputMixer.gain(i, p->gains[i][NUM_OPERATORS]); } - v->patch = p; + this->patch = p; } -void FMVoiceSetPitch(FMVoice *v, float freq) { +void FMVoice::SetPitch(float freq) { for (int i=0; i<4; i++) { - FMOperator op = v->patch->operators[i]; - v->oscillators[i].frequency(op.offset + (freq * op.multiplier)); + FMOperator op = this->patch->operators[i]; + this->oscillators[i].frequency(op.offset + (freq * op.multiplier)); } } -void FMVoiceNoteOn(FMVoice *v, float freq) { - FMVoiceSetPitch(v, freq); +void FMVoice::NoteOn(float freq) { + SetPitch(freq); for (int i=0; i<4; i++) { - v->envelopes[i].noteOn(); + this->envelopes[i].noteOn(); } - v->playing = true; + this->playing = true; } -void FMVoiceNoteOff(FMVoice *v) { +void FMVoice::NoteOff() { for (int i=0; i<4; i++) { - v->envelopes[i].noteOff(); + this->envelopes[i].noteOff(); } - v->playing = false; + this->playing = false; } diff --git a/synth.h b/synth.h index 8c35942..fc0d08f 100644 --- a/synth.h +++ b/synth.h @@ -80,14 +80,54 @@ typedef struct FMPatch { /** FMVoice sets up all the Audio objects for a voice. */ -typedef struct FMVoice { - AudioMixer4 mixers[NUM_OPERATORS]; - AudioSynthWaveformModulated oscillators[NUM_OPERATORS]; - AudioEffectEnvelope envelopes[NUM_OPERATORS]; - AudioMixer4 outputMixer; - FMPatch *patch; - bool playing; -} FMVoice; +class FMVoice { + public: + /** LoadPatch loads a patch into a voice. + */ + void LoadPatch(FMPatch *p); + + /** SetPitch sets the pitch (Hz) of a voice. + * + * This does not signal the envelope in any way. + * You would use this for a glissando, portamento, or pitch bend. + * In my bagpipe, this prevents "reed noise" when changing notes. + */ + void SetPitch(float pitch); + + /** SetModulation sets the modulation amount of a voice. + * + * What this means depends on the loaded patch. + * For a "normal" bagpipe patch, this would adjust the intensity of + * of a filter, or set the level of an oscillator. + * In an old-school keyboard patch, this would set the + * intensity of a Low Frequency Oscillator to set a vibrato. + */ + void setModulation(float level); + + /** NoteOn sets the pitch (Hz) of a voice, and starts in playing. + * + * This tells the envelope generators to begin. + * On a piano, this is what you would use when a key is pressed. + * In my bagpipe, this triggers "reed noise". + */ + void NoteOn(float pitch); + + /** NoteOff stops a note from playing. + * + * This turns the voice "off" by shutting down all the envelope generators. + * On a piano, this is what you would use when a key is released. + * In my bagpipe, this corresponds to all holes being closed. + */ + void NoteOff(); + + + AudioMixer4 mixers[NUM_OPERATORS]; + AudioSynthWaveformModulated oscillators[NUM_OPERATORS]; + AudioEffectEnvelope envelopes[NUM_OPERATORS]; + AudioMixer4 outputMixer; + FMPatch *patch; + bool playing; +}; /** FMOperatorWiring outputs AudioConnection initializers to wire one FM Operator */ @@ -108,30 +148,4 @@ typedef struct FMVoice { FMOperatorWiring(name, 2), \ FMOperatorWiring(name, 3) -/** FMVoiceLoadPatch loads a patch into a voice. - */ -void FMVoiceLoadPatch(FMVoice *v, FMPatch *p); -/** FMVoiceSetPitch sets the pitch (Hz) of a voice. - * - * This does not signal the envelope in any way. - * You would use this for a glissando, portamento, or pitch bend. - * In my bagpipe, this prevents "reed noise" when changing notes. - */ -void FMVoiceSetPitch(FMVoice *v, float pitch); - -/** FMVoiceNoteOn sets the pitch (Hz) of a voice, and starts in playing. - * - * This tells the envelope generators to begin. - * On a piano, this is what you would use when a key is pressed. - * In my bagpipe, this triggers "reed noise". - */ -void FMVoiceNoteOn(FMVoice *v, float pitch); - -/** FMVoiceNoteOff stops a note from playing. - * - * This turns the voice "off" by shutting down all the envelope generators. - * On a piano, this is what you would use when a key is released. - * In my bagpipe, this corresponds to all holes being closed. - */ -void FMVoiceNoteOff(FMVoice *v); diff --git a/uilleann.ino b/uilleann.ino index 1fd2fe1..18e39e7 100644 --- a/uilleann.ino +++ b/uilleann.ino @@ -128,8 +128,8 @@ void setup() { // Turn on drones for (int i=0; i<3; i++) { - FMVoiceLoadPatch(&Drones[i], &Bank[0]); - FMVoiceNoteOn(&Drones[i], JustPitches[NOTE_D4 - 12*i] + i); + Drones[i].LoadPatch(&Bank[0]); + Drones[i].NoteOn(JustPitches[NOTE_D4 - 12*i] + i); } // Turn on all mixer channels @@ -201,7 +201,7 @@ void updateTunables(uint8_t buttons, int note) { patch = (patch + bankSize) % bankSize; FMPatch *p = &Bank[patch]; - FMVoiceLoadPatch(&Chanter, p); + Chanter.LoadPatch(p); display.clearDisplay(); display.setCursor(0, 0); @@ -302,7 +302,7 @@ void loop() { if (silent) { - FMVoiceNoteOff(&Chanter); + Chanter.NoteOff(); } else { // Calculate pitch, and glissando pitch uint16_t pitch = JustPitches[note]; @@ -321,9 +321,9 @@ void loop() { } if (Chanter.playing) { - FMVoiceSetPitch(&Chanter, pitch); + Chanter.SetPitch(pitch); } else { - FMVoiceNoteOn(&Chanter, pitch); + Chanter.NoteOn(pitch); } } From 5eea74871501a7ab46f0bb171dcfbe5e6a546882 Mon Sep 17 00:00:00 2001 From: Neale Pickett Date: Wed, 11 Nov 2020 20:20:21 -0700 Subject: [PATCH 03/16] Pipe is now a class! --- algorithms.h | 2 +- fingering.h | 3 + patches.h | 1 + pipe.cpp | 84 +++++++++++++++++++++++++ pipe.h | 51 +++++++++++++++ synth.h | 2 +- uilleann.ino | 172 ++++++++++++++++++--------------------------------- 7 files changed, 202 insertions(+), 113 deletions(-) create mode 100644 pipe.cpp create mode 100644 pipe.h 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; } } From d1214e803bfbe4fdb6dc9e71bb0eb26b1e5b4629 Mon Sep 17 00:00:00 2001 From: Neale Pickett Date: Sun, 15 Nov 2020 14:48:54 -0700 Subject: [PATCH 04/16] trying to locate source of freeze --- synth_waveform.cpp | 249 +++++++++++++++++++++++++++++++++++++++++++++ synth_waveform.h | 121 ++++++++++++++++++++++ 2 files changed, 370 insertions(+) create mode 100644 synth_waveform.cpp create mode 100644 synth_waveform.h diff --git a/synth_waveform.cpp b/synth_waveform.cpp new file mode 100644 index 0000000..c0cf6dc --- /dev/null +++ b/synth_waveform.cpp @@ -0,0 +1,249 @@ +/* Audio Library for Teensy 3.X + * Copyright (c) 2018, Paul Stoffregen, paul@pjrc.com + * + * Development of this audio library was funded by PJRC.COM, LLC by sales of + * Teensy and Audio Adaptor boards. Please support PJRC's efforts to develop + * open source software by purchasing Teensy or other PJRC products. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice, development funding notice, and this permission + * notice shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#if defined(ADAFRUIT_TRELLIS_M4_EXPRESS) + +#include +#include "synth_waveform.h" +#include "arm_math.h" +#include "utility/dspinst.h" + +void AudioSynthWaveformModulated::update(void) +{ + audio_block_t *block, *moddata, *shapedata; + int16_t *bp, *end; + int32_t val1, val2; + int16_t magnitude15; + uint32_t i, ph, index, index2, scale, priorphase; + const uint32_t inc = phase_increment; + + moddata = receiveReadOnly(0); + shapedata = receiveReadOnly(1); + + // Pre-compute the phase angle for every output sample of this update + ph = phase_accumulator; + priorphase = phasedata[AUDIO_BLOCK_SAMPLES-1]; + if (moddata && modulation_type == 0) { + // Frequency Modulation + bp = moddata->data; + for (i=0; i < AUDIO_BLOCK_SAMPLES; i++) { + int32_t n = (*bp++) * modulation_factor; // n is # of octaves to mod + int32_t ipart = n >> 27; // 4 integer bits + n &= 0x7FFFFFF; // 27 fractional bits + #ifdef IMPROVE_EXPONENTIAL_ACCURACY + // exp2 polynomial suggested by Stefan Stenzel on "music-dsp" + // mail list, Wed, 3 Sep 2014 10:08:55 +0200 + int32_t x = n << 3; + n = multiply_accumulate_32x32_rshift32_rounded(536870912, x, 1494202713); + int32_t sq = multiply_32x32_rshift32_rounded(x, x); + n = multiply_accumulate_32x32_rshift32_rounded(n, sq, 1934101615); + n = n + (multiply_32x32_rshift32_rounded(sq, + multiply_32x32_rshift32_rounded(x, 1358044250)) << 1); + n = n << 1; + #else + // exp2 algorithm by Laurent de Soras + // https://www.musicdsp.org/en/latest/Other/106-fast-exp2-approximation.html + n = (n + 134217728) << 3; + n = multiply_32x32_rshift32_rounded(n, n); + n = multiply_32x32_rshift32_rounded(n, 715827883) << 3; + n = n + 715827882; + #endif + uint32_t scale = n >> (14 - ipart); + uint64_t phstep = (uint64_t)inc * scale; + uint32_t phstep_msw = phstep >> 32; + if (phstep_msw < 0x7FFE) { + ph += phstep >> 16; + } else { + ph += 0x7FFE0000; + } + phasedata[i] = ph; + } + release(moddata); + } else if (moddata) { + // Phase Modulation + bp = moddata->data; + for (i=0; i < AUDIO_BLOCK_SAMPLES; i++) { + // more than +/- 180 deg shift by 32 bit overflow of "n" + uint32_t n = (uint16_t)(*bp++) * modulation_factor; + phasedata[i] = ph + n; + ph += inc; + } + release(moddata); + } else { + // No Modulation Input + for (i=0; i < AUDIO_BLOCK_SAMPLES; i++) { + phasedata[i] = ph; + ph += inc; + } + } + phase_accumulator = ph; + + // If the amplitude is zero, no output, but phase still increments properly + if (magnitude == 0) { + if (shapedata) release(shapedata); + return; + } + block = allocate(); + if (!block) { + if (shapedata) release(shapedata); + return; + } + bp = block->data; + + // Now generate the output samples using the pre-computed phase angles + switch(tone_type) { + case WAVEFORM_SINE: + for (i=0; i < AUDIO_BLOCK_SAMPLES; i++) { + ph = phasedata[i]; + index = ph >> 24; + val1 = AudioWaveformSine[index]; + val2 = AudioWaveformSine[index+1]; + scale = (ph >> 8) & 0xFFFF; + val2 *= scale; + val1 *= 0x10000 - scale; + *bp++ = multiply_32x32_rshift32(val1 + val2, magnitude); + } + break; + + case WAVEFORM_ARBITRARY: + if (!arbdata) { + release(block); + if (shapedata) release(shapedata); + return; + } + // len = 256 + for (i=0; i < AUDIO_BLOCK_SAMPLES; i++) { + ph = phasedata[i]; + index = ph >> 24; + index2 = index + 1; + if (index2 >= 256) index2 = 0; + val1 = *(arbdata + index); + val2 = *(arbdata + index2); + scale = (ph >> 8) & 0xFFFF; + val2 *= scale; + val1 *= 0x10000 - scale; + *bp++ = multiply_32x32_rshift32(val1 + val2, magnitude); + } + break; + + case WAVEFORM_PULSE: + if (shapedata) { + magnitude15 = signed_saturate_rshift(magnitude, 16, 1); + for (i=0; i < AUDIO_BLOCK_SAMPLES; i++) { + uint32_t width = ((shapedata->data[i] + 0x8000) & 0xFFFF) << 16; + if (phasedata[i] < width) { + *bp++ = magnitude15; + } else { + *bp++ = -magnitude15; + } + } + break; + } // else fall through to orginary square without shape modulation + + case WAVEFORM_SQUARE: + magnitude15 = signed_saturate_rshift(magnitude, 16, 1); + for (i=0; i < AUDIO_BLOCK_SAMPLES; i++) { + if (phasedata[i] & 0x80000000) { + *bp++ = -magnitude15; + } else { + *bp++ = magnitude15; + } + } + break; + + case WAVEFORM_SAWTOOTH: + for (i=0; i < AUDIO_BLOCK_SAMPLES; i++) { + *bp++ = signed_multiply_32x16t(magnitude, phasedata[i]); + } + break; + + case WAVEFORM_SAWTOOTH_REVERSE: + for (i=0; i < AUDIO_BLOCK_SAMPLES; i++) { + *bp++ = signed_multiply_32x16t(0xFFFFFFFFu - magnitude, phasedata[i]); + } + break; + + case WAVEFORM_TRIANGLE_VARIABLE: + if (shapedata) { + for (i=0; i < AUDIO_BLOCK_SAMPLES; i++) { + uint32_t width = (shapedata->data[i] + 0x8000) & 0xFFFF; + uint32_t rise = 0xFFFFFFFF / width; + uint32_t fall = 0xFFFFFFFF / (0xFFFF - width); + uint32_t halfwidth = width << 15; + uint32_t n; + ph = phasedata[i]; + if (ph < halfwidth) { + n = (ph >> 16) * rise; + *bp++ = ((n >> 16) * magnitude) >> 16; + } else if (ph < 0xFFFFFFFF - halfwidth) { + n = 0x7FFFFFFF - (((ph - halfwidth) >> 16) * fall); + *bp++ = (((int32_t)n >> 16) * magnitude) >> 16; + } else { + n = ((ph + halfwidth) >> 16) * rise + 0x80000000; + *bp++ = (((int32_t)n >> 16) * magnitude) >> 16; + } + ph += inc; + } + break; + } // else fall through to orginary triangle without shape modulation + + case WAVEFORM_TRIANGLE: + for (i=0; i < AUDIO_BLOCK_SAMPLES; i++) { + ph = phasedata[i]; + uint32_t phtop = ph >> 30; + if (phtop == 1 || phtop == 2) { + *bp++ = ((0xFFFF - (ph >> 15)) * magnitude) >> 16; + } else { + *bp++ = (((int32_t)ph >> 15) * magnitude) >> 16; + } + } + break; + case WAVEFORM_SAMPLE_HOLD: + for (i=0; i < AUDIO_BLOCK_SAMPLES; i++) { + ph = phasedata[i]; + if (ph < priorphase) { // does not work for phase modulation + sample = random(magnitude) - (magnitude >> 1); + } + priorphase = ph; + *bp++ = sample; + } + break; + } + + if (tone_offset) { + bp = block->data; + end = bp + AUDIO_BLOCK_SAMPLES; + do { + val1 = *bp; + *bp++ = signed_saturate_rshift(val1 + tone_offset, 16, 0); + } while (bp < end); + } + if (shapedata) release(shapedata); + transmit(block, 0); + release(block); +} + +#endif diff --git a/synth_waveform.h b/synth_waveform.h new file mode 100644 index 0000000..e206807 --- /dev/null +++ b/synth_waveform.h @@ -0,0 +1,121 @@ +/** Backport modulated waveform to Adafruit fork + */ + + #pragma once + +/* Audio Library for Teensy 3.X + * Copyright (c) 2014, Paul Stoffregen, paul@pjrc.com + * + * Development of this audio library was funded by PJRC.COM, LLC by sales of + * Teensy and Audio Adaptor boards. Please support PJRC's efforts to develop + * open source software by purchasing Teensy or other PJRC products. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice, development funding notice, and this permission + * notice shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#if defined(ADAFRUIT_TRELLIS_M4_EXPRESS) + +#include +#include +#include "AudioStream.h" +#include "arm_math.h" + +#define WAVEFORM_TRIANGLE_VARIABLE 8 + +class AudioSynthWaveformModulated : public AudioStream +{ +public: + AudioSynthWaveformModulated(void) : AudioStream(2, inputQueueArray), + phase_accumulator(0), phase_increment(0), modulation_factor(32768), + magnitude(0), arbdata(NULL), sample(0), tone_offset(0), + tone_type(WAVEFORM_SINE), modulation_type(0) { + } + + void frequency(float freq) { + if (freq < 0.0) { + freq = 0.0; + } else if (freq > AUDIO_SAMPLE_RATE_EXACT / 2) { + freq = AUDIO_SAMPLE_RATE_EXACT / 2; + } + phase_increment = freq * (4294967296.0 / AUDIO_SAMPLE_RATE_EXACT); + if (phase_increment > 0x7FFE0000u) phase_increment = 0x7FFE0000; + } + void amplitude(float n) { // 0 to 1.0 + if (n < 0) { + n = 0; + } else if (n > 1.0) { + n = 1.0; + } + magnitude = n * 65536.0; + } + void offset(float n) { + if (n < -1.0) { + n = -1.0; + } else if (n > 1.0) { + n = 1.0; + } + tone_offset = n * 32767.0; + } + void begin(short t_type) { + tone_type = t_type; + } + void begin(float t_amp, float t_freq, short t_type) { + amplitude(t_amp); + frequency(t_freq); + tone_type = t_type; + } + void arbitraryWaveform(const int16_t *data, float maxFreq) { + arbdata = data; + } + void frequencyModulation(float octaves) { + if (octaves > 12.0) { + octaves = 12.0; + } else if (octaves < 0.1) { + octaves = 0.1; + } + modulation_factor = octaves * 4096.0; + modulation_type = 0; + } + void phaseModulation(float degrees) { + if (degrees > 9000.0) { + degrees = 9000.0; + } else if (degrees < 30.0) { + degrees = 30.0; + } + modulation_factor = degrees * (65536.0 / 180.0); + modulation_type = 1; + } + virtual void update(void); + +private: + audio_block_t *inputQueueArray[2]; + uint32_t phase_accumulator; + uint32_t phase_increment; + uint32_t modulation_factor; + int32_t magnitude; + const int16_t *arbdata; + uint32_t phasedata[AUDIO_BLOCK_SAMPLES]; + int16_t sample; // for WAVEFORM_SAMPLE_HOLD + int16_t tone_offset; + uint8_t tone_type; + uint8_t modulation_type; +}; + + +#endif From a12e5f6276e9b719471d62812a87355e1a140c0c Mon Sep 17 00:00:00 2001 From: Neale Pickett Date: Sun, 15 Nov 2020 14:49:05 -0700 Subject: [PATCH 05/16] more --- Makefile | 9 +++++---- uilleann.ino | 49 +++++++++++++++++++++++++++++++++++-------------- 2 files changed, 40 insertions(+), 18 deletions(-) diff --git a/Makefile b/Makefile index c2ee968..150d86c 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,6 @@ FQBN = adafruit:samd:adafruit_trellis_m4 -UF2_MOUNT = /mnt/chromeos/removable/TRELM4BOOT +UF2_MOUNT = /media/neale/TRELM4BOOT +ARDUINO_DIR = /opt/arduino-1.8.13 default: build/uilleann.ino.uf2 install: build/uilleann.ino.uf2 @@ -21,11 +22,11 @@ build/%.bin: % *.cpp *.h -core-api-version 10813 \ -fqbn $(FQBN) \ -hardware ~/.arduino15/packages \ - -tools /app/Arduino/tools-builder \ + -tools $(ARDUINO_DIR)/tools-builder \ -tools ~/.arduino15/packages \ - -hardware /app/Arduino/hardware \ + -hardware $(ARDUINO_DIR)/hardware \ -hardware ~/.arduino15/packages \ - -built-in-libraries /app/Arduino/libraries \ + -built-in-libraries $(ARDUINO_DIR)/libraries \ -libraries ~/Arduino/libraries \ -compile \ $< diff --git a/uilleann.ino b/uilleann.ino index 625108b..3c1dff2 100644 --- a/uilleann.ino +++ b/uilleann.ino @@ -10,8 +10,13 @@ #include "notes.h" #include "pipe.h" +#if defined(ADAFRUIT_TRELLIS_M4_EXPRESS) +#include +Adafruit_NeoTrellisM4 trellis = Adafruit_NeoTrellisM4(); +#endif + #define DRONES -#define DEBUG +#define DEBUG false Pipe pipe; Adafruit_SSD1306 display(128, 32, &Wire, -1); @@ -26,13 +31,18 @@ AudioMixer4 mixDrones; AudioMixer4 mixRegulators; AudioMixer4 mixL; AudioMixer4 mixR; -AudioOutputI2S out1; AudioSynthNoiseWhite noise; +#if defined(ADAFRUIT_TRELLIS_M4_EXPRESS) +AudioOutputAnalogStereo out1; +#else +AudioOutputI2S out1; +#endif + AudioConnection FMVoicePatchCords[] = { //{0, 0, 0, 0}, // For some reason, the first one is ignored - {noise, 0, mixDrones, 3}, +// {noise, 0, mixDrones, 3}, {noise, 0, mixL, 3}, {noise, 0, mixR, 3}, @@ -75,15 +85,15 @@ void blink(bool forever) { } void setup() { - pinMode(LED_BUILTIN, OUTPUT); - digitalWrite(LED_BUILTIN, true); - + // Wire.begin needs a moment, so let's do some math. setupJustPitches(NOTE_D4, PITCH_D4); - // Wire.begin needs a moment - delay(100); Wire.begin(); + // PREPARE TO BLINK + pinMode(LED_BUILTIN, OUTPUT); + digitalWrite(LED_BUILTIN, true); + // Initialize display if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3c)) { blink(true); @@ -94,6 +104,10 @@ void setup() { display.print("Starting"); display.display(); +#if defined(ADAFRUIT_TRELLIS_M4_EXPRESS) + trellis.begin(); +#endif + while (!pipe.Init()) { display.clearDisplay(); display.setCursor(0, 0); @@ -203,22 +217,29 @@ void loop() { bool setupMode = false; pipe.Update(); + +#if defined(ADAFRUIT_TRELLIS_M4_EXPRESS) + trellis.tick(); + + trellis.setPixelColor(0, trellis.ColorHSV(120*pipe.kneeClosedness)); +#endif -#if 0 + static uint16_t loopno = 0; + if (false) { display.clearDisplay(); + display.setTextSize(1); display.setCursor(0, 0); display.print("mem: "); display.print(AudioMemoryUsageMax()); display.print(" prx: "); - display.print(paj_knee); + display.print(pipe.kneeClosedness); display.setCursor(0, 24); display.print("Note: "); - display.print(note); + display.print(pipe.note); display.print(" n: "); - display.print(loopno); + display.print(loopno++); display.display(); - return; -#endif + } // If we're infinitely (for the sensor) off the knee, // go into setup mode! From 28bcf63417e1ba0a5f5dc7e1083e232984f2b22a Mon Sep 17 00:00:00 2001 From: Neale Pickett Date: Sun, 22 Nov 2020 17:21:47 -0700 Subject: [PATCH 06/16] Get it working with SGTL5000 --- pipe.h | 62 ++++++++++++++++++++++++++-------------------------- uilleann.ino | 36 ++++++++++++++++++------------ 2 files changed, 53 insertions(+), 45 deletions(-) diff --git a/pipe.h b/pipe.h index 4b1d3c7..5a307cf 100644 --- a/pipe.h +++ b/pipe.h @@ -6,46 +6,46 @@ #include class Pipe { - public: - // kneeClosedness indicates how "closed" the knee sensor is. 0 = wide open. - uint8_t kneeClosedness; +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; + // keys are which keys are being pressed. + uint8_t keys; - // note holds the note being played, according to the fingering chart. - uint8_t note; + // 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; + // silent is true if all keys and the knee are closed. + bool silent; - // bag is true if the bag is being squished. - bool bag; + // 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; + // 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; + // 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; + // glissandoOpenness is how "open" the holes are in the direction of the glissandoNote. + float glissandoOpenness; - Pipe(); + Pipe(); - // Init initializes everything. - // - // Returns true if it all worked. You can run it again if it didn't. - bool Init(); + // 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(); + // 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; +private: + Adafruit_MPR121 capSensor; + QwiicButton bagSensor; + bool bag_enabled; }; diff --git a/uilleann.ino b/uilleann.ino index 3c1dff2..f08d933 100644 --- a/uilleann.ino +++ b/uilleann.ino @@ -12,7 +12,7 @@ #if defined(ADAFRUIT_TRELLIS_M4_EXPRESS) #include -Adafruit_NeoTrellisM4 trellis = Adafruit_NeoTrellisM4(); +Adafruit_NeoTrellisM4 trellis;// = Adafruit_NeoTrellisM4(); #endif #define DRONES @@ -39,6 +39,8 @@ AudioOutputAnalogStereo out1; AudioOutputI2S out1; #endif +AudioControlSGTL5000 sgtl5000; + AudioConnection FMVoicePatchCords[] = { //{0, 0, 0, 0}, // For some reason, the first one is ignored @@ -119,6 +121,10 @@ void setup() { // Set aside some memory for the audio library AudioMemory(20); + // Set up the SGTL5000 using I2C + sgtl5000.enable(); + sgtl5000.volume(0.3); + // initialize tunables updateTunables(3, 0); @@ -221,22 +227,24 @@ void loop() { #if defined(ADAFRUIT_TRELLIS_M4_EXPRESS) trellis.tick(); - trellis.setPixelColor(0, trellis.ColorHSV(120*pipe.kneeClosedness)); + trellis.setPixelColor(1, trellis.ColorHSV(millis(), 255, 120)); + trellis.setPixelColor(0, trellis.ColorHSV(64*pipe.kneeClosedness, 255, 120)); #endif - static uint16_t loopno = 0; if (false) { + static uint16_t loopno = 0; + display.clearDisplay(); display.setTextSize(1); display.setCursor(0, 0); - display.print("mem: "); - display.print(AudioMemoryUsageMax()); - display.print(" prx: "); - display.print(pipe.kneeClosedness); - display.setCursor(0, 24); - display.print("Note: "); - display.print(pipe.note); - display.print(" n: "); + // display.print("mem: "); + // display.print(AudioMemoryUsageMax()); + // display.print(" prx: "); + // display.print(pipe.kneeClosedness); + // display.setCursor(0, 24); + // display.print("Note: "); + // display.print(pipe.note); + // display.print(" n: "); display.print(loopno++); display.display(); } @@ -276,7 +284,7 @@ if (pipe.kneeClosedness == 0) { } } - // Look up the note name + // // Look up the note name const char *note_name = NoteNames[pipe.note % 12]; if (pipe.silent) { note_name = "--"; @@ -298,11 +306,11 @@ if (pipe.kneeClosedness == 0) { display.fillCircle(128-8, 16+8, 4, SSD1306_WHITE); display.fillCircle(128-8, 16+8, 2, SSD1306_BLACK); } else { - display.setCursor(0, 16); + display.setCursor(0, 8); display.print(note_name); } display.display(); - last_note = pipe.note; + last_note = pipe.note; } } From b65001ba84c47fe5e17210fa9f7d9ea916f05507 Mon Sep 17 00:00:00 2001 From: Neale Pickett Date: Mon, 23 Nov 2020 18:11:19 -0700 Subject: [PATCH 07/16] Start writing setup mode --- uilleann.ino | 90 +++++++++++++++++++++++++++++++--------------------- 1 file changed, 54 insertions(+), 36 deletions(-) diff --git a/uilleann.ino b/uilleann.ino index f08d933..1026df3 100644 --- a/uilleann.ino +++ b/uilleann.ino @@ -5,6 +5,7 @@ #include #include #include +#include #include "synth.h" #include "patches.h" #include "notes.h" @@ -218,10 +219,6 @@ void updateTunables(uint8_t buttons, int note) { void loop() { - static uint8_t last_note = 0; - bool updateDisplay = false; - bool setupMode = false; - pipe.Update(); #if defined(ADAFRUIT_TRELLIS_M4_EXPRESS) @@ -230,32 +227,53 @@ void loop() { trellis.setPixelColor(1, trellis.ColorHSV(millis(), 255, 120)); trellis.setPixelColor(0, trellis.ColorHSV(64*pipe.kneeClosedness, 255, 120)); #endif - - if (false) { - static uint16_t loopno = 0; - display.clearDisplay(); - display.setTextSize(1); - display.setCursor(0, 0); - // display.print("mem: "); - // display.print(AudioMemoryUsageMax()); - // display.print(" prx: "); - // display.print(pipe.kneeClosedness); - // display.setCursor(0, 24); - // display.print("Note: "); - // display.print(pipe.note); - // display.print(" n: "); - display.print(loopno++); - display.display(); + // If we're infinitely (for the sensor) off the knee, + // go into setup mode! + if (pipe.kneeClosedness == 0) { + doSetup(); + } else { + doPlay(); + } +} + +/** 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() { + display.clearDisplay(); + + bool alt = bitRead(pipe.keys, 7); + + display.fillRect(0, 0, 40, 32, SSD1306_WHITE); + display.setFont(&FreeSans9pt7b); + display.setCursor(1, 13); + display.setTextColor(SSD1306_BLACK); + if (alt) { + display.print("pipe"); + } else { + display.print("alt"); } -// If we're infinitely (for the sensor) off the knee, -// go into setup mode! -if (pipe.kneeClosedness == 0) { - setupMode = true; - updateDisplay = true; + display.display(); } +void doPlay() { + static uint8_t last_note = 0; + bool updateDisplay = false; + if (pipe.silent) { Chanter.NoteOff(); } else { @@ -284,7 +302,7 @@ if (pipe.kneeClosedness == 0) { } } - // // Look up the note name + // Look up the note name const char *note_name = NoteNames[pipe.note % 12]; if (pipe.silent) { note_name = "--"; @@ -297,18 +315,18 @@ if (pipe.kneeClosedness == 0) { if (updateDisplay) { display.clearDisplay(); + + display.setCursor(0, 16); display.setTextSize(2); - display.setCursor(0, 0); 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, 8); - display.print(note_name); - } + + display.setCursor(0, 0); + display.setTextSize(2); + display.print(note_name); + + display.setCursor(40, 0); + display.setTextSize(1); + display.print(pipe.kneeClosedness); display.display(); last_note = pipe.note; From c915b0ee6971c2c66661f54b9ef1c275e91be940 Mon Sep 17 00:00:00 2001 From: Neale Pickett Date: Tue, 24 Nov 2020 16:56:58 -0700 Subject: [PATCH 08/16] Setup menu, new Tuning class --- fingering.h | 71 ++++++----- notes.cpp | 41 ------- notes.h | 21 ---- pipe.cpp | 30 +++-- pipe.h | 22 ++-- tuning.cpp | 111 +++++++++++++++++ tuning.h | 99 ++++++++++++++++ uilleann.ino | 327 +++++++++++++++++++++++++++------------------------ 8 files changed, 458 insertions(+), 264 deletions(-) delete mode 100644 notes.cpp delete mode 100644 notes.h create mode 100644 tuning.cpp create mode 100644 tuning.h diff --git a/fingering.h b/fingering.h index 1e2de78..e8feab9 100644 --- a/fingering.h +++ b/fingering.h @@ -1,15 +1,22 @@ #pragma once -#include "notes.h" +#include "tuning.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 -#define DDDD NOTE_D5, NOTE_D5, NOTE_D5, NOTE_D5 -#define P 0x80 +struct Fingering { + Note note; + bool alt; // Alternate fingering: sounds more choked +}; -uint8_t uilleann_matrix[] = { +#define n(note) {note, false} +#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 DDDD n(NOTE_D5), n(NOTE_D5), n(NOTE_D5), n(NOTE_D5) + +struct Fingering uilleann_matrix[] = { // Open Back D - NOTE_CS5, NOTE_CS5, NOTE_CS5, 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.. @@ -39,40 +46,40 @@ uint8_t uilleann_matrix[] = { DDDD, // XXO XX.. DDDD, // XXX OO.. DDDD, // XXX OX.. - NOTE_D5, NOTE_D5, NOTE_D5, NOTE_D5|P, // XXX XO.. + n(NOTE_D5), n(NOTE_D5), n(NOTE_D5), P(NOTE_D5), // XXX XO.. DDDD, // XXX XX.. // Closed Back D CCCC, // OOO OO... - NOTE_CS5, NOTE_CS5, NOTE_CS5, NOTE_CS5|P, // 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.. - NOTE_CS5, NOTE_CS5|P, NOTE_CS5, NOTE_CS5|P, // 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.. - NOTE_CS5, NOTE_CS5|P, NOTE_CS5, NOTE_CS5|P, // OXO OX.. + n(NOTE_CS5), P(NOTE_CS5), n(NOTE_CS5), P(NOTE_CS5), // OXO OX.. CCCC, // OXO XO.. CCCC, // OXO XX.. - NOTE_C5|P, NOTE_C5|P, NOTE_C5|P, NOTE_C5|P, // OXX OO.. - NOTE_C5, NOTE_C5, NOTE_C5, NOTE_C5, // OXX OX.. - NOTE_C5, NOTE_C5, NOTE_C5, NOTE_C5|P, // OXX XO.. - NOTE_C5, NOTE_C5, NOTE_C5, NOTE_CS5, // OXX XX.. - NOTE_B4, NOTE_B4, NOTE_B4, NOTE_B4, // XOO OO.. - NOTE_B4|P, NOTE_B4|P, NOTE_B4, NOTE_B4|P, // XOO OX.. - NOTE_AS4, NOTE_B4, NOTE_AS4, NOTE_B4, // XOO XO.. - NOTE_B4, NOTE_B4, NOTE_B4, NOTE_B4, // XOO XX.. - NOTE_B4|P, NOTE_B4|P, NOTE_B4|P, NOTE_B4|P, // XOX OO.. - NOTE_B4|P, NOTE_B4|P, NOTE_B4|P, NOTE_B4|P, // XOX OX.. - NOTE_B4|P, NOTE_B4|P, NOTE_B4|P, NOTE_B4|P, // XOX XO.. - NOTE_B4|P, NOTE_B4|P, NOTE_B4|P, NOTE_B4|P, // XOX XX.. - NOTE_A4, NOTE_A4, NOTE_A4|P, NOTE_A4, // XXO OO.. - NOTE_A4|P, NOTE_A4|P, NOTE_A4|P, NOTE_A4|P, // XXO OX.. - NOTE_GS4, NOTE_GS4|P, NOTE_A4, NOTE_A4, // XXO XO.. - NOTE_A4|P, NOTE_A4|P, NOTE_A4|P, NOTE_A4, // XXO XX.. - NOTE_G4, NOTE_G4, NOTE_G4|P, NOTE_G4, // XXX OO.. - NOTE_G4|P, NOTE_G4|P, NOTE_G4|P, NOTE_G4|P, // XXX OX.. - NOTE_FS4, NOTE_FS4, NOTE_F4, NOTE_FS4|P, // XXX XO.. - NOTE_E4, NOTE_E4|P, NOTE_DS4, NOTE_D4, // XXX 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_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_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.. + P(NOTE_B4), P(NOTE_B4), P(NOTE_B4), P(NOTE_B4), // XOX XO.. + 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.. + 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.. }; diff --git a/notes.cpp b/notes.cpp deleted file mode 100644 index e562074..0000000 --- a/notes.cpp +++ /dev/null @@ -1,41 +0,0 @@ -#include -#include "notes.h" - -const char *NoteNames[] { - "C ", "C#", "D ", "Eb", "E ", "F ", "F#", "G ", "Ab", "A ", "Bb", "B ", -}; -float JustPitches[MaxNote + 1]; - -// Hat tip to Kyle Gann -// https://www.kylegann.com/tuning.html -void setupJustPitches(uint8_t baseNote, float basePitch) { - JustPitches[baseNote + 0] = basePitch * 1 / 1; // D - JustPitches[baseNote + 1] = basePitch * 16 / 15; // Eb - JustPitches[baseNote + 2] = basePitch * 9 / 8; // E - JustPitches[baseNote + 3] = basePitch * 6 / 5; // F - JustPitches[baseNote + 4] = basePitch * 5 / 4; // F# - JustPitches[baseNote + 5] = basePitch * 4 / 3; // G - JustPitches[baseNote + 6] = basePitch * 45 / 32; // Ab - JustPitches[baseNote + 7] = basePitch * 3 / 2; // A - JustPitches[baseNote + 8] = basePitch * 8 / 5; // Bb - JustPitches[baseNote + 9] = basePitch * 5 / 3; // B - JustPitches[baseNote + 10] = basePitch * 16 / 9; // C (fourth up from G) - JustPitches[baseNote + 11] = basePitch * 15 / 8; // C# - - // Octaves - for (int note = baseNote; note < baseNote + 12; note++) { - for (int i = 1; i < 9; i++) { - int multiplier = 1<= 0) { - JustPitches[dnNote] = JustPitches[note] / multiplier; - } - } - } -} diff --git a/notes.h b/notes.h deleted file mode 100644 index d8b4435..0000000 --- a/notes.h +++ /dev/null @@ -1,21 +0,0 @@ -#pragma once - -#define PITCH_D4 293.66 - -enum Notes { - NOTE_C0, NOTE_CS0, NOTE_D0, NOTE_DS0, NOTE_E0, NOTE_F0, NOTE_FS0, NOTE_G0, NOTE_GS0, NOTE_A0, NOTE_AS0, NOTE_B0, - NOTE_C1, NOTE_CS1, NOTE_D1, NOTE_DS1, NOTE_E1, NOTE_F1, NOTE_FS1, NOTE_G1, NOTE_GS1, NOTE_A1, NOTE_AS1, NOTE_B1, - NOTE_C2, NOTE_CS2, NOTE_D2, NOTE_DS2, NOTE_E2, NOTE_F2, NOTE_FS2, NOTE_G2, NOTE_GS2, NOTE_A2, NOTE_AS2, NOTE_B2, - NOTE_C3, NOTE_CS3, NOTE_D3, NOTE_DS3, NOTE_E3, NOTE_F3, NOTE_FS3, NOTE_G3, NOTE_GS3, NOTE_A3, NOTE_AS3, NOTE_B3, - NOTE_C4, NOTE_CS4, NOTE_D4, NOTE_DS4, NOTE_E4, NOTE_F4, NOTE_FS4, NOTE_G4, NOTE_GS4, NOTE_A4, NOTE_AS4, NOTE_B4, - NOTE_C5, NOTE_CS5, NOTE_D5, NOTE_DS5, NOTE_E5, NOTE_F5, NOTE_FS5, NOTE_G5, NOTE_GS5, NOTE_A5, NOTE_AS5, NOTE_B5, - NOTE_C6, NOTE_CS6, NOTE_D6, NOTE_DS6, NOTE_E6, NOTE_F6, NOTE_FS6, NOTE_G6, NOTE_GS6, NOTE_A6, NOTE_AS6, NOTE_B6, - NOTE_C7, NOTE_CS7, NOTE_D7, NOTE_DS7, NOTE_E7, NOTE_F7, NOTE_FS7, NOTE_G7, NOTE_GS7, NOTE_A7, NOTE_AS7, NOTE_B7, - NOTE_C8, NOTE_CS8, NOTE_D8, NOTE_DS8, NOTE_E8, NOTE_F8, NOTE_FS8, NOTE_G8, NOTE_GS8, NOTE_A8, NOTE_AS8, NOTE_B8, -}; -const uint8_t MaxNote = NOTE_B8; - -extern const char *NoteNames[]; -extern float JustPitches[MaxNote + 1]; - -void setupJustPitches(uint8_t baseNote, float basePitch); diff --git a/pipe.cpp b/pipe.cpp index 12dc309..67a93d4 100644 --- a/pipe.cpp +++ b/pipe.cpp @@ -1,4 +1,5 @@ #include "pipe.h" +#include "tuning.h" #include "fingering.h" // Kludge time: this is something I did just to make my breadboard look nicer. @@ -9,6 +10,7 @@ #define GLISSANDO_STEPS (OPENVAL - CLOSEDVAL) Pipe::Pipe() { + keysLast = 0; } bool Pipe::Init() { @@ -44,6 +46,7 @@ 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++) { @@ -63,22 +66,33 @@ void Pipe::Update() { } // Look up notes in the big table - note = uilleann_matrix[keys]; - glissandoNote = uilleann_matrix[glissandoKeys]; + struct Fingering f = uilleann_matrix[keys]; + struct Fingering gf = uilleann_matrix[glissandoKeys]; + + note = f.note; + glissandoNote = gf.note; // Was the high bit set? That indicates "alternate fingering", which sounds different. - altFingering = (note & 0x80); - - note &= 0x7f; - glissandoNote &= 0x7f; + altFingering = f.alt; // 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; + note += NOTE_OCTAVE; + glissandoNote += NOTE_OCTAVE; } // All keys closed + knee = no sound silent = ((kneeClosedness > 240) && (keys == 0xff)); } + +bool Pipe::Pressed(uint8_t key) { + return bitRead(keys, key); +} + +bool Pipe::JustPressed(uint8_t key) { + if (bitRead(keys, key)) { + return !bitRead(keysLast, key); + } + return false; +} diff --git a/pipe.h b/pipe.h index 5a307cf..055895c 100644 --- a/pipe.h +++ b/pipe.h @@ -4,6 +4,7 @@ #include #include #include +#include "tuning.h" class Pipe { public: @@ -12,9 +13,16 @@ public: // keys are which keys are being pressed. uint8_t keys; + uint8_t keysLast; // note holds the note being played, according to the fingering chart. - uint8_t note; + Note note; + + // glissandoNote is the note that would be played if partially open keys were fully open. + Note glissandoNote; + + // glissandoOpenness is how "open" the holes are in the direction of the glissandoNote. + float glissandoOpenness; // silent is true if all keys and the knee are closed. bool silent; @@ -26,12 +34,6 @@ public: // 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. @@ -44,6 +46,12 @@ public: // It should be run once per loop. void Update(); + // Pressed returns whether the given key is pressed. + bool Pressed(uint8_t key); + + // JustPressed returns whether the given key was just pressed. + bool JustPressed(uint8_t key); + private: Adafruit_MPR121 capSensor; QwiicButton bagSensor; diff --git a/tuning.cpp b/tuning.cpp new file mode 100644 index 0000000..d56f8b5 --- /dev/null +++ b/tuning.cpp @@ -0,0 +1,111 @@ +#include "tuning.h" + +#include + +// 12th root of 2, used for Twelvetone Equal Temperament +#define TET_CONST 1.059463 + +Tuning::Tuning(Note base, float pitch, TuningSystem system) { + Setup(base, pitch, system); +} +Tuning::Tuning(Note base, float pitch) { + Tuning(base, pitch, TUNINGSYSTEM_JUST); +} + +// I like just Intonation. +Tuning::Tuning() { +#if 0 + Tuning(NOTE_D4, PITCH_CONCERT_D4, TUNINGSYSTEM_JUST); +#endif +} + +Note Tuning::GetBaseNote() { return baseNote; } + +void Tuning::SetTuningSystem(TuningSystem system) { + Setup(baseNote, GetPitch(baseNote), system); +} + +TuningSystem Tuning::GetSystem() { return system; } + +// setupOctaves computes the entire tuning frequency chart. +// +// You must call this after setting a full octave chromatic scale, rooted at +// base. +void Tuning::setupOctaves(Note base) { + int multiplier = 1; + for (Note octave = NOTE_ZERO; octave < NOTE_MAX; octave += NOTE_OCTAVE) { + for (Note note = base; note < base + NOTE_OCTAVE; note += NOTE_SEMITONE) { + Note upNote = note + octave; + Note dnNote = note - octave; + + if (upNote < NOTE_MAX) { + pitches[upNote] = pitches[note] * multiplier; + } + if (dnNote >= NOTE_ZERO) { + pitches[dnNote] = pitches[note] / multiplier; + } + } + multiplier <<= 1; + } +} + +// setupEqual sets an even-temperament chromatic scale rooted at base +void Tuning::setupEqual(Note base, float pitch) { + pitches[base] = pitch; + for (int i = 1; i < 12; i++) { + pitches[base + i] = pitches[base + i - 1] * TET_CONST; + } +} + +// setupJust sets a just-temperament chromatic scale rooted at base +void Tuning::setupJust(Note base, float pitch) { + // Diatonic scale + pitches[base + 0] = pitch * 1 / 1; // Unison + pitches[base + 2] = pitch * 9 / 8; // Second + pitches[base + 4] = pitch * 5 / 4; // Third + pitches[base + 5] = pitch * 4 / 3; // Fourth + pitches[base + 7] = pitch * 3 / 2; // Fifth + pitches[base + 9] = pitch * 5 / 3; // Sixth + pitches[base + 11] = pitch * 15 / 8; // Seventh + + // I got this off various Wikipedia pages. + // The main thing here is that the minor seventh works out to be a diatonic + // fourth up from the fourth computed above, since the music I want to play + // frequently wants to play G major on a D major instrument + pitches[base + 1] = pitch * 16 / 15; // min2 + pitches[base + 3] = pitch * 6 / 5; // min3 + pitches[base + 6] = pitch * 10 / 7; // dim5 + pitches[base + 8] = pitch * 8 / 5; // min6 + pitches[base + 10] = pitch * 16 / 9; // min7 = fourth + fourth +} + +void Tuning::Setup(Note base, float pitch, TuningSystem system) { + this->baseNote = base; + this->system = system; + + switch (system) { + case TUNINGSYSTEM_EQUAL: + setupEqual(base, pitch); + break; + case TUNINGSYSTEM_JUST: + default: + setupJust(base, pitch); + break; + } + setupOctaves(base); +} + +void Tuning::Setup(Note base, float pitch) { Setup(base, pitch, system); } + +float Tuning::GetPitch(Note note) { return pitches[note]; } + +Note NearestNote(float pitch) { + return Note(round(log2(pitch / PITCH_CONCERT_C0))); +} + +const char *noteNames[]{ + "C ", "C#", "D ", "Eb", "E ", "F ", "F#", "G ", "Ab", "A ", "Bb", "B ", +}; + +const char *NoteName(Note note) { return noteNames[note % 12]; } + diff --git a/tuning.h b/tuning.h new file mode 100644 index 0000000..5483cdc --- /dev/null +++ b/tuning.h @@ -0,0 +1,99 @@ +#pragma once +#include + +enum TuningSystem { + TUNINGSYSTEM_JUST, + TUNINGSYSTEM_EQUAL, +}; +#define TUNINGSYSTEM_MAX TUNINGSYSTEM_EQUAL + +enum Note { + NOTE_C0, NOTE_CS0, NOTE_D0, NOTE_DS0, NOTE_E0, NOTE_F0, NOTE_FS0, NOTE_G0, NOTE_GS0, NOTE_A0, NOTE_AS0, NOTE_B0, + NOTE_C1, NOTE_CS1, NOTE_D1, NOTE_DS1, NOTE_E1, NOTE_F1, NOTE_FS1, NOTE_G1, NOTE_GS1, NOTE_A1, NOTE_AS1, NOTE_B1, + NOTE_C2, NOTE_CS2, NOTE_D2, NOTE_DS2, NOTE_E2, NOTE_F2, NOTE_FS2, NOTE_G2, NOTE_GS2, NOTE_A2, NOTE_AS2, NOTE_B2, + NOTE_C3, NOTE_CS3, NOTE_D3, NOTE_DS3, NOTE_E3, NOTE_F3, NOTE_FS3, NOTE_G3, NOTE_GS3, NOTE_A3, NOTE_AS3, NOTE_B3, + NOTE_C4, NOTE_CS4, NOTE_D4, NOTE_DS4, NOTE_E4, NOTE_F4, NOTE_FS4, NOTE_G4, NOTE_GS4, NOTE_A4, NOTE_AS4, NOTE_B4, + NOTE_C5, NOTE_CS5, NOTE_D5, NOTE_DS5, NOTE_E5, NOTE_F5, NOTE_FS5, NOTE_G5, NOTE_GS5, NOTE_A5, NOTE_AS5, NOTE_B5, + NOTE_C6, NOTE_CS6, NOTE_D6, NOTE_DS6, NOTE_E6, NOTE_F6, NOTE_FS6, NOTE_G6, NOTE_GS6, NOTE_A6, NOTE_AS6, NOTE_B6, + NOTE_C7, NOTE_CS7, NOTE_D7, NOTE_DS7, NOTE_E7, NOTE_F7, NOTE_FS7, NOTE_G7, NOTE_GS7, NOTE_A7, NOTE_AS7, NOTE_B7, + NOTE_C8, NOTE_CS8, NOTE_D8, NOTE_DS8, NOTE_E8, NOTE_F8, NOTE_FS8, NOTE_G8, NOTE_GS8, NOTE_A8, NOTE_AS8, NOTE_B8, + NOTE_ZERO = 0, + NOTE_SEMITONE = 1, + NOTE_WHOLETONE = 2, + NOTE_OCTAVE = NOTE_C1, + NOTE_MAX = NOTE_B8, +}; + +#define PITCH_CONCERT_C0 16.35 +#define PITCH_CONCERT_A4 440.00 +#define PITCH_CONCERT_D4 293.66 + +class Tuning { +public: + // name contains the name of the current tuning system + const char *name; + + Tuning(Note base, float pitch, TuningSystem system); + Tuning(Note base, float pitch); + Tuning(); + void Setup(Note base, float pitch, TuningSystem system); + void Setup(Note base, float pitch); + void SetTuningSystem(TuningSystem system); + TuningSystem GetSystem(); + Note GetBaseNote(); + float GetPitch(Note note); + +private: + TuningSystem system; + Note baseNote; + float pitches[NOTE_MAX]; + + void setupOctaves(Note base); + void setupJust(Note base, float pitch); + void setupEqual(Note base, float pitch); +}; + +// NearestNote returns the note nearest to pitch. +Note NearestNote(float pitch); + +// NoteName return the name of a note (without octave). +const char *NoteName(Note note); + + +// Make notes support some arithmetic +inline Note toNote(int a) { + if (a < NOTE_ZERO) { + return NOTE_ZERO; + } else if (a > NOTE_MAX) { + return NOTE_MAX; + } else { + return Note(a); + } +} +inline Note operator+(const Note &a, const Note b) { + return toNote(int(a) + int(b)); +} +inline Note &operator+=(Note &a, const Note b) { + return a = a + b; +} +inline Note &operator++(Note &a) { + return a += NOTE_SEMITONE; +} +inline Note operator-(const Note a, const Note b) { + return toNote(int(a) - int(b)); +} +inline Note &operator-=(Note &a, const Note b) { + return a = a - b; +} +inline Note &operator--(Note &a) { + return a -= NOTE_SEMITONE; +} +inline Note operator*(const Note a, const int b) { + return toNote(int(a) * b); +} +inline int operator/(const Note a, const int b) { + return int(a)/b; +} +inline int operator/(const Note a, const Note b) { + return int(a)/b; +} diff --git a/uilleann.ino b/uilleann.ino index 1026df3..37a3eff 100644 --- a/uilleann.ino +++ b/uilleann.ino @@ -1,80 +1,86 @@ -#include -#include #include -#include -#include #include -#include +#include +#include #include -#include "synth.h" +#include +#include +#include +#include + #include "patches.h" -#include "notes.h" #include "pipe.h" +#include "synth.h" +#include "tuning.h" #if defined(ADAFRUIT_TRELLIS_M4_EXPRESS) #include -Adafruit_NeoTrellisM4 trellis;// = Adafruit_NeoTrellisM4(); +Adafruit_NeoTrellisM4 trellis; // = Adafruit_NeoTrellisM4(); #endif -#define DRONES -#define DEBUG false - Pipe pipe; +Tuning tuning = Tuning(NOTE_D4, PITCH_CONCERT_D4, TUNINGSYSTEM_JUST); + Adafruit_SSD1306 display(128, 32, &Wire, -1); int currentPatch = 0; +// Settings +uint8_t intonation = 0; +int16_t pitchAdjust = 0; +uint8_t patch[4] = {0}; +float volume[4] = {0}; +const char *settingNames[4] = {"c", "r", "d", "*"}; + +// Pipes FMVoice Chanter; FMVoice Drones[3]; FMVoice Regulators[3]; -AudioFilterBiquad biquad1; -AudioMixer4 mixDrones; -AudioMixer4 mixRegulators; -AudioMixer4 mixL; -AudioMixer4 mixR; +AudioFilterBiquad biquad1; +AudioMixer4 mixDrones; +AudioMixer4 mixRegulators; +AudioMixer4 mixL; +AudioMixer4 mixR; AudioSynthNoiseWhite noise; #if defined(ADAFRUIT_TRELLIS_M4_EXPRESS) AudioOutputAnalogStereo out1; #else -AudioOutputI2S out1; +AudioOutputI2S out1; #endif AudioControlSGTL5000 sgtl5000; AudioConnection FMVoicePatchCords[] = { - //{0, 0, 0, 0}, // For some reason, the first one is ignored + {noise, 0, mixL, 3}, + {noise, 0, mixR, 3}, -// {noise, 0, mixDrones, 3}, - {noise, 0, mixL, 3}, - {noise, 0, mixR, 3}, + {Chanter.outputMixer, 0, biquad1, 0}, + {biquad1, 0, mixL, 0}, + {biquad1, 0, mixR, 0}, - {Chanter.outputMixer, 0, biquad1, 0}, - {biquad1, 0, mixL, 0}, - {biquad1, 0, mixR, 0}, + {Drones[0].outputMixer, 0, mixDrones, 0}, + {Drones[1].outputMixer, 0, mixDrones, 1}, + {Drones[2].outputMixer, 0, mixDrones, 2}, + {mixDrones, 0, mixL, 1}, + {mixDrones, 0, mixR, 1}, - {Drones[0].outputMixer, 0, mixDrones, 0}, - {Drones[1].outputMixer, 0, mixDrones, 1}, - {Drones[2].outputMixer, 0, mixDrones, 2}, - {mixDrones, 0, mixL, 1}, - {mixDrones, 0, mixR, 1}, + {Regulators[0].outputMixer, 0, mixRegulators, 0}, + {Regulators[1].outputMixer, 0, mixRegulators, 1}, + {Regulators[2].outputMixer, 0, mixRegulators, 2}, + {mixRegulators, 0, mixL, 2}, + {mixRegulators, 0, mixR, 2}, - {Regulators[0].outputMixer, 0, mixRegulators, 0}, - {Regulators[1].outputMixer, 0, mixRegulators, 1}, - {Regulators[2].outputMixer, 0, mixRegulators, 2}, - {mixRegulators, 0, mixL, 2}, - {mixRegulators, 0, mixR, 2}, + {mixL, 0, out1, 0}, + {mixR, 0, out1, 1}, - {mixL, 0, out1, 0}, - {mixR, 0, out1, 1}, - - FMVoiceWiring(Chanter), - FMVoiceWiring(Drones[0]), - FMVoiceWiring(Drones[1]), - FMVoiceWiring(Drones[2]), - FMVoiceWiring(Regulators[0]), - FMVoiceWiring(Regulators[1]), - FMVoiceWiring(Regulators[2]), + FMVoiceWiring(Chanter), + FMVoiceWiring(Drones[0]), + FMVoiceWiring(Drones[1]), + FMVoiceWiring(Drones[2]), + FMVoiceWiring(Regulators[0]), + FMVoiceWiring(Regulators[1]), + FMVoiceWiring(Regulators[2]), }; void blink(bool forever) { @@ -83,13 +89,48 @@ void blink(bool forever) { delay(200); digitalWrite(LED_BUILTIN, false); delay(200); - if (! forever) break; + if (!forever) { + return; + } } } +void diag(const char *fmt, ...) { + va_list args; + char s[80]; + + va_start(args, fmt); + vsnprintf(s, sizeof(s)-1, fmt, args); + va_end(args); + + display.clearDisplay(); + display.drawRect(124, 16, 4, 16, SSD1306_WHITE); + display.setTextColor(SSD1306_WHITE); + display.setTextSize(1); + + display.setCursor(56, 24); + display.print(__DATE__); + +#if 0 + display.setCursor(0, 16); + display.print(fn); + display.print(":"); + display.print(lineno); +#endif + + display.setCursor(0, 0); + display.print(s); + + display.display(); +} + void setup() { - // Wire.begin needs a moment, so let's do some math. - setupJustPitches(NOTE_D4, PITCH_D4); + // Initialize settings + // XXX: Read these from persistent storage later + for (int i = 0; i < 4; i++) { + patch[i] = 0; + volume[i] = 0.75; + } Wire.begin(); @@ -101,131 +142,62 @@ void setup() { if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3c)) { blink(true); } - display.clearDisplay(); - display.setTextSize(1); - display.setTextColor(SSD1306_WHITE); - display.print("Starting"); - display.display(); + diag("Hello!"); #if defined(ADAFRUIT_TRELLIS_M4_EXPRESS) + diag("Trellis..."); trellis.begin(); #endif + diag("Pipe..."); while (!pipe.Init()) { - display.clearDisplay(); - display.setCursor(0, 0); - display.print("Pipe?"); - display.display(); + diag("No pipe. Is it connected?"); blink(false); } - // Set aside some memory for the audio library + diag("Audio..."); + // Set aside some memory for the audio library AudioMemory(20); // Set up the SGTL5000 using I2C sgtl5000.enable(); sgtl5000.volume(0.3); - // initialize tunables - updateTunables(3, 0); - // Initialize processor and memory measurements AudioProcessorUsageMaxReset(); AudioMemoryUsageMaxReset(); + diag("Drones..."); // Turn on drones - for (int i=0; i<3; i++) { + 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(JustPitches[NOTE_D4 - 12*i] + i); + Drones[i].NoteOn(pitch); } // Turn on all mixer channels - for (int i=0; i<4; i++) { + for (int i = 0; i < 4; i++) { mixL.gain(i, 0.5); mixR.gain(i, 0.6); } - -#ifdef DEBUG - noise.amplitude(0.1); - mixL.gain(3, 0.1); - mixR.gain(3, 0.1); -#endif - display.clearDisplay(); - display.setCursor(0, 0); - display.print("Done!"); - display.display(); + diag("Done!"); } -#define INIT_PITCH_ADJUST 0 -#define INIT_GAIN 0.7 -#define INIT_PATCH 0 - -int16_t pitchAdjust; -float chanterGain; -int patch; - -void updateTunables(uint8_t buttons, int note) { - // Pitch adjust if playing A - if (!note || (note == NOTE_A4)) { - switch (buttons) { - case 3: - pitchAdjust = INIT_PITCH_ADJUST; - break; - case 2: - pitchAdjust += 4; - break; - case 1: - pitchAdjust -= 4; - break; - } - } - - float adj = pow(2, pitchAdjust / 32768.0); - setupJustPitches(NOTE_D4, PITCH_D4*adj); - - if (!note || (note == NOTE_G4)) { - // Volume adjust if playing G - switch (buttons) { - case 3: - chanterGain = INIT_GAIN; - break; - case 2: - chanterGain = min(chanterGain+0.005, 1.0); - break; - case 1: - chanterGain = max(chanterGain-0.005, 0.0); - break; - } - } - for (int i=0; i<3; i++) { - mixL.gain(i, chanterGain); - mixR.gain(i, chanterGain); - } - - if (!note || (note == NOTE_CS5)) { - if (buttons == 3) { - patch = INIT_PATCH; - } - - // wrap - int bankSize = sizeof(Bank) / sizeof(Bank[0]); - patch = (patch + bankSize) % bankSize; - - FMPatch *p = &Bank[patch]; - Chanter.LoadPatch(p); - } -} - - void loop() { pipe.Update(); + diag("loop %d", millis()); #if defined(ADAFRUIT_TRELLIS_M4_EXPRESS) trellis.tick(); trellis.setPixelColor(1, trellis.ColorHSV(millis(), 255, 120)); - trellis.setPixelColor(0, trellis.ColorHSV(64*pipe.kneeClosedness, 255, 120)); + { + uint16_t color = trellis.ColorHSV(64 * pipe.kneeClosedness, 255, 120); + trellis.setPixelColor(0, color); + } #endif // If we're infinitely (for the sensor) off the knee, @@ -240,7 +212,7 @@ void loop() { /** 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 @@ -250,21 +222,66 @@ void loop() { * E♮: Down [- coarse] * E♭: + [+ fine] * D♮: - [- fine] - * + * */ void doSetup() { display.clearDisplay(); - bool alt = bitRead(pipe.keys, 7); + bool alt = pipe.Pressed(7); + + // Draw indicator bar + display.fillRect(126, 0, 2, 32, SSD1306_WHITE); + display.setTextSize(1); + display.setCursor(0, 0); - display.fillRect(0, 0, 40, 32, SSD1306_WHITE); - display.setFont(&FreeSans9pt7b); - display.setCursor(1, 13); - display.setTextColor(SSD1306_BLACK); if (alt) { - display.print("pipe"); + // Show settings for each of Chanter, Regulators, Drones + for (int i = 0; i < 3; i++) { + int p = patch[i]; + int16_t x = 0; + 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 { - display.print("alt"); + if (pipe.Pressed(6)) { + float freq = PITCH_CONCERT_D4 + pitchAdjust; + Note note = NearestNote(freq); + + display.setCursor(0, 0); + display.print(NoteName(note)); + display.setCursor(6 + 6, 0); + display.print(freq); + } else if (pipe.Pressed(5)) { + display.print("fn2"); + } else if (pipe.Pressed(4)) { + display.print("fn3"); + } else { + display.setCursor(56, 8); + display.setTextSize(2); + display.print("Setup"); + } } display.display(); @@ -278,8 +295,8 @@ void doPlay() { Chanter.NoteOff(); } else { // Calculate pitch, and glissando pitch - uint16_t pitch = JustPitches[pipe.note]; - uint16_t glissandoPitch = JustPitches[pipe.glissandoNote]; + 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) { @@ -287,13 +304,13 @@ void doPlay() { pitch += diff * pipe.glissandoOpenness; } - // Apply a low shelf filter if this is the alternate fingering + // 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); @@ -303,11 +320,11 @@ void doPlay() { } // Look up the note name - const char *note_name = NoteNames[pipe.note % 12]; - if (pipe.silent) { - note_name = "--"; - updateDisplay = true; - } + const char *note_name = NoteName(pipe.note); + if (pipe.silent) { + note_name = "--"; + updateDisplay = true; + } if (pipe.note != last_note) { updateDisplay = true; @@ -319,7 +336,7 @@ void doPlay() { display.setCursor(0, 16); display.setTextSize(2); display.print(Chanter.patch->name); - + display.setCursor(0, 0); display.setTextSize(2); display.print(note_name); @@ -329,6 +346,6 @@ void doPlay() { display.print(pipe.kneeClosedness); display.display(); - last_note = pipe.note; + last_note = pipe.note; } } From 036dcf592eca0b41e6911a044b5cec83fc8b892a Mon Sep 17 00:00:00 2001 From: Neale Pickett Date: Tue, 24 Nov 2020 21:33:55 -0700 Subject: [PATCH 09/16] Now you can adjust pitch and intonation --- .clang-format | 3 + fingering.h | 140 ++++++++++++++++----------------- pipe.cpp | 126 +++++++++++++++--------------- pipe.h | 76 +++++++++--------- tuning.cpp | 51 ++++++++---- tuning.h | 210 ++++++++++++++++++++++++++++++++++++++------------ uilleann.ino | 109 +++++++++++++++++--------- 7 files changed, 448 insertions(+), 267 deletions(-) create mode 100644 .clang-format diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..ce4f900 --- /dev/null +++ b/.clang-format @@ -0,0 +1,3 @@ +BasedOnStyle: Chromium +ColumnLimit: 0 +PointerAlignment: Right diff --git a/fingering.h b/fingering.h index e8feab9..ac85cef 100644 --- a/fingering.h +++ b/fingering.h @@ -3,11 +3,13 @@ struct Fingering { Note note; - bool alt; // Alternate fingering: sounds more choked + bool alt; // Alternate fingering: sounds more choked }; -#define n(note) {note, false} -#define P(note) {note, true} +#define n(note) \ + { note, false } +#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) @@ -15,71 +17,71 @@ struct Fingering { #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.. - CCDD, // OOO OX.. - CDCD, // OOO XO.. - DDDD, // OOO XX.. - CDCD, // OOX OO.. - DDDD, // OOX OX.. - CDCD, // OOX XO.. - DDDD, // OOX XX.. - CCDD, // OXO OO.. - CCDD, // OXO OX.. - DDDD, // OXO XO.. - DDDD, // OXO XX.. - DDDD, // OXX OO.. - DDDD, // OXX OX.. - DDDD, // OXX XO.. - DDDD, // OXX XX.. - CDCD, // XOO OO.. - DDDD, // XOO OX.. - CDCD, // XOO XO.. - DDDD, // XOO XX.. - CDCD, // XOX OO.. - DDDD, // XOX OX.. - CDCD, // XOX XO.. - DDDD, // XOX XX.. - DDDD, // XXO OO.. - DDDD, // XXO OX.. - DDDD, // XXO XO.. - DDDD, // XXO XX.. - DDDD, // XXX OO.. - DDDD, // XXX OX.. - n(NOTE_D5), n(NOTE_D5), n(NOTE_D5), P(NOTE_D5), // XXX XO.. - DDDD, // XXX XX.. + // Open Back D + n(NOTE_CS5), n(NOTE_CS5), n(NOTE_CS5), n(NOTE_D5), // OOO OO.. + CCDD, // OOO OX.. + CDCD, // OOO XO.. + DDDD, // OOO XX.. + CDCD, // OOX OO.. + DDDD, // OOX OX.. + CDCD, // OOX XO.. + DDDD, // OOX XX.. + CCDD, // OXO OO.. + CCDD, // OXO OX.. + DDDD, // OXO XO.. + DDDD, // OXO XX.. + DDDD, // OXX OO.. + DDDD, // OXX OX.. + DDDD, // OXX XO.. + DDDD, // OXX XX.. + CDCD, // XOO OO.. + DDDD, // XOO OX.. + CDCD, // XOO XO.. + DDDD, // XOO XX.. + CDCD, // XOX OO.. + DDDD, // XOX OX.. + CDCD, // XOX XO.. + DDDD, // XOX XX.. + DDDD, // XXO OO.. + DDDD, // XXO OX.. + DDDD, // XXO XO.. + DDDD, // XXO XX.. + DDDD, // XXX OO.. + DDDD, // XXX OX.. + n(NOTE_D5), n(NOTE_D5), n(NOTE_D5), P(NOTE_D5), // XXX XO.. + DDDD, // XXX XX.. - // Closed Back D - CCCC, // OOO OO... - 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.. - CCCC, // OOX XO.. - CCCC, // OOX XX.. - CCCC, // OXO OO.. - 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_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_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.. - P(NOTE_B4), P(NOTE_B4), P(NOTE_B4), P(NOTE_B4), // XOX XO.. - 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.. - 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.. + // Closed Back D + CCCC, // OOO OO... + 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.. + CCCC, // OOX XO.. + CCCC, // OOX XX.. + CCCC, // OXO OO.. + 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_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_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.. + P(NOTE_B4), P(NOTE_B4), P(NOTE_B4), P(NOTE_B4), // XOX XO.. + 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.. + 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.. }; diff --git a/pipe.cpp b/pipe.cpp index 67a93d4..0ded6b1 100644 --- a/pipe.cpp +++ b/pipe.cpp @@ -1,6 +1,6 @@ #include "pipe.h" -#include "tuning.h" #include "fingering.h" +#include "tuning.h" // Kludge time: this is something I did just to make my breadboard look nicer. #define KEY_OFFSET 2 @@ -10,89 +10,89 @@ #define GLISSANDO_STEPS (OPENVAL - CLOSEDVAL) Pipe::Pipe() { - keysLast = 0; + keysLast = 0; } bool Pipe::Init() { - // Capacative touch sensor - if (!capSensor.begin(0x5A)) { - return false; - } + // Capacative touch sensor + if (!capSensor.begin(0x5A)) { + return false; + } - // Proximity sensor - if (paj7620Init()) { - 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(); + // 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; + return true; } void Pipe::Update() { - uint8_t glissandoKeys = 0; + 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; + // 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); + + keysLast = keys; + 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); } - - // 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); - 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); - } + if (openness == 0.0) { + bitSet(glissandoKeys, i); } + } - // Look up notes in the big table - struct Fingering f = uilleann_matrix[keys]; - struct Fingering gf = uilleann_matrix[glissandoKeys]; + // Look up notes in the big table + struct Fingering f = uilleann_matrix[keys]; + struct Fingering gf = uilleann_matrix[glissandoKeys]; - note = f.note; - glissandoNote = gf.note; + note = f.note; + glissandoNote = gf.note; - // Was the high bit set? That indicates "alternate fingering", which sounds different. - altFingering = f.alt; + // Was the high bit set? That indicates "alternate fingering", which sounds different. + altFingering = f.alt; - // If the bag is squished, jump up an octave - // But only if the left thumb is down! - if (bag && (keys & bit(7))) { - note += NOTE_OCTAVE; - glissandoNote += NOTE_OCTAVE; - } + // If the bag is squished, jump up an octave + // But only if the left thumb is down! + if (bag && (keys & bit(7))) { + note += NOTE_OCTAVE; + glissandoNote += NOTE_OCTAVE; + } - // All keys closed + knee = no sound - silent = ((kneeClosedness > 240) && (keys == 0xff)); + // All keys closed + knee = no sound + silent = ((kneeClosedness > 240) && (keys == 0xff)); } bool Pipe::Pressed(uint8_t key) { - return bitRead(keys, key); + return bitRead(keys, key); } bool Pipe::JustPressed(uint8_t key) { - if (bitRead(keys, key)) { - return !bitRead(keysLast, key); - } - return false; + if (bitRead(keys, key)) { + return !bitRead(keysLast, key); + } + return false; } diff --git a/pipe.h b/pipe.h index 055895c..44c4862 100644 --- a/pipe.h +++ b/pipe.h @@ -1,59 +1,59 @@ #pragma once -#include -#include #include +#include #include +#include #include "tuning.h" class Pipe { -public: - // kneeClosedness indicates how "closed" the knee sensor is. 0 = wide open. - uint8_t kneeClosedness; + 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; + // keys are which keys are being pressed. + uint8_t keys; + uint8_t keysLast; - // note holds the note being played, according to the fingering chart. - Note note; + // note holds the note being played, according to the fingering chart. + Note note; - // glissandoNote is the note that would be played if partially open keys were fully open. - Note glissandoNote; + // glissandoNote is the note that would be played if partially open keys were fully open. + Note glissandoNote; - // glissandoOpenness is how "open" the holes are in the direction of the glissandoNote. - float glissandoOpenness; + // glissandoOpenness is how "open" the holes are in the direction of the glissandoNote. + float glissandoOpenness; - // silent is true if all keys and the knee are closed. - bool silent; + // silent is true if all keys and the knee are closed. + bool silent; - // bag is true if the bag is being squished. - bool bag; + // 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; + // altFingering is true if the "alternate fingering" is being played. + // This should sound different than the standard fingering. + bool altFingering; - Pipe(); + Pipe(); - // Init initializes everything. - // - // Returns true if it all worked. You can run it again if it didn't. - bool Init(); + // 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(); + // Update reads sensors and updates pipe state. + // + // It should be run once per loop. + void Update(); - // Pressed returns whether the given key is pressed. - bool Pressed(uint8_t key); + // Pressed returns whether the given key is pressed. + bool Pressed(uint8_t key); - // JustPressed returns whether the given key was just pressed. - bool JustPressed(uint8_t key); + // JustPressed returns whether the given key was just pressed. + bool JustPressed(uint8_t key); -private: - Adafruit_MPR121 capSensor; - QwiicButton bagSensor; - bool bag_enabled; + private: + Adafruit_MPR121 capSensor; + QwiicButton bagSensor; + bool bag_enabled; }; diff --git a/tuning.cpp b/tuning.cpp index d56f8b5..a605f41 100644 --- a/tuning.cpp +++ b/tuning.cpp @@ -2,9 +2,6 @@ #include -// 12th root of 2, used for Twelvetone Equal Temperament -#define TET_CONST 1.059463 - Tuning::Tuning(Note base, float pitch, TuningSystem system) { Setup(base, pitch, system); } @@ -14,18 +11,20 @@ Tuning::Tuning(Note base, float pitch) { // I like just Intonation. Tuning::Tuning() { -#if 0 Tuning(NOTE_D4, PITCH_CONCERT_D4, TUNINGSYSTEM_JUST); -#endif } -Note Tuning::GetBaseNote() { return baseNote; } +Note Tuning::GetBaseNote() { + return baseNote; +} void Tuning::SetTuningSystem(TuningSystem system) { Setup(baseNote, GetPitch(baseNote), system); } -TuningSystem Tuning::GetSystem() { return system; } +TuningSystem Tuning::GetTuningSystem() { + return system; +} // setupOctaves computes the entire tuning frequency chart. // @@ -53,7 +52,7 @@ void Tuning::setupOctaves(Note base) { void Tuning::setupEqual(Note base, float pitch) { pitches[base] = pitch; for (int i = 1; i < 12; i++) { - pitches[base + i] = pitches[base + i - 1] * TET_CONST; + pitches[base + i] = pitches[base + i - 1] * TET_SEMITONE_MULTIPLIER; } } @@ -95,17 +94,43 @@ void Tuning::Setup(Note base, float pitch, TuningSystem system) { setupOctaves(base); } -void Tuning::Setup(Note base, float pitch) { Setup(base, pitch, system); } +void Tuning::Setup(Note base, float pitch) { + Setup(base, pitch, system); +} -float Tuning::GetPitch(Note note) { return pitches[note]; } +float Tuning::GetPitch(Note note) { + return pitches[note]; +} Note NearestNote(float pitch) { - return Note(round(log2(pitch / PITCH_CONCERT_C0))); + return Note(round(log(pitch / PITCH_CONCERT_C0) / log(TET_SEMITONE_MULTIPLIER))); } const char *noteNames[]{ - "C ", "C#", "D ", "Eb", "E ", "F ", "F#", "G ", "Ab", "A ", "Bb", "B ", + "C", + "C#", + "D", + "Eb", + "E", + "F", + "F#", + "G", + "Ab", + "A", + "Bb", + "B", }; -const char *NoteName(Note note) { return noteNames[note % 12]; } +const char *NoteName(Note note) { + return noteNames[note % 12]; +} +const char *TuningSystemName(TuningSystem system) { + switch (system) { + case TUNINGSYSTEM_EQUAL: + return "Equal"; + case TUNINGSYSTEM_JUST: + default: + return "Just"; + } +} diff --git a/tuning.h b/tuning.h index 5483cdc..bcb54a5 100644 --- a/tuning.h +++ b/tuning.h @@ -2,21 +2,120 @@ #include enum TuningSystem { - TUNINGSYSTEM_JUST, - TUNINGSYSTEM_EQUAL, + TUNINGSYSTEM_JUST, + TUNINGSYSTEM_EQUAL, + TUNINGSYSTEM_MAX = TUNINGSYSTEM_EQUAL, }; -#define TUNINGSYSTEM_MAX TUNINGSYSTEM_EQUAL enum Note { - NOTE_C0, NOTE_CS0, NOTE_D0, NOTE_DS0, NOTE_E0, NOTE_F0, NOTE_FS0, NOTE_G0, NOTE_GS0, NOTE_A0, NOTE_AS0, NOTE_B0, - NOTE_C1, NOTE_CS1, NOTE_D1, NOTE_DS1, NOTE_E1, NOTE_F1, NOTE_FS1, NOTE_G1, NOTE_GS1, NOTE_A1, NOTE_AS1, NOTE_B1, - NOTE_C2, NOTE_CS2, NOTE_D2, NOTE_DS2, NOTE_E2, NOTE_F2, NOTE_FS2, NOTE_G2, NOTE_GS2, NOTE_A2, NOTE_AS2, NOTE_B2, - NOTE_C3, NOTE_CS3, NOTE_D3, NOTE_DS3, NOTE_E3, NOTE_F3, NOTE_FS3, NOTE_G3, NOTE_GS3, NOTE_A3, NOTE_AS3, NOTE_B3, - NOTE_C4, NOTE_CS4, NOTE_D4, NOTE_DS4, NOTE_E4, NOTE_F4, NOTE_FS4, NOTE_G4, NOTE_GS4, NOTE_A4, NOTE_AS4, NOTE_B4, - NOTE_C5, NOTE_CS5, NOTE_D5, NOTE_DS5, NOTE_E5, NOTE_F5, NOTE_FS5, NOTE_G5, NOTE_GS5, NOTE_A5, NOTE_AS5, NOTE_B5, - NOTE_C6, NOTE_CS6, NOTE_D6, NOTE_DS6, NOTE_E6, NOTE_F6, NOTE_FS6, NOTE_G6, NOTE_GS6, NOTE_A6, NOTE_AS6, NOTE_B6, - NOTE_C7, NOTE_CS7, NOTE_D7, NOTE_DS7, NOTE_E7, NOTE_F7, NOTE_FS7, NOTE_G7, NOTE_GS7, NOTE_A7, NOTE_AS7, NOTE_B7, - NOTE_C8, NOTE_CS8, NOTE_D8, NOTE_DS8, NOTE_E8, NOTE_F8, NOTE_FS8, NOTE_G8, NOTE_GS8, NOTE_A8, NOTE_AS8, NOTE_B8, + NOTE_C0, + NOTE_CS0, + NOTE_D0, + NOTE_DS0, + NOTE_E0, + NOTE_F0, + NOTE_FS0, + NOTE_G0, + NOTE_GS0, + NOTE_A0, + NOTE_AS0, + NOTE_B0, + NOTE_C1, + NOTE_CS1, + NOTE_D1, + NOTE_DS1, + NOTE_E1, + NOTE_F1, + NOTE_FS1, + NOTE_G1, + NOTE_GS1, + NOTE_A1, + NOTE_AS1, + NOTE_B1, + NOTE_C2, + NOTE_CS2, + NOTE_D2, + NOTE_DS2, + NOTE_E2, + NOTE_F2, + NOTE_FS2, + NOTE_G2, + NOTE_GS2, + NOTE_A2, + NOTE_AS2, + NOTE_B2, + NOTE_C3, + NOTE_CS3, + NOTE_D3, + NOTE_DS3, + NOTE_E3, + NOTE_F3, + NOTE_FS3, + NOTE_G3, + NOTE_GS3, + NOTE_A3, + NOTE_AS3, + NOTE_B3, + NOTE_C4, + NOTE_CS4, + NOTE_D4, + NOTE_DS4, + NOTE_E4, + NOTE_F4, + NOTE_FS4, + NOTE_G4, + NOTE_GS4, + NOTE_A4, + NOTE_AS4, + NOTE_B4, + NOTE_C5, + NOTE_CS5, + NOTE_D5, + NOTE_DS5, + NOTE_E5, + NOTE_F5, + NOTE_FS5, + NOTE_G5, + NOTE_GS5, + NOTE_A5, + NOTE_AS5, + NOTE_B5, + NOTE_C6, + NOTE_CS6, + NOTE_D6, + NOTE_DS6, + NOTE_E6, + NOTE_F6, + NOTE_FS6, + NOTE_G6, + NOTE_GS6, + NOTE_A6, + NOTE_AS6, + NOTE_B6, + NOTE_C7, + NOTE_CS7, + NOTE_D7, + NOTE_DS7, + NOTE_E7, + NOTE_F7, + NOTE_FS7, + NOTE_G7, + NOTE_GS7, + NOTE_A7, + NOTE_AS7, + NOTE_B7, + NOTE_C8, + NOTE_CS8, + NOTE_D8, + NOTE_DS8, + NOTE_E8, + NOTE_F8, + NOTE_FS8, + NOTE_G8, + NOTE_GS8, + NOTE_A8, + NOTE_AS8, + NOTE_B8, NOTE_ZERO = 0, NOTE_SEMITONE = 1, NOTE_WHOLETONE = 2, @@ -25,75 +124,88 @@ enum Note { }; #define PITCH_CONCERT_C0 16.35 -#define PITCH_CONCERT_A4 440.00 +#define PITCH_CONCERT_A4 440.00 #define PITCH_CONCERT_D4 293.66 +// Twelvetone Equal Temperament semitone multiplier +// Take any frequency and multiply it by this magic number to get a semitone higher! +// Divide to get a semitone lower! +// This is an approximation of exp(2, 1/12), +// which was worked out in around the 1500s. +#define TET_SEMITONE_MULTIPLIER 1.059463 + class Tuning { -public: - // name contains the name of the current tuning system - const char *name; + public: + // name contains the name of the current tuning system + const char *name; - Tuning(Note base, float pitch, TuningSystem system); - Tuning(Note base, float pitch); - Tuning(); - void Setup(Note base, float pitch, TuningSystem system); - void Setup(Note base, float pitch); - void SetTuningSystem(TuningSystem system); - TuningSystem GetSystem(); - Note GetBaseNote(); - float GetPitch(Note note); + Tuning(Note base, float pitch, TuningSystem system); + Tuning(Note base, float pitch); + Tuning(); + void Setup(Note base, float pitch, TuningSystem system); + void Setup(Note base, float pitch); + void SetTuningSystem(TuningSystem system); + TuningSystem GetTuningSystem(); + Note GetBaseNote(); + float GetPitch(Note note); -private: - TuningSystem system; - Note baseNote; - float pitches[NOTE_MAX]; + private: + TuningSystem system; + Note baseNote; + float pitches[NOTE_MAX]; - void setupOctaves(Note base); - void setupJust(Note base, float pitch); - void setupEqual(Note base, float pitch); + void setupOctaves(Note base); + void setupJust(Note base, float pitch); + void setupEqual(Note base, float pitch); }; // NearestNote returns the note nearest to pitch. Note NearestNote(float pitch); -// NoteName return the name of a note (without octave). +// NoteName returns the name of a note (without octave). const char *NoteName(Note note); +// TuningSystemName returns the name of a tuning system. +const char *TuningSystemName(TuningSystem system); // Make notes support some arithmetic inline Note toNote(int a) { - if (a < NOTE_ZERO) { - return NOTE_ZERO; - } else if (a > NOTE_MAX) { - return NOTE_MAX; - } else { - return Note(a); - } + if (a < NOTE_ZERO) { + return NOTE_ZERO; + } else if (a > NOTE_MAX) { + return NOTE_MAX; + } else { + return Note(a); + } } inline Note operator+(const Note &a, const Note b) { - return toNote(int(a) + int(b)); + return toNote(int(a) + int(b)); } inline Note &operator+=(Note &a, const Note b) { - return a = a + b; + return a = a + b; } inline Note &operator++(Note &a) { - return a += NOTE_SEMITONE; + return a += NOTE_SEMITONE; } inline Note operator-(const Note a, const Note b) { - return toNote(int(a) - int(b)); + return toNote(int(a) - int(b)); } inline Note &operator-=(Note &a, const Note b) { - return a = a - b; + return a = a - b; } inline Note &operator--(Note &a) { - return a -= NOTE_SEMITONE; + return a -= NOTE_SEMITONE; } inline Note operator*(const Note a, const int b) { - return toNote(int(a) * b); + return toNote(int(a) * b); } inline int operator/(const Note a, const int b) { - return int(a)/b; + return int(a) / b; } inline int operator/(const Note a, const Note b) { - return int(a)/b; + return int(a) / b; +} + +inline TuningSystem operator++(TuningSystem &a) { + return a = TuningSystem((int(a) + 1) % int(TUNINGSYSTEM_MAX + 1)); } diff --git a/uilleann.ino b/uilleann.ino index 37a3eff..89616fa 100644 --- a/uilleann.ino +++ b/uilleann.ino @@ -3,7 +3,6 @@ #include #include #include -#include #include #include #include @@ -22,14 +21,12 @@ Pipe pipe; Tuning tuning = Tuning(NOTE_D4, PITCH_CONCERT_D4, TUNINGSYSTEM_JUST); Adafruit_SSD1306 display(128, 32, &Wire, -1); -int currentPatch = 0; // Settings -uint8_t intonation = 0; -int16_t pitchAdjust = 0; uint8_t patch[4] = {0}; float volume[4] = {0}; const char *settingNames[4] = {"c", "r", "d", "*"}; +const char *buildDate = __DATE__; // Pipes FMVoice Chanter; @@ -100,7 +97,7 @@ void diag(const char *fmt, ...) { char s[80]; va_start(args, fmt); - vsnprintf(s, sizeof(s)-1, fmt, args); + vsnprintf(s, sizeof(s) - 1, fmt, args); va_end(args); display.clearDisplay(); @@ -109,7 +106,7 @@ void diag(const char *fmt, ...) { display.setTextSize(1); display.setCursor(56, 24); - display.print(__DATE__); + display.print(buildDate); #if 0 display.setCursor(0, 16); @@ -171,8 +168,7 @@ void setup() { // 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 + float pitch = tuning.GetPitch(note) * (0.01 * (i - 1)); // Detune just a touch Drones[i].LoadPatch(&Bank[0]); Drones[i].NoteOn(pitch); } @@ -187,25 +183,22 @@ void setup() { } void loop() { + static bool forceDisplayUpdate = true; + pipe.Update(); - diag("loop %d", millis()); #if defined(ADAFRUIT_TRELLIS_M4_EXPRESS) trellis.tick(); - - trellis.setPixelColor(1, trellis.ColorHSV(millis(), 255, 120)); - { - uint16_t color = trellis.ColorHSV(64 * pipe.kneeClosedness, 255, 120); - trellis.setPixelColor(0, color); - } #endif // If we're infinitely (for the sensor) off the knee, // go into setup mode! if (pipe.kneeClosedness == 0) { doSetup(); + forceDisplayUpdate = true; } else { - doPlay(); + doPlay(forceDisplayUpdate); + forceDisplayUpdate = false; } } @@ -225,12 +218,21 @@ void loop() { * */ 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); @@ -238,7 +240,7 @@ void doSetup() { // Show settings for each of Chanter, Regulators, Drones for (int i = 0; i < 3; i++) { int p = patch[i]; - int16_t x = 0; + int16_t x = 1; int16_t y = i * 8; display.setCursor(x, y); @@ -265,31 +267,71 @@ void doSetup() { display.print(Bank[p].name); } } else { - if (pipe.Pressed(6)) { - float freq = PITCH_CONCERT_D4 + pitchAdjust; - Note note = NearestNote(freq); - display.setCursor(0, 0); + 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.setCursor(6 + 6, 0); + 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.setCursor(56, 8); - display.setTextSize(2); + 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() { +void doPlay(bool forceUpdate) { static uint8_t last_note = 0; - bool updateDisplay = false; + bool updateDisplay = forceUpdate; if (pipe.silent) { Chanter.NoteOff(); @@ -332,19 +374,16 @@ void doPlay() { if (updateDisplay) { display.clearDisplay(); + display.setFont(&FreeSans9pt7b); + + if (Chanter.patch) { + display.setCursor(0, 32); + display.print(Chanter.patch->name); + } display.setCursor(0, 16); - display.setTextSize(2); - display.print(Chanter.patch->name); - - display.setCursor(0, 0); - display.setTextSize(2); display.print(note_name); - display.setCursor(40, 0); - display.setTextSize(1); - display.print(pipe.kneeClosedness); - display.display(); last_note = pipe.note; } From f7f2bc3cfec4be475890c0be4598cf002c8646bf Mon Sep 17 00:00:00 2001 From: Neale Pickett Date: Wed, 25 Nov 2020 09:28:53 -0700 Subject: [PATCH 10/16] Make tuning.h easier to read --- tuning.h | 126 ++++++++----------------------------------------------- 1 file changed, 18 insertions(+), 108 deletions(-) diff --git a/tuning.h b/tuning.h index bcb54a5..a2f6375 100644 --- a/tuning.h +++ b/tuning.h @@ -7,115 +7,25 @@ enum TuningSystem { TUNINGSYSTEM_MAX = TUNINGSYSTEM_EQUAL, }; +// Twelve-Tone Note (one chromatic scale) +#define NOTE_TT(o) NOTE_C##o, NOTE_Cs##o, NOTE_Db##o = NOTE_Cs##o, \ + NOTE_D##o, NOTE_Ds##o, NOTE_Eb##o = NOTE_Ds##o, \ + NOTE_E##o, \ + NOTE_F##o, NOTE_Fs##o, NOTE_Gb##o = NOTE_Fs##o, \ + NOTE_G##o, NOTE_Gs##o, NOTE_Ab##o = NOTE_Gs##o, \ + NOTE_A##o, NOTE_As##o, NOTE_Bb##o = NOTE_As##o, \ + NOTE_B##o + enum Note { - NOTE_C0, - NOTE_CS0, - NOTE_D0, - NOTE_DS0, - NOTE_E0, - NOTE_F0, - NOTE_FS0, - NOTE_G0, - NOTE_GS0, - NOTE_A0, - NOTE_AS0, - NOTE_B0, - NOTE_C1, - NOTE_CS1, - NOTE_D1, - NOTE_DS1, - NOTE_E1, - NOTE_F1, - NOTE_FS1, - NOTE_G1, - NOTE_GS1, - NOTE_A1, - NOTE_AS1, - NOTE_B1, - NOTE_C2, - NOTE_CS2, - NOTE_D2, - NOTE_DS2, - NOTE_E2, - NOTE_F2, - NOTE_FS2, - NOTE_G2, - NOTE_GS2, - NOTE_A2, - NOTE_AS2, - NOTE_B2, - NOTE_C3, - NOTE_CS3, - NOTE_D3, - NOTE_DS3, - NOTE_E3, - NOTE_F3, - NOTE_FS3, - NOTE_G3, - NOTE_GS3, - NOTE_A3, - NOTE_AS3, - NOTE_B3, - NOTE_C4, - NOTE_CS4, - NOTE_D4, - NOTE_DS4, - NOTE_E4, - NOTE_F4, - NOTE_FS4, - NOTE_G4, - NOTE_GS4, - NOTE_A4, - NOTE_AS4, - NOTE_B4, - NOTE_C5, - NOTE_CS5, - NOTE_D5, - NOTE_DS5, - NOTE_E5, - NOTE_F5, - NOTE_FS5, - NOTE_G5, - NOTE_GS5, - NOTE_A5, - NOTE_AS5, - NOTE_B5, - NOTE_C6, - NOTE_CS6, - NOTE_D6, - NOTE_DS6, - NOTE_E6, - NOTE_F6, - NOTE_FS6, - NOTE_G6, - NOTE_GS6, - NOTE_A6, - NOTE_AS6, - NOTE_B6, - NOTE_C7, - NOTE_CS7, - NOTE_D7, - NOTE_DS7, - NOTE_E7, - NOTE_F7, - NOTE_FS7, - NOTE_G7, - NOTE_GS7, - NOTE_A7, - NOTE_AS7, - NOTE_B7, - NOTE_C8, - NOTE_CS8, - NOTE_D8, - NOTE_DS8, - NOTE_E8, - NOTE_F8, - NOTE_FS8, - NOTE_G8, - NOTE_GS8, - NOTE_A8, - NOTE_AS8, - NOTE_B8, + NOTE_TT(0), + NOTE_TT(1), + NOTE_TT(2), + NOTE_TT(3), + NOTE_TT(4), + NOTE_TT(5), + NOTE_TT(6), + NOTE_TT(7), + NOTE_TT(8), NOTE_ZERO = 0, NOTE_SEMITONE = 1, NOTE_WHOLETONE = 2, From 13c72dae2aa817aee6c29c15ac415b545985d492 Mon Sep 17 00:00:00 2001 From: Neale Pickett Date: Wed, 25 Nov 2020 17:13:54 -0700 Subject: [PATCH 11/16] Playing now, with a crash somewhere --- fingering.h | 28 ++--- main-play.h | 71 +++++++++++++ main-setup.h | 188 ++++++++++++++++++++++++++++++++ patches.h | 2 + pipe.cpp | 53 +++++++-- pipe.h | 21 +++- synth.cpp | 8 ++ synth.h | 5 + tuning.cpp | 4 + tuning.h | 11 +- uilleann.ino | 295 ++++++++++++++------------------------------------- 11 files changed, 444 insertions(+), 242 deletions(-) create mode 100644 main-play.h create mode 100644 main-setup.h 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!"); } From 5d6bc5465350efd1fd33a3c0a328d00d23ba65b7 Mon Sep 17 00:00:00 2001 From: Neale Pickett Date: Wed, 25 Nov 2020 21:38:29 -0700 Subject: [PATCH 12/16] Working now! --- main-play.h | 22 ++++++++++++---------- main-setup.h | 2 +- uilleann.ino | 7 +++---- 3 files changed, 16 insertions(+), 15 deletions(-) diff --git a/main-play.h b/main-play.h index 8a957f2..fd2b7b9 100644 --- a/main-play.h +++ b/main-play.h @@ -9,26 +9,29 @@ void playDrones() { } } -void doPlay(Pipe pipe, Adafruit_SSD1306 display, bool forceDisplayUpdate) { +void doPlay(bool forceDisplayUpdate) { static Note last_note = NOTE_ZERO; bool updateDisplay = forceDisplayUpdate; - diag("silent?"); + if (updateDisplay) { + display.clearDisplay(); + display.fillRect(0, 0, 2, 2, SSD1306_WHITE); + display.display(); + } + 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); + float pitch = tuning.GetPitch(pipe.note); + float 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); @@ -44,12 +47,11 @@ void doPlay(Pipe pipe, Adafruit_SSD1306 display, bool forceDisplayUpdate) { } } - diag("check last note"); if (pipe.note != last_note) { updateDisplay = true; } - diag("update display"); +#if 0 if (updateDisplay) { // Look up the note name const char *note_name = NoteName(pipe.note); @@ -65,7 +67,7 @@ void doPlay(Pipe pipe, Adafruit_SSD1306 display, bool forceDisplayUpdate) { display.print(note_name); display.display(); - //last_note = pipe.note; + last_note = pipe.note; } - diag("play done"); +#endif } diff --git a/main-setup.h b/main-setup.h index 62f0e5c..1e77f86 100644 --- a/main-setup.h +++ b/main-setup.h @@ -140,7 +140,7 @@ void setupInfo() { display.setFont(); display.setTextSize(1); display.setCursor(0, 16); - display.print("build"); + display.print("FC-1"); display.setCursor(0, 24); display.print(buildDate); } diff --git a/uilleann.ino b/uilleann.ino index 2dceacc..b03e1c5 100644 --- a/uilleann.ino +++ b/uilleann.ino @@ -194,6 +194,7 @@ void setup() { playDrones(); diag("Done!"); + display.dim(true); } void loadPatch(uint8_t where) { @@ -220,7 +221,7 @@ void loadPatch(uint8_t where) { void loop() { - static bool upSetting = false; // GET IT? + static bool upSetting = true; // GET IT? pipe.Update(); @@ -242,8 +243,6 @@ void loop() { } } - diag("Play!"); - doPlay(pipe, display, upSetting); + doPlay(upSetting); upSetting = false; - diag("Done!"); } From d31398ed63a30ff7cd9ee15123585d086c5e77e9 Mon Sep 17 00:00:00 2001 From: Neale Pickett Date: Wed, 25 Nov 2020 21:53:45 -0700 Subject: [PATCH 13/16] Sorting out glissando --- main-play.h | 1 + pipe.cpp | 16 +++++++++++----- pipe.h | 3 ++- 3 files changed, 14 insertions(+), 6 deletions(-) diff --git a/main-play.h b/main-play.h index fd2b7b9..ca34163 100644 --- a/main-play.h +++ b/main-play.h @@ -51,6 +51,7 @@ void doPlay(bool forceDisplayUpdate) { updateDisplay = true; } + diag("%d %f", pipe.keys, pipe.glissandoOpenness); #if 0 if (updateDisplay) { // Look up the note name diff --git a/pipe.cpp b/pipe.cpp index e322b9e..e5c82ec 100644 --- a/pipe.cpp +++ b/pipe.cpp @@ -47,22 +47,28 @@ void Pipe::Update() { // 0x6c is actually 8 bytes, but all 8 are always the same... paj7620ReadReg(0x6c, 1, &kneeClosedness); - for (int i = 0; i < NUM_KEYS; i++) { + for (int i = 0; i < NUM_KEYS; ++i) { uint16_t val = max(capSensor.filteredData(i), CLOSEDVAL); - float openness = ((val - CLOSEDVAL) / float(GLISSANDO_STEPS)); + keyOpen[i] = ((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); + if (keyOpen[i] < 1.0) { + glissandoOpenness = max(glissandoOpenness, keyOpen[i]); bitSet(keys, i); } - if (openness == 0.0) { + if (keyOpen[i] == 0.0) { bitSet(glissandoKeys, i); } } + // Compute glissando amount + glissandoOpenness = 0.0; + for (int i = 0; i < 8; ++i) { + glissandoOpenness = max(glissandoOpenness, keyOpen[i]); + } + // Look up notes in the big table struct Fingering f = FingeredNote(keys); struct Fingering gf = FingeredNote(glissandoKeys); diff --git a/pipe.h b/pipe.h index 0f2ea0f..8203b7a 100644 --- a/pipe.h +++ b/pipe.h @@ -6,7 +6,7 @@ #include #include "tuning.h" -#define NUM_KEYS 12 +#define NUM_KEYS 8 enum Adjust { ADJUST_DOWN = -1, @@ -23,6 +23,7 @@ class Pipe { // keys are which keys are being pressed. uint16_t keys; uint16_t keysLast; + float keyOpen[NUM_KEYS]; // note holds the note being played, according to the fingering chart. Note note; From 8e39c5118fe0b7262b79247b75760788d05e879b Mon Sep 17 00:00:00 2001 From: Neale Pickett Date: Thu, 26 Nov 2020 18:49:09 -0700 Subject: [PATCH 14/16] Glissando working again --- main-play.h | 25 +++++++++++----------- main-setup.h | 8 ++++++- pipe.cpp | 60 +++++++++++++++++++++++++++------------------------- pipe.h | 27 +++++++++++------------ uilleann.ino | 24 ++++++--------------- 5 files changed, 71 insertions(+), 73 deletions(-) diff --git a/main-play.h b/main-play.h index ca34163..71944b5 100644 --- a/main-play.h +++ b/main-play.h @@ -19,21 +19,21 @@ void doPlay(bool forceDisplayUpdate) { display.display(); } - if (pipe.silent) { + if (pipe.Silent) { Chanter.NoteOff(); } else { // Calculate pitch, and glissando pitch - float pitch = tuning.GetPitch(pipe.note); - float glissandoPitch = tuning.GetPitch(pipe.glissandoNote); + float pitch = tuning.GetPitch(pipe.CurrentNote); + float glissandoPitch = tuning.GetPitch(pipe.GlissandoNote); // Bend pitch if fewer than 3 half steps away - if (abs(pipe.glissandoNote - pipe.note) < 3) { + if (abs(pipe.GlissandoNote - pipe.CurrentNote) < 3) { float diff = glissandoPitch - pitch; - pitch += diff * pipe.glissandoOpenness; + pitch = glissandoPitch - (diff * pipe.GlissandoPressure); } // Apply a low shelf filter if this is the alternate fingering - if (pipe.altFingering) { + if (pipe.AltFingering) { biquad1.setLowShelf(0, 2000, 0.2, 1); } else { biquad1.setHighShelf(0, 1000, 1.0, 1); @@ -47,17 +47,16 @@ void doPlay(bool forceDisplayUpdate) { } } - if (pipe.note != last_note) { + if (pipe.CurrentNote != last_note) { updateDisplay = true; } - diag("%d %f", pipe.keys, pipe.glissandoOpenness); #if 0 if (updateDisplay) { // Look up the note name - const char *note_name = NoteName(pipe.note); - if (pipe.silent) { - note_name = "--"; + const char *noteName = NoteName(pipe.CurrentNote); + if (pipe.Silent) { + noteName = "--"; updateDisplay = true; } @@ -65,10 +64,10 @@ void doPlay(bool forceDisplayUpdate) { display.setFont(&FreeSans9pt7b); display.setCursor(0, 16); - display.print(note_name); + display.print(noteName); display.display(); - last_note = pipe.note; + last_note = pipe.CurrentNote; } #endif } diff --git a/main-setup.h b/main-setup.h index 1e77f86..79d5ca8 100644 --- a/main-setup.h +++ b/main-setup.h @@ -36,7 +36,10 @@ void setupVolume() { break; case ADJUST_UP: case ADJUST_DOWN: - volume[i] += float(volAdjust)*0.02; + { + float vol = volume[i] + float(volAdjust)*0.02; + volume[i] = min(max(vol, 1.0), 0.0); + } break; default: break; @@ -143,6 +146,9 @@ void setupInfo() { display.print("FC-1"); display.setCursor(0, 24); display.print(buildDate); + display.setCursor(0, 0); + display.print("M:"); + display.print(AudioMemoryUsageMax()); } /** doSetup performs "setup mode" behavior for the pipe. diff --git a/pipe.cpp b/pipe.cpp index e5c82ec..97a3e75 100644 --- a/pipe.cpp +++ b/pipe.cpp @@ -8,7 +8,7 @@ #define GLISSANDO_STEPS (OPENVAL - CLOSEDVAL) Pipe::Pipe() { - keysLast = 0; + KeysLast = 0; } bool Pipe::Init() { @@ -17,11 +17,11 @@ bool Pipe::Init() { return false; } - // Proximity sensor - if (paj7620Init()) { + // Knee sensor + if (!kneeSensor.begin()) { return false; } - + // Bag button bagSensor.begin(); // This library takes the entire program out if you poll it 5-40 times without anything connected @@ -33,70 +33,72 @@ bool Pipe::Init() { void Pipe::Update() { uint8_t glissandoKeys = 0; - keysLast = keys; - keys = 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) { - bag = bagSensor.isPressed(); + Bag = bagSensor.isPressed(); } else { - bag = false; + Bag = false; } // 0x6c is actually 8 bytes, but all 8 are always the same... - paj7620ReadReg(0x6c, 1, &kneeClosedness); + KneeClosedness = 255 - kneeSensor.readRange(); for (int i = 0; i < NUM_KEYS; ++i) { - uint16_t val = max(capSensor.filteredData(i), CLOSEDVAL); - keyOpen[i] = ((val - CLOSEDVAL) / float(GLISSANDO_STEPS)); + uint16_t sensorReading = capSensor.filteredData(i); + uint16_t val = OPENVAL - min(max(sensorReading, CLOSEDVAL), OPENVAL); + KeyPressure[i] = val / 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 (keyOpen[i] < 1.0) { - glissandoOpenness = max(glissandoOpenness, keyOpen[i]); - bitSet(keys, i); + if (KeyPressure[i] > 0.0) { + bitSet(Keys, i); } - if (keyOpen[i] == 0.0) { + if (KeyPressure[i] == 1.0) { bitSet(glissandoKeys, i); } } // Compute glissando amount - glissandoOpenness = 0.0; + GlissandoPressure = 1.0; for (int i = 0; i < 8; ++i) { - glissandoOpenness = max(glissandoOpenness, keyOpen[i]); + if (KeyPressure[i] > 0) { + GlissandoPressure = min(GlissandoPressure, KeyPressure[i]); + } } // Look up notes in the big table - struct Fingering f = FingeredNote(keys); + struct Fingering f = FingeredNote(Keys); struct Fingering gf = FingeredNote(glissandoKeys); - note = f.note; - glissandoNote = gf.note; + CurrentNote = f.note; + GlissandoNote = gf.note; // Was the high bit set? That indicates "alternate fingering", which sounds different. - altFingering = f.alt; + AltFingering = f.alt; // If the bag is squished, jump up an octave // But only if the left thumb is down! - if (bag && (keys & bit(7))) { - note += NOTE_OCTAVE; - glissandoNote += NOTE_OCTAVE; + if (Bag && (Keys & bit(7))) { + CurrentNote += NOTE_OCTAVE; + GlissandoNote += NOTE_OCTAVE; } // All keys closed + knee = no sound - silent = ((kneeClosedness > 240) && (keys == 0xff)); + Silent = ((KneeClosedness > 240) && (Keys == 0xff)); } bool Pipe::Pressed(uint8_t key) { - return bitRead(keys, key); + return bitRead(Keys, key); } bool Pipe::JustPressed(uint8_t key) { - if (bitRead(keys, key)) { - return !bitRead(keysLast, key); + if (bitRead(Keys, key)) { + return !bitRead(KeysLast, key); } return false; } @@ -136,4 +138,4 @@ Adjust Pipe::ReadAdjust(uint8_t keyUp, uint8_t keyDown, uint16_t delay, uint16_t return ADJUST_DOWN; } return ADJUST_NONE; -} \ No newline at end of file +} diff --git a/pipe.h b/pipe.h index 8203b7a..6ef4955 100644 --- a/pipe.h +++ b/pipe.h @@ -1,12 +1,12 @@ #pragma once #include +#include #include -#include #include #include "tuning.h" -#define NUM_KEYS 8 +#define NUM_KEYS 12 enum Adjust { ADJUST_DOWN = -1, @@ -18,31 +18,31 @@ enum Adjust { class Pipe { public: // kneeClosedness indicates how "closed" the knee sensor is. 0 = wide open. - uint8_t kneeClosedness; + uint8_t KneeClosedness; // keys are which keys are being pressed. - uint16_t keys; - uint16_t keysLast; - float keyOpen[NUM_KEYS]; + uint16_t Keys; + uint16_t KeysLast; + float KeyPressure[NUM_KEYS]; // note holds the note being played, according to the fingering chart. - Note note; + Note CurrentNote; // glissandoNote is the note that would be played if partially open keys were fully open. - Note glissandoNote; + Note GlissandoNote; - // glissandoOpenness is how "open" the holes are in the direction of the glissandoNote. - float glissandoOpenness; + // glissandoPressure is how "closed" the holes are in the direction away from the glissandoNote. + float GlissandoPressure; // silent is true if all keys and the knee are closed. - bool silent; + bool Silent; // bag is true if the bag is being squished. - bool bag; + bool Bag; // altFingering is true if the "alternate fingering" is being played. // This should sound different than the standard fingering. - bool altFingering; + bool AltFingering; Pipe(); @@ -70,6 +70,7 @@ class Pipe { private: Adafruit_MPR121 capSensor; + Adafruit_VL6180X kneeSensor; QwiicButton bagSensor; bool bag_enabled; unsigned long nextRepeat[NUM_KEYS]; diff --git a/uilleann.ino b/uilleann.ino index b03e1c5..0c6c71c 100644 --- a/uilleann.ino +++ b/uilleann.ino @@ -1,10 +1,7 @@ #include -#include #include #include #include -#include -#include #include #include "patches.h" @@ -26,7 +23,7 @@ Adafruit_SSD1306 display(128, 32, &Wire, -1); // Settings uint8_t patch[4] = {0}; -float volume[4] = {0}; +float volume[5] = {0.75, 0.75, 0.75, 0.75, 0.3}; // Pipes #define NUM_DRONES 3 @@ -132,13 +129,6 @@ void diag(const char *fmt, ...) { #include "main-setup.h" void setup() { - // Initialize settings - // XXX: Read these from persistent storage later - for (int i = 0; i < 4; i++) { - patch[i] = 0; - volume[i] = 0.75; - } - // PREPARE TO BLINK pinMode(LED_BUILTIN, OUTPUT); digitalWrite(LED_BUILTIN, true); @@ -160,16 +150,16 @@ void setup() { diag("Pipe..."); while (!pipe.Init()) { - diag("No pipe. Is it connected?"); + diag("Pipe connected?"); blink(false); } diag("Audio..."); - AudioMemory(20); + AudioMemory(120); AudioProcessorUsageMaxReset(); AudioMemoryUsageMaxReset(); sgtl5000.enable(); - sgtl5000.volume(0.3); + sgtl5000.volume(volume[4]); diag("Synth..."); loadPatch(0); @@ -188,7 +178,7 @@ void setup() { for (int i = 0; i < NUM_DRONES; ++i) { mixDrones.gain(i, 1); } - biquad1.setBandpass(0, PITCH_CONCERT_A4, 1.0); + biquad1.setNotch(0, PITCH_CONCERT_A4, 0.001); diag("Drones..."); playDrones(); @@ -231,12 +221,12 @@ void loop() { // If we're infinitely (for the sensor) off the knee, // we might be in setup mode. - if (pipe.kneeClosedness == 0) { + if (pipe.KneeClosedness == 0) { // 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)) { + if (upSetting || (pipe.Keys == 0)) { doSetup(); upSetting = true; return; From 9745b18316c20213dd1e8931e93a2e5f6afb746b Mon Sep 17 00:00:00 2001 From: Neale Pickett Date: Thu, 26 Nov 2020 19:05:51 -0700 Subject: [PATCH 15/16] turn on white noise for debugging --- uilleann.ino | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/uilleann.ino b/uilleann.ino index 0c6c71c..02ef5bc 100644 --- a/uilleann.ino +++ b/uilleann.ino @@ -23,7 +23,7 @@ Adafruit_SSD1306 display(128, 32, &Wire, -1); // Settings uint8_t patch[4] = {0}; -float volume[5] = {0.75, 0.75, 0.75, 0.75, 0.3}; +float volume[5] = {0.8, 0.8, 0.8, 0.8, 0.5}; // Pipes #define NUM_DRONES 3 @@ -165,6 +165,7 @@ void setup() { loadPatch(0); loadPatch(1); loadPatch(2); + noise.amplitude(1.0); diag("Mixer..."); // Turn on all mixer channels From 34485f5a5772b8a2672d4c27666bdf7dbd9a130d Mon Sep 17 00:00:00 2001 From: Neale Pickett Date: Thu, 26 Nov 2020 19:30:19 -0700 Subject: [PATCH 16/16] Fix volume adjustment --- main-setup.h | 4 +--- uilleann.ino | 3 ++- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/main-setup.h b/main-setup.h index 79d5ca8..f4f869c 100644 --- a/main-setup.h +++ b/main-setup.h @@ -4,8 +4,6 @@ #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. @@ -38,7 +36,7 @@ void setupVolume() { case ADJUST_DOWN: { float vol = volume[i] + float(volAdjust)*0.02; - volume[i] = min(max(vol, 1.0), 0.0); + volume[i] = max(min(vol, 1.0), 0.0); } break; default: diff --git a/uilleann.ino b/uilleann.ino index 02ef5bc..e14b679 100644 --- a/uilleann.ino +++ b/uilleann.ino @@ -22,8 +22,9 @@ Tuning tuning = Tuning(NOTE_D4, PITCH_CONCERT_D4, TUNINGSYSTEM_JUST); Adafruit_SSD1306 display(128, 32, &Wire, -1); // Settings +#define VOLUME_INITIAL 0.8 uint8_t patch[4] = {0}; -float volume[5] = {0.8, 0.8, 0.8, 0.8, 0.5}; +float volume[5] = {VOLUME_INITIAL, VOLUME_INITIAL, VOLUME_INITIAL, VOLUME_INITIAL, 0.5}; // Pipes #define NUM_DRONES 3