10 Commits

Author SHA1 Message Date
99c2542890 Merge branch 'main' of github.com:erik-toth/audio-synth 2025-12-01 22:46:12 +01:00
855fd01821 Firmware MCU: Update sequencer block to support dynamic step count and enhance documentation 2025-12-01 22:45:58 +01:00
Erik Tóth
f86db9c917 Update TO-DO list to mark simulations as complete 2025-12-01 16:21:14 +01:00
b106859252 Firmware MCU: Readme hinzugefügt als Erklärung --> später DA 2025-11-30 22:26:23 +01:00
dac90a977b Firmware MCU: Sequencer, erfassen und widergabe im Sequencerblock von beiden Channel, playback im single und loop modus, test OK 2025-11-30 20:20:05 +01:00
ce4e6cb536 LM386 Schaltung angepasst 2025-11-28 11:19:18 +01:00
unknown
ae7b6f6114 Laboraufbau + Messung VCF 2025-11-27 19:40:42 +01:00
Erik Tóth
aae20f701c VCO Bilder Aufbau 2025-11-25 22:59:44 +01:00
Erik Tóth
173c031371 Output Stage Aufbau Bilder 2025-11-25 22:58:40 +01:00
Erik Tóth
9705029f0a Output Stage Update
Schaltung neu aufgebaut, neu simuliert, bei Erik mit Analog Discovery überprüft, OK
2025-11-25 22:48:51 +01:00
93 changed files with 8976 additions and 5496 deletions

File diff suppressed because one or more lines are too long

View File

