Playing now, with a crash somewhere

This commit is contained in:
Neale Pickett 2020-11-25 17:13:54 -07:00
parent f7f2bc3cfe
commit 13c72dae2a
11 changed files with 444 additions and 242 deletions

View File

@ -11,14 +11,14 @@ struct Fingering {
#define P(note) \ #define P(note) \
{ note, true } { note, true }
#define CCCC n(NOTE_CS5), n(NOTE_CS5), n(NOTE_CS5), n(NOTE_CS5) #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 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 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) #define DDDD n(NOTE_D5), n(NOTE_D5), n(NOTE_D5), n(NOTE_D5)
struct Fingering uilleann_matrix[] = { struct Fingering uilleann_matrix[] = {
// Open Back D // 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.. CCDD, // OOO OX..
CDCD, // OOO XO.. CDCD, // OOO XO..
DDDD, // OOO XX.. DDDD, // OOO XX..
@ -53,24 +53,24 @@ struct Fingering uilleann_matrix[] = {
// Closed Back D // Closed Back D
CCCC, // OOO OO... 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 XO..
CCCC, // OOO XX.. CCCC, // OOO XX..
CCCC, // OOX OO.. 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 XO..
CCCC, // OOX XX.. CCCC, // OOX XX..
CCCC, // OXO OO.. 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 XO..
CCCC, // OXO XX.. CCCC, // OXO XX..
P(NOTE_C5), P(NOTE_C5), P(NOTE_C5), P(NOTE_C5), // OXX OO.. 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), 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), 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.. 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.. 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.. 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 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 OX..
@ -78,10 +78,14 @@ struct Fingering uilleann_matrix[] = {
P(NOTE_B4), P(NOTE_B4), P(NOTE_B4), P(NOTE_B4), // XOX XX.. 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.. 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.. 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.. 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.. 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.. 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_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_E4), P(NOTE_E4), n(NOTE_Ds4), n(NOTE_D4), // XXX XX..
}; };
inline Fingering FingeredNote(uint16_t keys) {
return uilleann_matrix[keys & 0xff];
}

71
main-play.h Normal file
View File

@ -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");
}

188
main-setup.h Normal file
View File

@ -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();
}

View File

@ -61,3 +61,5 @@ FMPatch Bank[] = {
}, },
}, },
}; };
const int PATCH_MAX = sizeof(Bank) / sizeof(Bank[0]);

View File

