Initial commit: works
This commit is contained in:
commit
96fc240f93
|
@ -0,0 +1,6 @@
|
|||
It's a Uilleann bagpipe with a 4-operator FM synthesizer behind it.
|
||||
Right now it uses an Adafrut M4 Trellis,
|
||||
but it should probably be retooled to use a Teensy with the
|
||||
audio shield on it.
|
||||
|
||||
I'll write some stuff up later if people show interest.
|
|
@ -0,0 +1,75 @@
|
|||
#define CCCC NOTE_CS5, NOTE_CS5, NOTE_CS5, NOTE_CS5
|
||||
#define CCDD NOTE_CS5, NOTE_CS5, NOTE_D5, NOTE_D5
|
||||
#define CDCD NOTE_CS5, NOTE_D5, NOTE_CS5, NOTE_D5
|
||||
#define DDDD NOTE_D5, NOTE_D5, NOTE_D5, NOTE_D5
|
||||
#define P 0x80
|
||||
|
||||
uint8_t uilleann_matrix[] = {
|
||||
// Open Back D
|
||||
NOTE_CS5, NOTE_CS5, NOTE_CS5, NOTE_D5, // OOO OO..
|
||||
CCDD, // OOO OX..
|
||||
CDCD, // OOO XO..
|
||||
DDDD, // OOO XX..
|
||||
CDCD, // OOX OO..
|
||||
DDDD, // OOX OX..
|
||||
CDCD, // OOX XO..
|
||||
DDDD, // OOX XX..
|
||||
CCDD, // OXO OO..
|
||||
CCDD, // OXO OX..
|
||||
DDDD, // OXO XO..
|
||||
DDDD, // OXO XX..
|
||||
DDDD, // OXX OO..
|
||||
DDDD, // OXX OX..
|
||||
DDDD, // OXX XO..
|
||||
DDDD, // OXX XX..
|
||||
CDCD, // XOO OO..
|
||||
DDDD, // XOO OX..
|
||||
CDCD, // XOO XO..
|
||||
DDDD, // XOO XX..
|
||||
CDCD, // XOX OO..
|
||||
DDDD, // XOX OX..
|
||||
CDCD, // XOX XO..
|
||||
DDDD, // XOX XX..
|
||||
DDDD, // XXO OO..
|
||||
DDDD, // XXO OX..
|
||||
DDDD, // XXO XO..
|
||||
DDDD, // XXO XX..
|
||||
DDDD, // XXX OO..
|
||||
DDDD, // XXX OX..
|
||||
NOTE_D5, NOTE_D5, NOTE_D5, NOTE_D5|P, // XXX XO..
|
||||
DDDD, // XXX XX..
|
||||
|
||||
// Closed Back D
|
||||
CCCC, // OOO OO...
|
||||
NOTE_CS5, NOTE_CS5, NOTE_CS5, NOTE_CS5|P, // OOO OX..
|
||||
CCCC, // OOO XO..
|
||||
CCCC, // OOO XX..
|
||||
CCCC, // OOX OO..
|
||||
NOTE_CS5, NOTE_CS5|P, NOTE_CS5, NOTE_CS5|P, // OOX OX..
|
||||
CCCC, // OOX XO..
|
||||
CCCC, // OOX XX..
|
||||
CCCC, // OXO OO..
|
||||
NOTE_CS5, NOTE_CS5|P, NOTE_CS5, NOTE_CS5|P, // OXO OX..
|
||||
CCCC, // OXO XO..
|
||||
CCCC, // OXO XX..
|
||||
NOTE_C5|P, NOTE_C5|P, NOTE_C5|P, NOTE_C5|P, // OXX OO..
|
||||
NOTE_C5, NOTE_C5, NOTE_C5, NOTE_C5, // OXX OX..
|
||||
NOTE_C5, NOTE_C5, NOTE_C5, NOTE_C5|P, // OXX XO..
|
||||
NOTE_C5, NOTE_C5, NOTE_C5, NOTE_CS5, // OXX XX..
|
||||
NOTE_B4, NOTE_B4, NOTE_B4, NOTE_B4, // XOO OO..
|
||||
NOTE_B4|P, NOTE_B4|P, NOTE_B4, NOTE_B4|P, // XOO OX..
|
||||
NOTE_AS4, NOTE_B4, NOTE_AS4, NOTE_B4, // XOO XO..
|
||||
NOTE_B4, NOTE_B4, NOTE_B4, NOTE_B4, // XOO XX..
|
||||
NOTE_B4|P, NOTE_B4|P, NOTE_B4|P, NOTE_B4|P, // XOX OO..
|
||||
NOTE_B4|P, NOTE_B4|P, NOTE_B4|P, NOTE_B4|P, // XOX OX..
|
||||
NOTE_B4|P, NOTE_B4|P, NOTE_B4|P, NOTE_B4|P, // XOX XO..
|
||||
NOTE_B4|P, NOTE_B4|P, NOTE_B4|P, NOTE_B4|P, // XOX XX..
|
||||
NOTE_A4, NOTE_A4, NOTE_A4|P, NOTE_A4, // XXO OO..
|
||||
NOTE_A4|P, NOTE_A4|P, NOTE_A4|P, NOTE_A4|P, // XXO OX..
|
||||
NOTE_GS4, NOTE_GS4|P, NOTE_A4, NOTE_A4, // XXO XO..
|
||||
NOTE_A4|P, NOTE_A4|P, NOTE_A4|P, NOTE_A4, // XXO XX..
|
||||
NOTE_G4, NOTE_G4, NOTE_G4|P, NOTE_G4, // XXX OO..
|
||||
NOTE_G4|P, NOTE_G4|P, NOTE_G4|P, NOTE_G4|P, // XXX OX..
|
||||
NOTE_FS4|P, NOTE_FS4|P, NOTE_F4, NOTE_FS4, // XXX XO..
|
||||
NOTE_E4, NOTE_E4|P, NOTE_DS4, NOTE_D4, // XXX XX..
|
||||
};
|
|
@ -0,0 +1,18 @@
|
|||
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;
|
||||
|
||||
const char *NoteNames[] {
|
||||
"C ", "C#", "D ", "Eb", "E ", "F ", "F#", "G ", "Ab", "A ", "Bb", "B ",
|
||||
};
|
||||
|
||||
#define PITCH_D4 293.66
|
|
@ -0,0 +1,50 @@
|
|||
typedef struct Operator {
|
||||
float gain;
|
||||
float delay;
|
||||
float attack;
|
||||
float hold;
|
||||
float decay;
|
||||
float sustain;
|
||||
float release;
|
||||
float baseFrequency;
|
||||
float multiplier;
|
||||
} Operator;
|
||||
|
||||
typedef struct Patch {
|
||||
char *name;
|
||||
Operator operators[4];
|
||||
float feedback;
|
||||
} Patch;
|
||||
|
||||
Patch Bank[] = {
|
||||
{
|
||||
"Venus Oboe",
|
||||
{
|
||||
{1.0, 0, 10.5, 0, 5000, 0.75, 100, 0, 1.00},
|
||||
{1.0, 0, 10.5, 0, 2000, 0.80, 100, 0, 4.00},
|
||||
{0.0, 0, 10.5, 0, 2000, 0.50, 100, 0, 8.00},
|
||||
{0.0, 0, 50.0, 0, 800, 0.75, 100, 0, 16.00},
|
||||
},
|
||||
0.0,
|
||||
},
|
||||
{
|
||||
"IWantPizza",
|
||||
{
|
||||
{1.0, 0, 10.5, 0, 5000, 0.35, 100, 0, 4.00},
|
||||
{1.0, 0, 10.5, 0, 2000, 0.30, 100, 0, 1.00},
|
||||
{1.0, 0, 10.5, 0, 2000, 0.50, 100, 0, 8.00},
|
||||
{1.0, 0, 200, 0, 800, 0.25, 100, 0, 16.00},
|
||||
},
|
||||
0.0,
|
||||
},
|
||||
{
|
||||
"Ray Gun",
|
||||
{
|
||||
{1.0, 0, 10.5, 0, 5000, 0.35, 2000, 0, 1.00},
|
||||
{1.0, 0, 10.5, 0, 2000, 0.30, 2000, 0, 1.00},
|
||||
{1.0, 0, 10.5, 0, 2000, 0.00, 2000, 0, 9.00},
|
||||
{1.0, 0, 200, 0, 800, 0.25, 800, 0, 1.00},
|
||||
},
|
||||
0.0,
|
||||
},
|
||||
};
|
|
@ -0,0 +1,38 @@
|
|||
#include <Audio.h>
|
||||
#include <Wire.h>
|
||||
#include <SPI.h>
|
||||
#include <SD.h>
|
||||
|
||||
// GUItool: begin automatically generated code
|
||||
AudioMixer4 feedback; //xy=110,37
|
||||
AudioSynthWaveformSineModulated osc4; //xy=112,98
|
||||
AudioSynthWaveformSineModulated osc2; //xy=112,194
|
||||
AudioSynthWaveformSineModulated osc1; //xy=112,245
|
||||
AudioSynthWaveformSineModulated osc3; //xy=113,146
|
||||
AudioMixer4 mixOp; //xy=114,418
|
||||
AudioEffectEnvelope env4; //xy=251,97
|
||||
AudioEffectEnvelope env3; //xy=251,146
|
||||
AudioEffectEnvelope env2; //xy=252,194
|
||||
AudioEffectEnvelope env1; //xy=252,245
|
||||
AudioFilterBiquad biquad1; //xy=257,418
|
||||
AudioMixer4 mixL; //xy=472,402
|
||||
AudioMixer4 mixR; //xy=473,498
|
||||
AudioOutputAnalogStereo dacs1; //xy=724,452
|
||||
AudioConnection patchCord1(feedback, osc4);
|
||||
AudioConnection patchCord2(osc4, env4);
|
||||
AudioConnection patchCord3(osc4, 0, feedback, 0);
|
||||
AudioConnection patchCord4(osc2, env2);
|
||||
AudioConnection patchCord5(osc1, env1);
|
||||
AudioConnection patchCord6(osc3, env3);
|
||||
AudioConnection patchCord7(mixOp, biquad1);
|
||||
AudioConnection patchCord8(env4, osc3);
|
||||
AudioConnection patchCord9(env4, 0, mixOp, 3);
|
||||
AudioConnection patchCord10(env3, 0, mixOp, 2);
|
||||
AudioConnection patchCord11(env2, osc1);
|
||||
AudioConnection patchCord12(env2, 0, mixOp, 1);
|
||||
AudioConnection patchCord13(env1, 0, mixOp, 0);
|
||||
AudioConnection patchCord14(biquad1, 0, mixL, 0);
|
||||
AudioConnection patchCord15(biquad1, 0, mixR, 0);
|
||||
AudioConnection patchCord17(mixL, 0, dacs1, 0);
|
||||
AudioConnection patchCord18(mixR, 0, dacs1, 1);
|
||||
// GUItool: end automatically generated code
|
|
@ -0,0 +1,281 @@
|
|||
#include <Audio.h>
|
||||
#include <Wire.h>
|
||||
#include <Adafruit_NeoTrellisM4.h>
|
||||
#include <SFE_MicroOLED.h>
|
||||
#include <Adafruit_MPR121.h>
|
||||
#include "synth.h"
|
||||
#include "patches.h"
|
||||
#include "notes.h"
|
||||
#include "fingering.h"
|
||||
|
||||
#define KNEE_OFFSET 0
|
||||
#define KEY_OFFSET 2
|
||||
|
||||
float cmaj_low[8] = { 130.81, 146.83, 164.81, 174.61, 196.00, 220.00, 246.94, 261.63 };
|
||||
float cmaj_high[8] = { 261.6, 293.7, 329.6, 349.2, 392.0, 440.0, 493.9, 523.3 };
|
||||
|
||||
AudioEffectEnvelope *envs[] = {&env1, &env2, &env3, &env4};
|
||||
AudioSynthWaveformSineModulated *oscs[] = {&osc1, &osc2, &osc3, &osc4};
|
||||
|
||||
int currentPatch = 0;
|
||||
|
||||
Adafruit_MPR121 cap = Adafruit_MPR121();
|
||||
Adafruit_NeoTrellisM4 trellis = Adafruit_NeoTrellisM4();
|
||||
MicroOLED oled(9, 1);
|
||||
|
||||
// Hat tip to Kyle Gann
|
||||
// https://www.kylegann.com/tuning.html
|
||||
float JustPitches[MaxNote + 1];
|
||||
void setupJustPitches(uint8_t baseNote, float basePitch) {
|
||||
JustPitches[baseNote + 0] = basePitch * 1 / 1;
|
||||
JustPitches[baseNote + 1] = basePitch * 16 / 15;
|
||||
JustPitches[baseNote + 2] = basePitch * 9 / 8;
|
||||
JustPitches[baseNote + 3] = basePitch * 6 / 5;
|
||||
JustPitches[baseNote + 4] = basePitch * 5 / 4;
|
||||
JustPitches[baseNote + 5] = basePitch * 4 / 3;
|
||||
JustPitches[baseNote + 6] = basePitch * 45 / 32;
|
||||
JustPitches[baseNote + 7] = basePitch * 3 / 2;
|
||||
JustPitches[baseNote + 8] = basePitch * 8 / 5;
|
||||
JustPitches[baseNote + 9] = basePitch * 5 / 3;
|
||||
JustPitches[baseNote + 10] = basePitch * 9 / 5;
|
||||
JustPitches[baseNote + 11] = basePitch * 15 / 8;
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void loadPatch(Patch p) {
|
||||
oled.clear(PAGE);
|
||||
oled.setFontType(0);
|
||||
oled.setCursor(0, 0);
|
||||
oled.print(p.name);
|
||||
oled.display();
|
||||
|
||||
for (int i=0; i<4; i++) {
|
||||
Operator op = p.operators[i];
|
||||
|
||||
oscs[i]->amplitude(op.gain);
|
||||
envs[i]->delay(op.delay);
|
||||
envs[i]->attack(op.attack);
|
||||
envs[i]->hold(op.hold);
|
||||
envs[i]->decay(op.decay);
|
||||
envs[i]->sustain(op.sustain);
|
||||
envs[i]->release(op.release);
|
||||
}
|
||||
feedback.gain(0, p.feedback);
|
||||
}
|
||||
|
||||
void setup(){
|
||||
setupJustPitches(NOTE_D4, PITCH_D4);
|
||||
pinMode(LED_BUILTIN, OUTPUT);
|
||||
|
||||
// Wire.begin needs a moment
|
||||
delay(100);
|
||||
Wire.begin();
|
||||
|
||||
// Initialize OLED display
|
||||
oled.begin();
|
||||
oled.clear(ALL);
|
||||
oled.display();
|
||||
|
||||
// Initialize touch sensor
|
||||
if (!cap.begin(0x5A)) {
|
||||
oled.clear(PAGE);
|
||||
oled.print("Can't find MPR121");
|
||||
oled.display();
|
||||
|
||||
bool on = HIGH;
|
||||
while (1) {
|
||||
digitalWrite(LED_BUILTIN, on);
|
||||
on = ! on;
|
||||
delay(200);
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize the Trellis
|
||||
trellis.begin();
|
||||
trellis.setBrightness(20);
|
||||
|
||||
AudioMemory(120);
|
||||
|
||||
// Turn it down, man
|
||||
mixL.gain(0, 0.05);
|
||||
mixR.gain(0, 0.05);
|
||||
|
||||
// Set up high shelf filter, for vibrato effects
|
||||
biquad1.setHighShelf(0, 1100, 1.0, 1);
|
||||
|
||||
// load the patch
|
||||
loadPatch(Bank[0]);
|
||||
|
||||
// Initialize processor and memory measurements
|
||||
AudioProcessorUsageMaxReset();
|
||||
AudioMemoryUsageMaxReset();
|
||||
}
|
||||
|
||||
void wtfSetPitch(float freq) {
|
||||
for (int i=0; i<4; i++) {
|
||||
Operator op = Bank[currentPatch].operators[i];
|
||||
oscs[i]->frequency(op.baseFrequency + freq*op.multiplier);
|
||||
}
|
||||
}
|
||||
|
||||
void setPitch(float freq) {
|
||||
for (int i=0; i<4; i++) {
|
||||
Operator op = Bank[currentPatch].operators[i];
|
||||
oscs[i]->frequency(op.baseFrequency + freq*op.multiplier);
|
||||
}
|
||||
}
|
||||
|
||||
void noteOn(float freq) {
|
||||
AudioNoInterrupts();
|
||||
for (int i=0; i<4; i++) {
|
||||
Operator op = Bank[currentPatch].operators[i];
|
||||
oscs[i]->frequency(op.baseFrequency + freq*op.multiplier);
|
||||
envs[i]->noteOn();
|
||||
}
|
||||
AudioInterrupts();
|
||||
}
|
||||
|
||||
void noteOff() {
|
||||
AudioNoInterrupts();
|
||||
for (int i=0; i<4; i++) {
|
||||
envs[i]->noteOff();
|
||||
}
|
||||
AudioInterrupts();
|
||||
}
|
||||
|
||||
void trellisLoop() {
|
||||
trellis.tick();
|
||||
|
||||
while (trellis.available()) {
|
||||
keypadEvent e = trellis.read();
|
||||
int keyindex = e.bit.KEY;
|
||||
if (e.bit.EVENT == KEY_JUST_PRESSED) {
|
||||
trellis.setPixelColor(keyindex, 0x600000);
|
||||
noteOn(JustPitches[NOTE_D3 + keyindex]);
|
||||
} else if (e.bit.EVENT == KEY_JUST_RELEASED){
|
||||
noteOff();
|
||||
trellis.setPixelColor(keyindex, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#define CLOSEDVAL 0x30
|
||||
#define OPENVAL 0x70
|
||||
|
||||
bool playing = false;
|
||||
|
||||
void loop() {
|
||||
uint8_t keys = 0;
|
||||
uint8_t note;
|
||||
uint8_t gkeys = 0;
|
||||
uint8_t gnote;
|
||||
uint8_t gaffinity = 0;
|
||||
bool knee = true;
|
||||
bool bag = false;
|
||||
bool silent = false;
|
||||
|
||||
trellisLoop();
|
||||
|
||||
oled.clear(PAGE);
|
||||
for (int i = 0; i < 8; i++) {
|
||||
uint16_t val = cap.filteredData(i+KEY_OFFSET);
|
||||
uint8_t c = 0;
|
||||
if (val < OPENVAL) {
|
||||
bitSet(keys, i);
|
||||
c = 7;
|
||||
|
||||
// If they're just sort of touching it, we're doing a glissando!
|
||||
if (val > CLOSEDVAL) {
|
||||
int aff = val - CLOSEDVAL;
|
||||
|
||||
gaffinity = max(gaffinity, aff);
|
||||
c = 7 - (7 * aff / (OPENVAL - CLOSEDVAL));
|
||||
} else {
|
||||
bitSet(gkeys, i);
|
||||
}
|
||||
}
|
||||
// print key states
|
||||
oled.rectFill(32 - 4*i, 40, 3, c);
|
||||
}
|
||||
|
||||
note = uilleann_matrix[keys];
|
||||
gnote = uilleann_matrix[gkeys];
|
||||
|
||||
knee = cap.filteredData(KNEE_OFFSET) < CLOSEDVAL;
|
||||
bool alt = note & 0x80;
|
||||
bool galt = gnote & 0x80;
|
||||
note = note & 0x7f;
|
||||
gnote = gnote & 0x7f;
|
||||
|
||||
// All keys closed + knee = no sound
|
||||
if (knee) {
|
||||
oled.rectFill(36, 42, 3, 3);
|
||||
if (keys == 0xff) {
|
||||
silent = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Jump octave if the bag is squished
|
||||
//bag = !digitalRead(BAG);
|
||||
oled.setCursor(9*6, 10);
|
||||
if (bag) {
|
||||
oled.print("^");
|
||||
if (keys & bit(7)) {
|
||||
note += 12;
|
||||
gnote += 12;
|
||||
}
|
||||
} else {
|
||||
oled.print(" ");
|
||||
}
|
||||
|
||||
if (silent) {
|
||||
noteOff();
|
||||
playing = false;
|
||||
} else {
|
||||
// Calculate pitch, and glissando pitch
|
||||
uint16_t pitch = JustPitches[note];
|
||||
uint16_t gpitch = JustPitches[gnote];
|
||||
|
||||
if (alt) {
|
||||
biquad1.setHighShelf(0, 1000, 0.5, 1);
|
||||
} else {
|
||||
biquad1.setHighShelf(0, 1000, 1.0, 1);
|
||||
}
|
||||
|
||||
// Bend pitch
|
||||
if (gaffinity && (abs(gnote - note) < 3)) {
|
||||
uint32_t sum = (pitch * (OPENVAL-CLOSEDVAL)) + (gpitch * gaffinity);
|
||||
pitch = sum / ((OPENVAL-CLOSEDVAL) + gaffinity);
|
||||
}
|
||||
|
||||
if (playing) {
|
||||
setPitch(pitch);
|
||||
} else {
|
||||
noteOn(pitch);
|
||||
}
|
||||
playing = true;
|
||||
}
|
||||
|
||||
// Print status
|
||||
oled.setCursor(0*6, 20);
|
||||
oled.print(NoteNames[note % 12]);
|
||||
oled.print(alt?".":" ");
|
||||
|
||||
//oled.display();
|
||||
}
|
Loading…
Reference in New Issue