2020-10-11 20:29:04 -06:00
|
|
|
#include <Audio.h>
|
|
|
|
#include <Wire.h>
|
2020-11-11 16:29:09 -07:00
|
|
|
#include <Adafruit_GFX.h>
|
|
|
|
#include <Adafruit_SSD1306.h>
|
2020-10-18 13:27:32 -06:00
|
|
|
#include <SparkFun_Qwiic_Button.h>
|
2020-10-11 20:29:04 -06:00
|
|
|
#include <Adafruit_MPR121.h>
|
2020-11-11 16:29:09 -07:00
|
|
|
#include <paj7620.h>
|
2020-11-23 18:11:19 -07:00
|
|
|
#include <Fonts/FreeSans9pt7b.h>
|
2020-10-11 20:29:04 -06:00
|
|
|
#include "synth.h"
|
|
|
|
#include "patches.h"
|
|
|
|
#include "notes.h"
|
2020-11-11 20:20:21 -07:00
|
|
|
#include "pipe.h"
|
2020-10-11 20:29:04 -06:00
|
|
|
|
2020-11-15 14:49:05 -07:00
|
|
|
#if defined(ADAFRUIT_TRELLIS_M4_EXPRESS)
|
|
|
|
#include <Adafruit_NeoTrellisM4.h>
|
2020-11-22 17:21:47 -07:00
|
|
|
Adafruit_NeoTrellisM4 trellis;// = Adafruit_NeoTrellisM4();
|
2020-11-15 14:49:05 -07:00
|
|
|
#endif
|
|
|
|
|
2020-11-11 16:29:09 -07:00
|
|
|
#define DRONES
|
2020-11-15 14:49:05 -07:00
|
|
|
#define DEBUG false
|
2020-11-11 20:20:21 -07:00
|
|
|
|
|
|
|
Pipe pipe;
|
|
|
|
Adafruit_SSD1306 display(128, 32, &Wire, -1);
|
|
|
|
int currentPatch = 0;
|
2020-10-11 20:29:04 -06:00
|
|
|
|
2020-10-24 20:15:18 -06:00
|
|
|
FMVoice Chanter;
|
|
|
|
FMVoice Drones[3];
|
|
|
|
FMVoice Regulators[3];
|
|
|
|
|
2020-11-11 16:29:09 -07:00
|
|
|
AudioFilterBiquad biquad1;
|
|
|
|
AudioMixer4 mixDrones;
|
|
|
|
AudioMixer4 mixRegulators;
|
|
|
|
AudioMixer4 mixL;
|
|
|
|
AudioMixer4 mixR;
|
|
|
|
AudioSynthNoiseWhite noise;
|
2020-10-25 11:36:58 -06:00
|
|
|
|
2020-11-15 14:49:05 -07:00
|
|
|
#if defined(ADAFRUIT_TRELLIS_M4_EXPRESS)
|
|
|
|
AudioOutputAnalogStereo out1;
|
|
|
|
#else
|
|
|
|
AudioOutputI2S out1;
|
|
|
|
#endif
|
|
|
|
|
2020-11-22 17:21:47 -07:00
|
|
|
AudioControlSGTL5000 sgtl5000;
|
|
|
|
|
2020-10-24 20:15:18 -06:00
|
|
|
AudioConnection FMVoicePatchCords[] = {
|
2020-11-11 16:29:09 -07:00
|
|
|
//{0, 0, 0, 0}, // For some reason, the first one is ignored
|
|
|
|
|
2020-11-15 14:49:05 -07:00
|
|
|
// {noise, 0, mixDrones, 3},
|
2020-11-11 16:29:09 -07:00
|
|
|
{noise, 0, mixL, 3},
|
|
|
|
{noise, 0, mixR, 3},
|
2020-10-25 11:36:58 -06:00
|
|
|
|
2020-10-24 20:15:18 -06:00
|
|
|
{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},
|
|
|
|
|
|
|
|
{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},
|
|
|
|
|
2020-11-11 16:29:09 -07:00
|
|
|
{mixL, 0, out1, 0},
|
|
|
|
{mixR, 0, out1, 1},
|
2020-10-24 20:15:18 -06:00
|
|
|
|
|
|
|
FMVoiceWiring(Chanter),
|
|
|
|
FMVoiceWiring(Drones[0]),
|
|
|
|
FMVoiceWiring(Drones[1]),
|
|
|
|
FMVoiceWiring(Drones[2]),
|
|
|
|
FMVoiceWiring(Regulators[0]),
|
|
|
|
FMVoiceWiring(Regulators[1]),
|
|
|
|
FMVoiceWiring(Regulators[2]),
|
|
|
|
};
|
2020-10-11 20:29:04 -06:00
|
|
|
|
2020-11-11 16:29:09 -07:00
|
|
|
void blink(bool forever) {
|
|
|
|
for (;;) {
|
|
|
|
digitalWrite(LED_BUILTIN, true);
|
|
|
|
delay(200);
|
|
|
|
digitalWrite(LED_BUILTIN, false);
|
|
|
|
delay(200);
|
|
|
|
if (! forever) break;
|
|
|
|
}
|
|
|
|
}
|
2020-10-11 20:29:04 -06:00
|
|
|
|
2020-10-24 20:15:18 -06:00
|
|
|
void setup() {
|
2020-11-15 14:49:05 -07:00
|
|
|
// Wire.begin needs a moment, so let's do some math.
|
2020-11-11 16:29:09 -07:00
|
|
|
setupJustPitches(NOTE_D4, PITCH_D4);
|
2020-10-11 20:29:04 -06:00
|
|
|
|
|
|
|
Wire.begin();
|
|
|
|
|
2020-11-15 14:49:05 -07:00
|
|
|
// PREPARE TO BLINK
|
|
|
|
pinMode(LED_BUILTIN, OUTPUT);
|
|
|
|
digitalWrite(LED_BUILTIN, true);
|
|
|
|
|
2020-11-11 20:20:21 -07:00
|
|
|
// Initialize display
|
2020-11-11 16:29:09 -07:00
|
|
|
if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3c)) {
|
|
|
|
blink(true);
|
|
|
|
}
|
|
|
|
display.clearDisplay();
|
|
|
|
display.setTextSize(1);
|
|
|
|
display.setTextColor(SSD1306_WHITE);
|
|
|
|
display.print("Starting");
|
|
|
|
display.display();
|
2020-10-18 09:57:06 -06:00
|
|
|
|
2020-11-15 14:49:05 -07:00
|
|
|
#if defined(ADAFRUIT_TRELLIS_M4_EXPRESS)
|
|
|
|
trellis.begin();
|
|
|
|
#endif
|
|
|
|
|
2020-11-11 20:20:21 -07:00
|
|
|
while (!pipe.Init()) {
|
|
|
|
display.clearDisplay();
|
2020-11-11 16:29:09 -07:00
|
|
|
display.setCursor(0, 0);
|
|
|
|
display.print("Pipe?");
|
|
|
|
display.display();
|
|
|
|
blink(false);
|
2020-10-11 20:29:04 -06:00
|
|
|
}
|
|
|
|
|
2020-10-18 09:57:06 -06:00
|
|
|
// Set aside some memory for the audio library
|
2020-11-11 20:20:21 -07:00
|
|
|
AudioMemory(20);
|
2020-10-11 20:29:04 -06:00
|
|
|
|
2020-11-22 17:21:47 -07:00
|
|
|
// Set up the SGTL5000 using I2C
|
|
|
|
sgtl5000.enable();
|
|
|
|
sgtl5000.volume(0.3);
|
|
|
|
|
2020-10-18 09:57:06 -06:00
|
|
|
// initialize tunables
|
|
|
|
updateTunables(3, 0);
|
2020-10-11 20:29:04 -06:00
|
|
|
|
|
|
|
// Initialize processor and memory measurements
|
|
|
|
AudioProcessorUsageMaxReset();
|
|
|
|
AudioMemoryUsageMaxReset();
|
2020-10-25 11:36:58 -06:00
|
|
|
|
2020-10-25 19:20:18 -06:00
|
|
|
// Turn on drones
|
|
|
|
for (int i=0; i<3; i++) {
|
2020-11-11 17:05:28 -07:00
|
|
|
Drones[i].LoadPatch(&Bank[0]);
|
|
|
|
Drones[i].NoteOn(JustPitches[NOTE_D4 - 12*i] + i);
|
2020-10-25 19:20:18 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
// Turn on all mixer channels
|
|
|
|
for (int i=0; i<4; i++) {
|
|
|
|
mixL.gain(i, 0.5);
|
|
|
|
mixR.gain(i, 0.6);
|
|
|
|
}
|
|
|
|
|
2020-10-25 11:36:58 -06:00
|
|
|
#ifdef DEBUG
|
2020-11-11 16:29:09 -07:00
|
|
|
noise.amplitude(0.1);
|
2020-10-25 11:36:58 -06:00
|
|
|
mixL.gain(3, 0.1);
|
|
|
|
mixR.gain(3, 0.1);
|
|
|
|
#endif
|
2020-11-11 20:20:21 -07:00
|
|
|
|
|
|
|
display.clearDisplay();
|
|
|
|
display.setCursor(0, 0);
|
|
|
|
display.print("Done!");
|
|
|
|
display.display();
|
2020-10-11 20:29:04 -06:00
|
|
|
}
|
|
|
|
|
2020-10-18 09:57:06 -06:00
|
|
|
#define INIT_PITCH_ADJUST 0
|
2020-10-25 19:20:18 -06:00
|
|
|
#define INIT_GAIN 0.7
|
2020-10-18 09:57:06 -06:00
|
|
|
#define INIT_PATCH 0
|
|
|
|
|
|
|
|
int16_t pitchAdjust;
|
2020-10-25 19:20:18 -06:00
|
|
|
float chanterGain;
|
2020-10-18 09:57:06 -06:00
|
|
|
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:
|
2020-10-17 15:25:19 -06:00
|
|
|
pitchAdjust -= 4;
|
2020-10-18 09:57:06 -06:00
|
|
|
break;
|
2020-10-17 15:25:19 -06:00
|
|
|
}
|
2020-10-18 09:57:06 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
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:
|
2020-10-25 19:20:18 -06:00
|
|
|
chanterGain = INIT_GAIN;
|
2020-10-18 09:57:06 -06:00
|
|
|
break;
|
|
|
|
case 2:
|
2020-10-25 19:20:18 -06:00
|
|
|
chanterGain = min(chanterGain+0.005, 1.0);
|
2020-10-18 09:57:06 -06:00
|
|
|
break;
|
|
|
|
case 1:
|
2020-10-25 19:20:18 -06:00
|
|
|
chanterGain = max(chanterGain-0.005, 0.0);
|
2020-10-18 09:57:06 -06:00
|
|
|
break;
|
2020-10-17 15:25:19 -06:00
|
|
|
}
|
2020-10-18 09:57:06 -06:00
|
|
|
}
|
2020-10-25 11:36:58 -06:00
|
|
|
for (int i=0; i<3; i++) {
|
2020-10-25 19:20:18 -06:00
|
|
|
mixL.gain(i, chanterGain);
|
|
|
|
mixR.gain(i, chanterGain);
|
2020-10-25 11:36:58 -06:00
|
|
|
}
|
2020-10-18 09:57:06 -06:00
|
|
|
|
|
|
|
if (!note || (note == NOTE_CS5)) {
|
|
|
|
if (buttons == 3) {
|
|
|
|
patch = INIT_PATCH;
|
2020-10-11 20:29:04 -06:00
|
|
|
}
|
2020-10-17 15:25:19 -06:00
|
|
|
|
2020-10-18 09:57:06 -06:00
|
|
|
// wrap
|
|
|
|
int bankSize = sizeof(Bank) / sizeof(Bank[0]);
|
|
|
|
patch = (patch + bankSize) % bankSize;
|
|
|
|
|
2020-10-24 20:15:18 -06:00
|
|
|
FMPatch *p = &Bank[patch];
|
2020-11-11 17:05:28 -07:00
|
|
|
Chanter.LoadPatch(p);
|
2020-10-11 20:29:04 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void loop() {
|
2020-11-11 20:20:21 -07:00
|
|
|
pipe.Update();
|
2020-11-15 14:49:05 -07:00
|
|
|
|
|
|
|
#if defined(ADAFRUIT_TRELLIS_M4_EXPRESS)
|
|
|
|
trellis.tick();
|
|
|
|
|
2020-11-22 17:21:47 -07:00
|
|
|
trellis.setPixelColor(1, trellis.ColorHSV(millis(), 255, 120));
|
|
|
|
trellis.setPixelColor(0, trellis.ColorHSV(64*pipe.kneeClosedness, 255, 120));
|
2020-11-15 14:49:05 -07:00
|
|
|
#endif
|
2020-11-22 17:21:47 -07:00
|
|
|
|
2020-11-23 18:11:19 -07:00
|
|
|
// If we're infinitely (for the sensor) off the knee,
|
|
|
|
// go into setup mode!
|
|
|
|
if (pipe.kneeClosedness == 0) {
|
|
|
|
doSetup();
|
|
|
|
} else {
|
|
|
|
doPlay();
|
2020-11-15 14:49:05 -07:00
|
|
|
}
|
2020-11-23 18:11:19 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
/** 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();
|
2020-11-11 16:29:09 -07:00
|
|
|
|
2020-11-23 18:11:19 -07:00
|
|
|
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");
|
|
|
|
}
|
|
|
|
|
|
|
|
display.display();
|
2020-11-11 20:20:21 -07:00
|
|
|
}
|
2020-10-11 20:29:04 -06:00
|
|
|
|
2020-11-23 18:11:19 -07:00
|
|
|
void doPlay() {
|
|
|
|
static uint8_t last_note = 0;
|
|
|
|
bool updateDisplay = false;
|
|
|
|
|
2020-11-11 20:20:21 -07:00
|
|
|
if (pipe.silent) {
|
2020-11-11 17:05:28 -07:00
|
|
|
Chanter.NoteOff();
|
2020-10-11 20:29:04 -06:00
|
|
|
} else {
|
|
|
|
// Calculate pitch, and glissando pitch
|
2020-11-11 20:20:21 -07:00
|
|
|
uint16_t pitch = JustPitches[pipe.note];
|
|
|
|
uint16_t glissandoPitch = JustPitches[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;
|
|
|
|
}
|
2020-10-11 20:29:04 -06:00
|
|
|
|
2020-11-11 20:20:21 -07:00
|
|
|
// Apply a low shelf filter if this is the alternate fingering
|
|
|
|
if (pipe.altFingering) {
|
2020-10-18 12:35:50 -06:00
|
|
|
biquad1.setLowShelf(0, 2000, 0.2, 1);
|
2020-10-11 20:29:04 -06:00
|
|
|
} else {
|
|
|
|
biquad1.setHighShelf(0, 1000, 1.0, 1);
|
|
|
|
}
|
|
|
|
|
2020-11-11 20:20:21 -07:00
|
|
|
// We've figured out what pitch to play, now we can play it.
|
2020-10-25 19:20:18 -06:00
|
|
|
if (Chanter.playing) {
|
2020-11-11 17:05:28 -07:00
|
|
|
Chanter.SetPitch(pitch);
|
2020-10-11 20:29:04 -06:00
|
|
|
} else {
|
2020-11-11 17:05:28 -07:00
|
|
|
Chanter.NoteOn(pitch);
|
2020-10-11 20:29:04 -06:00
|
|
|
}
|
|
|
|
}
|
2020-11-11 16:29:09 -07:00
|
|
|
|
2020-11-23 18:11:19 -07:00
|
|
|
// Look up the note name
|
2020-11-11 20:20:21 -07:00
|
|
|
const char *note_name = NoteNames[pipe.note % 12];
|
|
|
|
if (pipe.silent) {
|
|
|
|
note_name = "--";
|
|
|
|
updateDisplay = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (pipe.note != last_note) {
|
|
|
|
updateDisplay = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (updateDisplay) {
|
2020-11-11 16:29:09 -07:00
|
|
|
display.clearDisplay();
|
2020-11-23 18:11:19 -07:00
|
|
|
|
|
|
|
display.setCursor(0, 16);
|
2020-11-11 20:20:21 -07:00
|
|
|
display.setTextSize(2);
|
|
|
|
display.print(Chanter.patch->name);
|
2020-11-23 18:11:19 -07:00
|
|
|
|
|
|
|
display.setCursor(0, 0);
|
|
|
|
display.setTextSize(2);
|
|
|
|
display.print(note_name);
|
|
|
|
|
|
|
|
display.setCursor(40, 0);
|
|
|
|
display.setTextSize(1);
|
|
|
|
display.print(pipe.kneeClosedness);
|
2020-11-11 20:20:21 -07:00
|
|
|
|
2020-11-11 16:29:09 -07:00
|
|
|
display.display();
|
2020-11-22 17:21:47 -07:00
|
|
|
last_note = pipe.note;
|
2020-11-11 16:29:09 -07:00
|
|
|
}
|
2020-10-11 20:29:04 -06:00
|
|
|
}
|