Software Update 4: Sequencer Block

Sequencer Klasse eingebaut
  Überprüfung noch ausständig
This commit is contained in:
Erik Tóth
2025-11-13 17:03:04 +01:00
parent 3ad41e6ba5
commit b3d66fdfd8
12 changed files with 641 additions and 234 deletions

View File

@@ -18,6 +18,7 @@
#define MS_DEBOUNCE 20
#define N_MAX_DAC_CH 4
#define N_MAX_SEQUENCE_STEPS 128
struct Key
{
@@ -25,6 +26,12 @@ struct Key
int col;
};
struct voltageDurationPair
{
uint16_t voltage;
uint16_t duration;
};
const Key NOT_A_KEY = {-1, -1};
bool isNotKey(Key k);
@@ -83,4 +90,58 @@ class CV
uint8_t _getKeyToVoltageIndex(Key k);
};
class SequencerBlock
{
public:
SequencerBlock(uint16_t maxDurationMS, uint16_t timeoutMS);
// Aufnahme-Funktionen
void startRecord();
void stopRecord();
void addStep(uint16_t voltage);
bool isRecording();
// Wiedergabe-Funktionen
void startPlay();
void stopPlay();
void update(); // Muss regelmäßig aufgerufen werden
bool isPlaying();
// Sequenz-Verwaltung
void clear();
void setLoop(bool loop);
// Status-Abfragen
bool timeLimitReached();
uint8_t getStepCount();
uint16_t getCurrentVoltage();
uint16_t getTotalDuration();
private:
// Sequenz-Speicher
voltageDurationPair _sequence[N_MAX_SEQUENCE_STEPS];
uint8_t _stepCount;
uint8_t _currentStep;
// Zeitverwaltung
uint16_t _maxDurationMS;
uint16_t _timeoutMS;
unsigned long _recordStartTime;
unsigned long _lastStepTime;
unsigned long _playStartTime;
unsigned long _stepStartTime;
// Status-Flags
bool _isRecording;
bool _isPlaying;
bool _loop;
// Letzte aufgenommene Spannung
uint16_t _lastVoltage;
// Hilfsfunktionen
void _finishCurrentStep();
bool _canAddStep();
};
#endif

View File

@@ -14,6 +14,7 @@
#define N_KEYBOARD_ROW 4
#define N_KEYBOARD_COL 3
#define N_CV_GATES 2
#define N_SB 2
#define BAUDRATE 115200
// PIN DEFENTITIONS
// I2C PINS
@@ -24,7 +25,15 @@
#define PIN_K_R1 8
#define PIN_K_R2 9
#define PIN_K_R3 10
#define PIN_K_R4 // NOT IN USE
#define PIN_K_C0 1
#define PIN_K_C1 2
#define PIN_K_C2 4
#define PIN_K_C3 // NOT IN USE
#define PIN_K_C4 // NOT IN USE
// SEQUENCER BUTTON PINS
#define PIN_SB_1_REC 0
#define PIN_SB_1_PLAY 0
#define PIN_SB_2_REC 0
#define PIN_SB_2_PLAY 0
#endif

View File