@ -2,8 +2,6 @@
#include "fingering.h" #include "fingering.h"
#include "tuning.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 CLOSEDVAL 0x30
#define OPENVAL 0x70 #define OPENVAL 0x70
@ -35,6 +33,9 @@ bool Pipe::Init() {
void Pipe::Update() { void Pipe::Update() {
uint8_t glissandoKeys = 0; uint8_t glissandoKeys = 0;
keysLast = keys;
keys = 0;
// Read the bag state, if there's a bag. // 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 there isn't a bag, don't try, or this library will crash the program.
if (bag_enabled) { if (bag_enabled) {
@ -46,11 +47,8 @@ void Pipe::Update() {
// 0x6c is actually 8 bytes, but all 8 are always the same... // 0x6c is actually 8 bytes, but all 8 are always the same...
paj7620ReadReg(0x6c, 1, &kneeClosedness); paj7620ReadReg(0x6c, 1, &kneeClosedness);
keysLast = keys; for (int i = 0; i < NUM_KEYS; i++) {
keys = 0; uint16_t val = max(capSensor.filteredData(i), CLOSEDVAL);
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)); float openness = ((val - CLOSEDVAL) / float(GLISSANDO_STEPS));
// keys = all keys which are at least touched // keys = all keys which are at least touched
@ -66,8 +64,8 @@ void Pipe::Update() {
} }
// Look up notes in the big table // Look up notes in the big table
struct Fingering f = uilleann_matrix[keys]; struct Fingering f = FingeredNote(keys);
struct Fingering gf = uilleann_matrix[glissandoKeys]; struct Fingering gf = FingeredNote(glissandoKeys);
note = f.note; note = f.note;
glissandoNote = gf.note; glissandoNote = gf.note;
@ -96,3 +94,40 @@ bool Pipe::JustPressed(uint8_t key) {
} }
return false; 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;
}

21
pipe.h
View File

@ -6,14 +6,23 @@
#include <stdint.h> #include <stdint.h>
#include "tuning.h" #include "tuning.h"
#define NUM_KEYS 12
enum Adjust {
ADJUST_DOWN = -1,
ADJUST_NONE = 0,
ADJUST_UP = 1,
ADJUST_BOTH,
};
class Pipe { class Pipe {
public: public:
// kneeClosedness indicates how "closed" the knee sensor is. 0 = wide open. // kneeClosedness indicates how "closed" the knee sensor is. 0 = wide open.
uint8_t kneeClosedness; uint8_t kneeClosedness;
// keys are which keys are being pressed. // keys are which keys are being pressed.
uint8_t keys; uint16_t keys;
uint8_t keysLast; uint16_t keysLast;
// note holds the note being played, according to the fingering chart. // note holds the note being played, according to the fingering chart.
Note note; Note note;
@ -52,8 +61,16 @@ class Pipe {
// JustPressed returns whether the given key was just pressed. // JustPressed returns whether the given key was just pressed.
bool JustPressed(uint8_t key); 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: private:
Adafruit_MPR121 capSensor; Adafruit_MPR121 capSensor;
QwiicButton bagSensor; QwiicButton bagSensor;
bool bag_enabled; bool bag_enabled;
unsigned long nextRepeat[NUM_KEYS];
bool typematicEvent(uint8_t key, uint16_t delay, uint16_t repeat);
}; };

View File

@ -2,6 +2,10 @@
#include "synth_waveform.h" #include "synth_waveform.h"
void FMVoice::LoadPatch(FMPatch *p) { void FMVoice::LoadPatch(FMPatch *p) {
bool playing = this->playing;
float pitch = this->pitch;
NoteOff();
for (int i=0; i<NUM_OPERATORS; i++) { for (int i=0; i<NUM_OPERATORS; i++) {
FMOperator op = p->operators[i]; FMOperator op = p->operators[i];
@ -22,6 +26,9 @@ void FMVoice::LoadPatch(FMPatch *p) {
this->outputMixer.gain(i, p->gains[i][NUM_OPERATORS]); this->outputMixer.gain(i, p->gains[i][NUM_OPERATORS]);
} }
this->patch = p; this->patch = p;
if (playing) {
NoteOn(pitch);
}
} }
void FMVoice::SetPitch(float freq) { void FMVoice::SetPitch(float freq) {
@ -29,6 +36,7 @@ void FMVoice::SetPitch(float freq) {
FMOperator op = this->patch->operators[i]; FMOperator op = this->patch->operators[i];
this->oscillators[i].frequency(op.offset + (freq * op.multiplier)); this->oscillators[i].frequency(op.offset + (freq * op.multiplier));
} }
this->pitch = freq;
} }
void FMVoice::NoteOn(float freq) { void FMVoice::NoteOn(float freq) {

View File

@ -94,6 +94,10 @@ class FMVoice {
*/ */
void SetPitch(float pitch); void SetPitch(float pitch);
/** GetPitch returns the pitch (Hz) of a voice.
*/
float GetPitch();
/** SetModulation sets the modulation amount of a voice. /** SetModulation sets the modulation amount of a voice.
* *
* What this means depends on the loaded patch. * What this means depends on the loaded patch.
@ -126,6 +130,7 @@ class FMVoice {
AudioEffectEnvelope envelopes[NUM_OPERATORS]; AudioEffectEnvelope envelopes[NUM_OPERATORS];
AudioMixer4 outputMixer; AudioMixer4 outputMixer;
FMPatch *patch; FMPatch *patch;
float pitch;
bool playing; bool playing;
}; };

View File

@ -125,6 +125,10 @@ const char *NoteName(Note note) {
return noteNames[note % 12]; return noteNames[note % 12];
} }
int NoteOctave(Note note) {
return int(note / NOTE_OCTAVE);
}
const char *TuningSystemName(TuningSystem system) { const char *TuningSystemName(TuningSystem system) {
switch (system) { switch (system) {
case TUNINGSYSTEM_EQUAL: case TUNINGSYSTEM_EQUAL:

View File

@ -72,6 +72,9 @@ class Tuning {
// NearestNote returns the note nearest to pitch. // NearestNote returns the note nearest to pitch.
Note NearestNote(float 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). // NoteName returns the name of a note (without octave).
const char *NoteName(Note note); const char *NoteName(Note note);
@ -88,8 +91,14 @@ inline Note toNote(int a) {
return Note(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) { 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) { inline Note &operator+=(Note &a, const Note b) {
return a = a + b; return a = a + b;

View File

@ -12,7 +12,9 @@
#include "synth.h" #include "synth.h"
#include "tuning.h" #include "tuning.h"
#if defined(ADAFRUIT_TRELLIS_M4_EXPRESS) const char *buildDate = __DATE__;
#if defined(ADAFRUIT_TRELLIS_MAdafruit_SSD1306EXPRESS)
#include <Adafruit_NeoTrellisM4.h> #include <Adafruit_NeoTrellisM4.h>
Adafruit_NeoTrellisM4 trellis; // = Adafruit_NeoTrellisM4(); Adafruit_NeoTrellisM4 trellis; // = Adafruit_NeoTrellisM4();
#endif #endif
@ -25,13 +27,13 @@ Adafruit_SSD1306 display(128, 32, &Wire, -1);
// Settings // Settings
uint8_t patch[4] = {0}; uint8_t patch[4] = {0};
float volume[4] = {0}; float volume[4] = {0};
const char *settingNames[4] = {"c", "r", "d", "*"};
const char *buildDate = __DATE__;
// Pipes // Pipes
#define NUM_DRONES 3
#define NUM_REGULATORS 3
FMVoice Chanter; FMVoice Chanter;
FMVoice Drones[3]; FMVoice Drones[NUM_DRONES];
FMVoice Regulators[3]; FMVoice Regulators[NUM_REGULATORS];
AudioFilterBiquad biquad1; AudioFilterBiquad biquad1;
AudioMixer4 mixDrones; AudioMixer4 mixDrones;
@ -103,6 +105,7 @@ void diag(const char *fmt, ...) {
display.clearDisplay(); display.clearDisplay();
display.drawRect(124, 16, 4, 16, SSD1306_WHITE); display.drawRect(124, 16, 4, 16, SSD1306_WHITE);
display.setTextColor(SSD1306_WHITE); display.setTextColor(SSD1306_WHITE);
display.setFont();
display.setTextSize(1); display.setTextSize(1);
display.setCursor(56, 24); display.setCursor(56, 24);
@ -121,6 +124,13 @@ void diag(const char *fmt, ...) {
display.display(); 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() { void setup() {
// Initialize settings // Initialize settings
// XXX: Read these from persistent storage later // XXX: Read these from persistent storage later
@ -129,16 +139,18 @@ void setup() {
volume[i] = 0.75; volume[i] = 0.75;
} }
Wire.begin();
// PREPARE TO BLINK // PREPARE TO BLINK
pinMode(LED_BUILTIN, OUTPUT); pinMode(LED_BUILTIN, OUTPUT);
digitalWrite(LED_BUILTIN, true); digitalWrite(LED_BUILTIN, true);
// Set up I2C. Apparently this needs a bit of startup delay.
Wire.begin();
// Initialize display // Initialize display
if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3c)) { if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3c)) {
blink(true); blink(true);
} }
digitalWrite(LED_BUILTIN, false);
diag("Hello!"); diag("Hello!");
#if defined(ADAFRUIT_TRELLIS_M4_EXPRESS) #if defined(ADAFRUIT_TRELLIS_M4_EXPRESS)
@ -153,37 +165,62 @@ void setup() {
} }
diag("Audio..."); diag("Audio...");
// Set aside some memory for the audio library
AudioMemory(20); AudioMemory(20);
AudioProcessorUsageMaxReset();
// Set up the SGTL5000 using I2C AudioMemoryUsageMaxReset();
sgtl5000.enable(); sgtl5000.enable();
sgtl5000.volume(0.3); sgtl5000.volume(0.3);
// Initialize processor and memory measurements diag("Synth...");
AudioProcessorUsageMaxReset(); loadPatch(0);
AudioMemoryUsageMaxReset(); loadPatch(1);
loadPatch(2);
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("Mixer...");
// Turn on all mixer channels // Turn on all mixer channels
for (int i = 0; i < 4; i++) { for (int i = 0; i < 4; i++) {
mixL.gain(i, 0.5); mixL.gain(i, volume[i]);
mixR.gain(i, 0.6); 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!"); 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() { void loop() {
static bool forceDisplayUpdate = true; static bool upSetting = false; // GET IT?
pipe.Update(); pipe.Update();
@ -192,199 +229,21 @@ void loop() {
#endif #endif
// If we're infinitely (for the sensor) off the knee, // If we're infinitely (for the sensor) off the knee,
// go into setup mode! // 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)) {
doSetup(); doSetup();
forceDisplayUpdate = true; upSetting = 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; 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)) { diag("Play!");
if (pipe.Pressed(1) && pipe.Pressed(0)) { doPlay(pipe, display, upSetting);
freq = PITCH_CONCERT_D4; upSetting = false;
quiet(); diag("Done!");
} 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;
}
} }