Cleaned up Schematics

This commit is contained in:
2026-03-18 11:01:39 +01:00
parent f0c2168e2b
commit a24b15c27b
493 changed files with 3025728 additions and 3024156 deletions

8
dev/general/Firmware/.gitignore vendored Normal file
View File

@@ -0,0 +1,8 @@
.pio/libdeps
.pio/build/project*
.pio/build/esp32-s3-devkitm-1/*
!.pio/build/esp32-s3-devkitm-1/firmware.bin
.vscode/.browse.c_cpp.db*
.vscode/c_cpp_properties.json
.vscode/launch.json
.vscode/ipch

View File

@@ -0,0 +1,10 @@
{
// See http://go.microsoft.com/fwlink/?LinkId=827846
// for the documentation about the extensions.json format
"recommendations": [
"platformio.platformio-ide"
],
"unwantedRecommendations": [
"ms-vscode.cpptools-extension-pack"
]
}

View File

View File

@@ -0,0 +1,172 @@
/*
@file: FIRMARE.h
@author: Erik Tóth
@contact: etoth@tsn.at
@date: 2025-10-26
@updated: 2025-12-06
@brief: Header for FIRMWARE.cpp (FIXED VERSION)
*/
#include <Arduino.h>
#include <Wire.h>
#include <Adafruit_MCP4728.h>
#ifndef FIRMWARE_H
#define FIRMWARE_H
#define N_MAX_QUEUE 10
#define N_MAX_ROWS 8
#define N_MAX_COLS 8
#define MS_DEBOUNCE 20
#define N_MAX_DAC_CH 4
/*!
@brief Key struct
@struct
*/
struct Key
{
int row;
int col;
};
/*!
@brief Voltage pair for both channels
@note might change arch
*/
struct DualVoltageDurationPair
{
uint16_t voltage_ch1;
uint16_t voltage_ch2;
uint16_t duration;
bool active;
};
/*!
@brief Sentinental value for invalid key
*/
const Key NOT_A_KEY = {-1, -1};
bool isNotKey(Key k);
bool isEqualKey(Key k1, Key k2);
class Keyboard
{
public:
Keyboard(uint8_t nRows, uint8_t nCols, uint8_t *pinsRow, uint8_t *pinsCol);
void begin();
void update();
int getQueueLength();
Key getQueue(uint8_t index);
private:
uint8_t _nRows;
uint8_t _nCols;
uint8_t *_pinsRow;
uint8_t *_pinsCol;
bool _keyState[N_MAX_COLS][N_MAX_ROWS];
bool _keyStateLatest[N_MAX_COLS][N_MAX_ROWS];
unsigned long _lastChangeTime[N_MAX_COLS][N_MAX_ROWS];
Key _activeKeys[N_MAX_QUEUE];
uint8_t _nActiveKeys;
uint8_t _nSticky;
void _addActiveKey(uint8_t row, uint8_t col);
void _removeActiveKey(uint8_t row, uint8_t col);
bool _inQueue(uint8_t row, uint8_t col);
bool _inQueue(Key k);
bool _isNotKey(Key k);
bool _isEqualKey(Key k1, Key k2);
};
class CV
{
public:
CV(Adafruit_MCP4728 *dac, TwoWire *wire, uint8_t nCV, MCP4728_channel_t *cvChannelMap, uint16_t *keyToVoltage, uint8_t row, uint8_t col);
bool begin(uint8_t pinSDA, uint8_t pinSCL);
void setVoltage(uint8_t cvIndex, Key k);
void setVoltage(uint8_t cvIndex, uint16_t mV);
void clearAll();
private:
Adafruit_MCP4728 *_dac;
TwoWire *_wire;
uint8_t _nCV;
uint8_t _row;
uint8_t _col;
MCP4728_channel_t _cvChannelMap[N_MAX_DAC_CH];
uint16_t *_keyToVoltage;
uint8_t _getKeyToVoltageIndex(uint8_t row, uint8_t col);
uint8_t _getKeyToVoltageIndex(Key k);
};
class SequencerBlock
{
public:
SequencerBlock(uint16_t maxDurationMS, uint16_t maxStepCount);
// Aufnahme-Funktionen
void startRecord();
void stopRecord();
void addStep(uint16_t voltage_ch1, uint16_t voltage_ch2);
bool isRecording();
// Wiedergabe-Funktionen
void startPlay();
void stopPlay();
void update();
bool isPlaying();
// Sequenz-Verwaltung
void clear();
void setLoop(bool loop);
// Status-Abfragen
bool timeLimitReached();
bool stepLimitReached();
uint16_t getStepCount();
uint16_t getCurrentVoltageCh1();
uint16_t getCurrentVoltageCh2();
bool isCurrentStepActive(); // NEU: Prüft ob aktueller Step aktive Noten hat
uint16_t getTotalDuration();
private:
/*!
* @brief Memory limiting
* @return (uint16_t) 1024
* @attention Increasing the value might lead to an overflow
* @note sizeOf(DualVoltageDurationPair) = 8 Byte ==> 8 Byte * 1024 = 8192 Byte
*/
const static uint16_t _MAX_SEQUENCE_STEPS = 1024;
// Sequenz memory
DualVoltageDurationPair _sequence[_MAX_SEQUENCE_STEPS];
uint16_t _stepCount;
uint16_t _currentStep;
// Time management
uint16_t _maxDurationMS;
uint16_t _maxStepCount;
unsigned long _recordStartTime;
unsigned long _lastStepTime;
unsigned long _playStartTime;
unsigned long _stepStartTime;
unsigned long _lastAddStepTime; // NEU: Rate-Limiting
// Status flags
bool _isRecording;
bool _isPlaying;
bool _loop;
// Last recorded Voltage: at n-th step minus one
uint16_t _lastVoltageCh1;
uint16_t _lastVoltageCh2;
// helper functions
void _finishCurrentStep();
bool _canAddStep();
};
#endif

