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

@ -28,12 +28,19 @@ Adafruit_SSD1306 display(OLED_W, OLED_H, &Wire);
// 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 ----
//constexpr uint32_t SAMPLE_RATE = 96000; //constexpr uint32_t SAMPLE_RATE = 96000;
@ -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;
} }
} }
@ -146,11 +159,16 @@ int genWaveTable(){
} }
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;
@ -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,6 +212,13 @@ 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
@ -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])) {
@ -311,7 +345,8 @@ void handleInputs(){
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; }
break;
case 1: // Sound case 1: // Sound
sound = sound - 1; sound = sound - 1;
if (sound < 0) { sound = sound_count - 1; } if (sound < 0) { sound = sound_count - 1; }
@ -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);
);
} }
} }
@ -432,8 +466,6 @@ void drawInfo(){
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) {