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) \
|
||||
{ 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];
|
||||
}
|
||||
|
|
|
@ -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 "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;
|
||||
}
|
21
pipe.h
21
pipe.h
|
@ -6,14 +6,23 @@
|
|||
#include <stdint.h>
|
||||
#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);
|
||||
};
|
||||
|
|
|
@ -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; i<NUM_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->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) {
|
||||
|
|
5
synth.h
5
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;
|
||||
};
|
||||
|
||||
|
|
|
@ -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:
|
||||
|
|
11
tuning.h
11
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;
|
||||
|
|
287
uilleann.ino
287
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.h>
|
||||
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) {
|
||||
// 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();
|
||||
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) {
|
||||
upSetting = true;
|
||||
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