Now you can adjust pitch and intonation

This commit is contained in:
Neale Pickett 2020-11-24 21:33:55 -07:00
parent c915b0ee69
commit 036dcf592e
7 changed files with 448 additions and 267 deletions

3
.clang-format Normal file
View File

@ -0,0 +1,3 @@
BasedOnStyle: Chromium
ColumnLimit: 0
PointerAlignment: Right

View File

@ -6,8 +6,10 @@ struct Fingering {
bool alt; // Alternate fingering: sounds more choked bool alt; // Alternate fingering: sounds more choked
}; };
#define n(note) {note, false} #define n(note) \
#define P(note) {note, true} { note, false }
#define P(note) \
{ 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)

View File

@ -1,6 +1,6 @@
#include "pipe.h" #include "pipe.h"
#include "tuning.h"
#include "fingering.h" #include "fingering.h"
#include "tuning.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.
#define KEY_OFFSET 2 #define KEY_OFFSET 2
@ -49,8 +49,8 @@ void Pipe::Update() {
keysLast = keys; keysLast = keys;
keys = 0; keys = 0;
glissandoKeys = 0; glissandoKeys = 0;
for (int i=0; i<8; i++) { for (int i = 0; i < 8; i++) {
uint16_t val = max(capSensor.filteredData(i+KEY_OFFSET), CLOSEDVAL); 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

8
pipe.h
View File

@ -1,13 +1,13 @@
#pragma once #pragma once
#include <stdint.h>
#include <SparkFun_Qwiic_Button.h>
#include <Adafruit_MPR121.h> #include <Adafruit_MPR121.h>
#include <SparkFun_Qwiic_Button.h>
#include <paj7620.h> #include <paj7620.h>
#include <stdint.h>
#include "tuning.h" #include "tuning.h"
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;
@ -52,7 +52,7 @@ public:
// 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);
private: private:
Adafruit_MPR121 capSensor; Adafruit_MPR121 capSensor;
QwiicButton bagSensor; QwiicButton bagSensor;
bool bag_enabled; bool bag_enabled;

View File

@ -2,9 +2,6 @@
#include <math.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) { Tuning::Tuning(Note base, float pitch, TuningSystem system) {
Setup(base, pitch, system); Setup(base, pitch, system);
} }
@ -14,18 +11,20 @@ Tuning::Tuning(Note base, float pitch) {
// I like just Intonation. // I like just Intonation.
Tuning::Tuning() { Tuning::Tuning() {
#if 0
Tuning(NOTE_D4, PITCH_CONCERT_D4, TUNINGSYSTEM_JUST); Tuning(NOTE_D4, PITCH_CONCERT_D4, TUNINGSYSTEM_JUST);
#endif
} }
Note Tuning::GetBaseNote() { return baseNote; } Note Tuning::GetBaseNote() {
return baseNote;
}
void Tuning::SetTuningSystem(TuningSystem system) { void Tuning::SetTuningSystem(TuningSystem system) {
Setup(baseNote, GetPitch(baseNote), system); Setup(baseNote, GetPitch(baseNote), system);
} }
TuningSystem Tuning::GetSystem() { return system; } TuningSystem Tuning::GetTuningSystem() {
return system;
}
// setupOctaves computes the entire tuning frequency chart. // setupOctaves computes the entire tuning frequency chart.
// //
@ -53,7 +52,7 @@ void Tuning::setupOctaves(Note base) {
void Tuning::setupEqual(Note base, float pitch) { void Tuning::setupEqual(Note base, float pitch) {
pitches[base] = pitch; pitches[base] = pitch;
for (int i = 1; i < 12; i++) { for (int i = 1; i < 12; i++) {
pitches[base + i] = pitches[base + i - 1] * TET_CONST; pitches[base + i] = pitches[base + i - 1] * TET_SEMITONE_MULTIPLIER;
} }
} }
@ -95,17 +94,43 @@ void Tuning::Setup(Note base, float pitch, TuningSystem system) {
setupOctaves(base); setupOctaves(base);
} }
void Tuning::Setup(Note base, float pitch) { Setup(base, pitch, system); } void Tuning::Setup(Note base, float pitch) {
Setup(base, pitch, system);
}
float Tuning::GetPitch(Note note) { return pitches[note]; } float Tuning::GetPitch(Note note) {
return pitches[note];
}
Note NearestNote(float pitch) { Note NearestNote(float pitch) {
return Note(round(log2(pitch / PITCH_CONCERT_C0))); return Note(round(log(pitch / PITCH_CONCERT_C0) / log(TET_SEMITONE_MULTIPLIER)));
} }
const char *noteNames[]{ const char *noteNames[]{
"C ", "C#", "D ", "Eb", "E ", "F ", "F#", "G ", "Ab", "A ", "Bb", "B ", "C",
"C#",
"D",
"Eb",
"E",
"F",
"F#",
"G",
"Ab",
"A",
"Bb",
"B",
}; };
const char *NoteName(Note note) { return noteNames[note % 12]; } const char *NoteName(Note note) {
return noteNames[note % 12];
}
const char *TuningSystemName(TuningSystem system) {
switch (system) {
case TUNINGSYSTEM_EQUAL:
return "Equal";
case TUNINGSYSTEM_JUST:
default:
return "Just";
}
}

144
tuning.h
View File

@ -4,19 +4,118 @@
enum TuningSystem { enum TuningSystem {
TUNINGSYSTEM_JUST, TUNINGSYSTEM_JUST,
TUNINGSYSTEM_EQUAL, TUNINGSYSTEM_EQUAL,
TUNINGSYSTEM_MAX = TUNINGSYSTEM_EQUAL,
}; };
#define TUNINGSYSTEM_MAX TUNINGSYSTEM_EQUAL
enum Note { 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_C0,
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_CS0,
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_D0,
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_DS0,
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_E0,
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_F0,
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_FS0,
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_G0,
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_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_ZERO = 0,
NOTE_SEMITONE = 1, NOTE_SEMITONE = 1,
NOTE_WHOLETONE = 2, NOTE_WHOLETONE = 2,
@ -28,8 +127,15 @@ enum Note {
#define PITCH_CONCERT_A4 440.00 #define PITCH_CONCERT_A4 440.00
#define PITCH_CONCERT_D4 293.66 #define PITCH_CONCERT_D4 293.66
// Twelvetone Equal Temperament semitone multiplier
// Take any frequency and multiply it by this magic number to get a semitone higher!
// Divide to get a semitone lower!
// This is an approximation of exp(2, 1/12),
// which was worked out in around the 1500s.
#define TET_SEMITONE_MULTIPLIER 1.059463
class Tuning { class Tuning {
public: public:
// name contains the name of the current tuning system // name contains the name of the current tuning system
const char *name; const char *name;
@ -39,11 +145,11 @@ public:
void Setup(Note base, float pitch, TuningSystem system); void Setup(Note base, float pitch, TuningSystem system);
void Setup(Note base, float pitch); void Setup(Note base, float pitch);
void SetTuningSystem(TuningSystem system); void SetTuningSystem(TuningSystem system);
TuningSystem GetSystem(); TuningSystem GetTuningSystem();
Note GetBaseNote(); Note GetBaseNote();
float GetPitch(Note note); float GetPitch(Note note);
private: private:
TuningSystem system; TuningSystem system;
Note baseNote; Note baseNote;
float pitches[NOTE_MAX]; float pitches[NOTE_MAX];
@ -56,9 +162,11 @@ private:
// NearestNote returns the note nearest to pitch. // NearestNote returns the note nearest to pitch.
Note NearestNote(float pitch); Note NearestNote(float pitch);
// NoteName return 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);
// TuningSystemName returns the name of a tuning system.
const char *TuningSystemName(TuningSystem system);
// Make notes support some arithmetic // Make notes support some arithmetic
inline Note toNote(int a) { inline Note toNote(int a) {
@ -92,8 +200,12 @@ inline Note operator*(const Note a, const int b) {
return toNote(int(a) * b); return toNote(int(a) * b);
} }
inline int operator/(const Note a, const int b) { inline int operator/(const Note a, const int b) {
return int(a)/b; return int(a) / b;
} }
inline int operator/(const Note a, const Note b) { inline int operator/(const Note a, const Note b) {
return int(a)/b; return int(a) / b;
}
inline TuningSystem operator++(TuningSystem &a) {
return a = TuningSystem((int(a) + 1) % int(TUNINGSYSTEM_MAX + 1));
} }

View File

@ -3,7 +3,6 @@
#include <Adafruit_SSD1306.h> #include <Adafruit_SSD1306.h>
#include <Audio.h> #include <Audio.h>
#include <Fonts/FreeSans9pt7b.h> #include <Fonts/FreeSans9pt7b.h>
#include <SparkFun_Qwiic_Button.h>
#include <Wire.h> #include <Wire.h>
#include <paj7620.h> #include <paj7620.h>
#include <stdio.h> #include <stdio.h>
@ -22,14 +21,12 @@ Pipe pipe;
Tuning tuning = Tuning(NOTE_D4, PITCH_CONCERT_D4, TUNINGSYSTEM_JUST); 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;
// Settings // Settings
uint8_t intonation = 0;
int16_t pitchAdjust = 0;
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 *settingNames[4] = {"c", "r", "d", "*"};
const char *buildDate = __DATE__;
// Pipes // Pipes
FMVoice Chanter; FMVoice Chanter;
@ -100,7 +97,7 @@ void diag(const char *fmt, ...) {
char s[80]; char s[80];
va_start(args, fmt); va_start(args, fmt);
vsnprintf(s, sizeof(s)-1, fmt, args); vsnprintf(s, sizeof(s) - 1, fmt, args);
va_end(args); va_end(args);
display.clearDisplay(); display.clearDisplay();
@ -109,7 +106,7 @@ void diag(const char *fmt, ...) {
display.setTextSize(1); display.setTextSize(1);
display.setCursor(56, 24); display.setCursor(56, 24);
display.print(__DATE__); display.print(buildDate);
#if 0 #if 0
display.setCursor(0, 16); display.setCursor(0, 16);
@ -171,8 +168,7 @@ void setup() {
// 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); Note note = NOTE_D4 - (NOTE_OCTAVE * i);
float pitch = float pitch = tuning.GetPitch(note) * (0.01 * (i - 1)); // Detune just a touch
tuning.GetPitch(note) * (0.01 * (i - 1)); // Detune just a touch
Drones[i].LoadPatch(&Bank[0]); Drones[i].LoadPatch(&Bank[0]);
Drones[i].NoteOn(pitch); Drones[i].NoteOn(pitch);
} }
@ -187,25 +183,22 @@ void setup() {
} }
void loop() { void loop() {
static bool forceDisplayUpdate = true;
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));
{
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,
// go into setup mode! // go into setup mode!
if (pipe.kneeClosedness == 0) { if (pipe.kneeClosedness == 0) {
doSetup(); doSetup();
forceDisplayUpdate = true;
} else { } else {
doPlay(); doPlay(forceDisplayUpdate);
forceDisplayUpdate = false;
} }
} }
@ -225,12 +218,21 @@ void loop() {
* *
*/ */
void doSetup() { 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(); display.clearDisplay();
bool alt = pipe.Pressed(7); bool alt = pipe.Pressed(7);
// Draw indicator bar // Draw indicator bar
display.fillRect(126, 0, 2, 32, SSD1306_WHITE); display.fillRect(126, 0, 2, 32, SSD1306_WHITE);
display.setFont(0);
display.setTextSize(1); display.setTextSize(1);
display.setCursor(0, 0); display.setCursor(0, 0);
@ -238,7 +240,7 @@ void doSetup() {
// Show settings for each of Chanter, Regulators, Drones // Show settings for each of Chanter, Regulators, Drones
for (int i = 0; i < 3; i++) { for (int i = 0; i < 3; i++) {
int p = patch[i]; int p = patch[i];
int16_t x = 0; int16_t x = 1;
int16_t y = i * 8; int16_t y = i * 8;
display.setCursor(x, y); display.setCursor(x, y);
@ -265,31 +267,71 @@ void doSetup() {
display.print(Bank[p].name); display.print(Bank[p].name);
} }
} else { } else {
if (pipe.Pressed(6)) {
float freq = PITCH_CONCERT_D4 + pitchAdjust;
Note note = NearestNote(freq);
display.setCursor(0, 0); 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(NoteName(note));
display.setCursor(6 + 6, 0); display.print(octave);
display.setCursor(48, 12);
display.print(freq); display.print(freq);
display.setCursor(0, 27);
display.print(TuningSystemName(system));
} else if (pipe.Pressed(5)) { } else if (pipe.Pressed(5)) {
display.print("fn2"); display.print("fn2");
} else if (pipe.Pressed(4)) { } else if (pipe.Pressed(4)) {
display.print("fn3"); display.print("fn3");
} else { } else {
display.setCursor(56, 8); display.setFont(&FreeSans9pt7b);
display.setTextSize(2); display.setCursor(64, 18);
display.print("Setup"); display.print("Setup");
display.setFont();
display.setTextSize(1);
display.setCursor(0, 16);
display.print("build");
display.setCursor(0, 24);
display.print(buildDate);
} }
} }
display.display(); display.display();
} }
void doPlay() { void doPlay(bool forceUpdate) {
static uint8_t last_note = 0; static uint8_t last_note = 0;
bool updateDisplay = false; bool updateDisplay = forceUpdate;
if (pipe.silent) { if (pipe.silent) {
Chanter.NoteOff(); Chanter.NoteOff();
@ -332,19 +374,16 @@ void doPlay() {
if (updateDisplay) { if (updateDisplay) {
display.clearDisplay(); display.clearDisplay();
display.setFont(&FreeSans9pt7b);
if (Chanter.patch) {
display.setCursor(0, 32);
display.print(Chanter.patch->name);
}
display.setCursor(0, 16); display.setCursor(0, 16);
display.setTextSize(2);
display.print(Chanter.patch->name);
display.setCursor(0, 0);
display.setTextSize(2);
display.print(note_name); display.print(note_name);
display.setCursor(40, 0);
display.setTextSize(1);
display.print(pipe.kneeClosedness);
display.display(); display.display();
last_note = pipe.note; last_note = pipe.note;
} }