View File

@@ -0,0 +1,49 @@
/*
@file: FIRMARE_DEF.h
@author: Erik Tóth
@contact: etoth@tsn.at
@date: 2025-10-26
@updated: 2026-03-08
@brief: Header for constant definitions
*/
#ifndef FIRMWARE_DEF_H
#define FIRMWARE_DEF_H
#include <Arduino.h>
#include <Wire.h>
// CONSTANTS DEFINITONS
#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
#define PIN_SCL 16
// KEYBOARD PINS
#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
#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
#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

@@ -0,0 +1,16 @@
; PlatformIO Project Configuration File
;
; Build options: build flags, source filter
; Upload options: custom upload port, speed and extra flags
; Library options: dependencies, extra library storages
; Advanced options: extra scripting
;
; Please visit documentation for the other options and examples
; https://docs.platformio.org/page/projectconf.html
[env:esp32-s3-devkitm-1]
platform = espressif32
board = esp32-s3-devkitm-1
framework = arduino
build_flags = -DARDUINO_USB_MODE=1 -DARDUINO_USB_CDC_ON_BOOT=1 -DARDUINO_USB_JTAG_ON_BOOT=1
lib_deps = adafruit/Adafruit MCP4728@^1.0.10

View File

@@ -0,0 +1,523 @@
/*
@file: FIRMWARE.cpp
@author: Erik Tóth
@contact: etoth@tsn.at
@date: 2025-10-26
@updated: 2026-03-08
@brief: Firmware für MCU
*/
#include "FIRMWARE.h"
// Helper-Functions
bool isNotKey(Key k)
{
if((k.row == NOT_A_KEY.row) && (k.col == NOT_A_KEY.col)) return true;
else return false;
}
bool isEqualKey(Key k1, Key k2)
{
if((k1.row == k2.row) && (k1.col == k2.col)) return true;
else return false;
}
// Keyboard
Keyboard::Keyboard(uint8_t nRows, uint8_t nCols, uint8_t *pinsRow, uint8_t *pinsCol)
{
_nRows = nRows;
_nCols = nCols;
_pinsRow = pinsRow;
_pinsCol = pinsCol;
_nActiveKeys = 0;
_nSticky = 2;
for(uint8_t i = 0; i < _nRows; i++)
{
for(uint8_t j = 0; j < _nCols; j++)
{
_keyState[i][j] = false;
_keyStateLatest[i][j] = false;
_lastChangeTime[i][j] = 0;
}
}
}
void Keyboard::begin()
{
for(int i = 0; i < _nRows; i++) pinMode(_pinsRow[i], INPUT_PULLDOWN);
for(int i = 0; i < _nCols; i++) pinMode(_pinsCol[i], INPUT);
}
void Keyboard::update()
{
unsigned long now = millis();
for(uint8_t col = 0; col < _nCols; col++)
{
pinMode(_pinsCol[col], OUTPUT);
digitalWrite(_pinsCol[col], HIGH);
for(uint8_t row = 0; row < _nRows; ++row)
{
bool reading = (digitalRead(_pinsRow[row]) == HIGH);
if(reading != _keyStateLatest[row][col])
{
_keyStateLatest[row][col] = reading;
_lastChangeTime[row][col] = now;
}
if((now - _lastChangeTime[row][col]) > MS_DEBOUNCE)
{
if(reading != _keyState[row][col])
{
_keyState[row][col] = reading;
if(reading) _addActiveKey(row, col);
else _removeActiveKey(row, col);
}
}
}
digitalWrite(_pinsCol[col], LOW);
pinMode(_pinsCol[col], INPUT);
}
if((_nActiveKeys == 1) && _inQueue(NOT_A_KEY)) _nActiveKeys = 0;
}
int Keyboard::getQueueLength()
{
return _nActiveKeys;
}
Key Keyboard::getQueue(uint8_t index)
{
if(index < _nActiveKeys) return _activeKeys[index];
else return NOT_A_KEY;
}
bool Keyboard::_inQueue(uint8_t row, uint8_t col)
{
for(uint8_t i = 0; i < _nActiveKeys; i++)
{
if((_activeKeys[i].row == row) && (_activeKeys[i].col == col)) return true;
}
return false;
}
bool Keyboard::_inQueue(Key k)
{
for(uint8_t i = 0; i < _nActiveKeys; i++)
{
if(_isEqualKey(_activeKeys[i], k)) return true;
}
return false;
}
bool Keyboard::_isNotKey(Key k)
{
return isNotKey(k);
}
bool Keyboard::_isEqualKey(Key k1, Key k2)
{
return isEqualKey(k1, k2);
}
void Keyboard::_addActiveKey(uint8_t row, uint8_t col)
{
if(_inQueue(NOT_A_KEY))
{
for(int i = 0; i < _nSticky; i++)
{
if(_isNotKey(_activeKeys[i]))
{
_activeKeys[i] = {row, col};
return;
}
}
}
else if((_nActiveKeys < N_MAX_QUEUE) && !(_inQueue(row, col)))
{
_activeKeys[_nActiveKeys++] = {row, col};
}
else return;
}
void Keyboard::_removeActiveKey(uint8_t row, uint8_t col)
{
bool notKeyReplaced = true;
for(uint8_t i = 0; i < _nActiveKeys; i++)
{
if((_activeKeys[i].row == row) && (_activeKeys[i].col == col))
{
if(i < _nSticky)
{
_activeKeys[i] = NOT_A_KEY;
notKeyReplaced = false;
}
if((_isNotKey(_activeKeys[i])) && (_nActiveKeys-1 >= _nSticky))
{
_activeKeys[i] = _activeKeys[_nSticky];
notKeyReplaced = true;
}
for(uint8_t j = i; j < _nActiveKeys-1; j++)
{
if(j >= _nSticky) _activeKeys[j] = _activeKeys[j + 1];
}
if(notKeyReplaced || (i > _nSticky)) _nActiveKeys--;
else if(_isNotKey(_activeKeys[_nSticky-1])) _nActiveKeys--;
return;
}
}
}
// CV
CV::CV(Adafruit_MCP4728 *dac, TwoWire *wire, uint8_t nCV, MCP4728_channel_t *cvChannelMap, uint16_t *keyToVoltage, uint8_t row, uint8_t col)
{
_dac = dac;
_wire = wire;
_nCV = nCV;
_row = row;
_col = col;
_keyToVoltage = keyToVoltage;
for(uint8_t i = 0; i < N_MAX_DAC_CH; i++)
{
_cvChannelMap[i] = i < _nCV ? cvChannelMap[i] : (MCP4728_channel_t)(0);
}
}
bool CV::begin(uint8_t pinSDA, uint8_t pinSCL)
{
if((_wire->begin(pinSDA, pinSCL) && _dac->begin(96U, _wire)))
{
clearAll();
return true;
}
else return false;
}
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, 2048, 0, 4095), MCP4728_VREF_INTERNAL, MCP4728_GAIN_1X);
}
void CV::setVoltage(uint8_t cvIndex, Key k)
{
if(cvIndex >= _nCV) return;
if(isNotKey(k)) setVoltage(cvIndex, 0);
else setVoltage(cvIndex, _keyToVoltage[_getKeyToVoltageIndex(k)]);
}
void CV::clearAll()
{
for(uint8_t i = 0; i < _nCV; i++) setVoltage(i, 0);
}
uint8_t CV::_getKeyToVoltageIndex(uint8_t row, uint8_t col)
{
return (row*_col + col);
}
uint8_t CV::_getKeyToVoltageIndex(Key k)
{
return (k.row*_col + k.col);
}
// SequencerBlock
/*!
* @param maxDurationMS maximum loop duration of recording in milliseconds
* @param maxStepCount maximum number of steps that can be recorded
*/
SequencerBlock::SequencerBlock(uint16_t maxDurationMS, uint16_t maxStepCount)
{
_maxDurationMS = maxDurationMS;
_maxStepCount = maxStepCount;
_stepCount = 0;
_currentStep = 0;
_isRecording = false;
_isPlaying = false;
_loop = false;
_lastVoltageCh1 = 0;
_lastVoltageCh2 = 0;
_recordStartTime = 0;
_lastStepTime = 0;
_playStartTime = 0;
_stepStartTime = 0;
_lastAddStepTime = 0;
}
void SequencerBlock::startRecord()
{
if(_isPlaying) stopPlay();
clear();
_isRecording = true;
_recordStartTime = millis();
_lastStepTime = _recordStartTime;
_lastAddStepTime = _recordStartTime;
_lastVoltageCh1 = 0xFFFF;
_lastVoltageCh2 = 0xFFFF;
}
void SequencerBlock::stopRecord()
{
if(!_isRecording) return;
_finishCurrentStep();
_isRecording = false;
}
void SequencerBlock::addStep(uint16_t voltage_ch1, uint16_t voltage_ch2)
{
if(!_isRecording) return;
if(_stepCount >= _MAX_SEQUENCE_STEPS - 1)
{
Serial.println("\n\r[ERROR] Step limit reached! Stopping recording.");
stopRecord();
return;
}
if(timeLimitReached())
{
Serial.println("\n\r[WARNING] Time limit reached! Stopping recording.");
stopRecord();
return;
}
unsigned long now = millis();
if((unsigned long)(now - _lastAddStepTime) < 5)
{
return;
}
_lastAddStepTime = now;
bool voltageChanged = (voltage_ch1 != _lastVoltageCh1) || (voltage_ch2 != _lastVoltageCh2);
if(voltageChanged)
{
if(_stepCount >= _MAX_SEQUENCE_STEPS - 1)
{
Serial.println("\n\r[ERROR] Array full! Stopping recording.");
stopRecord();
return;
}
if(_stepCount > 0 && _stepCount <= _MAX_SEQUENCE_STEPS)
{
_finishCurrentStep();
}
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);
_stepCount++;
_lastStepTime = now;
_lastVoltageCh1 = voltage_ch1;
_lastVoltageCh2 = voltage_ch2;
}
}
else
{
if(_stepCount > 0 && _stepCount <= _MAX_SEQUENCE_STEPS)
{
_sequence[_stepCount - 1].duration = now - _lastStepTime;
}
}
}
bool SequencerBlock::isRecording()
{
return _isRecording;
}
void SequencerBlock::startPlay()
{
if(_stepCount == 0) return;
if(_isRecording) stopRecord();
_isPlaying = true;
_currentStep = 0;
_playStartTime = millis();
_stepStartTime = _playStartTime;
}
void SequencerBlock::stopPlay()
{
_isPlaying = false;
_currentStep = 0;
}
void SequencerBlock::update()
{
if(!_isPlaying || _stepCount == 0) return;
if(_currentStep >= _stepCount || _currentStep >= _MAX_SEQUENCE_STEPS)
{
Serial.println("\n\r[ERROR] Invalid step index in update()!");
stopPlay();
return;
}
unsigned long now = millis();
unsigned long elapsed = now - _stepStartTime;
if(_sequence[_currentStep].duration == 0)
{
_currentStep++;
_stepStartTime = now;
if(_currentStep >= _stepCount)
{
if(_loop)
{
_currentStep = 0;
}
else
{
stopPlay();
}
}
return;
}
if(elapsed >= _sequence[_currentStep].duration)
{
_currentStep++;
if(_currentStep >= _stepCount)
{
if(_loop)
{
_currentStep = 0;
_stepStartTime = now;
}
else
{
stopPlay();
return;
}
}
else
{
_stepStartTime = now;
}
}
}
bool SequencerBlock::isPlaying()
{
return _isPlaying;
}
void SequencerBlock::clear()
{
_stepCount = 0;
_currentStep = 0;
_lastVoltageCh1 = 0;
_lastVoltageCh2 = 0;
for(uint16_t i = 0; i < _MAX_SEQUENCE_STEPS; i++)
{
_sequence[i].voltage_ch1 = 0;
_sequence[i].voltage_ch2 = 0;
_sequence[i].duration = 0;
_sequence[i].active = false;
}
}
void SequencerBlock::setLoop(bool loop)
{
_loop = loop;
}
bool SequencerBlock::timeLimitReached()
{
if(!_isRecording) return false;
unsigned long now = millis();
unsigned long elapsed = now - _recordStartTime;
return (elapsed >= _maxDurationMS);
}
bool SequencerBlock::stepLimitReached()
{
return (_stepCount >= _maxStepCount) || (_stepCount >= _MAX_SEQUENCE_STEPS);
}
uint16_t SequencerBlock::getStepCount()
{
return _stepCount;
}
uint16_t SequencerBlock::getCurrentVoltageCh1()
{
if(!_isPlaying || _stepCount == 0) return 0;
if(_currentStep >= _stepCount || _currentStep >= _MAX_SEQUENCE_STEPS) return 0;
return _sequence[_currentStep].voltage_ch1;
}
uint16_t SequencerBlock::getCurrentVoltageCh2()
{
if(!_isPlaying || _stepCount == 0) return 0;
if(_currentStep >= _stepCount || _currentStep >= _MAX_SEQUENCE_STEPS) return 0;
return _sequence[_currentStep].voltage_ch2;
}
uint16_t SequencerBlock::getTotalDuration()
{
uint32_t total = 0;
for(uint16_t i = 0; i < _stepCount && i < _MAX_SEQUENCE_STEPS; i++)
{
total += _sequence[i].duration;
}
return (total > 65535) ? 65535 : (uint16_t)total; // Clamp auf uint16
}
bool SequencerBlock::isCurrentStepActive()
{
if(!_isPlaying || _stepCount == 0) return false;
if(_currentStep >= _stepCount || _currentStep >= _MAX_SEQUENCE_STEPS) return false;
return _sequence[_currentStep].active;
}
void SequencerBlock::_finishCurrentStep()
{
if(_stepCount == 0) return;
if(_stepCount > _MAX_SEQUENCE_STEPS) return;
unsigned long now = millis();
uint16_t duration = now - _lastStepTime;
_sequence[_stepCount - 1].duration = duration;
}
bool SequencerBlock::_canAddStep()
{
if(_stepCount >= _maxStepCount) return false;
if(_stepCount >= _MAX_SEQUENCE_STEPS) return false;
if(timeLimitReached()) return false;
return true;
}

