commit 96fc240f93d7f4f2adb4bc17be169484760f1b3b Author: Neale Pickett Date: Sun Oct 11 20:29:04 2020 -0600 Initial commit: works diff --git a/README.md b/README.md new file mode 100644 index 0000000..06d5e8d --- /dev/null +++ b/README.md @@ -0,0 +1,6 @@ +It's a Uilleann bagpipe with a 4-operator FM synthesizer behind it. +Right now it uses an Adafrut M4 Trellis, +but it should probably be retooled to use a Teensy with the +audio shield on it. + +I'll write some stuff up later if people show interest. diff --git a/fingering.h b/fingering.h new file mode 100644 index 0000000..3eba550 --- /dev/null +++ b/fingering.h @@ -0,0 +1,75 @@ +#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 + +uint8_t uilleann_matrix[] = { + // Open Back D + NOTE_CS5, NOTE_CS5, NOTE_CS5, 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.. + NOTE_D5, NOTE_D5, NOTE_D5, NOTE_D5|P, // XXX XO.. + DDDD, // XXX XX.. + + // Closed Back D + CCCC, // OOO OO... + NOTE_CS5, NOTE_CS5, NOTE_CS5, NOTE_CS5|P, // OOO OX.. + CCCC, // OOO XO.. + CCCC, // OOO XX.. + CCCC, // OOX OO.. + NOTE_CS5, NOTE_CS5|P, NOTE_CS5, NOTE_CS5|P, // OOX OX.. + CCCC, // OOX XO.. + CCCC, // OOX XX.. + CCCC, // OXO OO.. + NOTE_CS5, NOTE_CS5|P, NOTE_CS5, NOTE_CS5|P, // 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|P, NOTE_FS4|P, NOTE_F4, NOTE_FS4, // XXX XO.. + NOTE_E4, NOTE_E4|P, NOTE_DS4, NOTE_D4, // XXX XX.. +}; diff --git a/notes.h b/notes.h new file mode 100644 index 0000000..6a0b0fe --- /dev/null +++ b/notes.h @@ -0,0 +1,18 @@ +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; + +const char *NoteNames[] { + "C ", "C#", "D ", "Eb", "E ", "F ", "F#", "G ", "Ab", "A ", "Bb", "B ", +}; + +#define PITCH_D4 293.66 diff --git a/patches.h b/patches.h new file mode 100644 index 0000000..53f8264 --- /dev/null +++ b/patches.h @@ -0,0 +1,50 @@ +typedef struct Operator { + float gain; + float delay; + float attack; + float hold; + float decay; + float sustain; + float release; + float baseFrequency; + float multiplier; +} Operator; + +typedef struct Patch { + char *name; + Operator operators[4]; + float feedback; +} Patch; + +Patch Bank[] = { + { + "Venus Oboe", + { + {1.0, 0, 10.5, 0, 5000, 0.75, 100, 0, 1.00}, + {1.0, 0, 10.5, 0, 2000, 0.80, 100, 0, 4.00}, + {0.0, 0, 10.5, 0, 2000, 0.50, 100, 0, 8.00}, + {0.0, 0, 50.0, 0, 800, 0.75, 100, 0, 16.00}, + }, + 0.0, + }, + { + "IWantPizza", + { + {1.0, 0, 10.5, 0, 5000, 0.35, 100, 0, 4.00}, + {1.0, 0, 10.5, 0, 2000, 0.30, 100, 0, 1.00}, + {1.0, 0, 10.5, 0, 2000, 0.50, 100, 0, 8.00}, + {1.0, 0, 200, 0, 800, 0.25, 100, 0, 16.00}, + }, + 0.0, + }, + { + "Ray Gun", + { + {1.0, 0, 10.5, 0, 5000, 0.35, 2000, 0, 1.00}, + {1.0, 0, 10.5, 0, 2000, 0.30, 2000, 0, 1.00}, + {1.0, 0, 10.5, 0, 2000, 0.00, 2000, 0, 9.00}, + {1.0, 0, 200, 0, 800, 0.25, 800, 0, 1.00}, + }, + 0.0, + }, +}; diff --git a/synth.h b/synth.h new file mode 100644 index 0000000..0ac7068 --- /dev/null +++ b/synth.h @@ -0,0 +1,38 @@ +#include +#include +#include +#include + +// GUItool: begin automatically generated code +AudioMixer4 feedback; //xy=110,37 +AudioSynthWaveformSineModulated osc4; //xy=112,98 +AudioSynthWaveformSineModulated osc2; //xy=112,194 +AudioSynthWaveformSineModulated osc1; //xy=112,245 +AudioSynthWaveformSineModulated osc3; //xy=113,146 +AudioMixer4 mixOp; //xy=114,418 +AudioEffectEnvelope env4; //xy=251,97 +AudioEffectEnvelope env3; //xy=251,146 +AudioEffectEnvelope env2; //xy=252,194 +AudioEffectEnvelope env1; //xy=252,245 +AudioFilterBiquad biquad1; //xy=257,418 +AudioMixer4 mixL; //xy=472,402 +AudioMixer4 mixR; //xy=473,498 +AudioOutputAnalogStereo dacs1; //xy=724,452 +AudioConnection patchCord1(feedback, osc4); +AudioConnection patchCord2(osc4, env4); +AudioConnection patchCord3(osc4, 0, feedback, 0); +AudioConnection patchCord4(osc2, env2); +AudioConnection patchCord5(osc1, env1); +AudioConnection patchCord6(osc3, env3); +AudioConnection patchCord7(mixOp, biquad1); +AudioConnection patchCord8(env4, osc3); +AudioConnection patchCord9(env4, 0, mixOp, 3); +AudioConnection patchCord10(env3, 0, mixOp, 2); +AudioConnection patchCord11(env2, osc1); +AudioConnection patchCord12(env2, 0, mixOp, 1); +AudioConnection patchCord13(env1, 0, mixOp, 0); +AudioConnection patchCord14(biquad1, 0, mixL, 0); +AudioConnection patchCord15(biquad1, 0, mixR, 0); +AudioConnection patchCord17(mixL, 0, dacs1, 0); +AudioConnection patchCord18(mixR, 0, dacs1, 1); +// GUItool: end automatically generated code diff --git a/synth.ino b/synth.ino new file mode 100644 index 0000000..dd00ddd --- /dev/null +++ b/synth.ino @@ -0,0 +1,281 @@ +#include +#include +#include +#include +#include +#include "synth.h" +#include "patches.h" +#include "notes.h" +#include "fingering.h" + +#define KNEE_OFFSET 0 +#define KEY_OFFSET 2 + +float cmaj_low[8] = { 130.81, 146.83, 164.81, 174.61, 196.00, 220.00, 246.94, 261.63 }; +float cmaj_high[8] = { 261.6, 293.7, 329.6, 349.2, 392.0, 440.0, 493.9, 523.3 }; + +AudioEffectEnvelope *envs[] = {&env1, &env2, &env3, &env4}; +AudioSynthWaveformSineModulated *oscs[] = {&osc1, &osc2, &osc3, &osc4}; + +int currentPatch = 0; + +Adafruit_MPR121 cap = Adafruit_MPR121(); +Adafruit_NeoTrellisM4 trellis = Adafruit_NeoTrellisM4(); +MicroOLED oled(9, 1); + +// Hat tip to Kyle Gann +// https://www.kylegann.com/tuning.html +float JustPitches[MaxNote + 1]; +void setupJustPitches(uint8_t baseNote, float basePitch) { + JustPitches[baseNote + 0] = basePitch * 1 / 1; + JustPitches[baseNote + 1] = basePitch * 16 / 15; + JustPitches[baseNote + 2] = basePitch * 9 / 8; + JustPitches[baseNote + 3] = basePitch * 6 / 5; + JustPitches[baseNote + 4] = basePitch * 5 / 4; + JustPitches[baseNote + 5] = basePitch * 4 / 3; + JustPitches[baseNote + 6] = basePitch * 45 / 32; + JustPitches[baseNote + 7] = basePitch * 3 / 2; + JustPitches[baseNote + 8] = basePitch * 8 / 5; + JustPitches[baseNote + 9] = basePitch * 5 / 3; + JustPitches[baseNote + 10] = basePitch * 9 / 5; + JustPitches[baseNote + 11] = basePitch * 15 / 8; + + // 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; + } + } + } +} + +void loadPatch(Patch p) { + oled.clear(PAGE); + oled.setFontType(0); + oled.setCursor(0, 0); + oled.print(p.name); + oled.display(); + + for (int i=0; i<4; i++) { + Operator op = p.operators[i]; + + oscs[i]->amplitude(op.gain); + envs[i]->delay(op.delay); + envs[i]->attack(op.attack); + envs[i]->hold(op.hold); + envs[i]->decay(op.decay); + envs[i]->sustain(op.sustain); + envs[i]->release(op.release); + } + feedback.gain(0, p.feedback); +} + +void setup(){ + setupJustPitches(NOTE_D4, PITCH_D4); + pinMode(LED_BUILTIN, OUTPUT); + + // Wire.begin needs a moment + delay(100); + Wire.begin(); + + // Initialize OLED display + oled.begin(); + oled.clear(ALL); + oled.display(); + + // Initialize touch sensor + if (!cap.begin(0x5A)) { + oled.clear(PAGE); + oled.print("Can't find MPR121"); + oled.display(); + + bool on = HIGH; + while (1) { + digitalWrite(LED_BUILTIN, on); + on = ! on; + delay(200); + } + } + + // Initialize the Trellis + trellis.begin(); + trellis.setBrightness(20); + + AudioMemory(120); + + // Turn it down, man + mixL.gain(0, 0.05); + mixR.gain(0, 0.05); + + // Set up high shelf filter, for vibrato effects + biquad1.setHighShelf(0, 1100, 1.0, 1); + + // load the patch + loadPatch(Bank[0]); + + // Initialize processor and memory measurements + AudioProcessorUsageMaxReset(); + AudioMemoryUsageMaxReset(); +} + +void wtfSetPitch(float freq) { + for (int i=0; i<4; i++) { + Operator op = Bank[currentPatch].operators[i]; + oscs[i]->frequency(op.baseFrequency + freq*op.multiplier); + } +} + +void setPitch(float freq) { + for (int i=0; i<4; i++) { + Operator op = Bank[currentPatch].operators[i]; + oscs[i]->frequency(op.baseFrequency + freq*op.multiplier); + } +} + +void noteOn(float freq) { + AudioNoInterrupts(); + for (int i=0; i<4; i++) { + Operator op = Bank[currentPatch].operators[i]; + oscs[i]->frequency(op.baseFrequency + freq*op.multiplier); + envs[i]->noteOn(); + } + AudioInterrupts(); +} + +void noteOff() { + AudioNoInterrupts(); + for (int i=0; i<4; i++) { + envs[i]->noteOff(); + } + AudioInterrupts(); +} + +void trellisLoop() { + trellis.tick(); + + while (trellis.available()) { + keypadEvent e = trellis.read(); + int keyindex = e.bit.KEY; + if (e.bit.EVENT == KEY_JUST_PRESSED) { + trellis.setPixelColor(keyindex, 0x600000); + noteOn(JustPitches[NOTE_D3 + keyindex]); + } else if (e.bit.EVENT == KEY_JUST_RELEASED){ + noteOff(); + trellis.setPixelColor(keyindex, 0); + } + } +} + +#define CLOSEDVAL 0x30 +#define OPENVAL 0x70 + +bool playing = false; + +void loop() { + uint8_t keys = 0; + uint8_t note; + uint8_t gkeys = 0; + uint8_t gnote; + uint8_t gaffinity = 0; + bool knee = true; + bool bag = false; + bool silent = false; + + trellisLoop(); + + oled.clear(PAGE); + for (int i = 0; i < 8; i++) { + uint16_t val = cap.filteredData(i+KEY_OFFSET); + uint8_t c = 0; + if (val < OPENVAL) { + bitSet(keys, i); + c = 7; + + // If they're just sort of touching it, we're doing a glissando! + if (val > CLOSEDVAL) { + int aff = val - CLOSEDVAL; + + gaffinity = max(gaffinity, aff); + c = 7 - (7 * aff / (OPENVAL - CLOSEDVAL)); + } else { + bitSet(gkeys, i); + } + } + // print key states + oled.rectFill(32 - 4*i, 40, 3, c); + } + + note = uilleann_matrix[keys]; + gnote = uilleann_matrix[gkeys]; + + knee = cap.filteredData(KNEE_OFFSET) < CLOSEDVAL; + bool alt = note & 0x80; + bool galt = gnote & 0x80; + note = note & 0x7f; + gnote = gnote & 0x7f; + + // All keys closed + knee = no sound + if (knee) { + oled.rectFill(36, 42, 3, 3); + if (keys == 0xff) { + silent = true; + } + } + + // Jump octave if the bag is squished + //bag = !digitalRead(BAG); + oled.setCursor(9*6, 10); + if (bag) { + oled.print("^"); + if (keys & bit(7)) { + note += 12; + gnote += 12; + } + } else { + oled.print(" "); + } + + if (silent) { + noteOff(); + playing = false; + } else { + // Calculate pitch, and glissando pitch + uint16_t pitch = JustPitches[note]; + uint16_t gpitch = JustPitches[gnote]; + + if (alt) { + biquad1.setHighShelf(0, 1000, 0.5, 1); + } else { + biquad1.setHighShelf(0, 1000, 1.0, 1); + } + + // Bend pitch + if (gaffinity && (abs(gnote - note) < 3)) { + uint32_t sum = (pitch * (OPENVAL-CLOSEDVAL)) + (gpitch * gaffinity); + pitch = sum / ((OPENVAL-CLOSEDVAL) + gaffinity); + } + + if (playing) { + setPitch(pitch); + } else { + noteOn(pitch); + } + playing = true; + } + + // Print status + oled.setCursor(0*6, 20); + oled.print(NoteNames[note % 12]); + oled.print(alt?".":" "); + + //oled.display(); +}