mirror of
https://github.com/erik-toth/audio-synth.git
synced 2025-12-06 08:00:02 +00:00
Software Update 4: Sequencer Block
Sequencer Klasse eingebaut Überprüfung noch ausständig
This commit is contained in:
5
dev/digital/DAC_MCP4728_Test/.gitignore
vendored
5
dev/digital/DAC_MCP4728_Test/.gitignore
vendored
@@ -1,5 +0,0 @@
|
|||||||
.pio
|
|
||||||
.vscode/.browse.c_cpp.db*
|
|
||||||
.vscode/c_cpp_properties.json
|
|
||||||
.vscode/launch.json
|
|
||||||
.vscode/ipch
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
{
|
|
||||||
// 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"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
|
|
||||||
This directory is intended for project header files.
|
|
||||||
|
|
||||||
A header file is a file containing C declarations and macro definitions
|
|
||||||
to be shared between several project source files. You request the use of a
|
|
||||||
header file in your project source file (C, C++, etc) located in `src` folder
|
|
||||||
by including it, with the C preprocessing directive `#include'.
|
|
||||||
|
|
||||||
```src/main.c
|
|
||||||
|
|
||||||
#include "header.h"
|
|
||||||
|
|
||||||
int main (void)
|
|
||||||
{
|
|
||||||
...
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Including a header file produces the same results as copying the header file
|
|
||||||
into each source file that needs it. Such copying would be time-consuming
|
|
||||||
and error-prone. With a header file, the related declarations appear
|
|
||||||
in only one place. If they need to be changed, they can be changed in one
|
|
||||||
place, and programs that include the header file will automatically use the
|
|
||||||
new version when next recompiled. The header file eliminates the labor of
|
|
||||||
finding and changing all the copies as well as the risk that a failure to
|
|
||||||
find one copy will result in inconsistencies within a program.
|
|
||||||
|
|
||||||
In C, the convention is to give header files names that end with `.h'.
|
|
||||||
|
|
||||||
Read more about using header files in official GCC documentation:
|
|
||||||
|
|
||||||
* Include Syntax
|
|
||||||
* Include Operation
|
|
||||||
* Once-Only Headers
|
|
||||||
* Computed Includes
|
|
||||||
|
|
||||||
https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html
|
|
||||||
@@ -1,46 +0,0 @@
|
|||||||
|
|
||||||
This directory is intended for project specific (private) libraries.
|
|
||||||
PlatformIO will compile them to static libraries and link into the executable file.
|
|
||||||
|
|
||||||
The source code of each library should be placed in a separate directory
|
|
||||||
("lib/your_library_name/[Code]").
|
|
||||||
|
|
||||||
For example, see the structure of the following example libraries `Foo` and `Bar`:
|
|
||||||
|
|
||||||
|--lib
|
|
||||||
| |
|
|
||||||
| |--Bar
|
|
||||||
| | |--docs
|
|
||||||
| | |--examples
|
|
||||||
| | |--src
|
|
||||||
| | |- Bar.c
|
|
||||||
| | |- Bar.h
|
|
||||||
| | |- library.json (optional. for custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html
|
|
||||||
| |
|
|
||||||
| |--Foo
|
|
||||||
| | |- Foo.c
|
|
||||||
| | |- Foo.h
|
|
||||||
| |
|
|
||||||
| |- README --> THIS FILE
|
|
||||||
|
|
|
||||||
|- platformio.ini
|
|
||||||
|--src
|
|
||||||
|- main.c
|
|
||||||
|
|
||||||
Example contents of `src/main.c` using Foo and Bar:
|
|
||||||
```
|
|
||||||
#include <Foo.h>
|
|
||||||
#include <Bar.h>
|
|
||||||
|
|
||||||
int main (void)
|
|
||||||
{
|
|
||||||
...
|
|
||||||
}
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
The PlatformIO Library Dependency Finder will find automatically dependent
|
|
||||||
libraries by scanning project source files.
|
|
||||||
|
|
||||||
More information about PlatformIO Library Dependency Finder
|
|
||||||
- https://docs.platformio.org/page/librarymanager/ldf.html
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
; 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
|
|
||||||
@@ -1,82 +0,0 @@
|
|||||||
#include <Wire.h>
|
|
||||||
#include <Adafruit_MCP4728.h>
|
|
||||||
|
|
||||||
#define BAUDRATE 115200
|
|
||||||
|
|
||||||
#define I2C_SDA 15
|
|
||||||
#define I2C_SCL 16
|
|
||||||
#define I2C_FREQ 400000
|
|
||||||
|
|
||||||
#define DAC_MAX 4095
|
|
||||||
#define DAC_MIN 0
|
|
||||||
#define VREF_mV 2048
|
|
||||||
|
|
||||||
#define VREF_CONF_IN_USE MCP4728_VREF_INTERNAL
|
|
||||||
|
|
||||||
#define PIN_BTN 40
|
|
||||||
|
|
||||||
Adafruit_MCP4728 mcp;
|
|
||||||
|
|
||||||
static uint16_t value_mV = VREF_mV;
|
|
||||||
|
|
||||||
bool button_state_old = false;
|
|
||||||
bool button_state_now = false;
|
|
||||||
|
|
||||||
uint16_t voltage(uint16_t mV)
|
|
||||||
{
|
|
||||||
return map(mV, 0, VREF_mV, DAC_MIN, DAC_MAX);
|
|
||||||
}
|
|
||||||
|
|
||||||
void setup()
|
|
||||||
{
|
|
||||||
Serial.begin(BAUDRATE);
|
|
||||||
delay(1000);
|
|
||||||
Serial.print("\n\rMCP4728 DAC Test");
|
|
||||||
|
|
||||||
Wire.begin(I2C_SDA, I2C_SCL);
|
|
||||||
Wire.setClock(I2C_FREQ);
|
|
||||||
|
|
||||||
pinMode(PIN_BTN, INPUT);
|
|
||||||
|
|
||||||
Serial.print("\n\rInitialisiere MCP4728...");
|
|
||||||
uint8_t attempts = 0;
|
|
||||||
while (!mcp.begin())
|
|
||||||
{
|
|
||||||
if(attempts > 20)
|
|
||||||
{
|
|
||||||
Serial.print("\n\rError: Es konnte nach 20 Versuchen kein IC gefunden werden. Überprüfe die Verkabelung und starte den ESP neu!");
|
|
||||||
for(;;);
|
|
||||||
}
|
|
||||||
Serial.print(".");
|
|
||||||
delay(100);
|
|
||||||
attempts++;
|
|
||||||
}
|
|
||||||
Serial.print("\n\rErfolgreich verbunden!");
|
|
||||||
|
|
||||||
mcp.setChannelValue(MCP4728_CHANNEL_A, 4095, VREF_CONF_IN_USE);
|
|
||||||
Serial.print("\n\rDAC-Kanal A auf 0 gesetzt");
|
|
||||||
delay(1000);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void loop()
|
|
||||||
{
|
|
||||||
button_state_now = digitalRead(PIN_BTN);
|
|
||||||
if (button_state_now != button_state_old)
|
|
||||||
{
|
|
||||||
delay(10); // debounce delay
|
|
||||||
button_state_now = digitalRead(PIN_BTN);
|
|
||||||
if (button_state_now != button_state_old)
|
|
||||||
{
|
|
||||||
button_state_old = button_state_now;
|
|
||||||
if (button_state_now == HIGH)
|
|
||||||
{
|
|
||||||
if(value_mV < 2000) value_mV += (1000/12);
|
|
||||||
if(value_mV >= 2000) value_mV = 0;
|
|
||||||
|
|
||||||
if (mcp.setChannelValue(MCP4728_CHANNEL_A, voltage(value_mV), VREF_CONF_IN_USE)) Serial.printf("DAC A = %u -> mV: %u\r\n", voltage(value_mV), value_mV);
|
|
||||||
else Serial.println("Error: Neuer MCP4728 Wert konnte nicht gesetzt werden!");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
|
|
||||||
This directory is intended for PlatformIO Test Runner and project tests.
|
|
||||||
|
|
||||||
Unit Testing is a software testing method by which individual units of
|
|
||||||
source code, sets of one or more MCU program modules together with associated
|
|
||||||
control data, usage procedures, and operating procedures, are tested to
|
|
||||||
determine whether they are fit for use. Unit testing finds problems early
|
|
||||||
in the development cycle.
|
|
||||||
|
|
||||||
More information about PlatformIO Unit Testing:
|
|
||||||
- https://docs.platformio.org/en/latest/advanced/unit-testing/index.html
|
|
||||||
@@ -18,6 +18,7 @@
|
|||||||
#define MS_DEBOUNCE 20
|
#define MS_DEBOUNCE 20
|
||||||
|
|
||||||
#define N_MAX_DAC_CH 4
|
#define N_MAX_DAC_CH 4
|
||||||
|
#define N_MAX_SEQUENCE_STEPS 128
|
||||||
|
|
||||||
struct Key
|
struct Key
|
||||||
{
|
{
|
||||||
@@ -25,6 +26,12 @@ struct Key
|
|||||||
int col;
|
int col;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct voltageDurationPair
|
||||||
|
{
|
||||||
|
uint16_t voltage;
|
||||||
|
uint16_t duration;
|
||||||
|
};
|
||||||
|
|
||||||
const Key NOT_A_KEY = {-1, -1};
|
const Key NOT_A_KEY = {-1, -1};
|
||||||
|
|
||||||
bool isNotKey(Key k);
|
bool isNotKey(Key k);
|
||||||
@@ -83,4 +90,58 @@ class CV
|
|||||||
uint8_t _getKeyToVoltageIndex(Key k);
|
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
|
#endif
|
||||||
@@ -14,6 +14,7 @@
|
|||||||
#define N_KEYBOARD_ROW 4
|
#define N_KEYBOARD_ROW 4
|
||||||
#define N_KEYBOARD_COL 3
|
#define N_KEYBOARD_COL 3
|
||||||
#define N_CV_GATES 2
|
#define N_CV_GATES 2
|
||||||
|
#define N_SB 2
|
||||||
#define BAUDRATE 115200
|
#define BAUDRATE 115200
|
||||||
// PIN DEFENTITIONS
|
// PIN DEFENTITIONS
|
||||||
// I2C PINS
|
// I2C PINS
|
||||||
@@ -24,7 +25,15 @@
|
|||||||
#define PIN_K_R1 8
|
#define PIN_K_R1 8
|
||||||
#define PIN_K_R2 9
|
#define PIN_K_R2 9
|
||||||
#define PIN_K_R3 10
|
#define PIN_K_R3 10
|
||||||
|
#define PIN_K_R4 // NOT IN USE
|
||||||
#define PIN_K_C0 1
|
#define PIN_K_C0 1
|
||||||
#define PIN_K_C1 2
|
#define PIN_K_C1 2
|
||||||
#define PIN_K_C2 4
|
#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
|
#endif
|
||||||
@@ -8,6 +8,8 @@
|
|||||||
|
|
||||||
#include "FIRMWARE.h"
|
#include "FIRMWARE.h"
|
||||||
|
|
||||||
|
// ==================== Helper-Functions ====================
|
||||||
|
|
||||||
bool isNotKey(Key k)
|
bool isNotKey(Key k)
|
||||||
{
|
{
|
||||||
if((k.row == NOT_A_KEY.row) && (k.col == NOT_A_KEY.col)) return true;
|
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;
|
else return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ==================== Keyboard ====================
|
||||||
|
|
||||||
Keyboard::Keyboard(uint8_t nRows, uint8_t nCols, uint8_t *pinsRow, uint8_t *pinsCol)
|
Keyboard::Keyboard(uint8_t nRows, uint8_t nCols, uint8_t *pinsRow, uint8_t *pinsCol)
|
||||||
{
|
{
|
||||||
_nRows = nRows;
|
_nRows = nRows;
|
||||||
@@ -171,6 +175,8 @@ void Keyboard::_removeActiveKey(uint8_t row, uint8_t col)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ==================== CV ====================
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
* @param dac Adafruit_MCP4728 object
|
* @param dac Adafruit_MCP4728 object
|
||||||
* @param wire TwoWire 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)
|
uint8_t CV::_getKeyToVoltageIndex(Key k)
|
||||||
{
|
{
|
||||||
return (k.row*_col + k.col);
|
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;
|
||||||
}
|
}
|
||||||
@@ -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_DEF.h"
|
||||||
#include "FIRMWARE.h"
|
#include "FIRMWARE.h"
|
||||||
@@ -11,7 +19,7 @@ Keyboard keyboard(N_KEYBOARD_ROW, N_KEYBOARD_COL, pins_keyboard_row, pins_keyboa
|
|||||||
|
|
||||||
Adafruit_MCP4728 MCP4728;
|
Adafruit_MCP4728 MCP4728;
|
||||||
MCP4728_channel_t cvMap[N_CV_GATES] = {MCP4728_CHANNEL_A, MCP4728_CHANNEL_B};
|
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,
|
1*83, 5*83, 9*83,
|
||||||
2*83, 6*83, 10*83,
|
2*83, 6*83, 10*83,
|
||||||
3*83, 7*83, 11*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);
|
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()
|
void setup()
|
||||||
{
|
{
|
||||||
Serial.begin(BAUDRATE);
|
Serial.begin(BAUDRATE);
|
||||||
keyboard.begin();
|
keyboard.begin();
|
||||||
cv.begin(PIN_SDA, PIN_SCL);
|
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()
|
void loop()
|
||||||
{
|
{
|
||||||
keyboard.update();
|
keyboard.update();
|
||||||
|
handleSequencerButtons();
|
||||||
int n = keyboard.getQueueLength();
|
|
||||||
|
// Sequencer Update (für Wiedergabe)
|
||||||
if(n > 0)
|
sb1.update();
|
||||||
{
|
sb2.update();
|
||||||
Serial.printf("\n\rCurrent queue length: %i", n);
|
|
||||||
if(n == 1)
|
int n = keyboard.getQueueLength();
|
||||||
|
|
||||||
|
// Keyboard-Tasten verarbeiten
|
||||||
|
if(n > 0)
|
||||||
{
|
{
|
||||||
cv.setVoltage(0, keyboard.getQueue(0));
|
// Alle Keyboard-Tasten für CV-Ausgabe verwenden
|
||||||
cv.setVoltage(1, NOT_A_KEY);
|
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));
|
// Keine Tasten gedrückt
|
||||||
cv.setVoltage(1, keyboard.getQueue(1));
|
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);
|
cv.setVoltage(0, sb1.getCurrentVoltage());
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
}
|
if(sb2.isPlaying())
|
||||||
else cv.clearAll();
|
{
|
||||||
|
cv.setVoltage(1, sb2.getCurrentVoltage());
|
||||||
delay(50);
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
}
|
}
|
||||||
60
dev/digital/Firmware_TEST/src/main.cpp.2
Normal file
60
dev/digital/Firmware_TEST/src/main.cpp.2
Normal 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);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user