@@ -1,98 +1,32 @@
ETOTH-Amp_LM386
*SPICE Netlist generated by Advanced Sim server on 21.11.2025 08:22:05
*SPICE Netlist generated by Advanced Sim server on 28.11.2025 11:11:11
.options MixedSimGenerated
*Schematic Netlist:
CC1 NetC1_1 NetC1_2 47nF
CC_VCM1 NetC_VCM1_1 GND 47uF
CC_VCM2 VAP NetC_VCM1_1 47uF
CCblock NetC1_2 NetCblock_2 220uF
CCblock1 NetCblock1_1 NetCblock1_2 47uF
XIC1C NetIC1_10 NetC_VCM1_1 VAP GND NetIC1_8 TL074
XIC2 NetIC2_1 GND NetCblock1_2 GND NetC1_2 VAP NetIC2_7 NetIC2_8 LM386
LL_Speaker GND NetL_Speaker_2 0.1mH
RR1 NetC1_1 GND 10R
CC_DCBLOCK_IN IN NetC_DCBLOCK_IN_2 10uF
CC_DCBLOCK_OUT NetC_DCBLOCK_OUT_1 OUT 220uF
XIC1A NetIC1_1 0 NetIC1_3 0 NetC_DCBLOCK_OUT_1 VAP NetIC1_7 NetIC1_8 lm386
XIC1B NetIC1_1 0 NetIC1_3 0 NetC_DCBLOCK_OUT_1 VAP NetIC1_7 NetIC1_8 lm386
LL_Speaker 0 NetL_Speaker_2 0.1mH
RR_POTA 0 NetR_POT_2 {10k * {POS}}
RR_POTB NetR_POT_2 NetR_POT_3 {10k - (10k * {POS})}
RR_rs1 NetIC1_10 VAP 470k
RR_rs2 GND NetIC1_10 470k
RR_Speaker NetL_Speaker_2 NetCblock_2 8R
RR_static1 NetCblock1_1 NetR_POT_2 9400R
RR_static2 0 NetCblock1_1 600R
QT_rsN GND NetIC1_8 NetC_VCM1_1 2N2907
QT_rsP VAP NetIC1_8 NetC_VCM1_1 2N2222
VU_q VAP GND 10V
VU_VCM_CURRENT 0 NetC_VCM1_1 0
VUin NetR_POT_3 0 DC 0 SIN(0 2 440Hz 0 0 0) AC 1 0
RR_POTB NetR_POT_2 NetC_DCBLOCK_IN_2 {10k - (10k * {POS})}
RR_Speaker NetL_Speaker_2 OUT 8R
RR_static1 NetIC1_3 NetR_POT_2 100k
RR_static2 0 NetIC1_3 10k
VU_q VAP 0 10V
VUin IN 0 DC 0 SIN(5 2 220 0 0 0) AC 1 0
.PLOT TRAN {v(R_Speaker)+v(L_Speaker)} =PLOT(1) =AXIS(1) =NAME(U_speaker) =UNITS(V) =RGB(0, 255, 0)
.PLOT TRAN {i(R_Speaker)} =PLOT(2) =AXIS(1) =NAME(I_speaker) =UNITS(A)
.PLOT TRAN {p(R_Speaker)} =PLOT(3) =AXIS(1) =NAME(P_speaker) =UNITS(W)
.PLOT TRAN {i(U_VCM_CURRENT)} =PLOT(4) =AXIS(1) =NAME(I_VCM) =UNITS(A)
.PLOT TRAN {v(VAP)} =PLOT(1) =AXIS(1) =RGB(255, 0, 0)
.PLOT TRAN {v(GND)} =PLOT(1) =AXIS(1) =RGB(0, 0, 255)
.PLOT TRAN {i(Cblock1)} =PLOT(5) =AXIS(1) =NAME(I_C_in) =UNITS(A)
.PLOT TRAN {v(Cblock1)} =PLOT(5) =AXIS(2) =NAME(U_C_in) =UNITS(V)
.PLOT TRAN {i(Cblock1)} =PLOT(5) =AXIS(1) =NAME(I_C_out) =UNITS(A)
.PLOT TRAN {v(Cblock1)} =PLOT(5) =AXIS(2) =NAME(U_C_out) =UNITS(V)
.PLOT TRAN {v(IN)} =PLOT(1) =AXIS(1) =NAME(U_IN) =UNITS(V) =RGB(0, 0, 255)
.PLOT TRAN {v(OUT)} =PLOT(2) =AXIS(1) =NAME(U_OUT) =UNITS(V) =RGB(255, 153, 0)
.OPTIONS METHOD=GEAR MAXORD=2
*Selected Circuit Analyses:
.TRAN 45.45u 22.73m 0 45.45u
.TRAN 90.91u 22.73m 0 90.91u
*Global Parameters:
.PARAM POS={1}
*Models and Subcircuits:
*TL074
*Quad LoNoise JFETInput OpAmp pkg:DIP14
*+ (A:3,2,4,11,1)(B:5,6,4,11,7)(C:10,9,4,11,8)(D:12,13,4,11,14)
* Connections:
* Non-Inverting Input
* | Inverting Input
* | | Positive Power Supply
* | | | Negative Power Supply
* | | | | Output
* | | | | |
.SUBCKT TL074 1 2 3 4 5
C1 11 12 3.498E-12
C2 6 7 15E-12
DC 5 53 DX
DE 54 5 DX
DLP 90 91 DX
DLN 92 90 DX
DP 4 3 DX
BGND 99 0 V=V(3)*.5 + V(4)*.5
BB 7 99 I=I(VB)*4.715E6 - I(VC)*5E6 + I(VE)*5E6 +
+ I(VLP)*5E6 - I(VLN)*5E6
GA 6 0 11 12 282.8E-6
GCM 0 6 10 99 8.942E-9
ISS 3 10 DC 195E-6
HLIM 90 0 VLIM 1K
J1 11 2 10 JX
J2 12 1 10 JX
R2 6 9 100E3
RD1 4 11 3.536E3
RD2 4 12 3.536E3
RO1 8 5 150
RO2 7 99 150
RP 3 4 2.143E3
RSS 10 99 1.026E6
VB 9 0 DC 0
VC 3 53 DC 2.2
VE 54 4 DC 2.2
VLIM 7 8 DC 0
VLP 91 0 DC 25
VLN 0 92 DC 25
.MODEL DX D(IS=800E-18)
.MODEL JX PJF(IS=15E-12 BETA=270.1E-6 VTO=-1)
.ENDS TL074
*TopSPICE library: Models\MISCSEMI.MDB
*PART NUMBER: LM386
*MODEL NAME: LM386
*SYMBOL: X8PIN
*
*LM386 Audio power amplifier
* /*
* 1. The following model behavior shows good agreement with the
@@ -174,18 +108,4 @@ q14 out 10018 gnd ddnpn 100
+ Tf=1n Itf=1 Xtf=0 Vtf=10)
.ends LM386
*2N2907 MCE 5-27-97
*Ref: Motorola Small-Signal Device databook, Q4/94
*Si 400mW 40V 600mA 250MHz GenPurp pkg:TO-18 3,2,1
.MODEL 2N2907 PNP (IS=60.9F NF=1 BF=260 VAF=114 IKF=0.36 ISE=30.2P NE=2
+ BR=4 NR=1 VAR=20 IKR=0.54 RE=85.8M RB=0.343 RC=34.3M XTB=1.5
+ CJE=27.6P VJE=1.1 MJE=0.5 CJC=15.3P VJC=0.3 MJC=0.3 TF=636P TR=442N)
*2N2222 MCE 5-20-97
*Ref: Motorola Small-Signal Device Databook, Q4/94
*Si 400mW 30V 800mA 300MHz GenPurp pkg:TO-18 3,2,1
.MODEL 2N2222 NPN (IS=81.2F NF=1 BF=195 VAF=98.6 IKF=0.48 ISE=53.7P NE=2
+ BR=4 NR=1 VAR=20 IKR=0.72 RE=64.4M RB=0.258 RC=25.8M XTB=1.5
+ CJE=89.5P VJE=1.1 MJE=0.5 CJC=28.9P VJC=0.3 MJC=0.3 TF=530P TR=368N)
.END

View File

@@ -7,4 +7,4 @@ From : Project [ETOTH-Amp_LM386.PrjPcb]
Files Generated : 1
Documents Printed : 0
Finished Output Generation At 08:00:57 On 21.11.2025
Finished Output Generation At 11:00:20 On 28.11.2025

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -7,4 +7,4 @@ From : Project [TRI-SQR-VCO_OTA_SS.PrjPcb]
Files Generated : 1
Documents Printed : 0
Finished Output Generation At 21:27:47 On 23.10.2025
Finished Output Generation At 12:23:27 On 28.11.2025

View File

@@ -1,5 +1,5 @@
TRI-SQR-VCO_OTA_SS
*SPICE Netlist generated by Advanced Sim server on 18.11.2025 14:07:43
*SPICE Netlist generated by Advanced Sim server on 28.11.2025 12:29:53
.options MixedSimGenerated
*Schematic Netlist:
@@ -29,6 +29,8 @@ RR_A 0 U_SQR_OTA 3.63k
RR_CV NetR_CV_1 NetIC2_9 59.941k
RR_E NetC_an_2 NetR_E_2 10k
RR_lambda_T NetIC2_9 U_C 1.1k
RR_offset_1 NetR_CV_1 GND 10k
RR_offset_2 VAP NetR_CV_1 10k
RR_PWM_a GND NetIC3_6 15k
RR_PWM_b NetIC3_6 VAP 10k
RR_PWM_c U_PWM NetIC3_7 1k
@@ -55,15 +57,17 @@ VU_var NetR_CV_1 0 1
.PLOT TRAN {v(U_TRI)} =PLOT(2) =AXIS(1) =NAME(U_TRI) =UNITS(V)
.PLOT TRAN {v(U_SAW)} =PLOT(3) =AXIS(1) =NAME(U_SAW) =UNITS(V)
.PLOT TRAN {v(U_PWM)} =PLOT(4) =AXIS(1) =NAME(U_PWM) =UNITS(V)
.PLOT TRAN {i(U_mess)} =PLOT(5) =AXIS(1) =NAME(I_GND) =UNITS(A)
.PLOT TRAN {v(U_in)} =PLOT(2) =AXIS(1) =NAME(U_in) =UNITS(V)
.PLOT TRAN {v(U_C)} =PLOT(5) =AXIS(1) =NAME(U_C) =UNITS(V)
.OPTIONS ABSTOL=1e-10 RELTOL=1e-2 VNTOL=1e-4 METHOD=GEAR MAXORD=2
*Selected Circuit Analyses:
.TRAN 25u 20m 5m 25u UIC
.CONTROL
SWEEP R_offset_2 LIST 10k 20k 30k
.ENDC
*Global Parameters:
.PARAM POS=0
.PARAM POS={0}
*Models and Subcircuits:
* A dual opamp ngspice model

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

View File

@@ -0,0 +1 @@
SB=SB1.R,SB1.P,SB2.R,SB2.P

View File

@@ -66,6 +66,23 @@ DItemRevisionGUID=
GenerateClassCluster=0
DocumentUniqueId=QMXNWCKL
[Document2]
DocumentPath=ESP32-S3.Harness
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=
[Parameter1]
Name=ProjectTitle
Value=ESP32-S3

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

View File

@@ -0,0 +1,456 @@
# Firmware
## Inhaltsverzeichnis
1. [Projektübersicht](#projektübersicht)
2. [Systemarchitektur](#systemarchitektur)
3. [Komponenten](#komponenten)
4. [Speicheranalyse](#speicheranalyse)
5. [Installation](#installation)
6. [Verwendung](#verwendung)
7. [Hauptablauf](#hauptablauf)
---
## Projektübersicht
### Features
-**Dual-Channel CV-Sequencer** - 2 unabhängige Control Voltage Ausgänge
-**4×3 Tastaturmatrix** - Echtzeit-Tasten-Eingabe mit Entprellung
-**Recording & Playback** - Speichere Sequenzen bis 30 Sekunden
-**Loop-Funktion** - Endlose Wiederholung oder Einmaledition
-**Live-Modus** - Direkte Tastatur-zu-CV Ausgabe
-**Multi-Key Support** - Bis zu 10 gleichzeitig aktive Tasten
### Hardware
| Komponente | Modell | Funktion |
|-----------|--------|---------|
| Microcontroller | ESP32 | Hauptprozessor |
| DAC | MCP4728 | 4-Kanal 12-Bit DAC |
| I2C Bus | - | Kommunikation MCU ↔ DAC |
| Tastatur | 4×3 Matrix | Benutzereingabe |
| Buttons | 4× Push-Buttons | Record/Play Steuerung |
---
## Systemarchitektur
### 3-Schicht-Modell
```
┌─────────────────────────────────────────────────────┐
│ INPUT LAYER (📥) │
├──────────────────────┬──────────────────────────────┤
│ Keyboard Matrix │ Sequencer Buttons │
│ (4×3 Tasten) │ (Record/Play × 2) │
└──────────────┬───────┴──────────────┬───────────────┘
│ │
┌──────────────▼─────────────────────▼───────────────┐
│ PROCESSING LAYER (⚙️) │
├──────────────────────┬──────────────────────────────┤
│ Keyboard Klasse │ SequencerBlock (2×) │
│ (Queue-Management) │ (Recording & Playback) │
└──────────────┬───────┴──────────────┬───────────────┘
│ │
┌──────────────▼─────────────────────▼───────────────┐
│ OUTPUT LAYER (📤) │
├──────────────────────┬──────────────────────────────┤
│ CV Klasse (DAC) │ CV Ausgänge │
│ MCP4728 I2C │ (A=Ch1, B=Ch2) │
└──────────────┬───────┴──────────────┬───────────────┘
│ │
└──────────────┬───────┘
Externe Synthesizer / Module
```
### Datenfluss
```
TASTATUR → KEYBOARD → SEQUENCER/LIVE → DAC → CV AUSGÄNGE
BUTTONS
```
---
## Komponenten
### 1. Keyboard Klasse
**Funktion:** Verwaltet die 4×3 Matrix-Tastatur mit Entprellung
```cpp
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);
// ...
};
```
**Merkmale:**
- Debounce-Zeit: 20ms
- Max. 10 gleichzeitig aktive Tasten
- FIFO-Queue für Tastenreihenfolge
- Rückmeldung als `Key(row, col)` Struktur
### 2. CV Klasse
**Funktion:** Verwaltet DAC-Ausgänge über I2C (MCP4728)
```cpp
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();
// ...
};
```
**Merkmale:**
- 2 CV-Kanäle (A, B)
- I2C-Kommunikation (Pins 15, 16)
- Voltage-Mapping von Tasten
- Range: 0-4096mV (12-Bit)
### 3. SequencerBlock Klasse
**Funktion:** Speichert und spielt Sequenzen auf 2 Kanälen
```cpp
class SequencerBlock {
public:
SequencerBlock(uint16_t maxDurationMS, uint16_t minStepDurationMS);
// Recording
void startRecord();
void stopRecord();
void addStep(uint16_t voltage_ch1, uint16_t voltage_ch2);
// Playback
void startPlay();
void stopPlay();
void update(); // Must be called regularly
void setLoop(bool loop);
// Status
uint8_t getStepCount();
uint16_t getCurrentVoltageCh1();
uint16_t getCurrentVoltageCh2();
// ...
};
```
**Merkmale:**
- Max. 128 Steps pro Sequenz
- Max. 30 Sekunden Aufnahmetime
- Dual-Channel Recording
- Loop-Funktion
- Automatisches Time-Limit
### 4. Button-Verarbeitung
**3-Mode Play-Button-System:**
| Click | Aktion | Zustand |
|-------|--------|---------|
| 1x | Play (keine Loop) | Spielt einmalig ab |
| 2x | Loop aktivieren | Endlosschleife |
| 3x | Stop | Stoppt Wiedergabe |
**Record-Button:** Toggle zwischen Recording starten/stoppen
---
## Speicheranalyse
```bash
RAM: [= ] 6.5% (used 21176 bytes from 327680 bytes)
Flash: [= ] 8.5% (used 283165 bytes from 3342336 bytes)
```
### RAM-Verbrauch
| Komponente | Größe | Menge | Gesamt | Notizen |
|-----------|-------|-------|--------|---------|
| SequencerBlock #1 | ~550 B | 1x | 550 B | 128 Steps × 6 Bytes + Variablen |
| SequencerBlock #2 | ~550 B | 1x | 550 B | 128 Steps × 6 Bytes + Variablen |
| Keyboard Objekt | ~130 B | 1x | 130 B | 8×8 Bool Arrays + Pointer |
| CV Objekt | ~50 B | 1x | 50 B | DAC-Pointer + Config |
| keyToVoltage Array | 24 B | 1x | 24 B | 12 Keys × uint16_t |
| Button States | ~50 B | 1x | 50 B | 4 Buttons × ~12 Bytes |
| Lokale Variablen | ~100 B | 1x | 100 B | Loop-Variablen |
| **GESAMT (Schätzung)** | - | - | **~1.5 KB** | - |
### Flash-Verbrauch
| Komponente | Größe |
|-----------|-------|
| Arduino/Wire Libraries | ~150 KB |
| Adafruit_MCP4728 | ~20 KB |
| Firmware Code | ~80 KB |
| Bootloader | ~60 KB |
| **GESAMT (Schätzung)** | **~310 KB** |
### Sequenzen-Speicher Detail
```cpp
struct DualVoltageDurationPair {
uint16_t voltage_ch1; // 2 Bytes
uint16_t voltage_ch2; // 2 Bytes
uint16_t duration; // 2 Bytes
}; // = 6 Bytes pro Step
// Berechnung:
// - N_MAX_SEQUENCE_STEPS = 128
// - 128 Steps × 6 Bytes = 768 Bytes pro Sequenz
// - 2 Sequenzer = 1536 Bytes (1.5 KB)
```
### Optimierungspotential
| Feature | Größenänderung | Status |
|---------|-----------------|--------|
| 256 Steps statt 128 | +768 B | ✅ Problemlos möglich |
| 60s statt 30s Limit | 0 B | ✅ Kostenloses Upgrade |
| 4 Sequenzer statt 2 | +3.3 KB | ✅ Problemlos möglich |
| 8×8 Tastatur statt 4×3 | ~ +30 B | ✅ Kaum Mehraufwand |
---
## Verwendung
### Grundlegende Konfiguration
`include/FIRMWARE_DEF.h`:
```cpp
#define N_KEYBOARD_ROW 4 // Keyboard Reihen
#define N_KEYBOARD_COL 3 // Keyboard Spalten
#define N_CV_GATES 2 // CV-Ausgänge
#define N_SB 2 // Sequencer
// I2C Pins
#define PIN_SDA 15
#define PIN_SCL 16
// Keyboard Pins (Reihen)
#define PIN_K_R0 7
#define PIN_K_R1 8
#define PIN_K_R2 9
#define PIN_K_R3 10
// Keyboard Pins (Spalten)
#define PIN_K_C0 1
#define PIN_K_C1 2
#define PIN_K_C2 4
```
### Spannung-Mapping
`src/main.cpp`:
```cpp
// Voltage für jede Tastaturposition (in 1/12V = 83mV Schritten = 1 Halbtonschritt)
uint16_t keyToVoltage[N_KEYBOARD_ROW*N_KEYBOARD_COL] = {
1*83, 5*83, 9*83, // Row 0: C, E, G
2*83, 6*83, 10*83, // Row 1: D, F, A
3*83, 7*83, 11*83, // Row 2: E, G, B
4*83, 8*83, 12*83 // Row 3: F, A, C (Oktave)
};
```
---
## Hauptablauf
### Main Loop Flowchart
```
START
SETUP (Initialisierung)
┌─── MAIN LOOP ───────────────────────┐
│ │
├─ Keyboard Update │
│ (Tasten auslesen) │
│ │
├─ Button Handler │
│ (Record/Play Buttons) │
│ │
├─ Sequencer Update │
│ (sb1 & sb2 Playback) │
│ │
├─ Spannungen bestimmen │
│ voltage_ch1 = Queue[0] │
│ voltage_ch2 = Queue[1] │
│ │
├─ Recording Check │
│ IF Recording: addStep() │
│ │
├─ Output Priority │
│ IF sb1.playing() → Output SEQ1 │
│ ELSE IF sb2.playing() → Output SEQ2│
│ ELSE → Output Live │
│ │
├─ Time-Limit Check │
│ IF Limit reached: Stop Record │
│ │
├─ Delay 10ms │
│ │
└─────────────────────────────────────┘
[Loop zurück]
```
### State Machine
| State | Beschreibung | Aktion |
|-------|-------------|--------|
| **IDLE** | Leerlauf | Liest Tasten, gibt Live-Spannungen aus |
| **REC** | Recording aktiv | Speichert Sequenzen, überwacht Zeit-Limits |
| **PLAY** | Playback aktiv | Gibt Sequenzen aus, verwaltet Step-Übergänge |
| **LOOP** | Endlosschleife | Wiederholt Sequenz nahtlos |
### Ausgabe-Prioritätssystem
```
1. Sequencer 1 Playing?
├─ JA → Gebe SEQ1 Voltages aus (höchste Priorität)
└─ NEIN ↓
2. Sequencer 2 Playing?
├─ JA → Gebe SEQ2 Voltages aus (zweite Priorität)
└─ NEIN ↓
3. Live Input
└─ Gebe Tasten-Voltages aus (Standard)
```
Dies ermöglicht nahtlose Übergänge und verhindert Konflikte.
---
## Code-Beispiele
### Beispiel 1: Live-Modus (main.cpp.1)
Direkte Tastatur-zu-CV Verbindung ohne Sequencer:
```cpp
void loop() {
keyboard.update();
int n = keyboard.getQueueLength();
if(n > 0) {
for(int i = 0; (i < N_CV_GATES) && (i < n); i++) {
Key k = keyboard.getQueue(i);
cv.setVoltage(i, k); // Taste direkt auf CV ausgeben
}
} else {
cv.clearAll(); // Keine Taste → 0V
}
delay(50);
}
```
### Beispiel 2: Dual-Channel Sequencer (main.cpp)
Vollständiger Sequencer mit 2 unabhängigen Kanälen:
```cpp
void loop() {
keyboard.update();
handleSequencerButtons();
sb1.update();
sb2.update();
// ... Voltage determination ...
// Recording
if(sb1.isRecording()) {
sb1.addStep(voltage_ch1, voltage_ch2);
}
if(sb2.isRecording()) {
sb2.addStep(voltage_ch1, voltage_ch2);
}
// Output mit Priorität
if(sb1.isPlaying()) {
cv.setVoltage(0, sb1.getCurrentVoltageCh1());
cv.setVoltage(1, sb1.getCurrentVoltageCh2());
}
else if(sb2.isPlaying()) {
cv.setVoltage(0, sb2.getCurrentVoltageCh1());
cv.setVoltage(1, sb2.getCurrentVoltageCh2());
}
else {
cv.setVoltage(0, voltage_ch1);
cv.setVoltage(1, voltage_ch2);
}
delay(10);
}
```
---
## Dateistruktur
```
project/
├── include/
│ ├── FIRMWARE.h # Klassen-Definitionen
│ └── FIRMWARE_DEF.h # Konstanten & Pin-Definitionen
└── src/
├── main.cpp # Dual-Channel Sequencer Beispiel
├── main.cpp.1 # Live-Modus Beispiel
├── main.cpp.2 # Dual-Channel ohne Sequencer
└── FIRMWARE.cpp # Implementierungen
```
---
## Technische Spezifikationen
### Timings
| Parameter | Wert | Funktion |
|-----------|------|----------|
| Keyboard Debounce | 20ms | Anti-Prellen Verzögerung |
| Button Debounce | 50ms | Button Anti-Prellen |
| Main Loop Delay | 10ms | Update-Rate |
| Max Recording | 30s | Zeit-Limit pro Sequenz |
| Min Step Duration | 50ms | Minimale Step-Länge |
### Voltage-Mapping
Verwendet gleichmäßige 83mV Schritte (1/12 Oktave):
```
Key (1,0) = 1 × 83mV = 83mV (C)
Key (1,1) = 5 × 83mV = 415mV (E)
Key (1,2) = 9 × 83mV = 747mV (G)
... etc
Key (4,2) = 12 × 83mV = 996mV (C')
```
---
**Zuletzt aktualisiert:** 2025-11-30

View File

@@ -16,9 +16,7 @@
#define N_MAX_ROWS 8
#define N_MAX_COLS 8
#define MS_DEBOUNCE 20
#define N_MAX_DAC_CH 4
#define N_MAX_SEQUENCE_STEPS 128
struct Key
{
@@ -26,9 +24,10 @@ struct Key
int col;
};
struct voltageDurationPair
struct DualVoltageDurationPair
{
uint16_t voltage;
uint16_t voltage_ch1;
uint16_t voltage_ch2;
uint16_t duration;
};
@@ -93,18 +92,18 @@ class CV
class SequencerBlock
{
public:
SequencerBlock(uint16_t maxDurationMS, uint16_t timeoutMS);
SequencerBlock(uint16_t maxDurationMS, uint16_t maxStepCount);
// 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
void startPlay();
void stopPlay();
void update(); // Muss regelmäßig aufgerufen werden
void update();
bool isPlaying();
// Sequenz-Verwaltung
@@ -113,33 +112,44 @@ class SequencerBlock
// Status-Abfragen
bool timeLimitReached();
uint8_t getStepCount();
uint16_t getCurrentVoltage();
bool stepLimitReached();
uint16_t getStepCount();
uint16_t getCurrentVoltageCh1();
uint16_t getCurrentVoltageCh2();
uint16_t getTotalDuration();
private:
// Sequenz-Speicher
voltageDurationPair _sequence[N_MAX_SEQUENCE_STEPS];
uint8_t _stepCount;
uint8_t _currentStep;
/*!
* @brief Memory limiting
* @return (uint16_t) 1024
* @attention Increasing the value might lead to an overflow
* @note sizeOf(DualVoltageDurationPair) = 6 Byte ==> 6 Byte * 1024 = 6144 Byte
*/
const static uint16_t _MAX_SEQUENCE_STEPS = 1024;
// Zeitverwaltung
// Sequenz memory
DualVoltageDurationPair _sequence[_MAX_SEQUENCE_STEPS];
uint16_t _stepCount;
uint16_t _currentStep;
// Time management
uint16_t _maxDurationMS;
uint16_t _timeoutMS;
uint16_t _maxStepCount;
unsigned long _recordStartTime;
unsigned long _lastStepTime;
unsigned long _playStartTime;
unsigned long _stepStartTime;
// Status-Flags
// Status flags
bool _isRecording;
bool _isPlaying;
bool _loop;
// Letzte aufgenommene Spannung
uint16_t _lastVoltage;
// Last recorded Voltage: at n-th step minus one
uint16_t _lastVoltageCh1;
uint16_t _lastVoltageCh2;
// Hilfsfunktionen
// helper functions
void _finishCurrentStep();
bool _canAddStep();
};