View File

@@ -0,0 +1,505 @@
/*
* 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};
Keyboard keyboard(N_KEYBOARD_ROW, N_KEYBOARD_COL, pins_keyboard_row, pins_keyboard_col);
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] = {
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);
// Button States
struct ButtonState {
bool current;
bool last;
unsigned long lastDebounceTime;
};
ButtonState btn_sb1_rec;
ButtonState btn_sb1_play;
ButtonState btn_sb2_rec;
ButtonState btn_sb2_play;
ButtonState btn_metronome;
const unsigned long DEBOUNCE_DELAY = 50;
static bool seq1_loop_active = false;
static bool seq2_loop_active = false;
// 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)
{
bool reading = digitalRead(pin) == HIGH;
bool buttonPressed = false;
if(reading != state.last)
{
state.lastDebounceTime = millis();
}
if((millis() - state.lastDebounceTime) > DEBOUNCE_DELAY)
{
if(reading != state.current)
{
state.current = reading;
if(state.current == true)
{
buttonPressed = true;
}
}
}
state.last = reading;
return buttonPressed;
}
void initButtons()
{
pinMode(PIN_SB_1_REC, INPUT_PULLDOWN);
pinMode(PIN_SB_1_PLAY, INPUT_PULLDOWN);
pinMode(PIN_SB_2_REC, INPUT_PULLDOWN);
pinMode(PIN_SB_2_PLAY, INPUT_PULLDOWN);
pinMode(PIN_B_METRONOME, INPUT_PULLDOWN);
btn_sb1_rec.current = false;
btn_sb1_rec.last = false;
btn_sb1_rec.lastDebounceTime = 0;
btn_sb1_play.current = false;
btn_sb1_play.last = false;
btn_sb1_play.lastDebounceTime = 0;
btn_sb2_rec.current = false;
btn_sb2_rec.last = false;
btn_sb2_rec.lastDebounceTime = 0;
btn_sb2_play.current = false;
btn_sb2_play.last = false;
btn_sb2_play.lastDebounceTime = 0;
btn_metronome.current = false;
btn_metronome.last = false;
btn_metronome.lastDebounceTime = 0;
}
void initOutputs()
{
// VCO Gates
pinMode(PIN_VCO1_EN, OUTPUT);
pinMode(PIN_VCO2_EN, OUTPUT);
digitalWrite(PIN_VCO1_EN, LOW);
digitalWrite(PIN_VCO2_EN, LOW);
// Recording LED (active-low)
pinMode(PIN_REC, OUTPUT);
digitalWrite(PIN_REC, HIGH); // OFF
// Metronome LED (active-low)
pinMode(PIN_L_METRONOME, OUTPUT);
digitalWrite(PIN_L_METRONOME, HIGH); // OFF
// BPM Potentiometer
pinMode(PIN_BPM, INPUT);
}
void handleSequencerButtons()
{
if(readButton(PIN_SB_1_REC, btn_sb1_rec))
{
if(sb1.isRecording())
{
sb1.stopRecord();
Serial.printf("\n\r[SEQ1->VCO1] Recording stopped. Steps: %i, Duration: %ims",
sb1.getStepCount(), sb1.getTotalDuration());
}
else
{
if(sb1.isPlaying()) sb1.stopPlay();
sb1.startRecord();
sb1_last_voltage_ch1 = 0xFFFF;
sb1_last_voltage_ch2 = 0xFFFF;
Serial.printf("\n\r[SEQ1->VCO1] Recording started...");
}
}
if(readButton(PIN_SB_1_PLAY, btn_sb1_play))
{
if(!sb1.isPlaying())
{
if(sb1.isRecording()) sb1.stopRecord();
sb1.setLoop(false);
seq1_loop_active = false;
sb1.startPlay();
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->VCO1] Loop activated");
}
else
{
sb1.stopPlay();
seq1_loop_active = false;
Serial.printf("\n\r[SEQ1->VCO1] Playback stopped");
}
}
if(readButton(PIN_SB_2_REC, btn_sb2_rec))
{
if(sb2.isRecording())
{
sb2.stopRecord();
Serial.printf("\n\r[SEQ2->VCO2] Recording stopped. Steps: %i, Duration: %ims",
sb2.getStepCount(), sb2.getTotalDuration());
}
else
{
if(sb2.isPlaying()) sb2.stopPlay();
sb2.startRecord();
sb2_last_voltage_ch1 = 0xFFFF;
sb2_last_voltage_ch2 = 0xFFFF;
Serial.printf("\n\r[SEQ2->VCO2] Recording started...");
}
}
if(readButton(PIN_SB_2_PLAY, btn_sb2_play))
{
if(!sb2.isPlaying())
{
if(sb2.isRecording()) sb2.stopRecord();
sb2.setLoop(false);
seq2_loop_active = false;
sb2.startPlay();
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->VCO2] Loop activated");
}
else
{
sb2.stopPlay();
seq2_loop_active = false;
Serial.printf("\n\r[SEQ2->VCO2] Playback stopped");
}
}
}
static bool metronome_enabled = false;
static uint16_t current_bpm = 120;
static unsigned long last_beat_time = 0;
static unsigned long last_pulse_end_time = 0;
static bool metronome_led_on = false;
void updateMetronome()
{
unsigned long now = millis();
static unsigned long last_bpm_read = 0;
if((now - last_bpm_read) > 100)
{
int adc_value = analogRead(PIN_BPM);
current_bpm = map(adc_value, 0, 4095, 40, 240);
last_bpm_read = now;
}
if(readButton(PIN_B_METRONOME, btn_metronome))
{
metronome_enabled = !metronome_enabled;
Serial.printf("\n\r[METRONOME] %s (BPM: %d)",
metronome_enabled ? "ON" : "OFF", current_bpm);
if(!metronome_enabled)
{
digitalWrite(PIN_L_METRONOME, HIGH);
metronome_led_on = false;
}
}
if(!metronome_enabled) return;
unsigned long beat_interval = 60000UL / current_bpm;
if((now - last_beat_time) >= beat_interval)
{
digitalWrite(PIN_L_METRONOME, LOW);
metronome_led_on = true;
last_beat_time = now;
last_pulse_end_time = now + 50;
}
if(metronome_led_on && (now >= last_pulse_end_time))
{
digitalWrite(PIN_L_METRONOME, HIGH);
metronome_led_on = false;
}
}
void updateRecordingLED()
{
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=== DUAL SEQUENCER: SB1->VCO1 | SB2->VCO2 ===");
Serial.printf("\n\rSerial OK!");
keyboard.begin();
unsigned long timeout = millis() + 5000;
while(!cv.begin(PIN_SDA, PIN_SCL))
{
Serial.printf("\n\r[ERROR] CV initialization failed. Retrying...");
delay(500);
if(millis() > timeout)
{
Serial.printf("\n\r[FATAL] CV initialization timeout! Check I2C connection.");
break;
}
}
Serial.printf("\n\r[OK] CV initialized");
initButtons();
initOutputs();
sb1.setLoop(false);
sb2.setLoop(false);
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
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->VCO1: Rec=%d, Play=%d, Steps=%d",
sb1.isRecording(), sb1.isPlaying(), sb1.getStepCount());
Serial.printf("\n\r[DEBUG] SB2->VCO2: Rec=%d, Play=%d, Steps=%d",
sb2.isRecording(), sb2.isPlaying(), sb2.getStepCount());
lastDebugPrint = millis();
}
// NON-BLOCKING TIMING
static unsigned long lastLoopTime = 0;
unsigned long now = millis();
const unsigned long LOOP_INTERVAL = 10;
if((now - lastLoopTime) < LOOP_INTERVAL) return;
lastLoopTime = now;
// UPDATE
keyboard.update();
handleSequencerButtons();
updateMetronome();
updateRecordingLED();
sb1.update();
sb2.update();
// KEYBOARD INPUT
int n = keyboard.getQueueLength();
// 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 k = keyboard.getQueue(0);
if(!isNotKey(k))
{
manual_voltage_0 = keyToVoltage[k.row * N_KEYBOARD_COL + k.col];
manual_active_0 = true;
}
}
if(n > 1)
{
Key k = keyboard.getQueue(1);
if(!isNotKey(k))
{
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;
}
}
// SB2 nimmt ebenfalls den vollen Keyboard-Input auf
if(sb2.isRecording())
{
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;
}
}
// ===== 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)
{
// SB1 Sequenz läuft -> Sequenz-Ausgabe
out_vco1 = sb1.getCurrentVoltageCh1();
gate_vco1 = sb1.isCurrentStepActive();
}
else if(sb1_recording)
{
// SB1 nimmt auf -> Live-Ausgabe damit man hört was man spielt
out_vco1 = manual_voltage_0;
gate_vco1 = manual_active_0;
}
else
{
// 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;
}
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->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->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.

File diff suppressed because one or more lines are too long

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.

Binary file not shown.

Binary file not shown.

Binary file not shown.

File diff suppressed because one or more lines are too long

Binary file not shown.

Binary file not shown.

View File

@@ -338,6 +338,23 @@ DItemRevisionGUID=
GenerateClassCluster=0 GenerateClassCluster=0
DocumentUniqueId= DocumentUniqueId=
[Document18]
DocumentPath=Job1.OutJob
AnnotationEnabled=1
AnnotateStartValue=1
AnnotationIndexControlEnabled=0
AnnotateSuffix=
AnnotateScope=All
AnnotateOrder=-1
DoLibraryUpdate=1
DoDatabaseUpdate=1
ClassGenCCAutoEnabled=1
ClassGenCCAutoRoomEnabled=0
ClassGenNCAutoScope=None
DItemRevisionGUID=
GenerateClassCluster=0
DocumentUniqueId=
[GeneratedDocument1] [GeneratedDocument1]
DocumentPath=Project Outputs for MainSys\Design Rule Check - MAIN.html DocumentPath=Project Outputs for MainSys\Design Rule Check - MAIN.html
DItemRevisionGUID= DItemRevisionGUID=

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,2 @@
Change Component Parameter Comment in Y1 Old= New==Value
Replace Symbol from Y1 Y X322540MPB4SI in C:\HTL\5AHEL\DA\github\audio-synth\dev\general\MainSys\MCU.SchDoc with Y X322540MPB4SI from DA_LIB.IntLib

View File

@@ -0,0 +1,2 @@
Change Component Parameter Comment in R28 Old= New==VALUE
Replace Symbol from R28 R_POT TC33X-2-103E in C:\HTL\5AHEL\DA\github\audio-synth\dev\general\MainSys\VCO.SchDoc with R_POT TC33X-2-103E from DA_LIB.IntLib

View File

@@ -0,0 +1,6 @@
Change Component Parameter Comment in R28 Old= New==VALUE
Change Component Parameter Comment in R32 Old= New==VALUE
Change Component Parameter Comment in R36 Old= New==VALUE
Replace Symbol from R28 R_POT TC33X-2-103E in C:\HTL\5AHEL\DA\github\audio-synth\dev\general\MainSys\VCO.SchDoc with R_POT TC33X-2-103E from DA_LIB.IntLib
Replace Symbol from R32 R_POT TC33X-2-103E in C:\HTL\5AHEL\DA\github\audio-synth\dev\general\MainSys\VCO.SchDoc with R_POT TC33X-2-103E from DA_LIB.IntLib
Replace Symbol from R36 R_POT TC33X-2-103E in C:\HTL\5AHEL\DA\github\audio-synth\dev\general\MainSys\VCO.SchDoc with R_POT TC33X-2-103E from DA_LIB.IntLib

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

BIN
docs/general/BOM_doc.xlsx Normal file

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 MiB

Some files were not shown because too many files have changed in this diff Show More