Setup menu, new Tuning class
This commit is contained in:
parent
b65001ba84
commit
c915b0ee69
71
fingering.h
71
fingering.h
|
@ -1,15 +1,22 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
#include "notes.h"
|
#include "tuning.h"
|
||||||
|
|
||||||
#define CCCC NOTE_CS5, NOTE_CS5, NOTE_CS5, NOTE_CS5
|
struct Fingering {
|
||||||
#define CCDD NOTE_CS5, NOTE_CS5, NOTE_D5, NOTE_D5
|
Note note;
|
||||||
#define CDCD NOTE_CS5, NOTE_D5, NOTE_CS5, NOTE_D5
|
bool alt; // Alternate fingering: sounds more choked
|
||||||
#define DDDD NOTE_D5, NOTE_D5, NOTE_D5, NOTE_D5
|
};
|
||||||
#define P 0x80
|
|
||||||
|
|
||||||
uint8_t uilleann_matrix[] = {
|
#define n(note) {note, false}
|
||||||
|
#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 DDDD n(NOTE_D5), n(NOTE_D5), n(NOTE_D5), n(NOTE_D5)
|
||||||
|
|
||||||
|
struct Fingering uilleann_matrix[] = {
|
||||||
// Open Back D
|
// Open Back D
|
||||||
NOTE_CS5, NOTE_CS5, NOTE_CS5, 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..
|
||||||
|
@ -39,40 +46,40 @@ uint8_t uilleann_matrix[] = {
|
||||||
DDDD, // XXO XX..
|
DDDD, // XXO XX..
|
||||||
DDDD, // XXX OO..
|
DDDD, // XXX OO..
|
||||||
DDDD, // XXX OX..
|
DDDD, // XXX OX..
|
||||||
NOTE_D5, NOTE_D5, NOTE_D5, NOTE_D5|P, // XXX XO..
|
n(NOTE_D5), n(NOTE_D5), n(NOTE_D5), P(NOTE_D5), // XXX XO..
|
||||||
DDDD, // XXX XX..
|
DDDD, // XXX XX..
|
||||||
|
|
||||||
// Closed Back D
|
// Closed Back D
|
||||||
CCCC, // OOO OO...
|
CCCC, // OOO OO...
|
||||||
NOTE_CS5, NOTE_CS5, NOTE_CS5, NOTE_CS5|P, // 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..
|
||||||
NOTE_CS5, NOTE_CS5|P, NOTE_CS5, NOTE_CS5|P, // 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..
|
||||||
NOTE_CS5, NOTE_CS5|P, NOTE_CS5, NOTE_CS5|P, // 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..
|
||||||
NOTE_C5|P, NOTE_C5|P, NOTE_C5|P, NOTE_C5|P, // OXX OO..
|
P(NOTE_C5), P(NOTE_C5), P(NOTE_C5), P(NOTE_C5), // OXX OO..
|
||||||
NOTE_C5, NOTE_C5, NOTE_C5, NOTE_C5, // OXX OX..
|
n(NOTE_C5), n(NOTE_C5), n(NOTE_C5), n(NOTE_C5), // OXX OX..
|
||||||
NOTE_C5, NOTE_C5, NOTE_C5, NOTE_C5|P, // OXX XO..
|
n(NOTE_C5), n(NOTE_C5), n(NOTE_C5), P(NOTE_C5), // OXX XO..
|
||||||
NOTE_C5, NOTE_C5, NOTE_C5, NOTE_CS5, // OXX XX..
|
n(NOTE_C5), n(NOTE_C5), n(NOTE_C5), n(NOTE_CS5), // OXX XX..
|
||||||
NOTE_B4, NOTE_B4, NOTE_B4, NOTE_B4, // XOO OO..
|
n(NOTE_B4), n(NOTE_B4), n(NOTE_B4), n(NOTE_B4), // XOO OO..
|
||||||
NOTE_B4|P, NOTE_B4|P, NOTE_B4, NOTE_B4|P, // XOO OX..
|
P(NOTE_B4), P(NOTE_B4), n(NOTE_B4), P(NOTE_B4), // XOO OX..
|
||||||
NOTE_AS4, NOTE_B4, NOTE_AS4, NOTE_B4, // XOO XO..
|
n(NOTE_AS4), n(NOTE_B4), n(NOTE_AS4), n(NOTE_B4), // XOO XO..
|
||||||
NOTE_B4, NOTE_B4, NOTE_B4, NOTE_B4, // XOO XX..
|
n(NOTE_B4), n(NOTE_B4), n(NOTE_B4), n(NOTE_B4), // XOO XX..
|
||||||
NOTE_B4|P, NOTE_B4|P, NOTE_B4|P, NOTE_B4|P, // XOX OO..
|
P(NOTE_B4), P(NOTE_B4), P(NOTE_B4), P(NOTE_B4), // XOX OO..
|
||||||
NOTE_B4|P, NOTE_B4|P, NOTE_B4|P, NOTE_B4|P, // XOX OX..
|
P(NOTE_B4), P(NOTE_B4), P(NOTE_B4), P(NOTE_B4), // XOX OX..
|
||||||
NOTE_B4|P, NOTE_B4|P, NOTE_B4|P, NOTE_B4|P, // XOX XO..
|
P(NOTE_B4), P(NOTE_B4), P(NOTE_B4), P(NOTE_B4), // XOX XO..
|
||||||
NOTE_B4|P, NOTE_B4|P, NOTE_B4|P, NOTE_B4|P, // XOX XX..
|
P(NOTE_B4), P(NOTE_B4), P(NOTE_B4), P(NOTE_B4), // XOX XX..
|
||||||
NOTE_A4, NOTE_A4, NOTE_A4|P, NOTE_A4, // XXO OO..
|
n(NOTE_A4), n(NOTE_A4), P(NOTE_A4), n(NOTE_A4), // XXO OO..
|
||||||
NOTE_A4|P, NOTE_A4|P, NOTE_A4|P, NOTE_A4|P, // XXO OX..
|
P(NOTE_A4), P(NOTE_A4), P(NOTE_A4), P(NOTE_A4), // XXO OX..
|
||||||
NOTE_GS4, NOTE_GS4|P, NOTE_A4, NOTE_A4, // XXO XO..
|
n(NOTE_GS4), P(NOTE_GS4), n(NOTE_A4), n(NOTE_A4), // XXO XO..
|
||||||
NOTE_A4|P, NOTE_A4|P, NOTE_A4|P, NOTE_A4, // XXO XX..
|
P(NOTE_A4), P(NOTE_A4), P(NOTE_A4), n(NOTE_A4), // XXO XX..
|
||||||
NOTE_G4, NOTE_G4, NOTE_G4|P, NOTE_G4, // XXX OO..
|
n(NOTE_G4), n(NOTE_G4), P(NOTE_G4), n(NOTE_G4), // XXX OO..
|
||||||
NOTE_G4|P, NOTE_G4|P, NOTE_G4|P, NOTE_G4|P, // XXX OX..
|
P(NOTE_G4), P(NOTE_G4), P(NOTE_G4), P(NOTE_G4), // XXX OX..
|
||||||
NOTE_FS4, NOTE_FS4, NOTE_F4, NOTE_FS4|P, // XXX XO..
|
n(NOTE_FS4), n(NOTE_FS4), n(NOTE_F4), P(NOTE_FS4), // XXX XO..
|
||||||
NOTE_E4, NOTE_E4|P, NOTE_DS4, NOTE_D4, // XXX XX..
|
n(NOTE_E4), P(NOTE_E4), n(NOTE_DS4), n(NOTE_D4), // XXX XX..
|
||||||
};
|
};
|
||||||
|
|
41
notes.cpp
41
notes.cpp
|
@ -1,41 +0,0 @@
|
||||||
#include <Arduino.h>
|
|
||||||
#include "notes.h"
|
|
||||||
|
|
||||||
const char *NoteNames[] {
|
|
||||||
"C ", "C#", "D ", "Eb", "E ", "F ", "F#", "G ", "Ab", "A ", "Bb", "B ",
|
|
||||||
};
|
|
||||||
float JustPitches[MaxNote + 1];
|
|
||||||
|
|
||||||
// Hat tip to Kyle Gann
|
|
||||||
// https://www.kylegann.com/tuning.html
|
|
||||||
void setupJustPitches(uint8_t baseNote, float basePitch) {
|
|
||||||
JustPitches[baseNote + 0] = basePitch * 1 / 1; // D
|
|
||||||
JustPitches[baseNote + 1] = basePitch * 16 / 15; // Eb
|
|
||||||
JustPitches[baseNote + 2] = basePitch * 9 / 8; // E
|
|
||||||
JustPitches[baseNote + 3] = basePitch * 6 / 5; // F
|
|
||||||
JustPitches[baseNote + 4] = basePitch * 5 / 4; // F#
|
|
||||||
JustPitches[baseNote + 5] = basePitch * 4 / 3; // G
|
|
||||||
JustPitches[baseNote + 6] = basePitch * 45 / 32; // Ab
|
|
||||||
JustPitches[baseNote + 7] = basePitch * 3 / 2; // A
|
|
||||||
JustPitches[baseNote + 8] = basePitch * 8 / 5; // Bb
|
|
||||||
JustPitches[baseNote + 9] = basePitch * 5 / 3; // B
|
|
||||||
JustPitches[baseNote + 10] = basePitch * 16 / 9; // C (fourth up from G)
|
|
||||||
JustPitches[baseNote + 11] = basePitch * 15 / 8; // C#
|
|
||||||
|
|
||||||
// Octaves
|
|
||||||
for (int note = baseNote; note < baseNote + 12; note++) {
|
|
||||||
for (int i = 1; i < 9; i++) {
|
|
||||||
int multiplier = 1<<i;
|
|
||||||
int shift = i*12;
|
|
||||||
int upNote = note + shift;
|
|
||||||
int dnNote = note - shift;
|
|
||||||
|
|
||||||
if (upNote <= MaxNote) {
|
|
||||||
JustPitches[upNote] = JustPitches[note] * multiplier;
|
|
||||||
}
|
|
||||||
if (dnNote >= 0) {
|
|
||||||
JustPitches[dnNote] = JustPitches[note] / multiplier;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
21
notes.h
21
notes.h
|
@ -1,21 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#define PITCH_D4 293.66
|
|
||||||
|
|
||||||
enum Notes {
|
|
||||||
NOTE_C0, NOTE_CS0, NOTE_D0, NOTE_DS0, NOTE_E0, NOTE_F0, NOTE_FS0, NOTE_G0, NOTE_GS0, NOTE_A0, NOTE_AS0, NOTE_B0,
|
|
||||||
NOTE_C1, NOTE_CS1, NOTE_D1, NOTE_DS1, NOTE_E1, NOTE_F1, NOTE_FS1, NOTE_G1, NOTE_GS1, NOTE_A1, NOTE_AS1, NOTE_B1,
|
|
||||||
NOTE_C2, NOTE_CS2, NOTE_D2, NOTE_DS2, NOTE_E2, NOTE_F2, NOTE_FS2, NOTE_G2, NOTE_GS2, NOTE_A2, NOTE_AS2, NOTE_B2,
|
|
||||||
NOTE_C3, NOTE_CS3, NOTE_D3, NOTE_DS3, NOTE_E3, NOTE_F3, NOTE_FS3, NOTE_G3, NOTE_GS3, NOTE_A3, NOTE_AS3, NOTE_B3,
|
|
||||||
NOTE_C4, NOTE_CS4, NOTE_D4, NOTE_DS4, NOTE_E4, NOTE_F4, NOTE_FS4, NOTE_G4, NOTE_GS4, NOTE_A4, NOTE_AS4, NOTE_B4,
|
|
||||||
NOTE_C5, NOTE_CS5, NOTE_D5, NOTE_DS5, NOTE_E5, NOTE_F5, NOTE_FS5, NOTE_G5, NOTE_GS5, NOTE_A5, NOTE_AS5, NOTE_B5,
|
|
||||||
NOTE_C6, NOTE_CS6, NOTE_D6, NOTE_DS6, NOTE_E6, NOTE_F6, NOTE_FS6, NOTE_G6, NOTE_GS6, NOTE_A6, NOTE_AS6, NOTE_B6,
|
|
||||||
NOTE_C7, NOTE_CS7, NOTE_D7, NOTE_DS7, NOTE_E7, NOTE_F7, NOTE_FS7, NOTE_G7, NOTE_GS7, NOTE_A7, NOTE_AS7, NOTE_B7,
|
|
||||||
NOTE_C8, NOTE_CS8, NOTE_D8, NOTE_DS8, NOTE_E8, NOTE_F8, NOTE_FS8, NOTE_G8, NOTE_GS8, NOTE_A8, NOTE_AS8, NOTE_B8,
|
|
||||||
};
|
|
||||||
const uint8_t MaxNote = NOTE_B8;
|
|
||||||
|
|
||||||
extern const char *NoteNames[];
|
|
||||||
extern float JustPitches[MaxNote + 1];
|
|
||||||
|
|
||||||
void setupJustPitches(uint8_t baseNote, float basePitch);
|
|
30
pipe.cpp
30
pipe.cpp
|
@ -1,4 +1,5 @@
|
||||||
#include "pipe.h"
|
#include "pipe.h"
|
||||||
|
#include "tuning.h"
|
||||||
#include "fingering.h"
|
#include "fingering.h"
|
||||||
|
|
||||||
// Kludge time: this is something I did just to make my breadboard look nicer.
|
// Kludge time: this is something I did just to make my breadboard look nicer.
|
||||||
|
@ -9,6 +10,7 @@
|
||||||
#define GLISSANDO_STEPS (OPENVAL - CLOSEDVAL)
|
#define GLISSANDO_STEPS (OPENVAL - CLOSEDVAL)
|
||||||
|
|
||||||
Pipe::Pipe() {
|
Pipe::Pipe() {
|
||||||
|
keysLast = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Pipe::Init() {
|
bool Pipe::Init() {
|
||||||
|
@ -44,6 +46,7 @@ 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;
|
||||||
keys = 0;
|
keys = 0;
|
||||||
glissandoKeys = 0;
|
glissandoKeys = 0;
|
||||||
for (int i=0; i<8; i++) {
|
for (int i=0; i<8; i++) {
|
||||||
|
@ -63,22 +66,33 @@ void Pipe::Update() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Look up notes in the big table
|
// Look up notes in the big table
|
||||||
note = uilleann_matrix[keys];
|
struct Fingering f = uilleann_matrix[keys];
|
||||||
glissandoNote = uilleann_matrix[glissandoKeys];
|
struct Fingering gf = uilleann_matrix[glissandoKeys];
|
||||||
|
|
||||||
|
note = f.note;
|
||||||
|
glissandoNote = gf.note;
|
||||||
|
|
||||||
// Was the high bit set? That indicates "alternate fingering", which sounds different.
|
// Was the high bit set? That indicates "alternate fingering", which sounds different.
|
||||||
altFingering = (note & 0x80);
|
altFingering = f.alt;
|
||||||
|
|
||||||
note &= 0x7f;
|
|
||||||
glissandoNote &= 0x7f;
|
|
||||||
|
|
||||||
// If the bag is squished, jump up an octave
|
// If the bag is squished, jump up an octave
|
||||||
// But only if the left thumb is down!
|
// But only if the left thumb is down!
|
||||||
if (bag && (keys & bit(7))) {
|
if (bag && (keys & bit(7))) {
|
||||||
note += 12;
|
note += NOTE_OCTAVE;
|
||||||
glissandoNote += 12;
|
glissandoNote += NOTE_OCTAVE;
|
||||||
}
|
}
|
||||||
|
|
||||||
// All keys closed + knee = no sound
|
// All keys closed + knee = no sound
|
||||||
silent = ((kneeClosedness > 240) && (keys == 0xff));
|
silent = ((kneeClosedness > 240) && (keys == 0xff));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Pipe::Pressed(uint8_t key) {
|
||||||
|
return bitRead(keys, key);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Pipe::JustPressed(uint8_t key) {
|
||||||
|
if (bitRead(keys, key)) {
|
||||||
|
return !bitRead(keysLast, key);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
22
pipe.h
22
pipe.h
|
@ -4,6 +4,7 @@
|
||||||
#include <SparkFun_Qwiic_Button.h>
|
#include <SparkFun_Qwiic_Button.h>
|
||||||
#include <Adafruit_MPR121.h>
|
#include <Adafruit_MPR121.h>
|
||||||
#include <paj7620.h>
|
#include <paj7620.h>
|
||||||
|
#include "tuning.h"
|
||||||
|
|
||||||
class Pipe {
|
class Pipe {
|
||||||
public:
|
public:
|
||||||
|
@ -12,9 +13,16 @@ public:
|
||||||
|
|
||||||
// keys are which keys are being pressed.
|
// keys are which keys are being pressed.
|
||||||
uint8_t keys;
|
uint8_t keys;
|
||||||
|
uint8_t keysLast;
|
||||||
|
|
||||||
// note holds the note being played, according to the fingering chart.
|
// note holds the note being played, according to the fingering chart.
|
||||||
uint8_t note;
|
Note note;
|
||||||
|
|
||||||
|
// glissandoNote is the note that would be played if partially open keys were fully open.
|
||||||
|
Note glissandoNote;
|
||||||
|
|
||||||
|
// glissandoOpenness is how "open" the holes are in the direction of the glissandoNote.
|
||||||
|
float glissandoOpenness;
|
||||||
|
|
||||||
// silent is true if all keys and the knee are closed.
|
// silent is true if all keys and the knee are closed.
|
||||||
bool silent;
|
bool silent;
|
||||||
|
@ -26,12 +34,6 @@ public:
|
||||||
// This should sound different than the standard fingering.
|
// This should sound different than the standard fingering.
|
||||||
bool altFingering;
|
bool altFingering;
|
||||||
|
|
||||||
// glissandoNote is the note that would be played if partially open keys were fully open.
|
|
||||||
uint8_t glissandoNote;
|
|
||||||
|
|
||||||
// glissandoOpenness is how "open" the holes are in the direction of the glissandoNote.
|
|
||||||
float glissandoOpenness;
|
|
||||||
|
|
||||||
Pipe();
|
Pipe();
|
||||||
|
|
||||||
// Init initializes everything.
|
// Init initializes everything.
|
||||||
|
@ -44,6 +46,12 @@ public:
|
||||||
// It should be run once per loop.
|
// It should be run once per loop.
|
||||||
void Update();
|
void Update();
|
||||||
|
|
||||||
|
// Pressed returns whether the given key is pressed.
|
||||||
|
bool Pressed(uint8_t key);
|
||||||
|
|
||||||
|
// JustPressed returns whether the given key was just pressed.
|
||||||
|
bool JustPressed(uint8_t key);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Adafruit_MPR121 capSensor;
|
Adafruit_MPR121 capSensor;
|
||||||
QwiicButton bagSensor;
|
QwiicButton bagSensor;
|
||||||
|
|
|
@ -0,0 +1,111 @@
|
||||||
|
#include "tuning.h"
|
||||||
|
|
||||||
|
#include <math.h>
|
||||||
|
|
||||||
|
// 12th root of 2, used for Twelvetone Equal Temperament
|
||||||
|
#define TET_CONST 1.059463
|
||||||
|
|
||||||
|
Tuning::Tuning(Note base, float pitch, TuningSystem system) {
|
||||||
|
Setup(base, pitch, system);
|
||||||
|
}
|
||||||
|
Tuning::Tuning(Note base, float pitch) {
|
||||||
|
Tuning(base, pitch, TUNINGSYSTEM_JUST);
|
||||||
|
}
|
||||||
|
|
||||||
|
// I like just Intonation.
|
||||||
|
Tuning::Tuning() {
|
||||||
|
#if 0
|
||||||
|
Tuning(NOTE_D4, PITCH_CONCERT_D4, TUNINGSYSTEM_JUST);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
Note Tuning::GetBaseNote() { return baseNote; }
|
||||||
|
|
||||||
|
void Tuning::SetTuningSystem(TuningSystem system) {
|
||||||
|
Setup(baseNote, GetPitch(baseNote), system);
|
||||||
|
}
|
||||||
|
|
||||||
|
TuningSystem Tuning::GetSystem() { return system; }
|
||||||
|
|
||||||
|
// setupOctaves computes the entire tuning frequency chart.
|
||||||
|
//
|
||||||
|
// You must call this after setting a full octave chromatic scale, rooted at
|
||||||
|
// base.
|
||||||
|
void Tuning::setupOctaves(Note base) {
|
||||||
|
int multiplier = 1;
|
||||||
|
for (Note octave = NOTE_ZERO; octave < NOTE_MAX; octave += NOTE_OCTAVE) {
|
||||||
|
for (Note note = base; note < base + NOTE_OCTAVE; note += NOTE_SEMITONE) {
|
||||||
|
Note upNote = note + octave;
|
||||||
|
Note dnNote = note - octave;
|
||||||
|
|
||||||
|
if (upNote < NOTE_MAX) {
|
||||||
|
pitches[upNote] = pitches[note] * multiplier;
|
||||||
|
}
|
||||||
|
if (dnNote >= NOTE_ZERO) {
|
||||||
|
pitches[dnNote] = pitches[note] / multiplier;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
multiplier <<= 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// setupEqual sets an even-temperament chromatic scale rooted at base
|
||||||
|
void Tuning::setupEqual(Note base, float pitch) {
|
||||||
|
pitches[base] = pitch;
|
||||||
|
for (int i = 1; i < 12; i++) {
|
||||||
|
pitches[base + i] = pitches[base + i - 1] * TET_CONST;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// setupJust sets a just-temperament chromatic scale rooted at base
|
||||||
|
void Tuning::setupJust(Note base, float pitch) {
|
||||||
|
// Diatonic scale
|
||||||
|
pitches[base + 0] = pitch * 1 / 1; // Unison
|
||||||
|
pitches[base + 2] = pitch * 9 / 8; // Second
|
||||||
|
pitches[base + 4] = pitch * 5 / 4; // Third
|
||||||
|
pitches[base + 5] = pitch * 4 / 3; // Fourth
|
||||||
|
pitches[base + 7] = pitch * 3 / 2; // Fifth
|
||||||
|
pitches[base + 9] = pitch * 5 / 3; // Sixth
|
||||||
|
pitches[base + 11] = pitch * 15 / 8; // Seventh
|
||||||
|
|
||||||
|
// I got this off various Wikipedia pages.
|
||||||
|
// The main thing here is that the minor seventh works out to be a diatonic
|
||||||
|
// fourth up from the fourth computed above, since the music I want to play
|
||||||
|
// frequently wants to play G major on a D major instrument
|
||||||
|
pitches[base + 1] = pitch * 16 / 15; // min2
|
||||||
|
pitches[base + 3] = pitch * 6 / 5; // min3
|
||||||
|
pitches[base + 6] = pitch * 10 / 7; // dim5
|
||||||
|
pitches[base + 8] = pitch * 8 / 5; // min6
|
||||||
|
pitches[base + 10] = pitch * 16 / 9; // min7 = fourth + fourth
|
||||||
|
}
|
||||||
|
|
||||||
|
void Tuning::Setup(Note base, float pitch, TuningSystem system) {
|
||||||
|
this->baseNote = base;
|
||||||
|
this->system = system;
|
||||||
|
|
||||||
|
switch (system) {
|
||||||
|
case TUNINGSYSTEM_EQUAL:
|
||||||
|
setupEqual(base, pitch);
|
||||||
|
break;
|
||||||
|
case TUNINGSYSTEM_JUST:
|
||||||
|
default:
|
||||||
|
setupJust(base, pitch);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
setupOctaves(base);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Tuning::Setup(Note base, float pitch) { Setup(base, pitch, system); }
|
||||||
|
|
||||||
|
float Tuning::GetPitch(Note note) { return pitches[note]; }
|
||||||
|
|
||||||
|
Note NearestNote(float pitch) {
|
||||||
|
return Note(round(log2(pitch / PITCH_CONCERT_C0)));
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *noteNames[]{
|
||||||
|
"C ", "C#", "D ", "Eb", "E ", "F ", "F#", "G ", "Ab", "A ", "Bb", "B ",
|
||||||
|
};
|
||||||
|
|
||||||
|
const char *NoteName(Note note) { return noteNames[note % 12]; }
|
||||||
|
|
|
@ -0,0 +1,99 @@
|
||||||
|
#pragma once
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
enum TuningSystem {
|
||||||
|
TUNINGSYSTEM_JUST,
|
||||||
|
TUNINGSYSTEM_EQUAL,
|
||||||
|
};
|
||||||
|
#define TUNINGSYSTEM_MAX TUNINGSYSTEM_EQUAL
|
||||||
|
|
||||||
|
enum Note {
|
||||||
|
NOTE_C0, NOTE_CS0, NOTE_D0, NOTE_DS0, NOTE_E0, NOTE_F0, NOTE_FS0, NOTE_G0, NOTE_GS0, NOTE_A0, NOTE_AS0, NOTE_B0,
|
||||||
|
NOTE_C1, NOTE_CS1, NOTE_D1, NOTE_DS1, NOTE_E1, NOTE_F1, NOTE_FS1, NOTE_G1, NOTE_GS1, NOTE_A1, NOTE_AS1, NOTE_B1,
|
||||||
|
NOTE_C2, NOTE_CS2, NOTE_D2, NOTE_DS2, NOTE_E2, NOTE_F2, NOTE_FS2, NOTE_G2, NOTE_GS2, NOTE_A2, NOTE_AS2, NOTE_B2,
|
||||||
|
NOTE_C3, NOTE_CS3, NOTE_D3, NOTE_DS3, NOTE_E3, NOTE_F3, NOTE_FS3, NOTE_G3, NOTE_GS3, NOTE_A3, NOTE_AS3, NOTE_B3,
|
||||||
|
NOTE_C4, NOTE_CS4, NOTE_D4, NOTE_DS4, NOTE_E4, NOTE_F4, NOTE_FS4, NOTE_G4, NOTE_GS4, NOTE_A4, NOTE_AS4, NOTE_B4,
|
||||||
|
NOTE_C5, NOTE_CS5, NOTE_D5, NOTE_DS5, NOTE_E5, NOTE_F5, NOTE_FS5, NOTE_G5, NOTE_GS5, NOTE_A5, NOTE_AS5, NOTE_B5,
|
||||||
|
NOTE_C6, NOTE_CS6, NOTE_D6, NOTE_DS6, NOTE_E6, NOTE_F6, NOTE_FS6, NOTE_G6, NOTE_GS6, NOTE_A6, NOTE_AS6, NOTE_B6,
|
||||||
|
NOTE_C7, NOTE_CS7, NOTE_D7, NOTE_DS7, NOTE_E7, NOTE_F7, NOTE_FS7, NOTE_G7, NOTE_GS7, NOTE_A7, NOTE_AS7, NOTE_B7,
|
||||||
|
NOTE_C8, NOTE_CS8, NOTE_D8, NOTE_DS8, NOTE_E8, NOTE_F8, NOTE_FS8, NOTE_G8, NOTE_GS8, NOTE_A8, NOTE_AS8, NOTE_B8,
|
||||||
|
NOTE_ZERO = 0,
|
||||||
|
NOTE_SEMITONE = 1,
|
||||||
|
NOTE_WHOLETONE = 2,
|
||||||
|
NOTE_OCTAVE = NOTE_C1,
|
||||||
|
NOTE_MAX = NOTE_B8,
|
||||||
|
};
|
||||||
|
|
||||||
|
#define PITCH_CONCERT_C0 16.35
|
||||||
|
#define PITCH_CONCERT_A4 440.00
|
||||||
|
#define PITCH_CONCERT_D4 293.66
|
||||||
|
|
||||||
|
class Tuning {
|
||||||
|
public:
|
||||||
|
// name contains the name of the current tuning system
|
||||||
|
const char *name;
|
||||||
|
|
||||||
|
Tuning(Note base, float pitch, TuningSystem system);
|
||||||
|
Tuning(Note base, float pitch);
|
||||||
|
Tuning();
|
||||||
|
void Setup(Note base, float pitch, TuningSystem system);
|
||||||
|
void Setup(Note base, float pitch);
|
||||||
|
void SetTuningSystem(TuningSystem system);
|
||||||
|
TuningSystem GetSystem();
|
||||||
|
Note GetBaseNote();
|
||||||
|
float GetPitch(Note note);
|
||||||
|
|
||||||
|
private:
|
||||||
|
TuningSystem system;
|
||||||
|
Note baseNote;
|
||||||
|
float pitches[NOTE_MAX];
|
||||||
|
|
||||||
|
void setupOctaves(Note base);
|
||||||
|
void setupJust(Note base, float pitch);
|
||||||
|
void setupEqual(Note base, float pitch);
|
||||||
|
};
|
||||||
|
|
||||||
|
// NearestNote returns the note nearest to pitch.
|
||||||
|
Note NearestNote(float pitch);
|
||||||
|
|
||||||
|
// NoteName return the name of a note (without octave).
|
||||||
|
const char *NoteName(Note note);
|
||||||
|
|
||||||
|
|
||||||
|
// Make notes support some arithmetic
|
||||||
|
inline Note toNote(int a) {
|
||||||
|
if (a < NOTE_ZERO) {
|
||||||
|
return NOTE_ZERO;
|
||||||
|
} else if (a > NOTE_MAX) {
|
||||||
|
return NOTE_MAX;
|
||||||
|
} else {
|
||||||
|
return Note(a);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
inline Note operator+(const Note &a, const Note b) {
|
||||||
|
return toNote(int(a) + int(b));
|
||||||
|
}
|
||||||
|
inline Note &operator+=(Note &a, const Note b) {
|
||||||
|
return a = a + b;
|
||||||
|
}
|
||||||
|
inline Note &operator++(Note &a) {
|
||||||
|
return a += NOTE_SEMITONE;
|
||||||
|
}
|
||||||
|
inline Note operator-(const Note a, const Note b) {
|
||||||
|
return toNote(int(a) - int(b));
|
||||||
|
}
|
||||||
|
inline Note &operator-=(Note &a, const Note b) {
|
||||||
|
return a = a - b;
|
||||||
|
}
|
||||||
|
inline Note &operator--(Note &a) {
|
||||||
|
return a -= NOTE_SEMITONE;
|
||||||
|
}
|
||||||
|
inline Note operator*(const Note a, const int b) {
|
||||||
|
return toNote(int(a) * b);
|
||||||
|
}
|
||||||
|
inline int operator/(const Note a, const int b) {
|
||||||
|
return int(a)/b;
|
||||||
|
}
|
||||||
|
inline int operator/(const Note a, const Note b) {
|
||||||
|
return int(a)/b;
|
||||||
|
}
|
327
uilleann.ino
327
uilleann.ino
|
@ -1,80 +1,86 @@
|
||||||
#include <Audio.h>
|
|
||||||
#include <Wire.h>
|
|
||||||
#include <Adafruit_GFX.h>
|
#include <Adafruit_GFX.h>
|
||||||
#include <Adafruit_SSD1306.h>
|
|
||||||
#include <SparkFun_Qwiic_Button.h>
|
|
||||||
#include <Adafruit_MPR121.h>
|
#include <Adafruit_MPR121.h>
|
||||||
#include <paj7620.h>
|
#include <Adafruit_SSD1306.h>
|
||||||
|
#include <Audio.h>
|
||||||
#include <Fonts/FreeSans9pt7b.h>
|
#include <Fonts/FreeSans9pt7b.h>
|
||||||
#include "synth.h"
|
#include <SparkFun_Qwiic_Button.h>
|
||||||
|
#include <Wire.h>
|
||||||
|
#include <paj7620.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
#include "patches.h"
|
#include "patches.h"
|
||||||
#include "notes.h"
|
|
||||||
#include "pipe.h"
|
#include "pipe.h"
|
||||||
|
#include "synth.h"
|
||||||
|
#include "tuning.h"
|
||||||
|
|
||||||
#if defined(ADAFRUIT_TRELLIS_M4_EXPRESS)
|
#if defined(ADAFRUIT_TRELLIS_M4_EXPRESS)
|
||||||
#include <Adafruit_NeoTrellisM4.h>
|
#include <Adafruit_NeoTrellisM4.h>
|
||||||
Adafruit_NeoTrellisM4 trellis;// = Adafruit_NeoTrellisM4();
|
Adafruit_NeoTrellisM4 trellis; // = Adafruit_NeoTrellisM4();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#define DRONES
|
|
||||||
#define DEBUG false
|
|
||||||
|
|
||||||
Pipe pipe;
|
Pipe pipe;
|
||||||
|
Tuning tuning = Tuning(NOTE_D4, PITCH_CONCERT_D4, TUNINGSYSTEM_JUST);
|
||||||
|
|
||||||
Adafruit_SSD1306 display(128, 32, &Wire, -1);
|
Adafruit_SSD1306 display(128, 32, &Wire, -1);
|
||||||
int currentPatch = 0;
|
int currentPatch = 0;
|
||||||
|
|
||||||
|
// Settings
|
||||||
|
uint8_t intonation = 0;
|
||||||
|
int16_t pitchAdjust = 0;
|
||||||
|
uint8_t patch[4] = {0};
|
||||||
|
float volume[4] = {0};
|
||||||
|
const char *settingNames[4] = {"c", "r", "d", "*"};
|
||||||
|
|
||||||
|
// Pipes
|
||||||
FMVoice Chanter;
|
FMVoice Chanter;
|
||||||
FMVoice Drones[3];
|
FMVoice Drones[3];
|
||||||
FMVoice Regulators[3];
|
FMVoice Regulators[3];
|
||||||
|
|
||||||
AudioFilterBiquad biquad1;
|
AudioFilterBiquad biquad1;
|
||||||
AudioMixer4 mixDrones;
|
AudioMixer4 mixDrones;
|
||||||
AudioMixer4 mixRegulators;
|
AudioMixer4 mixRegulators;
|
||||||
AudioMixer4 mixL;
|
AudioMixer4 mixL;
|
||||||
AudioMixer4 mixR;
|
AudioMixer4 mixR;
|
||||||
AudioSynthNoiseWhite noise;
|
AudioSynthNoiseWhite noise;
|
||||||
|
|
||||||
#if defined(ADAFRUIT_TRELLIS_M4_EXPRESS)
|
#if defined(ADAFRUIT_TRELLIS_M4_EXPRESS)
|
||||||
AudioOutputAnalogStereo out1;
|
AudioOutputAnalogStereo out1;
|
||||||
#else
|
#else
|
||||||
AudioOutputI2S out1;
|
AudioOutputI2S out1;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
AudioControlSGTL5000 sgtl5000;
|
AudioControlSGTL5000 sgtl5000;
|
||||||
|
|
||||||
AudioConnection FMVoicePatchCords[] = {
|
AudioConnection FMVoicePatchCords[] = {
|
||||||
//{0, 0, 0, 0}, // For some reason, the first one is ignored
|
{noise, 0, mixL, 3},
|
||||||
|
{noise, 0, mixR, 3},
|
||||||
|
|
||||||
// {noise, 0, mixDrones, 3},
|
{Chanter.outputMixer, 0, biquad1, 0},
|
||||||
{noise, 0, mixL, 3},
|
{biquad1, 0, mixL, 0},
|
||||||
{noise, 0, mixR, 3},
|
{biquad1, 0, mixR, 0},
|
||||||
|
|
||||||
{Chanter.outputMixer, 0, biquad1, 0},
|
{Drones[0].outputMixer, 0, mixDrones, 0},
|
||||||
{biquad1, 0, mixL, 0},
|
{Drones[1].outputMixer, 0, mixDrones, 1},
|
||||||
{biquad1, 0, mixR, 0},
|
{Drones[2].outputMixer, 0, mixDrones, 2},
|
||||||
|
{mixDrones, 0, mixL, 1},
|
||||||
|
{mixDrones, 0, mixR, 1},
|
||||||
|
|
||||||
{Drones[0].outputMixer, 0, mixDrones, 0},
|
{Regulators[0].outputMixer, 0, mixRegulators, 0},
|
||||||
{Drones[1].outputMixer, 0, mixDrones, 1},
|
{Regulators[1].outputMixer, 0, mixRegulators, 1},
|
||||||
{Drones[2].outputMixer, 0, mixDrones, 2},
|
{Regulators[2].outputMixer, 0, mixRegulators, 2},
|
||||||
{mixDrones, 0, mixL, 1},
|
{mixRegulators, 0, mixL, 2},
|
||||||
{mixDrones, 0, mixR, 1},
|
{mixRegulators, 0, mixR, 2},
|
||||||
|
|
||||||
{Regulators[0].outputMixer, 0, mixRegulators, 0},
|
{mixL, 0, out1, 0},
|
||||||
{Regulators[1].outputMixer, 0, mixRegulators, 1},
|
{mixR, 0, out1, 1},
|
||||||
{Regulators[2].outputMixer, 0, mixRegulators, 2},
|
|
||||||
{mixRegulators, 0, mixL, 2},
|
|
||||||
{mixRegulators, 0, mixR, 2},
|
|
||||||
|
|
||||||
{mixL, 0, out1, 0},
|
FMVoiceWiring(Chanter),
|
||||||
{mixR, 0, out1, 1},
|
FMVoiceWiring(Drones[0]),
|
||||||
|
FMVoiceWiring(Drones[1]),
|
||||||
FMVoiceWiring(Chanter),
|
FMVoiceWiring(Drones[2]),
|
||||||
FMVoiceWiring(Drones[0]),
|
FMVoiceWiring(Regulators[0]),
|
||||||
FMVoiceWiring(Drones[1]),
|
FMVoiceWiring(Regulators[1]),
|
||||||
FMVoiceWiring(Drones[2]),
|
FMVoiceWiring(Regulators[2]),
|
||||||
FMVoiceWiring(Regulators[0]),
|
|
||||||
FMVoiceWiring(Regulators[1]),
|
|
||||||
FMVoiceWiring(Regulators[2]),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
void blink(bool forever) {
|
void blink(bool forever) {
|
||||||
|
@ -83,13 +89,48 @@ void blink(bool forever) {
|
||||||
delay(200);
|
delay(200);
|
||||||
digitalWrite(LED_BUILTIN, false);
|
digitalWrite(LED_BUILTIN, false);
|
||||||
delay(200);
|
delay(200);
|
||||||
if (! forever) break;
|
if (!forever) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void diag(const char *fmt, ...) {
|
||||||
|
va_list args;
|
||||||
|
char s[80];
|
||||||
|
|
||||||
|
va_start(args, fmt);
|
||||||
|
vsnprintf(s, sizeof(s)-1, fmt, args);
|
||||||
|
va_end(args);
|
||||||
|
|
||||||
|
display.clearDisplay();
|
||||||
|
display.drawRect(124, 16, 4, 16, SSD1306_WHITE);
|
||||||
|
display.setTextColor(SSD1306_WHITE);
|
||||||
|
display.setTextSize(1);
|
||||||
|
|
||||||
|
display.setCursor(56, 24);
|
||||||
|
display.print(__DATE__);
|
||||||
|
|
||||||
|
#if 0
|
||||||
|
display.setCursor(0, 16);
|
||||||
|
display.print(fn);
|
||||||
|
display.print(":");
|
||||||
|
display.print(lineno);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
display.setCursor(0, 0);
|
||||||
|
display.print(s);
|
||||||
|
|
||||||
|
display.display();
|
||||||
|
}
|
||||||
|
|
||||||
void setup() {
|
void setup() {
|
||||||
// Wire.begin needs a moment, so let's do some math.
|
// Initialize settings
|
||||||
setupJustPitches(NOTE_D4, PITCH_D4);
|
// XXX: Read these from persistent storage later
|
||||||
|
for (int i = 0; i < 4; i++) {
|
||||||
|
patch[i] = 0;
|
||||||
|
volume[i] = 0.75;
|
||||||
|
}
|
||||||
|
|
||||||
Wire.begin();
|
Wire.begin();
|
||||||
|
|
||||||
|
@ -101,131 +142,62 @@ void setup() {
|
||||||
if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3c)) {
|
if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3c)) {
|
||||||
blink(true);
|
blink(true);
|
||||||
}
|
}
|
||||||
display.clearDisplay();
|
diag("Hello!");
|
||||||
display.setTextSize(1);
|
|
||||||
display.setTextColor(SSD1306_WHITE);
|
|
||||||
display.print("Starting");
|
|
||||||
display.display();
|
|
||||||
|
|
||||||
#if defined(ADAFRUIT_TRELLIS_M4_EXPRESS)
|
#if defined(ADAFRUIT_TRELLIS_M4_EXPRESS)
|
||||||
|
diag("Trellis...");
|
||||||
trellis.begin();
|
trellis.begin();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
diag("Pipe...");
|
||||||
while (!pipe.Init()) {
|
while (!pipe.Init()) {
|
||||||
display.clearDisplay();
|
diag("No pipe. Is it connected?");
|
||||||
display.setCursor(0, 0);
|
|
||||||
display.print("Pipe?");
|
|
||||||
display.display();
|
|
||||||
blink(false);
|
blink(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set aside some memory for the audio library
|
diag("Audio...");
|
||||||
|
// Set aside some memory for the audio library
|
||||||
AudioMemory(20);
|
AudioMemory(20);
|
||||||
|
|
||||||
// Set up the SGTL5000 using I2C
|
// Set up the SGTL5000 using I2C
|
||||||
sgtl5000.enable();
|
sgtl5000.enable();
|
||||||
sgtl5000.volume(0.3);
|
sgtl5000.volume(0.3);
|
||||||
|
|
||||||
// initialize tunables
|
|
||||||
updateTunables(3, 0);
|
|
||||||
|
|
||||||
// Initialize processor and memory measurements
|
// Initialize processor and memory measurements
|
||||||
AudioProcessorUsageMaxReset();
|
AudioProcessorUsageMaxReset();
|
||||||
AudioMemoryUsageMaxReset();
|
AudioMemoryUsageMaxReset();
|
||||||
|
|
||||||
|
diag("Drones...");
|
||||||
// Turn on drones
|
// Turn on drones
|
||||||
for (int i=0; i<3; i++) {
|
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].LoadPatch(&Bank[0]);
|
||||||
Drones[i].NoteOn(JustPitches[NOTE_D4 - 12*i] + i);
|
Drones[i].NoteOn(pitch);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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, 0.5);
|
||||||
mixR.gain(i, 0.6);
|
mixR.gain(i, 0.6);
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef DEBUG
|
|
||||||
noise.amplitude(0.1);
|
|
||||||
mixL.gain(3, 0.1);
|
|
||||||
mixR.gain(3, 0.1);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
display.clearDisplay();
|
diag("Done!");
|
||||||
display.setCursor(0, 0);
|
|
||||||
display.print("Done!");
|
|
||||||
display.display();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#define INIT_PITCH_ADJUST 0
|
|
||||||
#define INIT_GAIN 0.7
|
|
||||||
#define INIT_PATCH 0
|
|
||||||
|
|
||||||
int16_t pitchAdjust;
|
|
||||||
float chanterGain;
|
|
||||||
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:
|
|
||||||
pitchAdjust -= 4;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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:
|
|
||||||
chanterGain = INIT_GAIN;
|
|
||||||
break;
|
|
||||||
case 2:
|
|
||||||
chanterGain = min(chanterGain+0.005, 1.0);
|
|
||||||
break;
|
|
||||||
case 1:
|
|
||||||
chanterGain = max(chanterGain-0.005, 0.0);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (int i=0; i<3; i++) {
|
|
||||||
mixL.gain(i, chanterGain);
|
|
||||||
mixR.gain(i, chanterGain);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!note || (note == NOTE_CS5)) {
|
|
||||||
if (buttons == 3) {
|
|
||||||
patch = INIT_PATCH;
|
|
||||||
}
|
|
||||||
|
|
||||||
// wrap
|
|
||||||
int bankSize = sizeof(Bank) / sizeof(Bank[0]);
|
|
||||||
patch = (patch + bankSize) % bankSize;
|
|
||||||
|
|
||||||
FMPatch *p = &Bank[patch];
|
|
||||||
Chanter.LoadPatch(p);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void loop() {
|
void loop() {
|
||||||
pipe.Update();
|
pipe.Update();
|
||||||
|
diag("loop %d", millis());
|
||||||
|
|
||||||
#if defined(ADAFRUIT_TRELLIS_M4_EXPRESS)
|
#if defined(ADAFRUIT_TRELLIS_M4_EXPRESS)
|
||||||
trellis.tick();
|
trellis.tick();
|
||||||
|
|
||||||
trellis.setPixelColor(1, trellis.ColorHSV(millis(), 255, 120));
|
trellis.setPixelColor(1, trellis.ColorHSV(millis(), 255, 120));
|
||||||
trellis.setPixelColor(0, trellis.ColorHSV(64*pipe.kneeClosedness, 255, 120));
|
{
|
||||||
|
uint16_t color = trellis.ColorHSV(64 * pipe.kneeClosedness, 255, 120);
|
||||||
|
trellis.setPixelColor(0, color);
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// If we're infinitely (for the sensor) off the knee,
|
// If we're infinitely (for the sensor) off the knee,
|
||||||
|
@ -240,7 +212,7 @@ void loop() {
|
||||||
/** doSetup performs "setup mode" behavior for the pipe.
|
/** doSetup performs "setup mode" behavior for the pipe.
|
||||||
*
|
*
|
||||||
* Setup mode sets the following new meanings to the buttons:
|
* Setup mode sets the following new meanings to the buttons:
|
||||||
*
|
*
|
||||||
* key: function [alternate]
|
* key: function [alternate]
|
||||||
* C♯: Alt
|
* C♯: Alt
|
||||||
* B♮: Chanter
|
* B♮: Chanter
|
||||||
|
@ -250,21 +222,66 @@ void loop() {
|
||||||
* E♮: Down [- coarse]
|
* E♮: Down [- coarse]
|
||||||
* E♭: + [+ fine]
|
* E♭: + [+ fine]
|
||||||
* D♮: - [- fine]
|
* D♮: - [- fine]
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
void doSetup() {
|
void doSetup() {
|
||||||
display.clearDisplay();
|
display.clearDisplay();
|
||||||
|
|
||||||
bool alt = bitRead(pipe.keys, 7);
|
bool alt = pipe.Pressed(7);
|
||||||
|
|
||||||
|
// Draw indicator bar
|
||||||
|
display.fillRect(126, 0, 2, 32, SSD1306_WHITE);
|
||||||
|
display.setTextSize(1);
|
||||||
|
display.setCursor(0, 0);
|
||||||
|
|
||||||
display.fillRect(0, 0, 40, 32, SSD1306_WHITE);
|
|
||||||
display.setFont(&FreeSans9pt7b);
|
|
||||||
display.setCursor(1, 13);
|
|
||||||
display.setTextColor(SSD1306_BLACK);
|
|
||||||
if (alt) {
|
if (alt) {
|
||||||
display.print("pipe");
|
// Show settings for each of Chanter, Regulators, Drones
|
||||||
|
for (int i = 0; i < 3; i++) {
|
||||||
|
int p = patch[i];
|
||||||
|
int16_t x = 0;
|
||||||
|
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 {
|
} else {
|
||||||
display.print("alt");
|
if (pipe.Pressed(6)) {
|
||||||
|
float freq = PITCH_CONCERT_D4 + pitchAdjust;
|
||||||
|
Note note = NearestNote(freq);
|
||||||
|
|
||||||
|
display.setCursor(0, 0);
|
||||||
|
display.print(NoteName(note));
|
||||||
|
display.setCursor(6 + 6, 0);
|
||||||
|
display.print(freq);
|
||||||
|
} else if (pipe.Pressed(5)) {
|
||||||
|
display.print("fn2");
|
||||||
|
} else if (pipe.Pressed(4)) {
|
||||||
|
display.print("fn3");
|
||||||
|
} else {
|
||||||
|
display.setCursor(56, 8);
|
||||||
|
display.setTextSize(2);
|
||||||
|
display.print("Setup");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
display.display();
|
display.display();
|
||||||
|
@ -278,8 +295,8 @@ void doPlay() {
|
||||||
Chanter.NoteOff();
|
Chanter.NoteOff();
|
||||||
} else {
|
} else {
|
||||||
// Calculate pitch, and glissando pitch
|
// Calculate pitch, and glissando pitch
|
||||||
uint16_t pitch = JustPitches[pipe.note];
|
uint16_t pitch = tuning.GetPitch(pipe.note);
|
||||||
uint16_t glissandoPitch = JustPitches[pipe.glissandoNote];
|
uint16_t glissandoPitch = tuning.GetPitch(pipe.glissandoNote);
|
||||||
|
|
||||||
// Bend pitch if fewer than 3 half steps away
|
// Bend pitch if fewer than 3 half steps away
|
||||||
if (abs(pipe.glissandoNote - pipe.note) < 3) {
|
if (abs(pipe.glissandoNote - pipe.note) < 3) {
|
||||||
|
@ -287,13 +304,13 @@ void doPlay() {
|
||||||
pitch += diff * pipe.glissandoOpenness;
|
pitch += diff * pipe.glissandoOpenness;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apply a low shelf filter if this is the alternate fingering
|
// Apply a low shelf filter if this is the alternate fingering
|
||||||
if (pipe.altFingering) {
|
if (pipe.altFingering) {
|
||||||
biquad1.setLowShelf(0, 2000, 0.2, 1);
|
biquad1.setLowShelf(0, 2000, 0.2, 1);
|
||||||
} else {
|
} else {
|
||||||
biquad1.setHighShelf(0, 1000, 1.0, 1);
|
biquad1.setHighShelf(0, 1000, 1.0, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
// We've figured out what pitch to play, now we can play it.
|
// We've figured out what pitch to play, now we can play it.
|
||||||
if (Chanter.playing) {
|
if (Chanter.playing) {
|
||||||
Chanter.SetPitch(pitch);
|
Chanter.SetPitch(pitch);
|
||||||
|
@ -303,11 +320,11 @@ void doPlay() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Look up the note name
|
// Look up the note name
|
||||||
const char *note_name = NoteNames[pipe.note % 12];
|
const char *note_name = NoteName(pipe.note);
|
||||||
if (pipe.silent) {
|
if (pipe.silent) {
|
||||||
note_name = "--";
|
note_name = "--";
|
||||||
updateDisplay = true;
|
updateDisplay = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pipe.note != last_note) {
|
if (pipe.note != last_note) {
|
||||||
updateDisplay = true;
|
updateDisplay = true;
|
||||||
|
@ -319,7 +336,7 @@ void doPlay() {
|
||||||
display.setCursor(0, 16);
|
display.setCursor(0, 16);
|
||||||
display.setTextSize(2);
|
display.setTextSize(2);
|
||||||
display.print(Chanter.patch->name);
|
display.print(Chanter.patch->name);
|
||||||
|
|
||||||
display.setCursor(0, 0);
|
display.setCursor(0, 0);
|
||||||
display.setTextSize(2);
|
display.setTextSize(2);
|
||||||
display.print(note_name);
|
display.print(note_name);
|
||||||
|
@ -329,6 +346,6 @@ void doPlay() {
|
||||||
display.print(pipe.kneeClosedness);
|
display.print(pipe.kneeClosedness);
|
||||||
|
|
||||||
display.display();
|
display.display();
|
||||||
last_note = pipe.note;
|
last_note = pipe.note;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue