7 Commits

Author SHA1 Message Date
c953f17090 Merge branch 'main' of github.com:erik-toth/audio-synth 2026-03-08 22:10:03 +01:00
04884bc243 Firmware Update
Changed mapping behaveiour of seqeuncer blocks.
2026-03-08 22:09:26 +01:00
Wendelin Waldhart
d9964ced3e Keys and Caps added 2026-02-24 15:58:38 +01:00
2108552078 Improvment on PCB mounting design 2026-02-14 23:24:11 +01:00
a7e466932b Added rounded edges 2026-02-14 11:48:21 +01:00
Wendelin Waldhart
1216ea923c Merge branch 'main' of https://github.com/erik-toth/audio-synth 2026-02-13 22:43:09 +01:00
Wendelin Waldhart
f2becb78b7 added missing 2026-02-13 22:38:50 +01:00
62 changed files with 69186 additions and 181155 deletions

View File

@@ -3,7 +3,7 @@
@author: Erik Tóth
@contact: etoth@tsn.at
@date: 2025-10-26
@updated: 2025-12-06
@updated: 2026-03-08
@brief: Header for constant definitions
*/
@@ -12,38 +12,38 @@
#include <Arduino.h>
#include <Wire.h>
// CONSTANTS DEFINITONS
#define N_KEYBOARD_ROW 5 // for PROD. change to 5
#define N_KEYBOARD_COL 5 // for PROD. change to 5
#define N_CV_GATES 2 // PROD. OK
#define N_SB 2 // PROD. OK
#define N_KEYBOARD_ROW 5
#define N_KEYBOARD_COL 5
#define N_CV_GATES 2
#define N_SB 2
#define BAUDRATE 115200
#define N_MAX_SEQ_STEPS 512
// PIN DEFENTITIONS
// I2C PINS
#define PIN_SDA 15 // PROD. pin OK
#define PIN_SCL 16 // PROD. pin OK
#define PIN_SDA 15
#define PIN_SCL 16
// KEYBOARD PINS
#define PIN_K_R0 7 // PROD. pin OK
#define PIN_K_R1 8 // PROD. pin OK
#define PIN_K_R2 9 // PROD. pin OK
#define PIN_K_R3 10 // PROD. pin OK
#define PIN_K_R4 11 // DEV. not in use - PROD. pin OK
#define PIN_K_C0 1 // PROD. pin OK
#define PIN_K_C1 2 // PROD. pin OK
#define PIN_K_C2 4 // PROD. pin OK
#define PIN_K_C3 5 // DEV. not in use - PROD. pin OK
#define PIN_K_C4 6 // DEV. not in use - PROD. pin OK
#define PIN_K_R0 7
#define PIN_K_R1 8
#define PIN_K_R2 9
#define PIN_K_R3 10
#define PIN_K_R4 11
#define PIN_K_C0 1
#define PIN_K_C1 2
#define PIN_K_C2 4
#define PIN_K_C3 5
#define PIN_K_C4 6
// SEQUENCER BUTTON PINS
#define PIN_SB_1_REC 33 // for PROD. change to 33 / not available on dev board
#define PIN_SB_1_PLAY 34 // for PROD. change to 34 / not available on dev board
#define PIN_SB_2_REC 35 // 35
#define PIN_SB_2_PLAY 36 // 36
#define PIN_SB_1_REC 33
#define PIN_SB_1_PLAY 34
#define PIN_SB_2_REC 35
#define PIN_SB_2_PLAY 36
// MISC/INFO PINS
#define PIN_VCO1_EN 38 // PROD. pin 38 TODO: if there is an active key mapped to CV-Gate 1 --> HIGH
#define PIN_VCO2_EN 39 // PROD. pin 39 TODO: if there is an active key mapped to CV-Gate 2 --> HIGH
#define PIN_REC 37 // PROD. pin 37 TODO: if any sb is recording LED on (active-low)
#define PIN_BPM 12 // PROD. pin 12 TODO: get bpm through potentiometer analog value -> ADC-Pin
#define PIN_B_METRONOME 14 // PROD. pin 13 TODO: button activates/deactivates bpm led output (pull-up)
#define PIN_L_METRONOME 13 // PROD. pin 14 TODO: led blinks according to bpm value (active-low)
#define PIN_VCO1_EN 38
#define PIN_VCO2_EN 39
#define PIN_REC 37
#define PIN_BPM 12
#define PIN_B_METRONOME 14
#define PIN_L_METRONOME 13
#endif

