mirror of
https://github.com/erik-toth/audio-synth.git
synced 2025-12-06 08:00:02 +00:00
Firmware MCU: Sequencer, erfassen und widergabe im Sequencerblock von beiden Channel, playback im single und loop modus, test OK
This commit is contained in:
1
dev/digital/ESP32-S3/ESP32-S3.Harness
Normal file
1
dev/digital/ESP32-S3/ESP32-S3.Harness
Normal file
@@ -0,0 +1 @@
|
||||
SB=SB1.R,SB1.P,SB2.R,SB2.P
|
||||
Binary file not shown.
BIN
dev/digital/ESP32-S3/History/ESP32-S3.~(20).SchDoc.Zip
Normal file
BIN
dev/digital/ESP32-S3/History/ESP32-S3.~(20).SchDoc.Zip
Normal file
Binary file not shown.
BIN
dev/digital/ESP32-S3/History/ESP32-S3.~(21).SchDoc.Zip
Normal file
BIN
dev/digital/ESP32-S3/History/ESP32-S3.~(21).SchDoc.Zip
Normal file
Binary file not shown.
BIN
dev/digital/ESP32-S3/History/ESP32-S3.~(22).SchDoc.Zip
Normal file
BIN
dev/digital/ESP32-S3/History/ESP32-S3.~(22).SchDoc.Zip
Normal file
Binary file not shown.
File diff suppressed because one or more lines are too long
@@ -26,9 +26,10 @@ struct Key
|
||||
int col;
|
||||
};
|
||||
|
||||
struct voltageDurationPair
|
||||
struct DualVoltageDurationPair
|
||||
{
|
||||
uint16_t voltage;
|
||||
uint16_t voltage_ch1;
|
||||
uint16_t voltage_ch2;
|
||||
uint16_t duration;
|
||||
};
|
||||
|
||||
@@ -93,12 +94,12 @@ class CV
|
||||
class SequencerBlock
|
||||
{
|
||||
public:
|
||||
SequencerBlock(uint16_t maxDurationMS, uint16_t timeoutMS);
|
||||
SequencerBlock(uint16_t maxDurationMS, uint16_t minStepDurationMS);
|
||||
|
||||
// Aufnahme-Funktionen
|
||||
void startRecord();
|
||||
void stopRecord();
|
||||
void addStep(uint16_t voltage);
|
||||
void addStep(uint16_t voltage_ch1, uint16_t voltage_ch2);
|
||||
bool isRecording();
|
||||
|
||||
// Wiedergabe-Funktionen
|
||||
@@ -114,18 +115,19 @@ class SequencerBlock
|
||||
// Status-Abfragen
|
||||
bool timeLimitReached();
|
||||
uint8_t getStepCount();
|
||||
uint16_t getCurrentVoltage();
|
||||
uint16_t getCurrentVoltageCh1();
|
||||
uint16_t getCurrentVoltageCh2();
|
||||
uint16_t getTotalDuration();
|
||||
|
||||
private:
|
||||
// Sequenz-Speicher
|
||||
voltageDurationPair _sequence[N_MAX_SEQUENCE_STEPS];
|
||||
DualVoltageDurationPair _sequence[N_MAX_SEQUENCE_STEPS];
|
||||
uint8_t _stepCount;
|
||||
uint8_t _currentStep;
|
||||
|
||||
// Zeitverwaltung
|
||||
uint16_t _maxDurationMS;
|
||||
uint16_t _timeoutMS;
|
||||
uint16_t _minStepDurationMS;
|
||||
unsigned long _recordStartTime;
|
||||
unsigned long _lastStepTime;
|
||||
unsigned long _playStartTime;
|
||||
@@ -136,8 +138,9 @@ class SequencerBlock
|
||||
bool _isPlaying;
|
||||
bool _loop;
|
||||
|
||||
// Letzte aufgenommene Spannung
|
||||
uint16_t _lastVoltage;
|
||||
// Letzte aufgenommene Spannungen
|
||||
uint16_t _lastVoltageCh1;
|
||||
uint16_t _lastVoltageCh2;
|
||||
|
||||
// Hilfsfunktionen
|
||||
void _finishCurrentStep();
|
||||
|
||||
@@ -25,15 +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_R4 // 11 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
|
||||
#define PIN_K_C3 // 5 NOT IN USE
|
||||
#define PIN_K_C4 // 6 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
|
||||
#define PIN_SB_1_REC 37 // 33 not available on dev board
|
||||
#define PIN_SB_1_PLAY 38 // 34 not available on dev board
|
||||
#define PIN_SB_2_REC 35
|
||||
#define PIN_SB_2_PLAY 36
|
||||
#endif
|
||||
@@ -177,15 +177,6 @@ void Keyboard::_removeActiveKey(uint8_t row, uint8_t col)
|
||||
|
||||
// ==================== CV ====================
|
||||
|
||||
/*!
|
||||
* @param dac Adafruit_MCP4728 object
|
||||
* @param wire TwoWire object
|
||||
* @param nCV Number of CV-Gates
|
||||
* @param cvChannelMap Maps CV-Gate-Index to a MCP4728 output-channel
|
||||
* @param keyToVoltage One dimensional array of size row times col
|
||||
* @param row Keyboard matrix rows
|
||||
* @param col Keyboard matrix cols
|
||||
*/
|
||||
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;
|
||||
@@ -242,31 +233,27 @@ uint8_t CV::_getKeyToVoltageIndex(Key k)
|
||||
|
||||
// ==================== SequencerBlock ====================
|
||||
|
||||
|
||||
/*!
|
||||
* @param maxDurationMS maximum loop duration of recording in milliseconds
|
||||
* @param timeoutMS stops recording after timeout in milliseconds
|
||||
* @brief TODO
|
||||
* @param minStepDurationMS minimum duration for a step to be recorded (prevents ultra-short steps)
|
||||
*/
|
||||
SequencerBlock::SequencerBlock(uint16_t maxDurationMS, uint16_t timeoutMS)
|
||||
SequencerBlock::SequencerBlock(uint16_t maxDurationMS, uint16_t minStepDurationMS)
|
||||
{
|
||||
_maxDurationMS = maxDurationMS;
|
||||
_timeoutMS = timeoutMS;
|
||||
_minStepDurationMS = minStepDurationMS;
|
||||
_stepCount = 0;
|
||||
_currentStep = 0;
|
||||
_isRecording = false;
|
||||
_isPlaying = false;
|
||||
_loop = false;
|
||||
_lastVoltage = 0;
|
||||
_lastVoltageCh1 = 0;
|
||||
_lastVoltageCh2 = 0;
|
||||
_recordStartTime = 0;
|
||||
_lastStepTime = 0;
|
||||
_playStartTime = 0;
|
||||
_stepStartTime = 0;
|
||||
}
|
||||
|
||||
/*!
|
||||
* @brief starts sequence block recording
|
||||
*/
|
||||
void SequencerBlock::startRecord()
|
||||
{
|
||||
if(_isPlaying) stopPlay();
|
||||
@@ -275,12 +262,10 @@ void SequencerBlock::startRecord()
|
||||
_isRecording = true;
|
||||
_recordStartTime = millis();
|
||||
_lastStepTime = _recordStartTime;
|
||||
_lastVoltage = 0;
|
||||
_lastVoltageCh1 = 0xFFFF; // Ungültiger Wert zum Triggern des ersten Steps
|
||||
_lastVoltageCh2 = 0xFFFF;
|
||||
}
|
||||
|
||||
/*!
|
||||
* @brief stops sequence block recording and saves it
|
||||
*/
|
||||
void SequencerBlock::stopRecord()
|
||||
{
|
||||
if(!_isRecording) return;
|
||||
@@ -289,36 +274,44 @@ void SequencerBlock::stopRecord()
|
||||
_isRecording = false;
|
||||
}
|
||||
|
||||
/*!
|
||||
* @brief adds step to sequencer block
|
||||
* @param voltage voltage step for CV-Gate in millivolts
|
||||
*/
|
||||
void SequencerBlock::addStep(uint16_t voltage)
|
||||
void SequencerBlock::addStep(uint16_t voltage_ch1, uint16_t voltage_ch2)
|
||||
{
|
||||
if(!_isRecording) return;
|
||||
if(!_canAddStep()) return;
|
||||
if(timeLimitReached())
|
||||
{
|
||||
stopRecord();
|
||||
return;
|
||||
}
|
||||
|
||||
unsigned long now = millis();
|
||||
|
||||
// Wenn sich die Spannung geändert hat, vorherigen Schritt abschließen
|
||||
if(voltage != _lastVoltage && _stepCount > 0)
|
||||
{
|
||||
_finishCurrentStep();
|
||||
}
|
||||
// Hat sich die Spannung geändert?
|
||||
bool voltageChanged = (voltage_ch1 != _lastVoltageCh1) || (voltage_ch2 != _lastVoltageCh2);
|
||||
|
||||
// Neuen Schritt beginnen oder vorhandenen aktualisieren
|
||||
if(voltage != _lastVoltage || _stepCount == 0)
|
||||
if(voltageChanged)
|
||||
{
|
||||
// Vorherigen Step abschließen (wenn vorhanden)
|
||||
if(_stepCount > 0)
|
||||
{
|
||||
_finishCurrentStep();
|
||||
}
|
||||
|
||||
// Neuen Step beginnen
|
||||
if(_canAddStep())
|
||||
{
|
||||
_sequence[_stepCount].voltage = voltage;
|
||||
_sequence[_stepCount].voltage_ch1 = voltage_ch1;
|
||||
_sequence[_stepCount].voltage_ch2 = voltage_ch2;
|
||||
_sequence[_stepCount].duration = 0;
|
||||
_stepCount++;
|
||||
|
||||
_lastStepTime = now;
|
||||
_lastVoltage = voltage;
|
||||
_lastVoltageCh1 = voltage_ch1;
|
||||
_lastVoltageCh2 = voltage_ch2;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Gleiche Spannung - Duration des aktuellen Steps aktualisieren
|
||||
if(_stepCount > 0)
|
||||
{
|
||||
_sequence[_stepCount - 1].duration = now - _lastStepTime;
|
||||
@@ -326,18 +319,11 @@ void SequencerBlock::addStep(uint16_t voltage)
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
* @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;
|
||||
@@ -349,19 +335,12 @@ void SequencerBlock::startPlay()
|
||||
_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;
|
||||
@@ -395,44 +374,31 @@ void SequencerBlock::update()
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
* @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;
|
||||
_lastVoltageCh1 = 0;
|
||||
_lastVoltageCh2 = 0;
|
||||
|
||||
for(uint8_t i = 0; i < N_MAX_SEQUENCE_STEPS; i++)
|
||||
{
|
||||
_sequence[i].voltage = 0;
|
||||
_sequence[i].voltage_ch1 = 0;
|
||||
_sequence[i].voltage_ch2 = 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;
|
||||
@@ -443,31 +409,27 @@ bool SequencerBlock::timeLimitReached()
|
||||
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()
|
||||
uint16_t SequencerBlock::getCurrentVoltageCh1()
|
||||
{
|
||||
if(!_isPlaying || _stepCount == 0) return 0;
|
||||
if(_currentStep >= _stepCount) return 0;
|
||||
|
||||
return _sequence[_currentStep].voltage;
|
||||
return _sequence[_currentStep].voltage_ch1;
|
||||
}
|
||||
|
||||
uint16_t SequencerBlock::getCurrentVoltageCh2()
|
||||
{
|
||||
if(!_isPlaying || _stepCount == 0) return 0;
|
||||
if(_currentStep >= _stepCount) return 0;
|
||||
|
||||
return _sequence[_currentStep].voltage_ch2;
|
||||
}
|
||||
|
||||
/*!
|
||||
* @brief gets the length of the recording in the block
|
||||
* @return uint16_t time in milliseconds
|
||||
*/
|
||||
uint16_t SequencerBlock::getTotalDuration()
|
||||
{
|
||||
uint16_t total = 0;
|
||||
@@ -483,12 +445,17 @@ void SequencerBlock::_finishCurrentStep()
|
||||
if(_stepCount == 0) return;
|
||||
|
||||
unsigned long now = millis();
|
||||
_sequence[_stepCount - 1].duration = now - _lastStepTime;
|
||||
uint16_t duration = now - _lastStepTime;
|
||||
|
||||
// Timeout prüfen - wenn zu lange keine Änderung, Schritt nicht hinzufügen
|
||||
if(_sequence[_stepCount - 1].duration < _timeoutMS)
|
||||
// Nur Steps mit ausreichender Dauer speichern
|
||||
if(duration >= _minStepDurationMS)
|
||||
{
|
||||
_stepCount++;
|
||||
_sequence[_stepCount - 1].duration = duration;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Step war zu kurz, verwerfen
|
||||
_stepCount--;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,19 +1,11 @@
|
||||
/*
|
||||
* 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
|
||||
* Example Code Three - Dual Channel Sequencer
|
||||
*/
|
||||
#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};
|
||||
byte pins_keyboard_row[N_KEYBOARD_ROW] = {PIN_K_R0, PIN_K_R1, PIN_K_R2, PIN_K_R3};
|
||||
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);
|
||||
|
||||
@@ -28,8 +20,9 @@ uint16_t keyToVoltage[N_KEYBOARD_ROW*N_KEYBOARD_COL] = { /* 83mV = 1/12V */
|
||||
|
||||
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);
|
||||
// Sequencer mit 30s max, 50ms Mindest-Step-Dauer
|
||||
SequencerBlock sb1(30000, 50);
|
||||
SequencerBlock sb2(30000, 50);
|
||||
|
||||
// Button States
|
||||
struct ButtonState {
|
||||
@@ -45,10 +38,9 @@ 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 reading = digitalRead(pin) == HIGH;
|
||||
bool buttonPressed = false;
|
||||
|
||||
if(reading != state.last)
|
||||
@@ -61,7 +53,7 @@ bool readButton(byte pin, ButtonState &state)
|
||||
if(reading != state.current)
|
||||
{
|
||||
state.current = reading;
|
||||
if(state.current == true) // Button wurde gerade gedrückt
|
||||
if(state.current == true)
|
||||
{
|
||||
buttonPressed = true;
|
||||
}
|
||||
@@ -74,10 +66,10 @@ bool readButton(byte pin, ButtonState &state)
|
||||
|
||||
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);
|
||||
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);
|
||||
|
||||
btn_sb1_rec.current = false;
|
||||
btn_sb1_rec.last = false;
|
||||
@@ -111,23 +103,54 @@ void handleSequencerButtons()
|
||||
{
|
||||
if(sb1.isPlaying()) sb1.stopPlay();
|
||||
sb1.startRecord();
|
||||
Serial.printf("\n\r[SEQ1] Recording started...");
|
||||
Serial.printf("\n\r[SEQ1] Recording started (2 channels)...");
|
||||
}
|
||||
}
|
||||
|
||||
// Sequencer 1 Play Button
|
||||
// Sequencer 1 Play Button - 3 Modi: Play / Loop / Stop
|
||||
if(readButton(PIN_SB_1_PLAY, btn_sb1_play))
|
||||
{
|
||||
if(sb1.isPlaying())
|
||||
if(!sb1.isPlaying())
|
||||
{
|
||||
sb1.stopPlay();
|
||||
Serial.printf("\n\r[SEQ1] Playback stopped");
|
||||
// Nicht am Spielen -> Starte Playback (ohne Loop)
|
||||
if(sb1.isRecording()) sb1.stopRecord();
|
||||
sb1.setLoop(false);
|
||||
sb1.startPlay();
|
||||
Serial.printf("\n\r[SEQ1] Playback started (single)\n\r\tSteps: %i, Duartion: %ims", sb1.getStepCount(), sb1.getTotalDuration());
|
||||
}
|
||||
else
|
||||
{
|
||||
if(sb1.isRecording()) sb1.stopRecord();
|
||||
sb1.startPlay();
|
||||
Serial.printf("\n\r[SEQ1] Playback started");
|
||||
// Am Spielen -> Prüfe Loop-Status
|
||||
if(!sb1.isPlaying()) // Falls schon gestoppt
|
||||
{
|
||||
// Starte neu
|
||||
sb1.setLoop(false);
|
||||
sb1.startPlay();
|
||||
Serial.printf("\n\r[SEQ1] Playback started (single)");
|
||||
}
|
||||
else
|
||||
{
|
||||
// Ist am Spielen - ermittle ob Loop aktiv ist
|
||||
// Wir testen das indirekt: Wenn ein Sequencer am Ende angekommen ist
|
||||
// und noch spielt, dann muss Loop aktiv sein
|
||||
// Alternative: Wir tracken den Loop-Status selbst
|
||||
static bool seq1_loop_active = false;
|
||||
|
||||
if(!seq1_loop_active)
|
||||
{
|
||||
// 2. Klick: Loop aktivieren
|
||||
sb1.setLoop(true);
|
||||
seq1_loop_active = true;
|
||||
Serial.printf("\n\r[SEQ1] Loop activated");
|
||||
}
|
||||
else
|
||||
{
|
||||
// 3. Klick: Stop
|
||||
sb1.stopPlay();
|
||||
seq1_loop_active = false;
|
||||
Serial.printf("\n\r[SEQ1] Playback stopped");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -144,23 +167,40 @@ void handleSequencerButtons()
|
||||
{
|
||||
if(sb2.isPlaying()) sb2.stopPlay();
|
||||
sb2.startRecord();
|
||||
Serial.printf("\n\r[SEQ2] Recording started...");
|
||||
Serial.printf("\n\r[SEQ2] Recording started (2 channels)...");
|
||||
}
|
||||
}
|
||||
|
||||
// Sequencer 2 Play Button
|
||||
// Sequencer 2 Play Button - 3 Modi: Play / Loop / Stop
|
||||
if(readButton(PIN_SB_2_PLAY, btn_sb2_play))
|
||||
{
|
||||
if(sb2.isPlaying())
|
||||
static bool seq2_loop_active = false;
|
||||
|
||||
if(!sb2.isPlaying())
|
||||
{
|
||||
sb2.stopPlay();
|
||||
Serial.printf("\n\r[SEQ2] Playback stopped");
|
||||
// Nicht am Spielen -> Starte Playback (ohne Loop)
|
||||
if(sb2.isRecording()) sb2.stopRecord();
|
||||
sb2.setLoop(false);
|
||||
seq2_loop_active = false;
|
||||
sb2.startPlay();
|
||||
Serial.printf("\n\r[SEQ2] Playback started (single)");
|
||||
}
|
||||
else
|
||||
{
|
||||
if(sb2.isRecording()) sb2.stopRecord();
|
||||
sb2.startPlay();
|
||||
Serial.printf("\n\r[SEQ2] Playback started");
|
||||
if(!seq2_loop_active)
|
||||
{
|
||||
// 2. Klick: Loop aktivieren
|
||||
sb2.setLoop(true);
|
||||
seq2_loop_active = true;
|
||||
Serial.printf("\n\r[SEQ2] Loop activated");
|
||||
}
|
||||
else
|
||||
{
|
||||
// 3. Klick: Stop
|
||||
sb2.stopPlay();
|
||||
seq2_loop_active = false;
|
||||
Serial.printf("\n\r[SEQ2] Playback stopped");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -175,13 +215,16 @@ void setup()
|
||||
sb1.setLoop(false);
|
||||
sb2.setLoop(false);
|
||||
|
||||
Serial.printf("\n\r=== Sequencer System Started ===");
|
||||
Serial.printf("\n\r=== Dual-Channel 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");
|
||||
Serial.printf("\n\r PIN_SB_1_REC: SEQ1 Record Start/Stop (CH1+CH2)");
|
||||
Serial.printf("\n\r PIN_SB_1_PLAY: SEQ1 Play Mode Toggle:");
|
||||
Serial.printf("\n\r 1st click: Play once");
|
||||
Serial.printf("\n\r 2nd click: Loop mode");
|
||||
Serial.printf("\n\r 3rd click: Stop");
|
||||
Serial.printf("\n\r PIN_SB_2_REC: SEQ2 Record Start/Stop (CH1+CH2)");
|
||||
Serial.printf("\n\r PIN_SB_2_PLAY: SEQ2 Play Mode (same as SEQ1)");
|
||||
Serial.printf("\n\r==============================================\n\r");
|
||||
}
|
||||
|
||||
void loop()
|
||||
@@ -195,71 +238,54 @@ void loop()
|
||||
|
||||
int n = keyboard.getQueueLength();
|
||||
|
||||
// Keyboard-Tasten verarbeiten
|
||||
// Aktuelle Spannungen für beide Kanäle ermitteln
|
||||
uint16_t voltage_ch1 = 0;
|
||||
uint16_t voltage_ch2 = 0;
|
||||
|
||||
if(n > 0)
|
||||
{
|
||||
// Alle Keyboard-Tasten für CV-Ausgabe verwenden
|
||||
int cvIndex = 0;
|
||||
for(int i = 0; i < n && cvIndex < N_CV_GATES; i++)
|
||||
Key k1 = keyboard.getQueue(0);
|
||||
if(!isNotKey(k1))
|
||||
{
|
||||
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
|
||||
{
|
||||
// Keine Tasten gedrückt
|
||||
if(sb1.isRecording())
|
||||
{
|
||||
sb1.addStep(0);
|
||||
}
|
||||
if(sb2.isRecording())
|
||||
{
|
||||
sb2.addStep(0);
|
||||
}
|
||||
|
||||
if(!sb1.isPlaying() && !sb2.isPlaying())
|
||||
{
|
||||
cv.clearAll();
|
||||
voltage_ch1 = keyToVoltage[k1.row * N_KEYBOARD_COL + k1.col];
|
||||
}
|
||||
}
|
||||
|
||||
// Sequencer-Wiedergabe auf CV-Ausgänge
|
||||
if(n > 1)
|
||||
{
|
||||
Key k2 = keyboard.getQueue(1);
|
||||
if(!isNotKey(k2))
|
||||
{
|
||||
voltage_ch2 = keyToVoltage[k2.row * N_KEYBOARD_COL + k2.col];
|
||||
}
|
||||
}
|
||||
|
||||
// Bei Recording: Beide Kanäle aufnehmen
|
||||
if(sb1.isRecording())
|
||||
{
|
||||
sb1.addStep(voltage_ch1, voltage_ch2);
|
||||
}
|
||||
if(sb2.isRecording())
|
||||
{
|
||||
sb2.addStep(voltage_ch1, voltage_ch2);
|
||||
}
|
||||
|
||||
// CV-Ausgabe: Priorität hat Sequencer-Wiedergabe
|
||||
if(sb1.isPlaying())
|
||||
{
|
||||
cv.setVoltage(0, sb1.getCurrentVoltage());
|
||||
cv.setVoltage(0, sb1.getCurrentVoltageCh1());
|
||||
cv.setVoltage(1, sb1.getCurrentVoltageCh2());
|
||||
}
|
||||
if(sb2.isPlaying())
|
||||
else if(sb2.isPlaying())
|
||||
{
|
||||
cv.setVoltage(1, sb2.getCurrentVoltage());
|
||||
cv.setVoltage(0, sb2.getCurrentVoltageCh1());
|
||||
cv.setVoltage(1, sb2.getCurrentVoltageCh2());
|
||||
}
|
||||
else
|
||||
{
|
||||
// Live-Ausgabe wenn kein Sequencer spielt
|
||||
cv.setVoltage(0, voltage_ch1);
|
||||
cv.setVoltage(1, voltage_ch2);
|
||||
}
|
||||
|
||||
// Time-Limit Warnung
|
||||
@@ -267,12 +293,16 @@ void loop()
|
||||
{
|
||||
sb1.stopRecord();
|
||||
Serial.printf("\n\r[SEQ1] Time limit reached! Recording stopped.");
|
||||
Serial.printf("\n\r[SEQ1] 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",
|
||||
sb2.getStepCount(), sb2.getTotalDuration());
|
||||
}
|
||||
|
||||
delay(10); // Kürzeres Delay für bessere Sequencer-Auflösung
|
||||
delay(10);
|
||||
}
|
||||
Reference in New Issue
Block a user