Added arpeggiato, fixed notes. TODO: Fix menu options for arpeggio. Persistent config/save+load option

This commit is contained in:
superminaren 2026-02-17 15:55:08 +01:00
parent 07917c0e6e
commit e780df7d22
1 changed files with 133 additions and 101 deletions

View File

@ -22,17 +22,24 @@ float ABS_VOL = 0.5f;
float decay = 0.99998f; float decay = 0.99998f;
Adafruit_SSD1306 display(OLED_W, OLED_H, &Wire); Adafruit_SSD1306 display(OLED_W, OLED_H, &Wire);
// ----- Menu Functions // ----- Menu Functions
int sound = 0; //Default voice int sound = 0; //Default voice
String sounds[6] = {"Saw+Sine", "Sine", "Saw", "Triangle", "Square", "Noise"}; String sounds[6] = { "Saw+Sine", "Sine", "Saw", "Triangle", "Square", "Noise" };
int sound_count = sizeof(sounds)/sizeof(sounds[0]); int sound_count = sizeof(sounds) / sizeof(sounds[0]);
// Modes // Modes
int mode = 0; int mode = 0;
String mode_names[4] = {"Vol", "Sou", "Oct", "Env"}; String mode_names[7] = { "Vol", "Sou", "Oct", "Env", "Arp", "ArpΔ", "Arp"};
int mode_count = sizeof(mode_names)/sizeof(mode_names[0]); int mode_count = sizeof(mode_names) / sizeof(mode_names[0]);
// Octave // Octave
int octave = 2; int octave = 2;
// Arpeggio
//int arp_pattern[5] = {1 ,3 ,5 ,3 ,1 };
int arp_pattern[5] = {0 ,4 ,7 ,3 ,1 };
int arpeggio = 1; // 0 = Off, 1 =
int arp_notes = 3; // Now many notes to arpeggiate
int arp_delay = 150; // 300 ms
// ---- Audio ---- // ---- Audio ----
@ -42,14 +49,14 @@ constexpr uint8_t CHANNELS = 1;
constexpr uint8_t BITS = 16; constexpr uint8_t BITS = 16;
// ----- Reverb ----- // ----- Reverb -----
constexpr int reverb_time = 200; // 200 ms. constexpr int reverb_time = 200; // 200 ms.
constexpr int reverb_samples = (int)(reverb_time/1000*SAMPLE_RATE); constexpr int reverb_samples = (int)(reverb_time / 1000 * SAMPLE_RATE);
float reverbqueue[reverb_samples] = {}; float reverbqueue[reverb_samples] = {};
float reverb_fade = 0.3f; float reverb_fade = 0.3f;
// ---- Matrix ---- // ---- Matrix ----
const int X_PINS[4] = {8, 9, 10, 7}; const int X_PINS[4] = { 8, 9, 10, 7 };
const int Y_PINS[4] = {19, 20, 21, 22}; const int Y_PINS[4] = { 19, 20, 21, 22 };
// ---- Synth ---- // ---- Synth ----
constexpr int MAX_VOICES = 200; constexpr int MAX_VOICES = 200;
@ -58,10 +65,10 @@ constexpr int MAX_VOICES = 200;
NOTES NOTES
========================================================= */ ========================================================= */
const char* noteNames[12] = { const char *noteNames[12] = {
"C","C#","D","D#", "C", "C#", "D", "D#",
"E","F","F#","G", "E", "F", "F#", "G",
"G#","A","A#","B" "G#", "A", "A#", "B"
}; };
float noteFreq[12]; float noteFreq[12];
@ -80,10 +87,13 @@ void initNotes() {
struct Voice { struct Voice {
bool active = false; bool active = false;
float freq = 0; float freq = 0;
float startFreq = 0;
float phase1 = 0; float phase1 = 0;
float phase2 = 0; float phase2 = 0;
float env = 0; float env = 0;
float amp = 0; float amp = 0;
long arp_time = 0;
int arp_position = 0;
}; };
Voice voices[MAX_VOICES]; Voice voices[MAX_VOICES];
@ -92,8 +102,11 @@ void noteOn(float freq) {
for (auto &v : voices) { for (auto &v : voices) {
if (!v.active) { if (!v.active) {
v.active = true; v.active = true;
v.freq = freq*(funcKey ? 1.25f : 1.0f); v.freq = freq;
v.startFreq = freq;
v.env = VOL; v.env = VOL;
v.arp_time = millis();
v.arp_position = 0;
v.phase1 = 0.5f; v.phase1 = 0.5f;
v.phase2 = 0.5f; v.phase2 = 0.5f;
return; return;
@ -103,7 +116,7 @@ void noteOn(float freq) {
void noteOff(float freq) { void noteOff(float freq) {
for (auto &v : voices) { for (auto &v : voices) {
if (v.active && fabs(v.freq - freq) < 0.01f) { if (v.active && fabs(v.startFreq - freq) < 0.01f) {
v.active = false; v.active = false;
} }
} }
@ -112,51 +125,56 @@ void noteOff(float freq) {
float sineWave(float phase) { float sineWave(float phase) {
// Convert phase (0-1) to radians (0-2π) // Convert phase (0-1) to radians (0-2π)
float radians = phase * 2.0f * 3.14156f; float radians = phase * 2.0f * 3.14156f;
// Generate sine value (-1 to 1) // Generate sine value (-1 to 1)
float sineValue = sin(radians); float sineValue = sin(radians);
// Map from (-1 to 1) to (0 to 1) // Map from (-1 to 1) to (0 to 1)
float output = (sineValue + 1.0f) / 2.0f; float output = (sineValue + 1.0f) / 2.0f;
return output; return output;
} }
float handleReverb(float val){ float handleReverb(float val) {
for(int i = 0; i<reverb_samples-1; i++){ for (int i = 0; i < reverb_samples - 1; i++) {
reverbqueue[i] = reverbqueue[i+1]; reverbqueue[i] = reverbqueue[i + 1];
} }
reverbqueue[reverb_samples-1] = val; reverbqueue[reverb_samples - 1] = val;
return reverbqueue[0]*reverb_fade; return reverbqueue[0] * reverb_fade;
} }
float wavetable[16] = {0.2f,0.4f,0.9f,0.2f,0.4f,0.9f,0.2f,0.4f,0.9f,0.2f,0.4f,0.9f,0.2f,0.4f,0.9f,0.2f}; float wavetable[16] = { 0.2f, 0.4f, 0.9f, 0.2f, 0.4f, 0.9f, 0.2f, 0.4f, 0.9f, 0.2f, 0.4f, 0.9f, 0.2f, 0.4f, 0.9f, 0.2f };
float wavetableInt[1024] = {}; float wavetableInt[1024] = {};
int genWaveTable(){ int genWaveTable() {
int size = 16; int size = 16;
int newSize = 1024; int newSize = 1024;
int div = newSize/size; int div = newSize / size;
for(int s = 0; s<size-1; s++){ for (int s = 0; s < size - 1; s++) {
for(int i = 0; i<div; i++){ for (int i = 0; i < div; i++) {
wavetableInt[(s*div)+i] = wavetable[s]+((wavetable[s+1]-wavetable[s])*(i/div)); wavetableInt[(s * div) + i] = wavetable[s] + ((wavetable[s + 1] - wavetable[s]) * (i / div));
} }
} }
return 0; return 0;
} }
float transposeSemitones(float freq, int semitones) {
return freq * powf(2.0f, semitones / 12.0f);
}
/* ========================================================= /* =========================================================
SYNTH STREAM SYNTH STREAM
========================================================= */ ========================================================= */
class JunoStream : public Stream { class SynthStream : public Stream {
public: public:
uint8_t frame[4]; uint8_t frame[4];
uint8_t index = 4; uint8_t index = 4;
float lp = 0.0f; float lp = 0.0f;
float cutoff = 1.0f*VOL; float cutoff = 1.0f * VOL;
float detune = 0.004f; float detune = 0.004f;
//float detune = 0.006f; //float detune = 0.006f;
@ -167,7 +185,9 @@ public:
float scope[128]{}; float scope[128]{};
int scopeIndex = 0; int scopeIndex = 0;
int available() override { return 4; } int available() override {
return 4;
}
int read() override { int read() override {
if (index >= 4) { if (index >= 4) {
@ -177,8 +197,12 @@ public:
return frame[index++]; return frame[index++];
} }
int peek() override { return -1; } int peek() override {
size_t write(uint8_t) override { return 0; } return -1;
}
size_t write(uint8_t) override {
return 0;
}
private: private:
void generate() { void generate() {
@ -188,11 +212,18 @@ private:
if (!v.active) continue; if (!v.active) continue;
float osc = 0.0f; float osc = 0.0f;
if (arpeggio>0 && (millis()-v.arp_time)>arp_delay){
v.arp_time = millis();
v.arp_position++;
v.arp_position = v.arp_position % arp_notes;
v.freq = transposeSemitones(v.startFreq, arp_pattern[v.arp_position]);
}
// Defined voices // Defined voices
switch(sound){ switch (sound) {
// Saw+Sine // Saw+Sine
case 0: case 0:
osc = (sineWave(v.phase1)+sineWave(v.phase2)+wavetableInt[(int)floor(1024.0f*v.phase2)])/3.0f; osc = (sineWave(v.phase1) + sineWave(v.phase2) + wavetableInt[(int)floor(1024.0f * v.phase2)]) / 3.0f;
break; break;
// Sine Wave // Sine Wave
case 1: case 1:
@ -204,20 +235,20 @@ private:
break; break;
// Triangle Wave // Triangle Wave
case 3: case 3:
osc = abs((v.phase1*2)-1.0f); osc = abs((v.phase1 * 2) - 1.0f);
break; break;
// Square Wave // Square Wave
case 4: case 4:
osc = (v.phase1>0.5f); osc = (v.phase1 > 0.5f);
break; break;
// Noise // Noise
case 5: case 5:
osc = sineWave(v.phase1)+(random(-1000,1000)/2000.0f); osc = sineWave(v.phase1) + (random(-1000, 1000) / 2000.0f);
break; break;
} }
v.phase1 += v.freq / SAMPLE_RATE; v.phase1 += v.freq / SAMPLE_RATE;
v.phase2 += (v.freq *1.0f * (1.25f + detune)) / SAMPLE_RATE; v.phase2 += (v.freq * 1.0f * (1.25f + detune)) / SAMPLE_RATE;
//v.phase2 += (v.freq *1.25f * (1.0f + detune)) / SAMPLE_RATE; //v.phase2 += (v.freq *1.25f * (1.0f + detune)) / SAMPLE_RATE;
if (v.phase1 >= 1) v.phase1 -= 1; if (v.phase1 >= 1) v.phase1 -= 1;
if (v.phase2 >= 1) v.phase2 -= 1; if (v.phase2 >= 1) v.phase2 -= 1;
@ -228,10 +259,13 @@ private:
mix += osc * v.amp; mix += osc * v.amp;
} }
// Handle reverb
mix = (handleReverb(mix) + mix) / 2.0f;
lp += cutoff * (mix - lp); lp += cutoff * (mix - lp);
float out = lp * 0.4f; float out = lp * 0.4f;
//out = (handleReverb(out)+out)/2.0f;
int r = (delayIndex - 480 + DELAY_SAMPLES) % DELAY_SAMPLES; int r = (delayIndex - 480 + DELAY_SAMPLES) % DELAY_SAMPLES;
float ch = out + delayL[r] * 0.6f; float ch = out + delayL[r] * 0.6f;
@ -256,7 +290,7 @@ private:
========================================================= */ ========================================================= */
I2SStream i2s; I2SStream i2s;
JunoStream synth; SynthStream synth;
StreamCopy copier(i2s, synth); StreamCopy copier(i2s, synth);
/* ========================================================= /* =========================================================
@ -269,7 +303,7 @@ uint16_t scanMatrix() {
for (int x = 0; x < 4; x++) { for (int x = 0; x < 4; x++) {
digitalWrite(X_PINS[x], HIGH); digitalWrite(X_PINS[x], HIGH);
delayMicroseconds(5); delayMicroseconds(10);
for (int y = 0; y < 4; y++) { for (int y = 0; y < 4; y++) {
if (digitalRead(Y_PINS[y])) { if (digitalRead(Y_PINS[y])) {
@ -288,61 +322,62 @@ uint16_t keys = 0;
uint16_t changed = keys ^ lastKeys; uint16_t changed = keys ^ lastKeys;
// Handle key scanning and menus // Handle key scanning and menus
void handleInputs(){ void handleInputs() {
lastKeys = keys; lastKeys = keys;
keys = scanMatrix(); keys = scanMatrix();
changed = keys ^ lastKeys; changed = keys ^ lastKeys;
for (int i = 0; i < 16; i++) { for (int i = 0; i < 16; i++) {
if (changed & (1 << i)) { if (changed & (1 << i)) {
if (i < 12) { if (i < 12) {
if ((keys & (1 << i))){ if ((keys & (1 << i))) {
noteOn(noteFreq[i]); noteOn(noteFreq[i]);
}else{ } else {
noteOff(noteFreq[i]); noteOff(noteFreq[i]);
} }
} else { } else {
funcKey = (keys & (1 << 12)); funcKey = (keys & (1 << 12));
// If Enter key pressed // If Enter key pressed
if(keys & (1 << 14)){ if (keys & (1 << 14)) {
mode = mode+1; mode = mode + 1;
mode = mode % mode_count; mode = mode % mode_count;
} }
// If Left key is pressed // If Left key is pressed
if(keys & (1 << 13)){ if (keys & (1 << 13)) {
switch(mode){ switch (mode) {
case 0: // Volume case 0: // Volume
if(VOL > 0.1f){ VOL-=0.1f; } break; if (VOL > 0.1f) { VOL -= 0.1f; }
case 1: // Sound
sound = sound - 1;
if( sound < 0 ){sound = sound_count-1;}
break; break;
case 2: // Octave case 1: // Sound
if(octave>1){ octave = octave-1;} sound = sound - 1;
if (sound < 0) { sound = sound_count - 1; }
break;
case 2: // Octave
if (octave > 1) { octave = octave - 1; }
initNotes(); initNotes();
break; break;
case 3: // Envelope case 3: // Envelope
// TODO // TODO
break; break;
} }
// If Right key is pressed // If Right key is pressed
}else if((keys & (1 << 15))){ } else if ((keys & (1 << 15))) {
switch(mode){ switch (mode) {
case 0: // Volume case 0: // Volume
if(VOL<1.0f){VOL+=0.1f;} if (VOL < 1.0f) { VOL += 0.1f; }
break; break;
case 1: // Sound case 1: // Sound
sound = sound + 1; sound = sound + 1;
sound = sound % sound_count; sound = sound % sound_count;
break; break;
case 2: // Octave case 2: // Octave
if(octave<8){octave = octave + 1;} if (octave < 8) { octave = octave + 1; }
initNotes(); initNotes();
break; break;
case 3: // Envelope case 3: // Envelope
break; break;
}
} }
}
//menuButton(i-12); //menuButton(i-12);
} }
} }
@ -376,18 +411,18 @@ void drawPads(uint16_t keys) {
} }
void drawScope() { void drawScope() {
int mid = 45; // middle of scope on Y position int mid = 45; // middle of scope on Y position
int height = 20; int height = 20;
float maxVal = 0.0f; float maxVal = 0.0f;
float threshold = 0.002f; float threshold = 0.002f;
float scale = 1.0f; float scale = 1.0f;
for (int x = 0; x < 127; x++) { for (int x = 0; x < 127; x++) {
int i1 = (synth.scopeIndex + x) & 127; int i1 = (synth.scopeIndex + x) & 127;
if(synth.scope[i1]>threshold && synth.scope[i1] > maxVal){ if (synth.scope[i1] > threshold && synth.scope[i1] > maxVal) {
maxVal = synth.scope[i1]; maxVal = synth.scope[i1];
} }
} }
scale = 1.0f/maxVal; scale = 1.0f / maxVal;
for (int x = 0; x < 127; x++) { for (int x = 0; x < 127; x++) {
int i1 = (synth.scopeIndex + x) & 127; int i1 = (synth.scopeIndex + x) & 127;
int i2 = (i1 + 1) & 127; int i2 = (i1 + 1) & 127;
@ -396,8 +431,7 @@ void drawScope() {
mid - synth.scope[i1] * scale * height, mid - synth.scope[i1] * scale * height,
x + 1, x + 1,
mid - synth.scope[i2] * scale * height, mid - synth.scope[i2] * scale * height,
SSD1306_WHITE SSD1306_WHITE);
);
} }
} }
@ -414,26 +448,24 @@ void drawVoices() {
} }
void drawVolumebar() { void drawVolumebar() {
display.fillRect(0,60,(128*VOL), 64, SSD1306_WHITE); display.fillRect(0, 60, (128 * VOL), 64, SSD1306_WHITE);
} }
// Draws the current Synthesizer sound preset // Draws the current Synthesizer sound preset
void drawInfo(){ void drawInfo() {
// Show current Sound preset // Show current Sound preset
display.setTextSize(1); display.setTextSize(1);
display.setTextColor(SSD1306_WHITE); display.setTextColor(SSD1306_WHITE);
display.setCursor(0,6); display.setCursor(0, 6);
display.print(sounds[sound]); display.print(sounds[sound]);
// Show current menu mode // Show current menu mode
display.setCursor(64,6); display.setCursor(64, 6);
display.print("Set:"); display.print("Set:");
display.setCursor(100,6); display.setCursor(100, 6);
display.print(mode_names[mode]); display.print(mode_names[mode]);
display.setCursor(90,18); display.setCursor(90, 18);
display.print("Oct:"+String(octave)); display.print("Oct:" + String(octave));
} }
void updateOLED(uint16_t keys) { void updateOLED(uint16_t keys) {
@ -455,12 +487,12 @@ void setup() {
for (int i = 0; i < 4; i++) { for (int i = 0; i < 4; i++) {
pinMode(X_PINS[i], OUTPUT); pinMode(X_PINS[i], OUTPUT);
digitalWrite(X_PINS[i], LOW); digitalWrite(X_PINS[i], LOW);
pinMode(Y_PINS[i], INPUT); // external pulldowns pinMode(Y_PINS[i], INPUT); // external pulldowns
} }
// Set up Display // Set up Display
Wire.setSDA(4); // GP4 (pin 6) Wire.setSDA(4); // GP4 (pin 6)
Wire.setSCL(5); // GP5 (pin 7) Wire.setSCL(5); // GP5 (pin 7)
Wire.begin(); Wire.begin();
display.begin(SSD1306_SWITCHCAPVCC, OLED_ADDR); display.begin(SSD1306_SWITCHCAPVCC, OLED_ADDR);
display.clearDisplay(); display.clearDisplay();
@ -487,7 +519,7 @@ void loop() {
} }
// Seocnd core does inputs + display + leds // Seocnd core does inputs + display + leds
void loop1(){ void loop1() {
handleInputs(); handleInputs();
if (millis() - lastUI > 30) { if (millis() - lastUI > 30) {