@@ -8,6 +8,8 @@
#include "FIRMWARE.h"
// ==================== Helper-Functions ====================
bool isNotKey(Key k)
{
if((k.row == NOT_A_KEY.row) && (k.col == NOT_A_KEY.col)) return true;
@@ -20,6 +22,8 @@ bool isEqualKey(Key k1, Key k2)
else return false;
}
// ==================== Keyboard ====================
Keyboard::Keyboard(uint8_t nRows, uint8_t nCols, uint8_t *pinsRow, uint8_t *pinsCol)
{
_nRows = nRows;
@@ -171,6 +175,8 @@ void Keyboard::_removeActiveKey(uint8_t row, uint8_t col)
}
}
// ==================== CV ====================
/*!
* @param dac Adafruit_MCP4728 object
* @param wire TwoWire object
@@ -232,4 +238,264 @@ uint8_t CV::_getKeyToVoltageIndex(uint8_t row, uint8_t col)
uint8_t CV::_getKeyToVoltageIndex(Key k)
{
return (k.row*_col + k.col);
}
// ==================== SequencerBlock ====================
/*!
* @param maxDurationMS maximum loop duration of recording in milliseconds
* @param timeoutMS stops recording after timeout in milliseconds
* @brief TODO
*/
SequencerBlock::SequencerBlock(uint16_t maxDurationMS, uint16_t timeoutMS)
{
_maxDurationMS = maxDurationMS;
_timeoutMS = timeoutMS;
_stepCount = 0;
_currentStep = 0;
_isRecording = false;
_isPlaying = false;
_loop = false;
_lastVoltage = 0;
_recordStartTime = 0;
_lastStepTime = 0;
_playStartTime = 0;
_stepStartTime = 0;
}
/*!
* @brief starts sequence block recording
*/
void SequencerBlock::startRecord()
{
if(_isPlaying) stopPlay();
clear();
_isRecording = true;
_recordStartTime = millis();
_lastStepTime = _recordStartTime;
_lastVoltage = 0;
}
/*!
* @brief stops sequence block recording and saves it
*/
void SequencerBlock::stopRecord()
{
if(!_isRecording) return;
_finishCurrentStep();
_isRecording = false;
}
/*!
* @brief adds step to sequencer block
* @param voltage voltage step for CV-Gate in millivolts
*/
void SequencerBlock::addStep(uint16_t voltage)
{
if(!_isRecording) return;
if(!_canAddStep()) return;
unsigned long now = millis();
// Wenn sich die Spannung geändert hat, vorherigen Schritt abschließen
if(voltage != _lastVoltage && _stepCount > 0)
{
_finishCurrentStep();
}
// Neuen Schritt beginnen oder vorhandenen aktualisieren
if(voltage != _lastVoltage || _stepCount == 0)
{
if(_canAddStep())
{
_sequence[_stepCount].voltage = voltage;
_sequence[_stepCount].duration = 0;
_lastStepTime = now;
_lastVoltage = voltage;
}
}
else
{
if(_stepCount > 0)
{
_sequence[_stepCount - 1].duration = now - _lastStepTime;
}
}
}
/*!
* @brief checks if sequencer block is recording
* @return true or false
*/
bool SequencerBlock::isRecording()
{
return _isRecording;
}
/*!
* @brief starts playing sequencer block
*/
void SequencerBlock::startPlay()
{
if(_stepCount == 0) return;
if(_isRecording) stopRecord();
_isPlaying = true;
_currentStep = 0;
_playStartTime = millis();
_stepStartTime = _playStartTime;
}
/*!
* @brief stops playing sequencer block
*/
void SequencerBlock::stopPlay()
{
_isPlaying = false;
_currentStep = 0;
}
/*!
* @brief updates sequencer block
* @attention Has to be called every cycle!
*/
void SequencerBlock::update()
{
if(!_isPlaying || _stepCount == 0) return;
unsigned long now = millis();
unsigned long elapsed = now - _stepStartTime;
// Prüfen ob aktueller Schritt abgelaufen ist
if(elapsed >= _sequence[_currentStep].duration)
{
_currentStep++;
// Sequenz-Ende erreicht?
if(_currentStep >= _stepCount)
{
if(_loop)
{
_currentStep = 0;
_stepStartTime = now;
}
else
{
stopPlay();
return;
}
}
else
{
_stepStartTime = now;
}
}
}
/*!
* @brief checks if sequencer block is playing
* @return true or false
*/
bool SequencerBlock::isPlaying()
{
return _isPlaying;
}
/*!
* @brief clears recording of sequencer block
*/
void SequencerBlock::clear()
{
_stepCount = 0;
_currentStep = 0;
_lastVoltage = 0;
for(uint8_t i = 0; i < N_MAX_SEQUENCE_STEPS; i++)
{
_sequence[i].voltage = 0;
_sequence[i].duration = 0;
}
}
/*!
* @brief sets configuation for looping over the recording
* @param loop if set to true, saved recording gets played in a loop
*/
void SequencerBlock::setLoop(bool loop)
{
_loop = loop;
}
/*!
* @brief checks if the recording time limit has been reached
* @return true of false
*/
bool SequencerBlock::timeLimitReached()
{
if(!_isRecording) return false;
unsigned long now = millis();
unsigned long elapsed = now - _recordStartTime;
return (elapsed >= _maxDurationMS);
}
/*!
* @brief returns the currently recoreded steps
* @return uint8_t between 0 and 128
*/
uint8_t SequencerBlock::getStepCount()
{
return _stepCount;
}
/*!
* @brief if sequencer is playing, returns the current voltage level
* @return uint16_t voltage range for CV
*/
uint16_t SequencerBlock::getCurrentVoltage()
{
if(!_isPlaying || _stepCount == 0) return 0;
if(_currentStep >= _stepCount) return 0;
return _sequence[_currentStep].voltage;
}
/*!
* @brief gets the length of the recording in the block
* @return uint16_t time in milliseconds
*/
uint16_t SequencerBlock::getTotalDuration()
{
uint16_t total = 0;
for(uint8_t i = 0; i < _stepCount; i++)
{
total += _sequence[i].duration;
}
return total;
}
void SequencerBlock::_finishCurrentStep()
{
if(_stepCount == 0) return;
unsigned long now = millis();
_sequence[_stepCount - 1].duration = now - _lastStepTime;
// Timeout prüfen - wenn zu lange keine Änderung, Schritt nicht hinzufügen
if(_sequence[_stepCount - 1].duration < _timeoutMS)
{
_stepCount++;
}
}
bool SequencerBlock::_canAddStep()
{
if(_stepCount >= N_MAX_SEQUENCE_STEPS) return false;
if(timeLimitReached()) return false;
return true;
}