View File

@@ -3,13 +3,13 @@
@author: Erik Tóth
@contact: etoth@tsn.at
@date: 2025-10-26
@updated: 2025-12-06
@brief: Firmware für MCU - FIXED VERSION mit Bounds Checks
@updated: 2026-03-08
@brief: Firmware für MCU
*/
#include "FIRMWARE.h"
// ==================== Helper-Functions ====================
// Helper-Functions
bool isNotKey(Key k)
{
@@ -23,7 +23,7 @@ bool isEqualKey(Key k1, Key k2)
else return false;
}
// ==================== Keyboard ====================
// Keyboard
Keyboard::Keyboard(uint8_t nRows, uint8_t nCols, uint8_t *pinsRow, uint8_t *pinsCol)
{
@@ -178,7 +178,7 @@ void Keyboard::_removeActiveKey(uint8_t row, uint8_t col)
}
}
// ==================== CV ====================
// CV
CV::CV(Adafruit_MCP4728 *dac, TwoWire *wire, uint8_t nCV, MCP4728_channel_t *cvChannelMap, uint16_t *keyToVoltage, uint8_t row, uint8_t col)
{
@@ -209,7 +209,7 @@ void CV::setVoltage(uint8_t cvIndex, uint16_t mV)
{
if(cvIndex >= _nCV) return;
MCP4728_channel_t ch = _cvChannelMap[cvIndex];
_dac->setChannelValue(ch, map(mV, 0, 1992, 0, 2048), MCP4728_VREF_INTERNAL);
_dac->setChannelValue(ch, map(mV, 0, 2048, 0, 4095), MCP4728_VREF_INTERNAL, MCP4728_GAIN_1X);
}
void CV::setVoltage(uint8_t cvIndex, Key k)
@@ -234,7 +234,7 @@ uint8_t CV::_getKeyToVoltageIndex(Key k)
return (k.row*_col + k.col);
}
// ==================== SequencerBlock (FIXED) ====================
// SequencerBlock
/*!
* @param maxDurationMS maximum loop duration of recording in milliseconds
@@ -255,7 +255,7 @@ SequencerBlock::SequencerBlock(uint16_t maxDurationMS, uint16_t maxStepCount)
_lastStepTime = 0;
_playStartTime = 0;
_stepStartTime = 0;
_lastAddStepTime = 0; // NEU: Rate-Limiting
_lastAddStepTime = 0;
}
void SequencerBlock::startRecord()
@@ -266,7 +266,7 @@ void SequencerBlock::startRecord()
_isRecording = true;
_recordStartTime = millis();
_lastStepTime = _recordStartTime;
_lastAddStepTime = _recordStartTime; // NEU
_lastAddStepTime = _recordStartTime;
_lastVoltageCh1 = 0xFFFF;
_lastVoltageCh2 = 0xFFFF;
}
@@ -281,10 +281,8 @@ void SequencerBlock::stopRecord()
void SequencerBlock::addStep(uint16_t voltage_ch1, uint16_t voltage_ch2)
{
// KRITISCHE SICHERHEITSPRÜFUNGEN ZUERST
if(!_isRecording) return;
// Prüfe ob wir überhaupt noch Platz haben (mit Sicherheitsabstand!)
if(_stepCount >= _MAX_SEQUENCE_STEPS - 1)
{
Serial.println("\n\r[ERROR] Step limit reached! Stopping recording.");
@@ -301,19 +299,16 @@ void SequencerBlock::addStep(uint16_t voltage_ch1, uint16_t voltage_ch2)
unsigned long now = millis();
// NEU: Rate-Limiting - ignoriere zu häufige Aufrufe
if((unsigned long)(now - _lastAddStepTime) < 5)
{
return;
}
_lastAddStepTime = now;
// Hat sich die Spannung geändert?
bool voltageChanged = (voltage_ch1 != _lastVoltageCh1) || (voltage_ch2 != _lastVoltageCh2);
if(voltageChanged)
{
// WICHTIG: Prüfe nochmal ob wir Platz haben BEVOR wir schreiben!
if(_stepCount >= _MAX_SEQUENCE_STEPS - 1)
{
Serial.println("\n\r[ERROR] Array full! Stopping recording.");
@@ -321,19 +316,17 @@ void SequencerBlock::addStep(uint16_t voltage_ch1, uint16_t voltage_ch2)
return;
}
// Vorherigen Step abschließen (wenn vorhanden)
if(_stepCount > 0 && _stepCount <= _MAX_SEQUENCE_STEPS)
{
_finishCurrentStep();
}
// Neuen Step beginnen - mit Bounds Check!
if(_stepCount < _MAX_SEQUENCE_STEPS)
{
_sequence[_stepCount].voltage_ch1 = voltage_ch1;
_sequence[_stepCount].voltage_ch2 = voltage_ch2;
_sequence[_stepCount].duration = 0;
_sequence[_stepCount].active = (voltage_ch1 > 0 || voltage_ch2 > 0); // NEU: Prüfe ob Note aktiv
_sequence[_stepCount].active = (voltage_ch1 > 0 || voltage_ch2 > 0);
_stepCount++;
_lastStepTime = now;
@@ -343,8 +336,6 @@ void SequencerBlock::addStep(uint16_t voltage_ch1, uint16_t voltage_ch2)
}
else
{
// Gleiche Spannung - Duration des aktuellen Steps aktualisieren
// WICHTIG: Bounds Check!
if(_stepCount > 0 && _stepCount <= _MAX_SEQUENCE_STEPS)
{
_sequence[_stepCount - 1].duration = now - _lastStepTime;
@@ -378,7 +369,6 @@ void SequencerBlock::update()
{
if(!_isPlaying || _stepCount == 0) return;
// WICHTIG: Bounds Check BEVOR wir auf Array zugreifen!
if(_currentStep >= _stepCount || _currentStep >= _MAX_SEQUENCE_STEPS)
{
Serial.println("\n\r[ERROR] Invalid step index in update()!");
@@ -389,7 +379,6 @@ void SequencerBlock::update()
unsigned long now = millis();
unsigned long elapsed = now - _stepStartTime;
// Sicherung gegen Division durch Null / Endlosschleife
if(_sequence[_currentStep].duration == 0)
{
_currentStep++;
@@ -409,12 +398,10 @@ void SequencerBlock::update()
return;
}
// Prüfen ob aktueller Schritt abgelaufen ist
if(elapsed >= _sequence[_currentStep].duration)
{
_currentStep++;
// Sequenz-Ende erreicht?
if(_currentStep >= _stepCount)
{
if(_loop)
@@ -447,7 +434,6 @@ void SequencerBlock::clear()
_lastVoltageCh1 = 0;
_lastVoltageCh2 = 0;
// Optional: Array löschen (kann je nach Use-Case weggelassen werden)
for(uint16_t i = 0; i < _MAX_SEQUENCE_STEPS; i++)
{
_sequence[i].voltage_ch1 = 0;
@@ -500,7 +486,7 @@ uint16_t SequencerBlock::getCurrentVoltageCh2()
uint16_t SequencerBlock::getTotalDuration()
{
uint32_t total = 0; // uint32 um Overflow zu vermeiden
uint32_t total = 0;
for(uint16_t i = 0; i < _stepCount && i < _MAX_SEQUENCE_STEPS; i++)
{
total += _sequence[i].duration;
@@ -519,7 +505,7 @@ bool SequencerBlock::isCurrentStepActive()
void SequencerBlock::_finishCurrentStep()
{
if(_stepCount == 0) return;
if(_stepCount > _MAX_SEQUENCE_STEPS) return; // Sicherheitsprüfung
if(_stepCount > _MAX_SEQUENCE_STEPS) return;
unsigned long now = millis();
uint16_t duration = now - _lastStepTime;

View File

@@ -1,11 +1,21 @@
/*
* Example Code Three - Dual Channel Sequencer (COMPLETE)
* - Alle TODOs implementiert
* - VCO Gates, Recording LED, Metronome
* Analoger Audiosynthesizer mit digitaler Steuereinheit
* Firmware-Code für die digitale Einheit
* Autor: Erik Tóth
*/
#include "FIRMWARE_DEF.h"
#include "FIRMWARE.h"
// Calibration table for optimal note accurarcy
const uint16_t NOTE_MV[25] = {
64, 140, 216, 293, 369,
445, 521, 597, 673, 750,
826, 902, 978, 1054, 1131,
1207, 1283, 1359, 1435, 1511,
1588, 1664, 1740, 1816, 1892,
};
#define HLFSTEP(n) NOTE_MV[n]
byte pins_keyboard_row[N_KEYBOARD_ROW] = {PIN_K_R0, PIN_K_R1, PIN_K_R2, PIN_K_R3, PIN_K_R4};
byte pins_keyboard_col[N_KEYBOARD_COL] = {PIN_K_C0, PIN_K_C1, PIN_K_C2, PIN_K_C3, PIN_K_C4};
@@ -14,15 +24,16 @@ Keyboard keyboard(N_KEYBOARD_ROW, N_KEYBOARD_COL, pins_keyboard_row, pins_keyboa
Adafruit_MCP4728 MCP4728;
MCP4728_channel_t cvMap[N_CV_GATES] = {MCP4728_CHANNEL_A, MCP4728_CHANNEL_B};
uint16_t keyToVoltage[N_KEYBOARD_ROW*N_KEYBOARD_COL] = {
0*83, 1*83, 2*83, 3*83, 4*83,
5*83, 6*83, 7*83, 8*83, 9*83,
10*83, 11*83, 12*83, 13*83, 14*83,
15*83, 16*83, 17*83, 18*83, 19*83,
20*83, 21*83, 22*83, 23*83, 24*83
HLFSTEP(0), HLFSTEP(1), HLFSTEP(2), HLFSTEP(3), HLFSTEP(4),
HLFSTEP(5), HLFSTEP(6), HLFSTEP(7), HLFSTEP(8), HLFSTEP(9),
HLFSTEP(10), HLFSTEP(11), HLFSTEP(12), HLFSTEP(13), HLFSTEP(14),
HLFSTEP(15), HLFSTEP(16), HLFSTEP(17), HLFSTEP(18), HLFSTEP(19),
HLFSTEP(20), HLFSTEP(21), HLFSTEP(22), HLFSTEP(23), HLFSTEP(24)
};
CV cv(&MCP4728, &Wire, N_CV_GATES, cvMap, keyToVoltage, N_KEYBOARD_ROW, N_KEYBOARD_COL);
// SB1 -> VCO1 (CV-Channel 0), SB2 -> VCO2 (CV-Channel 1)
SequencerBlock sb1(30000, N_MAX_SEQ_STEPS);
SequencerBlock sb2(30000, N_MAX_SEQ_STEPS);
@@ -43,8 +54,12 @@ const unsigned long DEBOUNCE_DELAY = 50;
static bool seq1_loop_active = false;
static bool seq2_loop_active = false;
static uint16_t last_voltage_ch1 = 0xFFFF;
static uint16_t last_voltage_ch2 = 0xFFFF;
// Separate last-voltage tracking per sequencer
static uint16_t sb1_last_voltage_ch1 = 0xFFFF;
static uint16_t sb1_last_voltage_ch2 = 0xFFFF;
static uint16_t sb2_last_voltage_ch1 = 0xFFFF;
static uint16_t sb2_last_voltage_ch2 = 0xFFFF;
bool readButton(byte pin, ButtonState &state)
{
@@ -123,26 +138,24 @@ void initOutputs()
void handleSequencerButtons()
{
// ===== Sequencer 1 Record Button =====
if(readButton(PIN_SB_1_REC, btn_sb1_rec))
{
if(sb1.isRecording())
{
sb1.stopRecord();
Serial.printf("\n\r[SEQ1] Recording stopped. Steps: %i, Duration: %ims",
Serial.printf("\n\r[SEQ1->VCO1] Recording stopped. Steps: %i, Duration: %ims",
sb1.getStepCount(), sb1.getTotalDuration());
}
else
{
if(sb1.isPlaying()) sb1.stopPlay();
sb1.startRecord();
last_voltage_ch1 = 0xFFFF;
last_voltage_ch2 = 0xFFFF;
Serial.printf("\n\r[SEQ1] Recording started (2 channels)...");
sb1_last_voltage_ch1 = 0xFFFF;
sb1_last_voltage_ch2 = 0xFFFF;
Serial.printf("\n\r[SEQ1->VCO1] Recording started...");
}
}
// ===== Sequencer 1 Play Button =====
if(readButton(PIN_SB_1_PLAY, btn_sb1_play))
{
if(!sb1.isPlaying())
@@ -151,43 +164,41 @@ void handleSequencerButtons()
sb1.setLoop(false);
seq1_loop_active = false;
sb1.startPlay();
Serial.printf("\n\r[SEQ1] Playback started (single)\n\r\tSteps: %i, Duration: %ims",
Serial.printf("\n\r[SEQ1->VCO1] Playback started (single)\n\r\tSteps: %i, Duration: %ims",
sb1.getStepCount(), sb1.getTotalDuration());
}
else if(!seq1_loop_active)
{
sb1.setLoop(true);
seq1_loop_active = true;
Serial.printf("\n\r[SEQ1] Loop activated");
Serial.printf("\n\r[SEQ1->VCO1] Loop activated");
}
else
{
sb1.stopPlay();
seq1_loop_active = false;
Serial.printf("\n\r[SEQ1] Playback stopped");
Serial.printf("\n\r[SEQ1->VCO1] Playback stopped");
}
}
// ===== Sequencer 2 Record Button =====
if(readButton(PIN_SB_2_REC, btn_sb2_rec))
{
if(sb2.isRecording())
{
sb2.stopRecord();
Serial.printf("\n\r[SEQ2] Recording stopped. Steps: %i, Duration: %ims",
Serial.printf("\n\r[SEQ2->VCO2] Recording stopped. Steps: %i, Duration: %ims",
sb2.getStepCount(), sb2.getTotalDuration());
}
else
{
if(sb2.isPlaying()) sb2.stopPlay();
sb2.startRecord();
last_voltage_ch1 = 0xFFFF;
last_voltage_ch2 = 0xFFFF;
Serial.printf("\n\r[SEQ2] Recording started (2 channels)...");
sb2_last_voltage_ch1 = 0xFFFF;
sb2_last_voltage_ch2 = 0xFFFF;
Serial.printf("\n\r[SEQ2->VCO2] Recording started...");
}
}
// ===== Sequencer 2 Play Button =====
if(readButton(PIN_SB_2_PLAY, btn_sb2_play))
{
if(!sb2.isPlaying())
@@ -196,20 +207,20 @@ void handleSequencerButtons()
sb2.setLoop(false);
seq2_loop_active = false;
sb2.startPlay();
Serial.printf("\n\r[SEQ2] Playback started (single)\n\r\tSteps: %i, Duration: %ims",
Serial.printf("\n\r[SEQ2->VCO2] Playback started (single)\n\r\tSteps: %i, Duration: %ims",
sb2.getStepCount(), sb2.getTotalDuration());
}
else if(!seq2_loop_active)
{
sb2.setLoop(true);
seq2_loop_active = true;
Serial.printf("\n\r[SEQ2] Loop activated");
Serial.printf("\n\r[SEQ2->VCO2] Loop activated");
}
else
{
sb2.stopPlay();
seq2_loop_active = false;
Serial.printf("\n\r[SEQ2] Playback stopped");
Serial.printf("\n\r[SEQ2->VCO2] Playback stopped");
}
}
}
@@ -224,17 +235,14 @@ void updateMetronome()
{
unsigned long now = millis();
// BPM von Potentiometer lesen (alle 100ms)
static unsigned long last_bpm_read = 0;
if((now - last_bpm_read) > 100)
{
int adc_value = analogRead(PIN_BPM);
// Map ADC (0-4095) zu BPM (40-240)
current_bpm = map(adc_value, 0, 4095, 40, 240);
last_bpm_read = now;
}
// Metronome Button (Toggle)
if(readButton(PIN_B_METRONOME, btn_metronome))
{
metronome_enabled = !metronome_enabled;
@@ -243,55 +251,41 @@ void updateMetronome()
if(!metronome_enabled)
{
digitalWrite(PIN_L_METRONOME, HIGH); // Active-low: HIGH = OFF
digitalWrite(PIN_L_METRONOME, HIGH);
metronome_led_on = false;
}
}
if(!metronome_enabled) return;
// Berechne Beat-Intervall in ms
unsigned long beat_interval = 60000UL / current_bpm;
// Neue Beat?
if((now - last_beat_time) >= beat_interval)
{
digitalWrite(PIN_L_METRONOME, LOW); // Active-low: LOW = ON
digitalWrite(PIN_L_METRONOME, LOW);
metronome_led_on = true;
last_beat_time = now;
last_pulse_end_time = now + 50; // 50ms Pulse
last_pulse_end_time = now + 50;
}
// Pulse beenden?
if(metronome_led_on && (now >= last_pulse_end_time))
{
digitalWrite(PIN_L_METRONOME, HIGH); // Active-low: HIGH = OFF
digitalWrite(PIN_L_METRONOME, HIGH);
metronome_led_on = false;
}
}
void updateVCOGates(bool cv1_active, bool cv2_active)
{
// PIN_VCO1_EN: HIGH wenn CV1 aktiv (Key mapped to CV-Gate 1)
digitalWrite(PIN_VCO1_EN, cv1_active ? HIGH : LOW);
// PIN_VCO2_EN: HIGH wenn CV2 aktiv (Key mapped to CV-Gate 2)
digitalWrite(PIN_VCO2_EN, cv2_active ? HIGH : LOW);
}
void updateRecordingLED()
{
// PIN_REC: Active-low (LOW = LED ON)
bool any_recording = sb1.isRecording() || sb2.isRecording();
digitalWrite(PIN_REC, any_recording ? LOW : HIGH);
}
void setup()
{
Serial.begin(BAUDRATE);
delay(2000);
Serial.printf("\n\r=== COMPLETE VERSION with TODOs ===");
Serial.printf("\n\r=== DUAL SEQUENCER: SB1->VCO1 | SB2->VCO2 ===");
Serial.printf("\n\rSerial OK!");
keyboard.begin();
@@ -317,45 +311,43 @@ void setup()
sb1.setLoop(false);
sb2.setLoop(false);
Serial.printf("\n\r=== Dual-Channel Sequencer System Started ===");
Serial.printf("\n\rFeatures:");
Serial.printf("\n\r - VCO1/VCO2 Gate Outputs");
Serial.printf("\n\r - Recording LED Indicator");
Serial.printf("\n\r - BPM Metronome (40-240 BPM)");
Serial.printf("\n\r==============================================\n\r");
Serial.printf("\n\r=== System Started ===");
Serial.printf("\n\rMapping:");
Serial.printf("\n\r SB1 -> VCO1 (CV-Ch 0) | SB2 -> VCO2 (CV-Ch 1)");
Serial.printf("\n\rManual fallback:");
Serial.printf("\n\r SB1 playing, SB2 idle -> VCO2 manual (Queue[0])");
Serial.printf("\n\r SB2 playing, SB1 idle -> VCO1 manual (Queue[0])");
Serial.printf("\n\r Both idle -> VCO1=Queue[0], VCO2=Queue[1]");
Serial.printf("\n\r=====================================\n\r");
}
void loop()
{
// ===== DEBUG HEARTBEAT =====
// DEBUG HEARTBEAT
static unsigned long lastDebugPrint = 0;
static unsigned long loopCounter = 0;
loopCounter++;
if(millis() - lastDebugPrint > 5000)
{
Serial.printf("\n\r[HEARTBEAT] Loop: %lu | BPM: %d | Metro: %s",
loopCounter, current_bpm, metronome_enabled ? "ON" : "OFF");
Serial.printf("\n\r[DEBUG] SB1: Rec=%d, Play=%d, Steps=%d",
Serial.printf("\n\r[DEBUG] SB1->VCO1: Rec=%d, Play=%d, Steps=%d",
sb1.isRecording(), sb1.isPlaying(), sb1.getStepCount());
Serial.printf("\n\r[DEBUG] SB2: Rec=%d, Play=%d, Steps=%d",
Serial.printf("\n\r[DEBUG] SB2->VCO2: Rec=%d, Play=%d, Steps=%d",
sb2.isRecording(), sb2.isPlaying(), sb2.getStepCount());
lastDebugPrint = millis();
}
// ===== NON-BLOCKING TIMING =====
// NON-BLOCKING TIMING
static unsigned long lastLoopTime = 0;
unsigned long now = millis();
const unsigned long LOOP_INTERVAL = 10;
if((now - lastLoopTime) < LOOP_INTERVAL)
{
return;
}
if((now - lastLoopTime) < LOOP_INTERVAL) return;
lastLoopTime = now;
// ===== UPDATE FUNCTIONS =====
// UPDATE
keyboard.update();
handleSequencerButtons();
updateMetronome();
@@ -364,97 +356,150 @@ void loop()
sb1.update();
sb2.update();
// KEYBOARD INPUT
int n = keyboard.getQueueLength();
// Aktuelle Spannungen ermitteln
uint16_t voltage_ch1 = 0;
uint16_t voltage_ch2 = 0;
bool cv1_active = false;
bool cv2_active = false;
// Key 0 -> wird als manueller Eingang für den jeweils freien VCO genutzt
uint16_t manual_voltage_0 = 0;
uint16_t manual_voltage_1 = 0;
bool manual_active_0 = false;
bool manual_active_1 = false;
if(n > 0)
{
Key k1 = keyboard.getQueue(0);
if(!isNotKey(k1))
Key k = keyboard.getQueue(0);
if(!isNotKey(k))
{
Serial.printf("\n\r[DEBUG] K1: R%iC%i", k1.row, k1.col);
voltage_ch1 = keyToVoltage[k1.row * N_KEYBOARD_COL + k1.col];
cv1_active = true;
manual_voltage_0 = keyToVoltage[k.row * N_KEYBOARD_COL + k.col];
manual_active_0 = true;
}
}
if(n > 1)
{
Key k2 = keyboard.getQueue(1);
if(!isNotKey(k2))
Key k = keyboard.getQueue(1);
if(!isNotKey(k))
{
Serial.printf("\n\r[DEBUG] K2: R%iC%i", k2.row, k2.col);
voltage_ch2 = keyToVoltage[k2.row * N_KEYBOARD_COL + k2.col];
cv2_active = true;
manual_voltage_1 = keyToVoltage[k.row * N_KEYBOARD_COL + k.col];
manual_active_1 = true;
}
}
// ===== RECORDING =====
// SB1 nimmt immer ch1=manual_voltage_0 / ch2=manual_voltage_1 auf
// (SB1 ist für VCO1 zuständig, nutzt den vollen Keyboard-Input)
if(sb1.isRecording())
{
bool changed = (manual_voltage_0 != sb1_last_voltage_ch1) ||
(manual_voltage_1 != sb1_last_voltage_ch2);
if(changed)
{
sb1.addStep(manual_voltage_0, manual_voltage_1);
sb1_last_voltage_ch1 = manual_voltage_0;
sb1_last_voltage_ch2 = manual_voltage_1;
}
}
// Recording
bool voltageChanged = (voltage_ch1 != last_voltage_ch1) || (voltage_ch2 != last_voltage_ch2);
if(sb1.isRecording() && voltageChanged)
// SB2 nimmt ebenfalls den vollen Keyboard-Input auf
if(sb2.isRecording())
{
sb1.addStep(voltage_ch1, voltage_ch2);
last_voltage_ch1 = voltage_ch1;
last_voltage_ch2 = voltage_ch2;
bool changed = (manual_voltage_0 != sb2_last_voltage_ch1) ||
(manual_voltage_1 != sb2_last_voltage_ch2);
if(changed)
{
sb2.addStep(manual_voltage_0, manual_voltage_1);
sb2_last_voltage_ch1 = manual_voltage_0;
sb2_last_voltage_ch2 = manual_voltage_1;
}
}
if(sb2.isRecording() && voltageChanged)
// ===== CV OUTPUT & VCO GATES =====
//
// SB1 state | SB2 state | VCO1 (ch 0) | VCO2 (ch 1)
// ------------|-------------|---------------------|----------------------
// playing | playing | SB1 seq voltage | SB2 seq voltage
// playing | recording | SB1 seq voltage | live manual Queue[0]
// playing | idle | SB1 seq voltage | live manual Queue[0]
// idle | playing | live manual Queue[0]| SB2 seq voltage
// idle | recording | live manual Queue[0]| live manual Queue[0]
// idle | idle | live manual Queue[0]| live manual Queue[1]
bool sb1_playing = sb1.isPlaying();
bool sb1_recording = sb1.isRecording();
bool sb2_playing = sb2.isPlaying();
bool sb2_recording = sb2.isRecording();
uint16_t out_vco1 = 0;
uint16_t out_vco2 = 0;
bool gate_vco1 = false;
bool gate_vco2 = false;
// VCO1
if(sb1_playing)
{
sb2.addStep(voltage_ch1, voltage_ch2);
last_voltage_ch1 = voltage_ch1;
last_voltage_ch2 = voltage_ch2;
// SB1 Sequenz läuft -> Sequenz-Ausgabe
out_vco1 = sb1.getCurrentVoltageCh1();
gate_vco1 = sb1.isCurrentStepActive();
}
// CV-Ausgabe & VCO Gates
if(sb1.isPlaying())
else if(sb1_recording)
{
uint16_t seq_v1 = sb1.getCurrentVoltageCh1();
uint16_t seq_v2 = sb1.getCurrentVoltageCh2();
cv.setVoltage(0, seq_v1);
cv.setVoltage(1, seq_v2);
// KORREKT: Nutze isCurrentStepActive() statt Spannung > 0
// Da 0V eine gültige Note sein kann!
bool gate_active = sb1.isCurrentStepActive();
updateVCOGates(gate_active, gate_active);
}
else if(sb2.isPlaying())
{
uint16_t seq_v1 = sb2.getCurrentVoltageCh1();
uint16_t seq_v2 = sb2.getCurrentVoltageCh2();
cv.setVoltage(0, seq_v1);
cv.setVoltage(1, seq_v2);
bool gate_active = sb2.isCurrentStepActive();
updateVCOGates(gate_active, gate_active);
// SB1 nimmt auf -> Live-Ausgabe damit man hört was man spielt
out_vco1 = manual_voltage_0;
gate_vco1 = manual_active_0;
}
else
{
// Live-Modus: cv1_active/cv2_active basieren auf tatsächlich gedrückten Tasten
cv.setVoltage(0, voltage_ch1);
cv.setVoltage(1, voltage_ch2);
updateVCOGates(cv1_active, cv2_active);
// SB1 idle -> manuell
out_vco1 = manual_voltage_0;
gate_vco1 = manual_active_0;
}
// VCO2
if(sb2_playing)
{
// SB2 Sequenz läuft -> Sequenz-Ausgabe
out_vco2 = sb2.getCurrentVoltageCh1();
gate_vco2 = sb2.isCurrentStepActive();
}
else if(sb2_recording)
{
// SB2 nimmt auf -> Live-Ausgabe damit man hört was man spielt
out_vco2 = manual_voltage_0;
gate_vco2 = manual_active_0;
gate_vco1 = false;
}
else if(sb1_playing)
{
// SB1 läuft, SB2 idle -> VCO2 manuell mit Queue[0]
out_vco2 = manual_voltage_0;
gate_vco2 = manual_active_0;
}
else
{
// Beide idle -> VCO2 bekommt Queue[1]
out_vco2 = manual_voltage_1;
gate_vco2 = manual_active_1;
}
// Time-Limit Check
cv.setVoltage(0, out_vco1); // CH_A -> VCO1
cv.setVoltage(1, out_vco2); // CH_B -> VCO2
digitalWrite(PIN_VCO1_EN, gate_vco1 ? HIGH : LOW);
digitalWrite(PIN_VCO2_EN, gate_vco2 ? HIGH : LOW);
// TIME-LIMIT CHECK
if(sb1.isRecording() && sb1.timeLimitReached())
{
sb1.stopRecord();
Serial.printf("\n\r[SEQ1] Time limit reached! Recording stopped.");
Serial.printf("\n\r[SEQ1] Final: Steps: %i, Duration: %ims",
Serial.printf("\n\r[SEQ1->VCO1] Time limit reached! Recording stopped.");
Serial.printf("\n\r[SEQ1->VCO1] Final: Steps: %i, Duration: %ims",
sb1.getStepCount(), sb1.getTotalDuration());
}
if(sb2.isRecording() && sb2.timeLimitReached())
{
sb2.stopRecord();
Serial.printf("\n\r[SEQ2] Time limit reached! Recording stopped.");
Serial.printf("\n\r[SEQ2] Final: Steps: %i, Duration: %ims",
Serial.printf("\n\r[SEQ2->VCO2] Time limit reached! Recording stopped.");
Serial.printf("\n\r[SEQ2->VCO2] Final: Steps: %i, Duration: %ims",
sb2.getStepCount(), sb2.getTotalDuration());
}
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

Binary file not shown.

Binary file not shown.

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
dev/print/4_caps/caps.iam Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

61185
dev/print/test/connection.stp Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

BIN
lit/USB-PD-ELKO.pdf Normal file

Binary file not shown.

BIN
lit/ds1117.pdf Normal file

Binary file not shown.

BIN
lit/lm2940c.pdf Normal file

Binary file not shown.