View File

@@ -11,11 +11,12 @@
#include <Arduino.h>
#include <Wire.h>
// CONSTANTS DEFINITONS
#define N_KEYBOARD_ROW 4
#define N_KEYBOARD_COL 3
#define N_KEYBOARD_ROW 4 // for PROD. change to 5
#define N_KEYBOARD_COL 3 // for PROD. change to 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
@@ -25,15 +26,22 @@
#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 // DEV. 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 // DEV. not in use
#define PIN_K_C4 6 // DEV. 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 // for PROD. change to 33 / not available on dev board
#define PIN_SB_1_PLAY 38 // for PROD. change to 34 / not available on dev board
#define PIN_SB_2_REC 35
#define PIN_SB_2_PLAY 36
// MISC/INFO PINS
#define PIN_ACTIVE -1 // TODO: if any key is played return HIGH
#define PIN_REC -1 // TODO: if any sb is recording return HIGH
#define PIN_BPM -1 // TODO: get bpm through potentiometer analog value
#define PIN_B_METRONOME -1 // TODO: button activates/deactivates bpm led output
#define PIN_L_METRONOME -1 // TODO: led blinks according to bpm value
#endif

View File

@@ -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 maxStepCount maximum number of steps that can be recorded
*/
SequencerBlock::SequencerBlock(uint16_t maxDurationMS, uint16_t timeoutMS)
SequencerBlock::SequencerBlock(uint16_t maxDurationMS, uint16_t maxStepCount)
{
_maxDurationMS = maxDurationMS;
_timeoutMS = timeoutMS;
_maxStepCount = maxStepCount;
_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)
// Hat sich die Spannung geändert?
bool voltageChanged = (voltage_ch1 != _lastVoltageCh1) || (voltage_ch2 != _lastVoltageCh2);
if(voltageChanged)
{
// Vorherigen Step abschließen (wenn vorhanden)
if(_stepCount > 0)
{
_finishCurrentStep();
}
// Neuen Schritt beginnen oder vorhandenen aktualisieren
if(voltage != _lastVoltage || _stepCount == 0)
{
// 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++)
for(uint8_t i = 0; i < _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,32 @@ bool SequencerBlock::timeLimitReached()
return (elapsed >= _maxDurationMS);
}
/*!
* @brief returns the currently recoreded steps
* @return uint8_t between 0 and 128
*/
uint8_t SequencerBlock::getStepCount()
bool SequencerBlock::stepLimitReached()
{
return (_stepCount >= _maxStepCount);
}
uint16_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,18 +450,15 @@ 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)
{
_stepCount++;
}
_sequence[_stepCount - 1].duration = duration;
}
bool SequencerBlock::_canAddStep()
{
if(_stepCount >= N_MAX_SEQUENCE_STEPS) return false;
if(_stepCount >= _maxStepCount) return false;
if(_stepCount >= _MAX_SEQUENCE_STEPS) return false;
if(timeLimitReached()) return false;
return true;

View File

@@ -1,35 +1,31 @@
/*
* 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
* TODO:
- add predefined sequence of voltage (e.g. for usage as startup sound)
- implement INFO and MISC pins form file FIRMWARE_DEF.h
*/
#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);
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] = { /* 83mV = 1/12V */
1*83, 5*83, 9*83,
2*83, 6*83, 10*83,
3*83, 7*83, 11*83,
4*83, 8*83, 12*83
1*83, 5*83, 9*83, /* ROW 1: B D Fis */
2*83, 6*83, 10*83, /* ROW 2: H Dis G */
3*83, 7*83, 11*83, /* ROW 3: C E Gis */
4*83, 8*83, 12*83 /* ROW 4: Cis F A' */
};
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 30s max, 512 max Steps
SequencerBlock sb1(30000, N_MAX_SEQ_STEPS);
SequencerBlock sb2(30000, N_MAX_SEQ_STEPS);
// Button States
struct ButtonState {
@@ -45,10 +41,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 +56,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 +69,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 +106,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();
// 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");
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 +170,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 +218,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 +241,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];
voltage_ch1 = keyToVoltage[k1.row * N_KEYBOARD_COL + k1.col];
}
}
// Bei Recording: Spannung aufnehmen
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);
sb1.addStep(voltage_ch1, voltage_ch2);
}
if(sb2.isRecording())
{
sb2.addStep(voltage);
sb2.addStep(voltage_ch1, voltage_ch2);
}
// Live-Ausgabe nur wenn nicht gerade wiedergegeben wird
if(!sb1.isPlaying() && !sb2.isPlaying())
// CV-Ausgabe: Priorität hat Sequencer-Wiedergabe
if(sb1.isPlaying())
{
cv.setVoltage(cvIndex++, k);
cv.setVoltage(0, sb1.getCurrentVoltageCh1());
cv.setVoltage(1, sb1.getCurrentVoltageCh2());
}
}
}
// Restliche CV-Ausgänge auf 0 setzen wenn live gespielt wird
if(!sb1.isPlaying() && !sb2.isPlaying())
else if(sb2.isPlaying())
{
for(int i = cvIndex; i < N_CV_GATES; i++)
{
cv.setVoltage(i, 0);
}
}
cv.setVoltage(0, sb2.getCurrentVoltageCh1());
cv.setVoltage(1, sb2.getCurrentVoltageCh2());
}
else
{
// Keine Tasten gedrückt
if(sb1.isRecording())
{
sb1.addStep(0);
}
if(sb2.isRecording())
{
sb2.addStep(0);
}
if(!sb1.isPlaying() && !sb2.isPlaying())
{
cv.clearAll();
}
}
// Sequencer-Wiedergabe auf CV-Ausgänge
if(sb1.isPlaying())
{
cv.setVoltage(0, sb1.getCurrentVoltage());
}
if(sb2.isPlaying())
{
cv.setVoltage(1, sb2.getCurrentVoltage());
// Live-Ausgabe wenn kein Sequencer spielt
cv.setVoltage(0, voltage_ch1);
cv.setVoltage(1, voltage_ch2);
}
// Time-Limit Warnung
@@ -267,12 +296,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);
}

View File

@@ -54,7 +54,7 @@ Die eigentliche __Diplomarbeit__ und die dazugehörigen (finalen) Fertigungsunte
## TO-DO Liste
- [x] Literaturrechreche
- [x] Schaltungsentwurf
- [ ] Simulationen
- [x] Simulationen
- [ ] Gesamt System zusammengeführt
- [ ] Prototyp entwickeln und bestellen (Deadline: Bis Weihnachtsferien)
- [ ] Start Diplomarbeit dokumentieren

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 MiB

BIN
lab/OutputStage/PIC1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 83 KiB

BIN
lab/OutputStage/PIC2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

BIN
lab/OutputStage/PIC3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 111 KiB

BIN
lab/OutputStage/PIC4.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 118 KiB

BIN
lab/OutputStage/PIC5.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 115 KiB

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 99 KiB

BIN
lab/VCF/TEK0000.JPG Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 181 KiB

BIN
lab/VCF/TEK0001.JPG Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 159 KiB

BIN
lab/VCF/VCF.xlsx Normal file

Binary file not shown.

BIN
lab/VCO/IMG_9639.jpeg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 MiB

BIN
lab/VCO/IMG_9640.jpeg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 MiB