View File

@@ -1,5 +1,13 @@
/*
* Example Code Two
* Example Code Three
* with Sequencer
*
* Bedienung:
* - Keyboard-Tasten: CV-Ausgabe direkt oder Recording
* - PIN_SB_1_REC: Sequencer 1 Record Start/Stop
* - PIN_SB_1_PLAY: Sequencer 1 Play/Stop
* - PIN_SB_2_REC: Sequencer 2 Record Start/Stop
* - PIN_SB_2_PLAY: Sequencer 2 Play/Stop
*/
#include "FIRMWARE_DEF.h"
#include "FIRMWARE.h"
@@ -11,7 +19,7 @@ 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] = {
uint16_t keyToVoltage[N_KEYBOARD_ROW*N_KEYBOARD_COL] = { /* 83mV = 1/12V */
1*83, 5*83, 9*83,
2*83, 6*83, 10*83,
3*83, 7*83, 11*83,
@@ -20,41 +28,251 @@ uint16_t keyToVoltage[N_KEYBOARD_ROW*N_KEYBOARD_COL] = {
CV cv(&MCP4728, &Wire, N_CV_GATES, cvMap, keyToVoltage, N_KEYBOARD_ROW, N_KEYBOARD_COL);
SequencerBlock sb1(30000, 250); // 30 Sekunden max, 250ms Timeout
SequencerBlock sb2(30000, 250);
// 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;
const unsigned long DEBOUNCE_DELAY = 50;
// Hilfsfunktion zum Lesen eines Buttons mit Debouncing
bool readButton(byte pin, ButtonState &state)
{
bool reading = digitalRead(pin) == LOW; // LOW = gedrückt (mit Pull-Up)
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) // Button wurde gerade gedrückt
{
buttonPressed = true;
}
}
}
state.last = reading;
return buttonPressed;
}
void initButtons()
{
pinMode(PIN_SB_1_REC, INPUT_PULLUP);
pinMode(PIN_SB_1_PLAY, INPUT_PULLUP);
pinMode(PIN_SB_2_REC, INPUT_PULLUP);
pinMode(PIN_SB_2_PLAY, INPUT_PULLUP);
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;
}
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",
sb1.getStepCount(), sb1.getTotalDuration());
}
else
{
if(sb1.isPlaying()) sb1.stopPlay();
sb1.startRecord();
Serial.printf("\n\r[SEQ1] Recording started...");
}
}
// Sequencer 1 Play Button
if(readButton(PIN_SB_1_PLAY, btn_sb1_play))
{
if(sb1.isPlaying())
{
sb1.stopPlay();
Serial.printf("\n\r[SEQ1] Playback stopped");
}
else
{
if(sb1.isRecording()) sb1.stopRecord();
sb1.startPlay();
Serial.printf("\n\r[SEQ1] Playback started");
}
}
// 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",
sb2.getStepCount(), sb2.getTotalDuration());
}
else
{
if(sb2.isPlaying()) sb2.stopPlay();
sb2.startRecord();
Serial.printf("\n\r[SEQ2] Recording started...");
}
}
// Sequencer 2 Play Button
if(readButton(PIN_SB_2_PLAY, btn_sb2_play))
{
if(sb2.isPlaying())
{
sb2.stopPlay();
Serial.printf("\n\r[SEQ2] Playback stopped");
}
else
{
if(sb2.isRecording()) sb2.stopRecord();
sb2.startPlay();
Serial.printf("\n\r[SEQ2] Playback started");
}
}
}
void setup()
{
Serial.begin(BAUDRATE);
keyboard.begin();
cv.begin(PIN_SDA, PIN_SCL);
Serial.begin(BAUDRATE);
keyboard.begin();
cv.begin(PIN_SDA, PIN_SCL);
initButtons();
sb1.setLoop(false);
sb2.setLoop(false);
Serial.printf("\n\r=== Sequencer System Started ===");
Serial.printf("\n\rControls:");
Serial.printf("\n\r PIN_SB_1_REC: SEQ1 Record Start/Stop");
Serial.printf("\n\r PIN_SB_1_PLAY: SEQ1 Play/Stop");
Serial.printf("\n\r PIN_SB_2_REC: SEQ2 Record Start/Stop");
Serial.printf("\n\r PIN_SB_2_PLAY: SEQ2 Play/Stop");
Serial.printf("\n\r================================\n\r");
}
void loop()
{
keyboard.update();
int n = keyboard.getQueueLength();
if(n > 0)
{
Serial.printf("\n\rCurrent queue length: %i", n);
if(n == 1)
keyboard.update();
handleSequencerButtons();
// Sequencer Update (für Wiedergabe)
sb1.update();
sb2.update();
int n = keyboard.getQueueLength();
// Keyboard-Tasten verarbeiten
if(n > 0)
{
cv.setVoltage(0, keyboard.getQueue(0));
cv.setVoltage(1, NOT_A_KEY);
// Alle Keyboard-Tasten für CV-Ausgabe verwenden
int cvIndex = 0;
for(int i = 0; i < n && cvIndex < N_CV_GATES; i++)
{
Key k = keyboard.getQueue(i);
if(!isNotKey(k))
{
uint16_t voltage = keyToVoltage[k.row * N_KEYBOARD_COL + k.col];
// Bei Recording: Spannung aufnehmen
if(sb1.isRecording())
{
sb1.addStep(voltage);
}
if(sb2.isRecording())
{
sb2.addStep(voltage);
}
// Live-Ausgabe nur wenn nicht gerade wiedergegeben wird
if(!sb1.isPlaying() && !sb2.isPlaying())
{
cv.setVoltage(cvIndex++, k);
}
}
}
// Restliche CV-Ausgänge auf 0 setzen wenn live gespielt wird
if(!sb1.isPlaying() && !sb2.isPlaying())
{
for(int i = cvIndex; i < N_CV_GATES; i++)
{
cv.setVoltage(i, 0);
}
}
}
else if(n >= 2)
else
{
cv.setVoltage(0, keyboard.getQueue(0));
cv.setVoltage(1, keyboard.getQueue(1));
// Keine Tasten gedrückt
if(sb1.isRecording())
{
sb1.addStep(0);
}
if(sb2.isRecording())
{
sb2.addStep(0);
}
if(!sb1.isPlaying() && !sb2.isPlaying())
{
cv.clearAll();
}
}
for(int i = 0; (i < N_CV_GATES) && (i < n); i++)
// Sequencer-Wiedergabe auf CV-Ausgänge
if(sb1.isPlaying())
{
Key k = keyboard.getQueue(i);
if(isNotKey(k)) Serial.printf("\n\rQueue position %i: NOT A KEY", i);
else Serial.printf("\n\rQueue position %i: R%iC%i", i, k.row, k.col);
cv.setVoltage(0, sb1.getCurrentVoltage());
}
}
else cv.clearAll();
delay(50);
if(sb2.isPlaying())
{
cv.setVoltage(1, sb2.getCurrentVoltage());
}
// Time-Limit Warnung
if(sb1.isRecording() && sb1.timeLimitReached())
{
sb1.stopRecord();
Serial.printf("\n\r[SEQ1] Time limit reached! Recording stopped.");
}
if(sb2.isRecording() && sb2.timeLimitReached())
{
sb2.stopRecord();
Serial.printf("\n\r[SEQ2] Time limit reached! Recording stopped.");
}
delay(10); // Kürzeres Delay für bessere Sequencer-Auflösung
}

View File

@@ -0,0 +1,60 @@
/*
* Example Code Two
*/
#include "FIRMWARE_DEF.h"
#include "FIRMWARE.h"
static byte pins_keyboard_row[N_KEYBOARD_ROW] = {PIN_K_R0, PIN_K_R1, PIN_K_R2, PIN_K_R3};
static byte pins_keyboard_col[N_KEYBOARD_COL] = {PIN_K_C0, PIN_K_C1, PIN_K_C2};
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] = {
1*83, 5*83, 9*83,
2*83, 6*83, 10*83,
3*83, 7*83, 11*83,
4*83, 8*83, 12*83
};
CV cv(&MCP4728, &Wire, N_CV_GATES, cvMap, keyToVoltage, N_KEYBOARD_ROW, N_KEYBOARD_COL);
void setup()
{
Serial.begin(BAUDRATE);
keyboard.begin();
cv.begin(PIN_SDA, PIN_SCL);
}
void loop()
{
keyboard.update();
int n = keyboard.getQueueLength();
if(n > 0)
{
Serial.printf("\n\rCurrent queue length: %i", n);
if(n == 1)
{
cv.setVoltage(0, keyboard.getQueue(0));
cv.setVoltage(1, NOT_A_KEY);
}
else if(n >= 2)
{
cv.setVoltage(0, keyboard.getQueue(0));
cv.setVoltage(1, keyboard.getQueue(1));
}
for(int i = 0; (i < N_CV_GATES) && (i < n); i++)
{
Key k = keyboard.getQueue(i);
if(isNotKey(k)) Serial.printf("\n\rQueue position %i: NOT A KEY", i);
else Serial.printf("\n\rQueue position %i: R%iC%i", i, k.row, k.col);
}
}
else cv.clearAll();
delay(50);
}