Playing now, with a crash somewhere
This commit is contained in:
parent
f7f2bc3cfe
commit
13c72dae2a
28
fingering.h
28
fingering.h
|
@ -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];
|
||||||
|
}
|
||||||
|
|
|
@ -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");
|
||||||
|
}
|
|
@ -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();
|
||||||
|
}
|
|
@ -61,3 +61,5 @@ FMPatch Bank[] = {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const int PATCH_MAX = sizeof(Bank) / sizeof(Bank[0]);
|
||||||
|
|
53
pipe.cpp
53
pipe.cpp
|
@ -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
21
pipe.h
|
@ -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);
|
||||||
};
|
};
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
5
synth.h
5
synth.h
|
@ -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;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -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:
|
||||||
|
|
11
tuning.h
11
tuning.h
|
@ -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;
|
||||||
|
|
295
uilleann.ino
295
uilleann.ino
|
@ -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) {
|
||||||
doSetup();
|
// We only enter into setup mode if no keys are pressed.
|
||||||
forceDisplayUpdate = true;
|
// This hopefully avoids accidentally entering setup while playing.
|
||||||
} else {
|
// Like say you're playing a jaunty tune and suddenly there's an earthquake.
|
||||||
doPlay(forceDisplayUpdate);
|
// You ought to be able to finish the tune off before your pipe goes into setup mode.
|
||||||
forceDisplayUpdate = false;
|
if (upSetting || (pipe.keys == 0)) {
|
||||||
}
|
doSetup();
|
||||||
}
|
upSetting = true;
|
||||||
|
return;
|
||||||
/** 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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
diag("Play!");
|
||||||
|
doPlay(pipe, display, upSetting);
|
||||||
|
upSetting = false;
|
||||||
|
diag("Done!");
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue