elektron-lfo 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bun.lock +23 -0
- package/bunfig.toml +3 -0
- package/docs/PLAN.md +446 -0
- package/docs/TEST_QUESTIONS.md +550 -0
- package/package.json +26 -0
- package/src/cli/args.ts +182 -0
- package/src/cli/display.ts +237 -0
- package/src/cli/index.ts +129 -0
- package/src/cli/keyboard.ts +143 -0
- package/src/engine/fade.ts +137 -0
- package/src/engine/index.ts +72 -0
- package/src/engine/lfo.ts +269 -0
- package/src/engine/timing.ts +157 -0
- package/src/engine/triggers.ts +179 -0
- package/src/engine/types.ts +126 -0
- package/src/engine/waveforms.ts +152 -0
- package/src/index.ts +31 -0
- package/tests/depth-fade.test.ts +306 -0
- package/tests/phase.test.ts +219 -0
- package/tests/presets.test.ts +344 -0
- package/tests/timing.test.ts +232 -0
- package/tests/triggers.test.ts +345 -0
- package/tests/waveforms.test.ts +273 -0
- package/tsconfig.json +13 -0
package/bun.lock
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"lockfileVersion": 1,
|
|
3
|
+
"workspaces": {
|
|
4
|
+
"": {
|
|
5
|
+
"name": "elektron-lfo",
|
|
6
|
+
"devDependencies": {
|
|
7
|
+
"@types/bun": "latest",
|
|
8
|
+
"typescript": "^5.0.0",
|
|
9
|
+
},
|
|
10
|
+
},
|
|
11
|
+
},
|
|
12
|
+
"packages": {
|
|
13
|
+
"@types/bun": ["@types/bun@1.3.6", "", { "dependencies": { "bun-types": "1.3.6" } }, "sha512-uWCv6FO/8LcpREhenN1d1b6fcspAB+cefwD7uti8C8VffIv0Um08TKMn98FynpTiU38+y2dUO55T11NgDt8VAA=="],
|
|
14
|
+
|
|
15
|
+
"@types/node": ["@types/node@25.0.9", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-/rpCXHlCWeqClNBwUhDcusJxXYDjZTyE8v5oTO7WbL8eij2nKhUeU89/6xgjU7N4/Vh3He0BtyhJdQbDyhiXAw=="],
|
|
16
|
+
|
|
17
|
+
"bun-types": ["bun-types@1.3.6", "", { "dependencies": { "@types/node": "*" } }, "sha512-OlFwHcnNV99r//9v5IIOgQ9Uk37gZqrNMCcqEaExdkVq3Avwqok1bJFmvGMCkCE0FqzdY8VMOZpfpR3lwI+CsQ=="],
|
|
18
|
+
|
|
19
|
+
"typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
|
|
20
|
+
|
|
21
|
+
"undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="],
|
|
22
|
+
}
|
|
23
|
+
}
|
package/bunfig.toml
ADDED
package/docs/PLAN.md
ADDED
|
@@ -0,0 +1,446 @@
|
|
|
1
|
+
# Elektron LFO - Implementation Plan
|
|
2
|
+
|
|
3
|
+
A TypeScript implementation of the Elektron Digitakt II LFO engine with real-time terminal visualization, built with Bun.
|
|
4
|
+
|
|
5
|
+
## Project Requirements
|
|
6
|
+
|
|
7
|
+
**Runtime & Build:**
|
|
8
|
+
- Runtime: Bun (fast TypeScript/JavaScript runtime)
|
|
9
|
+
- Language: TypeScript with strict type checking
|
|
10
|
+
- Testing: Bun's built-in test runner (`bun test`)
|
|
11
|
+
|
|
12
|
+
**Core Features:**
|
|
13
|
+
- Complete Digitakt II LFO engine with all 7 waveforms (TRI, SIN, SQR, SAW, EXP, RMP, RND)
|
|
14
|
+
- All 5 trigger modes (FRE, TRG, HLD, ONE, HLF)
|
|
15
|
+
- Full parameter support: SPD (-64 to +63), MULT (1 through 2k), SPH (0-127), DEP (-64 to +63), FADE (-64 to +63)
|
|
16
|
+
- Accurate timing mathematics matching hardware behavior
|
|
17
|
+
- Support for both BPM-synced and fixed 120 BPM modes
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## 1. Project Structure
|
|
22
|
+
|
|
23
|
+
```
|
|
24
|
+
elektron-lfo/
|
|
25
|
+
├── src/
|
|
26
|
+
│ ├── engine/
|
|
27
|
+
│ │ ├── index.ts # Main exports
|
|
28
|
+
│ │ ├── types.ts # LFOConfig, LFOState, enums
|
|
29
|
+
│ │ ├── waveforms.ts # All 7 waveform generators
|
|
30
|
+
│ │ ├── timing.ts # Timing/phase calculations
|
|
31
|
+
│ │ ├── triggers.ts # Trigger mode handling
|
|
32
|
+
│ │ ├── fade.ts # Fade in/out logic
|
|
33
|
+
│ │ └── lfo.ts # Main LFO class
|
|
34
|
+
│ ├── cli/
|
|
35
|
+
│ │ ├── index.ts # CLI entry point
|
|
36
|
+
│ │ ├── args.ts # Argument parsing
|
|
37
|
+
│ │ ├── display.ts # Terminal visualization
|
|
38
|
+
│ │ └── keyboard.ts # Keyboard input handling
|
|
39
|
+
│ └── index.ts # Library exports
|
|
40
|
+
├── tests/
|
|
41
|
+
│ ├── waveforms.test.ts
|
|
42
|
+
│ ├── timing.test.ts
|
|
43
|
+
│ ├── triggers.test.ts
|
|
44
|
+
│ ├── phase.test.ts
|
|
45
|
+
│ ├── depth-fade.test.ts
|
|
46
|
+
│ └── presets.test.ts # Integration tests with 5 presets
|
|
47
|
+
├── package.json
|
|
48
|
+
├── tsconfig.json
|
|
49
|
+
├── bunfig.toml
|
|
50
|
+
└── README.md
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
**Dependencies:**
|
|
54
|
+
- `@types/bun` (dev)
|
|
55
|
+
- `typescript` (dev)
|
|
56
|
+
- For terminal UI: Start with raw ANSI escape codes (zero dependencies)
|
|
57
|
+
|
|
58
|
+
---
|
|
59
|
+
|
|
60
|
+
## 2. Core Types (`src/engine/types.ts`)
|
|
61
|
+
|
|
62
|
+
```typescript
|
|
63
|
+
export type Waveform = 'TRI' | 'SIN' | 'SQR' | 'SAW' | 'EXP' | 'RMP' | 'RND';
|
|
64
|
+
export type TriggerMode = 'FRE' | 'TRG' | 'HLD' | 'ONE' | 'HLF';
|
|
65
|
+
export type Multiplier = 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128 | 256 | 512 | 1024 | 2048;
|
|
66
|
+
|
|
67
|
+
export interface LFOConfig {
|
|
68
|
+
waveform: Waveform;
|
|
69
|
+
speed: number; // -64.00 to +63.00
|
|
70
|
+
multiplier: Multiplier;
|
|
71
|
+
useFixedBPM: boolean; // true = 120 BPM (dot suffix)
|
|
72
|
+
startPhase: number; // 0 to 127
|
|
73
|
+
mode: TriggerMode;
|
|
74
|
+
depth: number; // -64.00 to +63.00
|
|
75
|
+
fade: number; // -64 to +63
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export interface LFOState {
|
|
79
|
+
phase: number; // 0.0 to 1.0
|
|
80
|
+
output: number;
|
|
81
|
+
rawOutput: number;
|
|
82
|
+
isRunning: boolean;
|
|
83
|
+
fadeMultiplier: number;
|
|
84
|
+
fadeProgress: number;
|
|
85
|
+
randomValue: number;
|
|
86
|
+
previousPhase: number;
|
|
87
|
+
heldOutput: number;
|
|
88
|
+
startPhaseNormalized: number;
|
|
89
|
+
cycleCount: number;
|
|
90
|
+
triggerCount: number;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export interface TimingInfo {
|
|
94
|
+
cycleTimeMs: number;
|
|
95
|
+
noteValue: string;
|
|
96
|
+
frequencyHz: number;
|
|
97
|
+
cyclesPerBar: number;
|
|
98
|
+
}
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
---
|
|
102
|
+
|
|
103
|
+
## 3. Waveform Generation (`src/engine/waveforms.ts`)
|
|
104
|
+
|
|
105
|
+
Implement exact formulas from the Digitakt II specification:
|
|
106
|
+
|
|
107
|
+
```typescript
|
|
108
|
+
// Triangle: Bipolar, starts at 0, peak at 0.25, trough at 0.75
|
|
109
|
+
function generateTriangle(phase: number): number {
|
|
110
|
+
if (phase < 0.25) return phase * 4;
|
|
111
|
+
if (phase < 0.75) return 1 - (phase - 0.25) * 4;
|
|
112
|
+
return -1 + (phase - 0.75) * 4;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Sine: Bipolar, standard sine wave
|
|
116
|
+
function generateSine(phase: number): number {
|
|
117
|
+
return Math.sin(phase * 2 * Math.PI);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Square: Bipolar, +1 first half, -1 second half
|
|
121
|
+
function generateSquare(phase: number): number {
|
|
122
|
+
return phase < 0.5 ? 1 : -1;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Sawtooth: Bipolar, linear rise -1 to +1
|
|
126
|
+
function generateSawtooth(phase: number): number {
|
|
127
|
+
return phase * 2 - 1;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Exponential: Unipolar 0 to +1, accelerating curve
|
|
131
|
+
function generateExponential(phase: number): number {
|
|
132
|
+
const k = 4;
|
|
133
|
+
return (Math.exp(phase * k) - 1) / (Math.exp(k) - 1);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Ramp: Unipolar +1 to 0, linear fall
|
|
137
|
+
function generateRamp(phase: number): number {
|
|
138
|
+
return 1 - phase;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Random: Bipolar, sample-and-hold, 8 steps per cycle
|
|
142
|
+
function generateRandom(phase: number, state: LFOState): {
|
|
143
|
+
value: number;
|
|
144
|
+
newRandomValue: number
|
|
145
|
+
}
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
---
|
|
149
|
+
|
|
150
|
+
## 4. Timing Calculations (`src/engine/timing.ts`)
|
|
151
|
+
|
|
152
|
+
From the spec:
|
|
153
|
+
```
|
|
154
|
+
phase_steps_per_bar = |SPD| × MULT
|
|
155
|
+
cycle_time_ms = (60000 / BPM) × 4 × (128 / (|SPD| × MULT))
|
|
156
|
+
frequency_hz = (BPM / 60) × (|SPD| × MULT / 128)
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
Key functions:
|
|
160
|
+
- `calculatePhaseIncrement(config, bpm)` - phase change per millisecond
|
|
161
|
+
- `calculateTimingInfo(config, bpm)` - returns cycle time, note value, frequency
|
|
162
|
+
- `calculateNoteValue(product)` - converts SPD × MULT product to musical notation
|
|
163
|
+
|
|
164
|
+
---
|
|
165
|
+
|
|
166
|
+
## 5. Test Plan
|
|
167
|
+
|
|
168
|
+
### Waveform Tests (`tests/waveforms.test.ts`)
|
|
169
|
+
|
|
170
|
+
For each waveform, test:
|
|
171
|
+
- Starting value at phase 0
|
|
172
|
+
- Peak/trough positions
|
|
173
|
+
- End value near phase 1
|
|
174
|
+
- Correct polarity range (bipolar -1 to +1, unipolar 0 to +1)
|
|
175
|
+
- Shape characteristics (linear vs exponential)
|
|
176
|
+
|
|
177
|
+
### Timing Tests (`tests/timing.test.ts`)
|
|
178
|
+
|
|
179
|
+
Verify against spec examples:
|
|
180
|
+
| SPD | MULT | Expected Result |
|
|
181
|
+
|-----|------|-----------------|
|
|
182
|
+
| 32 | 64 | 1/16th note, 125ms at 120 BPM |
|
|
183
|
+
| 16 | 8 | 1 bar, 2000ms at 120 BPM |
|
|
184
|
+
| 1 | 1 | 128 bars, 256000ms at 120 BPM |
|
|
185
|
+
| 8 | 16 | 1 bar, 2000ms at 120 BPM |
|
|
186
|
+
|
|
187
|
+
Test fixed BPM mode (always 120 regardless of project BPM).
|
|
188
|
+
|
|
189
|
+
### Trigger Mode Tests (`tests/triggers.test.ts`)
|
|
190
|
+
|
|
191
|
+
- **FRE**: Phase unchanged after trigger
|
|
192
|
+
- **TRG**: Phase resets to startPhase after trigger, fade resets
|
|
193
|
+
- **HLD**: Output value held after trigger, LFO continues in background
|
|
194
|
+
- **ONE**: Runs one cycle then stops, can be retriggered
|
|
195
|
+
- **HLF**: Stops at phase 0.5 from start (or backward equivalent)
|
|
196
|
+
|
|
197
|
+
### Phase Tests (`tests/phase.test.ts`)
|
|
198
|
+
|
|
199
|
+
- Phase wraps correctly from 1 back to 0
|
|
200
|
+
- Phase stays within 0-1 range
|
|
201
|
+
- Negative speed runs phase backwards
|
|
202
|
+
- startPhase correctly positions initial phase
|
|
203
|
+
- ONE/HLF modes stop correctly with non-zero startPhase
|
|
204
|
+
|
|
205
|
+
### Depth/Fade Tests (`tests/depth-fade.test.ts`)
|
|
206
|
+
|
|
207
|
+
- Depth 0 produces 0 output
|
|
208
|
+
- Negative depth inverts output
|
|
209
|
+
- Negative fade starts at 0 and increases (fade IN)
|
|
210
|
+
- Positive fade starts at 1 and decreases (fade OUT)
|
|
211
|
+
- Fade resets on trigger for TRG/ONE/HLF modes
|
|
212
|
+
|
|
213
|
+
### Preset Integration Tests (`tests/presets.test.ts`)
|
|
214
|
+
|
|
215
|
+
Test all 5 presets from the DIGITAKT_II_LFO_PRESETS.md document:
|
|
216
|
+
|
|
217
|
+
1. **Fade-In One-Shot**: RMP, SPD=8, MULT=16, ONE mode, FADE=-32
|
|
218
|
+
- Verify 2000ms cycle at 120 BPM
|
|
219
|
+
- Verify stops after one cycle
|
|
220
|
+
- Verify fade multiplier increases over time
|
|
221
|
+
|
|
222
|
+
2. **Ambient Drift**: SIN, SPD=1, MULT=1, FRE mode
|
|
223
|
+
- Verify 256000ms (4+ minutes) cycle
|
|
224
|
+
- Verify continuous running despite triggers
|
|
225
|
+
|
|
226
|
+
3. **Hi-Hat Humanizer**: RND, SPD=32, MULT=64, FRE mode
|
|
227
|
+
- Verify 125ms cycle (1/16 note)
|
|
228
|
+
- Verify random values change
|
|
229
|
+
- Verify output within depth range
|
|
230
|
+
|
|
231
|
+
4. **Pumping Sidechain**: EXP, SPD=32, MULT=4, TRG mode, DEP=-63
|
|
232
|
+
- Verify 2000ms cycle
|
|
233
|
+
- Verify restarts on trigger
|
|
234
|
+
- Verify inverted (negative) output
|
|
235
|
+
|
|
236
|
+
5. **Wobble Bass**: SIN, SPD=16, MULT=8, TRG mode, SPH=32
|
|
237
|
+
- Verify 2000ms cycle (1 bar)
|
|
238
|
+
- Verify starts at peak (phase 90 degrees)
|
|
239
|
+
|
|
240
|
+
---
|
|
241
|
+
|
|
242
|
+
## 6. CLI Tool
|
|
243
|
+
|
|
244
|
+
### Command-Line Arguments (`src/cli/args.ts`)
|
|
245
|
+
|
|
246
|
+
```
|
|
247
|
+
Usage: elektron-lfo [options]
|
|
248
|
+
|
|
249
|
+
Options:
|
|
250
|
+
-w, --waveform <type> TRI, SIN, SQR, SAW, EXP, RMP, RND (default: TRI)
|
|
251
|
+
-s, --speed <value> -64.00 to +63.00 (default: 16)
|
|
252
|
+
-m, --multiplier <val> 1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048 (default: 8)
|
|
253
|
+
-f, --fixed Use fixed 120 BPM instead of project BPM
|
|
254
|
+
-p, --phase <value> 0-127 start phase (default: 0)
|
|
255
|
+
-M, --mode <mode> FRE, TRG, HLD, ONE, HLF (default: FRE)
|
|
256
|
+
-d, --depth <value> -64.00 to +63.00 (default: 63)
|
|
257
|
+
-F, --fade <value> -64 to +63 (default: 0)
|
|
258
|
+
-b, --bpm <value> 1-999 project BPM (default: 120)
|
|
259
|
+
-h, --help Show help
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
### Terminal Display (`src/cli/display.ts`)
|
|
263
|
+
|
|
264
|
+
Display should include:
|
|
265
|
+
- Parameter values (WAVE, SPD, MULT, MODE, SPH, DEP, FADE)
|
|
266
|
+
- BPM and timing info (cycle time in ms, note value, Hz)
|
|
267
|
+
- ASCII waveform with current phase indicator (vertical line)
|
|
268
|
+
- Current output value (numerical + visual bar)
|
|
269
|
+
- Phase percentage, fade percentage, cycle count
|
|
270
|
+
- Status (STOPPED/RUNNING/FREE RUNNING)
|
|
271
|
+
- Control hints
|
|
272
|
+
|
|
273
|
+
Example display:
|
|
274
|
+
```
|
|
275
|
+
=== Elektron LFO Visualizer ===
|
|
276
|
+
|
|
277
|
+
WAVE: SIN SPD: 16.00 MULT: 8 MODE: TRG
|
|
278
|
+
SPH: 32 (90°) DEP: +48.00 FADE: 0
|
|
279
|
+
BPM: 120 Cycle: 2000.0ms (1 bar) Hz: 0.500
|
|
280
|
+
|
|
281
|
+
.-""-.
|
|
282
|
+
/ \
|
|
283
|
+
/ | \ <- Phase indicator
|
|
284
|
+
/ \
|
|
285
|
+
'-.____.____.-
|
|
286
|
+
|
|
287
|
+
Output: +0.7500 [=======| ]
|
|
288
|
+
Phase: 25.0% Fade: 100% Cycles: 3 Triggers: 5
|
|
289
|
+
|
|
290
|
+
[RUNNING] - Press SPACE to trigger
|
|
291
|
+
|
|
292
|
+
Controls: [SPACE] Trigger [Q] Quit [↑/↓] BPM [←/→] Speed
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
### Keyboard Handling (`src/cli/keyboard.ts`)
|
|
296
|
+
|
|
297
|
+
- **Space**: Send trigger
|
|
298
|
+
- **Q / Ctrl+C**: Quit
|
|
299
|
+
- **Up/Down arrows**: Adjust BPM (+/- 1)
|
|
300
|
+
- **Left/Right arrows**: Adjust speed (+/- 1)
|
|
301
|
+
|
|
302
|
+
Use `process.stdin.setRawMode(true)` for single-keypress detection.
|
|
303
|
+
|
|
304
|
+
---
|
|
305
|
+
|
|
306
|
+
## 7. Package Configuration
|
|
307
|
+
|
|
308
|
+
### package.json
|
|
309
|
+
|
|
310
|
+
```json
|
|
311
|
+
{
|
|
312
|
+
"name": "elektron-lfo",
|
|
313
|
+
"version": "1.0.0",
|
|
314
|
+
"description": "Digitakt II LFO engine implementation with CLI visualization",
|
|
315
|
+
"main": "dist/index.js",
|
|
316
|
+
"module": "src/index.ts",
|
|
317
|
+
"types": "dist/index.d.ts",
|
|
318
|
+
"bin": {
|
|
319
|
+
"elektron-lfo": "./src/cli/index.ts"
|
|
320
|
+
},
|
|
321
|
+
"scripts": {
|
|
322
|
+
"dev": "bun run src/cli/index.ts",
|
|
323
|
+
"test": "bun test",
|
|
324
|
+
"test:watch": "bun test --watch",
|
|
325
|
+
"build": "bun build src/index.ts --outdir dist --target node",
|
|
326
|
+
"cli": "bun run src/cli/index.ts",
|
|
327
|
+
"typecheck": "tsc --noEmit"
|
|
328
|
+
},
|
|
329
|
+
"devDependencies": {
|
|
330
|
+
"@types/bun": "latest",
|
|
331
|
+
"typescript": "^5.0.0"
|
|
332
|
+
},
|
|
333
|
+
"engines": {
|
|
334
|
+
"bun": ">=1.0.0"
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
### tsconfig.json
|
|
340
|
+
|
|
341
|
+
```json
|
|
342
|
+
{
|
|
343
|
+
"compilerOptions": {
|
|
344
|
+
"target": "ES2022",
|
|
345
|
+
"module": "ESNext",
|
|
346
|
+
"moduleResolution": "bundler",
|
|
347
|
+
"lib": ["ES2022"],
|
|
348
|
+
"strict": true,
|
|
349
|
+
"noEmit": true,
|
|
350
|
+
"esModuleInterop": true,
|
|
351
|
+
"types": ["bun-types"]
|
|
352
|
+
},
|
|
353
|
+
"include": ["src/**/*"]
|
|
354
|
+
}
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
### bunfig.toml
|
|
358
|
+
|
|
359
|
+
```toml
|
|
360
|
+
[test]
|
|
361
|
+
coverage = true
|
|
362
|
+
coverageDir = "./coverage"
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
---
|
|
366
|
+
|
|
367
|
+
## 8. Implementation Order
|
|
368
|
+
|
|
369
|
+
### Phase 1: Foundation (Day 1)
|
|
370
|
+
1. Project setup (bun init, configs)
|
|
371
|
+
2. Types definition (`src/engine/types.ts`)
|
|
372
|
+
|
|
373
|
+
### Phase 2: Core Engine (Days 2-3)
|
|
374
|
+
3. Waveform generators + tests
|
|
375
|
+
4. Timing system + tests
|
|
376
|
+
5. Trigger handling + tests
|
|
377
|
+
|
|
378
|
+
### Phase 3: Advanced Features (Day 4)
|
|
379
|
+
6. Fade system + tests
|
|
380
|
+
7. Main LFO class + integration tests
|
|
381
|
+
|
|
382
|
+
### Phase 4: CLI (Days 5-6)
|
|
383
|
+
8. Argument parsing
|
|
384
|
+
9. Display rendering
|
|
385
|
+
10. Keyboard handling
|
|
386
|
+
11. CLI entry point with 60fps loop
|
|
387
|
+
|
|
388
|
+
### Phase 5: Verification (Day 7)
|
|
389
|
+
12. Preset integration tests (all 5 presets)
|
|
390
|
+
13. Documentation
|
|
391
|
+
|
|
392
|
+
### Dependency Graph
|
|
393
|
+
|
|
394
|
+
```
|
|
395
|
+
types.ts
|
|
396
|
+
|
|
|
397
|
+
+-- waveforms.ts
|
|
398
|
+
| |
|
|
399
|
+
+-- timing.ts
|
|
400
|
+
| |
|
|
401
|
+
+-- triggers.ts
|
|
402
|
+
| |
|
|
403
|
+
+-- fade.ts
|
|
404
|
+
| |
|
|
405
|
+
+-------+-- lfo.ts
|
|
406
|
+
|
|
|
407
|
+
+--------+--------+
|
|
408
|
+
| | |
|
|
409
|
+
args.ts display.ts keyboard.ts
|
|
410
|
+
| | |
|
|
411
|
+
+--------+--------+
|
|
412
|
+
|
|
|
413
|
+
cli/index.ts
|
|
414
|
+
```
|
|
415
|
+
|
|
416
|
+
---
|
|
417
|
+
|
|
418
|
+
## 9. Key Implementation Notes
|
|
419
|
+
|
|
420
|
+
1. **Phase is normalized to 0-1**, not 0-127. Convert on input/output.
|
|
421
|
+
|
|
422
|
+
2. **Random waveform state** needs special handling - must track previous phase to detect step changes.
|
|
423
|
+
|
|
424
|
+
3. **ONE mode stopping** requires careful detection of phase wrapping past the start phase. Must work for both positive AND negative speed.
|
|
425
|
+
|
|
426
|
+
4. **HLF mode** stops at the phase 0.5 beyond start phase (wrapping), not absolute 0.5.
|
|
427
|
+
|
|
428
|
+
5. **HLD mode** captures the current output when triggered and holds it until the next trigger, but the LFO continues running in the background.
|
|
429
|
+
|
|
430
|
+
6. **Fade timing** is relative to LFO cycles, not absolute time. This ensures consistency across BPM changes.
|
|
431
|
+
|
|
432
|
+
7. **Fade resets** on trigger for TRG, ONE, and HLF modes. FRE mode does not reset fade.
|
|
433
|
+
|
|
434
|
+
8. **60fps update rate** is sufficient for visualization but may need adjustment for audio-rate LFO applications.
|
|
435
|
+
|
|
436
|
+
9. **BPM can be project-synced or fixed** - the `useFixedBPM` config option determines whether to use the passed `bpm` value or always use 120 BPM.
|
|
437
|
+
|
|
438
|
+
10. **ANSI terminal codes** work on most modern terminals (macOS Terminal, iTerm2, VSCode terminal) but may need fallbacks for Windows CMD.
|
|
439
|
+
|
|
440
|
+
---
|
|
441
|
+
|
|
442
|
+
## 10. Reference Documents
|
|
443
|
+
|
|
444
|
+
- `/Users/brent/code/field-rec/DIGITAKT_II_LFO_SPEC.md` - Complete LFO specification
|
|
445
|
+
- `/Users/brent/code/field-rec/DIGITAKT_II_LFO_PRESETS.md` - 5 preset examples with timing calculations
|
|
446
|
+
- `/Users/brent/code/field-rec/EXPO_LFO_VISUALIZER_PLAN.md` - React Native Skia visualizer component plan
|