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
|
@@ -0,0 +1,550 @@
|
|
|
1
|
+
# Elektron Digitakt II LFO - Comprehensive Test Plan
|
|
2
|
+
|
|
3
|
+
## Test Overview
|
|
4
|
+
|
|
5
|
+
This document contains 18 specific test cases for the Digitakt II LFO system, focusing on edge cases and tricky behavioral scenarios. Each test includes the question, expected behavior based on the specification, and detailed test case procedures.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Part 1: Test Questions with Expected Answers
|
|
10
|
+
|
|
11
|
+
| # | Question | Expected Answer | Difficulty |
|
|
12
|
+
|---|----------|-----------------|------------|
|
|
13
|
+
| 1 | What happens when SPD is -64 and the LFO plays backward? Does it advance the same number of phase steps as SPD=+64 forward? | Yes. Both SPD=64 and SPD=-64 produce the same cycle duration when multiplied by MULT. The difference is direction: positive plays forward (0→1→0), negative plays backward (0→-1→0). | High |
|
|
14
|
+
| 2 | In ONE mode with SPH=64 (middle of cycle) and SPD=-32, does the LFO stop after 1 full backward cycle from phase 0.5? | Yes. ONE mode requires exactly one complete cycle from start phase regardless of direction. With backward motion, it still travels the full cycle length before stopping. | High |
|
|
15
|
+
| 3 | If DEPTH=0, does the LFO still consume CPU/generate output internally even though modulation = 0? | Yes (likely). The LFO state machine continues running internally; only the final output is scaled to zero. This affects fade envelope timing and internal phase position. | Medium |
|
|
16
|
+
| 4 | When DEPTH is negative (e.g., -32), is the modulation simply inverted, or does it also invert the waveform itself? | Modulation is scaled by (depth/64). For negative depth, output = raw_lfo_value * (-32/64) = -0.5 * raw_value. The waveform shape stays the same; only polarity inverts. | Medium |
|
|
17
|
+
| 5 | In MODE=HLD (Hold), if a trigger arrives while FADE is non-zero, does the fade continue to progress in the background, or does it reset? | The fade should reset on trigger (based on handleTrigger code). HLD samples the current output at trigger time, but the underlying LFO continues running. Fade progress resets to 0. | High |
|
|
18
|
+
| 6 | For the RND (Random) waveform, if MULT is set very high (2048) creating audio-rate modulation, does the random step timing scale proportionally, or stay fixed at ~16 steps per cycle? | Timing scales proportionally. RND generates ~16 steps per cycle regardless of MULT. At MULT=2048, you get 16 random changes per audio-rate cycle, which translates to very rapid random modulation. | High |
|
|
19
|
+
| 7 | When MODE=HLF with SPH=0 and the LFO reaches phase 0.5, does it stop exactly at 0.5 or does it overshoot slightly due to discrete update intervals? | It should stop at exactly 0.5 (phase wrapping handles boundary cases). However, if update intervals are large, slight overshoot is possible. Hardware likely samples at fixed intervals. | Medium |
|
|
20
|
+
| 8 | If FADE=-64 (maximum fade IN) in ONE mode, does the modulation reach full depth before ONE completes its cycle, or is the fade slower than the cycle? | Fade rate is calculated as |FADE|/64 applied to cycle timing. At FADE=-64, it fades at maximum speed, likely completing fade-in before or during the ONE cycle. | Medium |
|
|
21
|
+
| 9 | Can LFO3 modulate LFO2's MULT, LFO2 modulate LFO1's MULT, and all three cross-modulate simultaneously without feedback loops breaking the system? | Yes, the hierarchy allows this: LFO3 can modulate LFO1 and LFO2; LFO2 can modulate LFO1. No circular dependencies exist. However, rapid changes could cause audio artifacts. | High |
|
|
22
|
+
| 10 | When SPD=1 and MULT=1 (slowest possible, ~4 minutes per cycle at 120 BPM), does the LFO continue updating smoothly, or does it become quantized/stepped due to low phase increment? | Updates are continuous (not quantized to phase steps). Phase increments by tiny amounts each update. Visual quantization might appear due to display resolution. | Low |
|
|
23
|
+
| 11 | If MODE=TRG with a very fast trigger rate and FADE=-30 (fade IN), can you trigger faster than the fade completes, stacking multiple fading envelopes? | Yes. Each trigger resets fade progress to 0. Multiple rapid triggers create overlapping fade envelopes. Without proper modulation destination mixing, only the most recent envelope is audible. | Medium |
|
|
24
|
+
| 12 | In FADE, is the fade duration absolute (e.g., "always 500ms to complete") or relative to the LFO cycle time? | Relative to cycle time. Fade rate = |FADE|/64 applied per update. Longer cycles = slower fade in absolute time; shorter cycles = faster fade. | High |
|
|
25
|
+
| 13 | When waveform is changed from TRI to RND while the LFO is mid-cycle, does it continue from the current phase position, or does it reset? | Should continue from current phase position. Changing waveform only affects the output calculation function, not the phase tracker. Output may jump abruptly. | Medium |
|
|
26
|
+
| 14 | For unipolar waveforms (EXP, RMP), does DEPTH=+32 produce a range of 0 to +0.5, or does it scale from the unipolar -1.0 to +1.0 range like bipolar waveforms? | Unipolar output (0 to +1) is scaled by depth/64. DEPTH=+32 produces 0 to +0.5. Unipolar waveforms never go negative regardless of depth sign (before scaling). | Medium |
|
|
27
|
+
| 15 | What happens when FADE switches from -30 (fade IN) to +30 (fade OUT) mid-cycle? Does the modulation fade out from its current level, or does it immediately jump back to full depth before fading out? | Should fade out from current level. Fade multiplier is already at some value; switching fade sign changes direction without resetting progress. Result: smoother envelope transition. | High |
|
|
28
|
+
| 16 | If MODE=HLF starting at SPH=100 going forward, is the "halfway point" at phase 0.5 from the start (0.5 + 0.78 = 1.28 wrapped = 0.28), or is it always globally at phase 0.5? | Halfway is relative to start phase: `(start_phase + 0.5) % 1.0`. From SPH=100 (0.78), halfway = 0.28. LFO stops at phase 0.28, not 0.5. | High |
|
|
29
|
+
| 17 | For MODE=ONE with SPD=-48 (backward), starting from SPH=0, does the LFO travel backward to reach the start phase again, or does it travel forward (interpreting "one cycle" as always going positive)? | Travels backward. ONE mode follows the direction set by SPD sign. Negative speed = backward travel. Still requires exactly one complete 360° cycle to complete. | High |
|
|
30
|
+
| 18 | When MODE=FRE and the LFO has been running continuously for hours, does phase wrapping at 1.0 ever cause accumulated floating-point rounding errors that gradually shift the LFO frequency? | Theoretically possible in 32-bit implementations. 64-bit double-precision should be stable indefinitely. Hardware likely uses fixed-point or integer phase math to avoid this. | Low |
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## Part 2: Detailed Test Case Descriptions
|
|
35
|
+
|
|
36
|
+
### TEST 1: Negative Speed Symmetry
|
|
37
|
+
**Question:** Does SPD=-64 produce the same cycle duration as SPD=+64?
|
|
38
|
+
|
|
39
|
+
**Setup:**
|
|
40
|
+
- Create two identical LFO configurations
|
|
41
|
+
- Config A: WAVE=SIN, SPD=+64, MULT=16, MODE=FRE, all others default
|
|
42
|
+
- Config B: WAVE=SIN, SPD=-64, MULT=16, MODE=FRE, all others default
|
|
43
|
+
|
|
44
|
+
**Test Procedure:**
|
|
45
|
+
1. Start both LFOs simultaneously
|
|
46
|
+
2. Measure time for Config A to complete 1 full cycle
|
|
47
|
+
3. Measure time for Config B to complete 1 full cycle (backward)
|
|
48
|
+
4. Record phase direction for each (Config A: 0→1→0, Config B: 0→-1→0)
|
|
49
|
+
|
|
50
|
+
**Expected Result:**
|
|
51
|
+
- Both complete in identical time (128 phase steps / (64 × 16) = 0.125 bars)
|
|
52
|
+
- Config A phase increases, Config B phase decreases
|
|
53
|
+
- Both produce same frequency of modulation
|
|
54
|
+
|
|
55
|
+
**Spec Reference:** Line 96-102 (SPD range and negative behavior)
|
|
56
|
+
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
### TEST 2: ONE Mode with Non-Zero Start Phase (Backward)
|
|
60
|
+
**Question:** Does ONE mode with negative speed respect the start phase boundary?
|
|
61
|
+
|
|
62
|
+
**Setup:**
|
|
63
|
+
- WAVE=SIN, SPD=-32, MULT=8, MODE=ONE, SPH=64, all others default
|
|
64
|
+
|
|
65
|
+
**Test Procedure:**
|
|
66
|
+
1. Reset LFO to initial state
|
|
67
|
+
2. Trigger note (starts phase at 0.5)
|
|
68
|
+
3. Record phase position every 100ms until LFO stops
|
|
69
|
+
4. Verify stopping condition: phase returns to exactly 0.5
|
|
70
|
+
|
|
71
|
+
**Expected Result:**
|
|
72
|
+
- LFO travels backward from 0.5 through 0.25, 0.0, wraps to 1.0, 0.75, 0.5
|
|
73
|
+
- Completes exactly 360° of backward travel
|
|
74
|
+
- Stops at phase 0.5 (same as start phase)
|
|
75
|
+
- isRunning flag becomes false
|
|
76
|
+
|
|
77
|
+
**Spec Reference:** Lines 443-461 (ONE mode stop detection for both directions)
|
|
78
|
+
|
|
79
|
+
---
|
|
80
|
+
|
|
81
|
+
### TEST 3: Depth = 0 Behavior
|
|
82
|
+
**Question:** Does a LFO with DEPTH=0 still update internal state?
|
|
83
|
+
|
|
84
|
+
**Setup:**
|
|
85
|
+
- WAVE=RND, SPD=32, MULT=4, MODE=TRG, DEPTH=0, FADE=-30
|
|
86
|
+
|
|
87
|
+
**Test Procedure:**
|
|
88
|
+
1. Configure LFO as above
|
|
89
|
+
2. Trigger at t=0ms
|
|
90
|
+
3. Immediately change DEPTH from 0 to +32
|
|
91
|
+
4. Observe output
|
|
92
|
+
|
|
93
|
+
**Expected Result:**
|
|
94
|
+
- With DEPTH=0, output=0 before step 3 (modulation calculation scales by 0/64)
|
|
95
|
+
- Internal LFO is still updating: phase advancing, fade progressing
|
|
96
|
+
- After changing DEPTH, modulation appears immediately (not delayed by previous fade)
|
|
97
|
+
- This proves internal state continued updating despite DEPTH=0
|
|
98
|
+
|
|
99
|
+
**Spec Reference:** Lines 189-194 (DEP calculation)
|
|
100
|
+
|
|
101
|
+
---
|
|
102
|
+
|
|
103
|
+
### TEST 4: Negative Depth with Unipolar Waveform
|
|
104
|
+
**Question:** Does negative depth on EXP waveform invert the unipolar (0 to +1) shape?
|
|
105
|
+
|
|
106
|
+
**Setup:**
|
|
107
|
+
- Config A: WAVE=EXP, SPD=16, MULT=8, DEPTH=+32, MODE=FRE
|
|
108
|
+
- Config B: WAVE=EXP, SPD=16, MULT=8, DEPTH=-32, MODE=FRE
|
|
109
|
+
- Sample output at phase 0.0, 0.5, 1.0
|
|
110
|
+
|
|
111
|
+
**Test Procedure:**
|
|
112
|
+
1. Generate Config A output values at phases: 0.0, 0.25, 0.5, 0.75, 1.0
|
|
113
|
+
- EXP at these phases: ~0.0, ~0.18, ~0.47, ~0.81, ~1.0
|
|
114
|
+
- With DEPTH=+32: multiply each by 32/64 = 0.5
|
|
115
|
+
- Expected: 0.0, 0.09, 0.235, 0.405, 0.5
|
|
116
|
+
2. Generate Config B same phases
|
|
117
|
+
- With DEPTH=-32: multiply each by -32/64 = -0.5
|
|
118
|
+
- Expected: 0.0, -0.09, -0.235, -0.405, -0.5
|
|
119
|
+
3. Compare values
|
|
120
|
+
|
|
121
|
+
**Expected Result:**
|
|
122
|
+
- Negative depth inverts the output without changing waveform shape
|
|
123
|
+
- EXP waveform still accelerates upward; values are simply negative
|
|
124
|
+
- Result: modulation destination moves in opposite direction but with same curve character
|
|
125
|
+
|
|
126
|
+
**Spec Reference:** Lines 180-187 (DEP behavior)
|
|
127
|
+
|
|
128
|
+
---
|
|
129
|
+
|
|
130
|
+
### TEST 5: HLD Mode with Fade Reset on Trigger
|
|
131
|
+
**Question:** Does FADE progress reset when a trigger occurs in HLD mode?
|
|
132
|
+
|
|
133
|
+
**Setup:**
|
|
134
|
+
- WAVE=TRI, SPD=8, MULT=4, MODE=HLD, DEPTH=+48, FADE=-16
|
|
135
|
+
- Trigger spacing: 250ms apart
|
|
136
|
+
|
|
137
|
+
**Test Procedure:**
|
|
138
|
+
1. Trigger at t=0ms
|
|
139
|
+
- Capture output at t=0, 50, 100, 150, 200ms
|
|
140
|
+
- Note fade progression (should fade in from 0 to some value)
|
|
141
|
+
2. Trigger at t=250ms
|
|
142
|
+
- Immediately capture output (should sample current LFO value)
|
|
143
|
+
- Continue capturing at t=250, 300, 350, 400ms
|
|
144
|
+
- Note if fade resets to 0 or continues from previous value
|
|
145
|
+
|
|
146
|
+
**Expected Result:**
|
|
147
|
+
- First segment: fade progresses from 0 upward (fade IN starts at 0)
|
|
148
|
+
- Trigger at 250ms: fadeProgress resets to 0
|
|
149
|
+
- Second segment: fade starts ramping up again from 0
|
|
150
|
+
- HLD captures the held value, but fade envelope restarts
|
|
151
|
+
|
|
152
|
+
**Spec Reference:** Lines 545-561 (HLD trigger handler resets fadeProgress)
|
|
153
|
+
|
|
154
|
+
---
|
|
155
|
+
|
|
156
|
+
### TEST 6: RND Waveform at Audio-Rate Speed
|
|
157
|
+
**Question:** Does RND waveform generate 16 steps per cycle regardless of MULT?
|
|
158
|
+
|
|
159
|
+
**Setup:**
|
|
160
|
+
- WAVE=RND, SPD=32, MULT=512, MODE=FRE, DEPTH=+63
|
|
161
|
+
- MULT=512 creates: 32 × 512 = 16384 phase steps per bar = 128 cycles per bar
|
|
162
|
+
|
|
163
|
+
**Test Procedure:**
|
|
164
|
+
1. Generate RND waveform output at 16384 sample positions across one bar
|
|
165
|
+
2. Count number of distinct output values (steps) generated
|
|
166
|
+
3. Calculate average "hold time" per step
|
|
167
|
+
|
|
168
|
+
**Expected Result:**
|
|
169
|
+
- Approximately 128 complete "16-step" cycles in one bar
|
|
170
|
+
- Each cycle contains ~16 distinct random values
|
|
171
|
+
- Average 128 phase steps per random value (16384 / 128 ≈ 128)
|
|
172
|
+
- RND does NOT accelerate to 128 steps per cycle; stays at 16 steps per cycle
|
|
173
|
+
|
|
174
|
+
**Spec Reference:** Lines 38, 361-373 (RND generates new value every 1/16th of phase, ~16 steps per cycle)
|
|
175
|
+
|
|
176
|
+
---
|
|
177
|
+
|
|
178
|
+
### TEST 7: HLF Mode Boundary Precision
|
|
179
|
+
**Question:** Does the LFO stop exactly at the halfway point or does it overshoot?
|
|
180
|
+
|
|
181
|
+
**Setup:**
|
|
182
|
+
- WAVE=SIN, SPD=16, MULT=4, MODE=HLF, SPH=0
|
|
183
|
+
- Polling interval: 1ms (precise sampling)
|
|
184
|
+
|
|
185
|
+
**Test Procedure:**
|
|
186
|
+
1. Trigger at t=0 (phase = 0.0)
|
|
187
|
+
2. Sample phase position every 1ms until isRunning becomes false
|
|
188
|
+
3. Record final phase position with 4 decimal places
|
|
189
|
+
4. Calculate "overshoot" if any: |final_phase - 0.5|
|
|
190
|
+
|
|
191
|
+
**Expected Result:**
|
|
192
|
+
- Final phase should be 0.5000 (±0.001 tolerance)
|
|
193
|
+
- Overshoot should be < 0.01 phase units
|
|
194
|
+
- isRunning becomes false in the same update where phase reaches 0.5
|
|
195
|
+
|
|
196
|
+
**Spec Reference:** Lines 464-487 (HLF mode stop detection with boundary crossing)
|
|
197
|
+
|
|
198
|
+
---
|
|
199
|
+
|
|
200
|
+
### TEST 8: Maximum FADE IN with ONE Mode Completion
|
|
201
|
+
**Question:** Does FADE=-64 complete fade-in before ONE mode stops?
|
|
202
|
+
|
|
203
|
+
**Setup:**
|
|
204
|
+
- WAVE=TRI, SPD=8, MULT=16, MODE=ONE, SPH=0, FADE=-64, DEPTH=+48
|
|
205
|
+
- Measure fade multiplier and phase position continuously
|
|
206
|
+
|
|
207
|
+
**Test Procedure:**
|
|
208
|
+
1. Trigger at t=0
|
|
209
|
+
2. Sample (phase, fadeMultiplier, output) every 10ms
|
|
210
|
+
3. Record when isRunning becomes false
|
|
211
|
+
4. Check fadeMultiplier value at that moment
|
|
212
|
+
|
|
213
|
+
**Expected Result:**
|
|
214
|
+
- fadeMultiplier should reach 1.0 (or very close) before ONE mode completes
|
|
215
|
+
- If FADE=-64 (maximum fade rate), fade should be nearly complete by cycle end
|
|
216
|
+
- Fade rate = 64/64 = 1.0 (fastest possible)
|
|
217
|
+
- ONE cycle duration at these settings = 1 bar = ~2000ms at 120 BPM
|
|
218
|
+
- Fade-in should complete in << 2000ms
|
|
219
|
+
|
|
220
|
+
**Spec Reference:** Lines 496-511 (Fade envelope timing)
|
|
221
|
+
|
|
222
|
+
---
|
|
223
|
+
|
|
224
|
+
### TEST 9: Three-LFO Cross-Modulation Chain
|
|
225
|
+
**Question:** Can LFO3→LFO2→LFO1 modulation work simultaneously without instability?
|
|
226
|
+
|
|
227
|
+
**Setup:**
|
|
228
|
+
- LFO1: WAVE=SIN, SPD=16, MULT=4, MODE=FRE
|
|
229
|
+
- LFO1.SPD is modulation destination for LFO2
|
|
230
|
+
- LFO2: WAVE=TRI, SPD=8, MULT=8, MODE=FRE
|
|
231
|
+
- LFO2.MULT is modulation destination for LFO3
|
|
232
|
+
- LFO3: WAVE=RMP, SPD=32, MULT=2, MODE=FRE
|
|
233
|
+
|
|
234
|
+
**Test Procedure:**
|
|
235
|
+
1. Run all three LFOs simultaneously for 10 seconds
|
|
236
|
+
2. Monitor LFO1.SPD: should oscillate due to LFO2 modulation
|
|
237
|
+
3. Monitor LFO2.MULT: should appear to change due to LFO3 modulation
|
|
238
|
+
4. Observe output waveforms for discontinuities or artifacts
|
|
239
|
+
|
|
240
|
+
**Expected Result:**
|
|
241
|
+
- LFO3 modulates LFO2.MULT smoothly
|
|
242
|
+
- LFO2 (with modulated MULT) modulates LFO1.SPD
|
|
243
|
+
- LFO1 (with modulated SPD) produces output with dynamic modulation rate
|
|
244
|
+
- No feedback loops (since hierarchy only goes LFO3→LFO2→LFO1)
|
|
245
|
+
- Outputs remain continuous without jumps or discontinuities
|
|
246
|
+
|
|
247
|
+
**Spec Reference:** Lines 10-14 (LFO hierarchy), Lines 276-277 (modulation destinations)
|
|
248
|
+
|
|
249
|
+
---
|
|
250
|
+
|
|
251
|
+
### TEST 10: Slowest LFO Smooth Continuity
|
|
252
|
+
**Question:** At SPD=1, MULT=1 (~4-minute cycle), is phase smooth or stepped?
|
|
253
|
+
|
|
254
|
+
**Setup:**
|
|
255
|
+
- WAVE=SIN, SPD=1, MULT=1, MODE=FRE, DEPTH=+32
|
|
256
|
+
- Polling at 60 FPS (16.67ms intervals)
|
|
257
|
+
|
|
258
|
+
**Test Procedure:**
|
|
259
|
+
1. Run LFO for at least 30 seconds
|
|
260
|
+
2. Capture phase position and output value every frame
|
|
261
|
+
3. Calculate phase delta between consecutive frames
|
|
262
|
+
4. Verify phase delta is not quantized to discrete steps
|
|
263
|
+
|
|
264
|
+
**Expected Result:**
|
|
265
|
+
- Phase delta per frame: ~16.67ms ÷ 256000ms (cycle time) ≈ 0.000065 phase units
|
|
266
|
+
- Phase values should be smoothly increasing, not jumping to discrete steps
|
|
267
|
+
- Output may appear stepped on screen due to display resolution, but internal phase is continuous
|
|
268
|
+
|
|
269
|
+
**Spec Reference:** Lines 418-441 (phase update calculations use floating-point, not quantized steps)
|
|
270
|
+
|
|
271
|
+
---
|
|
272
|
+
|
|
273
|
+
### TEST 11: Overlapping Fade Envelopes with Rapid Triggers
|
|
274
|
+
**Question:** Can you trigger faster than a fade completes?
|
|
275
|
+
|
|
276
|
+
**Setup:**
|
|
277
|
+
- WAVE=EXP, SPD=32, MULT=4, MODE=TRG, DEPTH=+48, FADE=-30
|
|
278
|
+
- Trigger rhythm: every 100ms (faster than expected fade completion)
|
|
279
|
+
|
|
280
|
+
**Test Procedure:**
|
|
281
|
+
1. Trigger at t=0, 100, 200, 300ms
|
|
282
|
+
2. Capture output at:
|
|
283
|
+
- t=0: just after trigger 1
|
|
284
|
+
- t=50: before trigger 2 (fade mid-progress)
|
|
285
|
+
- t=100: trigger 2 happens
|
|
286
|
+
- t=150: fade resets and progresses again
|
|
287
|
+
- t=200: trigger 3
|
|
288
|
+
- Continue pattern
|
|
289
|
+
3. Verify each trigger resets fade progress
|
|
290
|
+
|
|
291
|
+
**Expected Result:**
|
|
292
|
+
- Each trigger resets fadeProgress to 0
|
|
293
|
+
- Output rises from 0 toward full depth, then resets on next trigger
|
|
294
|
+
- Creates a "staggered" ramp pattern, not smooth crescendo
|
|
295
|
+
- Each trigger is independent; no accumulated envelope
|
|
296
|
+
|
|
297
|
+
**Spec Reference:** Lines 545-572 (handleTrigger resets fadeProgress for TRG and ONE modes)
|
|
298
|
+
|
|
299
|
+
---
|
|
300
|
+
|
|
301
|
+
### TEST 12: Fade Timing Relative to Cycle, Not Absolute
|
|
302
|
+
**Question:** Is fade duration relative to LFO cycle, or fixed in milliseconds?
|
|
303
|
+
|
|
304
|
+
**Setup:**
|
|
305
|
+
- Config A: WAVE=TRI, SPD=16, MULT=8, FADE=-32, DEPTH=+48 (1-bar cycle)
|
|
306
|
+
- Config B: WAVE=TRI, SPD=32, MULT=16, FADE=-32, DEPTH=+48 (1/2-bar cycle)
|
|
307
|
+
- Both at 120 BPM
|
|
308
|
+
|
|
309
|
+
**Test Procedure:**
|
|
310
|
+
1. Trigger Config A at t=0
|
|
311
|
+
2. Measure time until fadeMultiplier reaches 0.95
|
|
312
|
+
- Expected: ~50% of cycle time (fade rate = 32/64)
|
|
313
|
+
- Config A cycle: 1 bar = 2000ms
|
|
314
|
+
- Expected fade-to-0.95: ~1000ms
|
|
315
|
+
3. Repeat for Config B
|
|
316
|
+
- Cycle: 1/2 bar = 1000ms
|
|
317
|
+
- Expected fade-to-0.95: ~500ms
|
|
318
|
+
4. Compare fade completion times
|
|
319
|
+
|
|
320
|
+
**Expected Result:**
|
|
321
|
+
- Fade rate is 32/64 = 0.5 (half-speed)
|
|
322
|
+
- Each LFO reaches 95% fade in ~50% of its own cycle time
|
|
323
|
+
- Config A takes ~1000ms, Config B takes ~500ms
|
|
324
|
+
- Proves fade timing is RELATIVE to cycle, not absolute
|
|
325
|
+
|
|
326
|
+
**Spec Reference:** Lines 496-511 (fadeRate calculation uses cyclesPerMs, making fade relative to cycle time)
|
|
327
|
+
|
|
328
|
+
---
|
|
329
|
+
|
|
330
|
+
### TEST 13: Waveform Switch Mid-Cycle
|
|
331
|
+
**Question:** Changing waveform mid-cycle: continuous phase or reset?
|
|
332
|
+
|
|
333
|
+
**Setup:**
|
|
334
|
+
- WAVE=TRI, SPD=16, MULT=8, MODE=FRE
|
|
335
|
+
- Run for 500ms, then change WAVE to RND
|
|
336
|
+
- Monitor phase continuity
|
|
337
|
+
|
|
338
|
+
**Test Procedure:**
|
|
339
|
+
1. Start LFO at t=0 with WAVE=TRI
|
|
340
|
+
2. Capture phase and output every 50ms
|
|
341
|
+
3. At t=500ms, change WAVE to RND
|
|
342
|
+
4. Continue capturing every 50ms for 500ms more
|
|
343
|
+
5. Observe discontinuities in output values
|
|
344
|
+
|
|
345
|
+
**Expected Result:**
|
|
346
|
+
- Phase position continues smoothly across waveform change
|
|
347
|
+
- At t=500ms, if phase=0.2, output changes from TRI(0.2) to RND(0.2)
|
|
348
|
+
- RND may produce wildly different output value due to random sampling
|
|
349
|
+
- But phase itself doesn't reset; it continues from 0.2
|
|
350
|
+
- Output can jump abruptly, but no "pop" or discontinuity in phase
|
|
351
|
+
|
|
352
|
+
**Spec Reference:** Lines 325-373 (Waveform functions are stateless; phase is the state)
|
|
353
|
+
|
|
354
|
+
---
|
|
355
|
+
|
|
356
|
+
### TEST 14: Unipolar Waveform Depth Scaling
|
|
357
|
+
**Question:** Does unipolar EXP with DEPTH=+32 produce 0 to 0.5 or something else?
|
|
358
|
+
|
|
359
|
+
**Setup:**
|
|
360
|
+
- Config A: WAVE=EXP, SPD=16, MULT=8, DEPTH=+32, MODE=FRE
|
|
361
|
+
- Config B: WAVE=EXP, SPD=16, MULT=8, DEPTH=+64, MODE=FRE
|
|
362
|
+
- Sample output at phase 0.0, 0.5, 1.0
|
|
363
|
+
|
|
364
|
+
**Test Procedure:**
|
|
365
|
+
1. Generate EXP waveform raw values:
|
|
366
|
+
- Phase 0.0: ~0.0
|
|
367
|
+
- Phase 0.5: ~0.47 (middle of exponential)
|
|
368
|
+
- Phase 1.0: ~1.0
|
|
369
|
+
2. For Config A, multiply by 32/64 = 0.5:
|
|
370
|
+
- Expected: 0.0, 0.235, 0.5
|
|
371
|
+
3. For Config B, multiply by 64/64 = 1.0:
|
|
372
|
+
- Expected: 0.0, 0.47, 1.0
|
|
373
|
+
|
|
374
|
+
**Expected Result:**
|
|
375
|
+
- EXP (unipolar 0 to 1) × (32/64) = 0 to 0.5
|
|
376
|
+
- EXP (unipolar 0 to 1) × (64/64) = 0 to 1.0
|
|
377
|
+
- Unipolar waveforms ALWAYS 0 to +1 before depth scaling
|
|
378
|
+
- Negative depth only applies to bipolar waveforms in a meaningful way (negating bipolar range)
|
|
379
|
+
- Negative depth on unipolar: scales downward from 0, producing 0 to -0.5 etc.
|
|
380
|
+
|
|
381
|
+
**Spec Reference:** Lines 36-37, 189-194 (Waveform polarity, DEP calculation)
|
|
382
|
+
|
|
383
|
+
---
|
|
384
|
+
|
|
385
|
+
### TEST 15: Fade Direction Switch Mid-Envelope
|
|
386
|
+
**Question:** Switching FADE from -30 to +30 mid-cycle: fade out from current, or jump?
|
|
387
|
+
|
|
388
|
+
**Setup:**
|
|
389
|
+
- WAVE=SIN, SPD=8, MULT=8, MODE=TRG, DEPTH=+48, FADE=-30
|
|
390
|
+
- Trigger at t=0
|
|
391
|
+
- At t=500ms, change FADE to +30
|
|
392
|
+
|
|
393
|
+
**Test Procedure:**
|
|
394
|
+
1. Trigger at t=0 (fades in from 0 toward 1.0)
|
|
395
|
+
2. Capture output at t=0, 100, 200, 300, 400, 500ms
|
|
396
|
+
3. At t=500ms, switch FADE from -30 to +30
|
|
397
|
+
4. Continue capturing at t=500, 600, 700, 800, 900ms
|
|
398
|
+
5. Note fadeMultiplier values
|
|
399
|
+
|
|
400
|
+
**Expected Result:**
|
|
401
|
+
- t=0-500ms: fadeMultiplier increases from 0 toward 1.0 (fade IN)
|
|
402
|
+
- At t=500ms, fadeMultiplier ≈ 0.5 (halfway faded in)
|
|
403
|
+
- t=500ms: FADE changes to +30
|
|
404
|
+
- fadeMultiplier does NOT jump; stays at 0.5
|
|
405
|
+
- Fade direction reverses (positive FADE = fade OUT)
|
|
406
|
+
- t=500-1000ms: fadeMultiplier decreases from 0.5 toward 0 (fade OUT)
|
|
407
|
+
- Result: smooth envelope transition, no jump
|
|
408
|
+
|
|
409
|
+
**Spec Reference:** Lines 504-510 (Fade direction is determined by FADE sign; fadeMultiplier continues from current value)
|
|
410
|
+
|
|
411
|
+
---
|
|
412
|
+
|
|
413
|
+
### TEST 16: HLF Mode with Non-Zero Start Phase
|
|
414
|
+
**Question:** Is halfway relative to start phase, or always at global 0.5?
|
|
415
|
+
|
|
416
|
+
**Setup:**
|
|
417
|
+
- WAVE=SIN, SPD=16, MULT=8, MODE=HLF, SPH=100 (≈0.78 normalized), DEPTH=+48
|
|
418
|
+
|
|
419
|
+
**Test Procedure:**
|
|
420
|
+
1. Trigger at t=0 (phase starts at 0.78)
|
|
421
|
+
2. Calculate expected stop point: (0.78 + 0.5) % 1.0 = 0.28
|
|
422
|
+
3. Run LFO until isRunning becomes false
|
|
423
|
+
4. Record final phase position
|
|
424
|
+
|
|
425
|
+
**Expected Result:**
|
|
426
|
+
- LFO travels forward from 0.78 → 0.9 → 1.0 (wraps) → 0.0 → 0.15 → 0.28
|
|
427
|
+
- Stops at phase 0.28 (not at global 0.5)
|
|
428
|
+
- Confirms halfway calculation is `(start_phase + 0.5) % 1.0`
|
|
429
|
+
|
|
430
|
+
**Spec Reference:** Lines 464-487 (HLF mode calculation: `halfwayPhase = (startPhase + 0.5) % 1.0`)
|
|
431
|
+
|
|
432
|
+
---
|
|
433
|
+
|
|
434
|
+
### TEST 17: ONE Mode Backward with Zero Start Phase
|
|
435
|
+
**Question:** Does backward ONE mode travel backward through a full cycle?
|
|
436
|
+
|
|
437
|
+
**Setup:**
|
|
438
|
+
- WAVE=TRI, SPD=-48, MULT=4, MODE=ONE, SPH=0, DEPTH=+48
|
|
439
|
+
|
|
440
|
+
**Test Procedure:**
|
|
441
|
+
1. Trigger at t=0 (phase = 0.0)
|
|
442
|
+
2. Capture phase every 50ms until isRunning becomes false
|
|
443
|
+
3. Verify direction (should be negative/decreasing)
|
|
444
|
+
|
|
445
|
+
**Expected Result:**
|
|
446
|
+
- Phase travels backward: 0.0 → -0.1 (wraps to 0.9) → 0.8 → ... → 0.1 → 0.0
|
|
447
|
+
- Complete 360° backward rotation
|
|
448
|
+
- Stops at phase 0.0 (start phase)
|
|
449
|
+
- isRunning becomes false after full cycle
|
|
450
|
+
- Output is inverted TRI shape due to backward motion
|
|
451
|
+
|
|
452
|
+
**Spec Reference:** Lines 443-461 (ONE mode detects backward completion: `newPhase <= startPhase && prevPhase > startPhase`)
|
|
453
|
+
|
|
454
|
+
---
|
|
455
|
+
|
|
456
|
+
### TEST 18: Long-Running FRE Mode Floating-Point Stability
|
|
457
|
+
**Question:** Does a continuously running FRE LFO drift frequency over hours due to rounding errors?
|
|
458
|
+
|
|
459
|
+
**Setup:**
|
|
460
|
+
- WAVE=SIN, SPD=16, MULT=8, MODE=FRE, DEPTH=+32
|
|
461
|
+
- Run for simulated 24 hours at 1000 FPS (simulated time)
|
|
462
|
+
|
|
463
|
+
**Test Procedure:**
|
|
464
|
+
1. Calculate theoretical cycle time: 1 bar = 2000ms
|
|
465
|
+
2. Measure actual cycles in:
|
|
466
|
+
- First hour (simulated)
|
|
467
|
+
- 12th hour
|
|
468
|
+
- 24th hour
|
|
469
|
+
3. Compare cycle counts to expected (should be identical)
|
|
470
|
+
|
|
471
|
+
**Expected Result:**
|
|
472
|
+
- 32-bit floating point: may accumulate small errors after many hours
|
|
473
|
+
- 64-bit double precision: should be stable indefinitely
|
|
474
|
+
- Hardware likely uses integer/fixed-point arithmetic: no drift
|
|
475
|
+
|
|
476
|
+
**Spec Reference:** Lines 429-441 (Phase wrapping uses floating-point math)
|
|
477
|
+
|
|
478
|
+
---
|
|
479
|
+
|
|
480
|
+
## Part 3: Discrepancy Analysis
|
|
481
|
+
|
|
482
|
+
### Identified Specification Ambiguities
|
|
483
|
+
|
|
484
|
+
1. **Random Waveform Step Timing at Audio Rates**
|
|
485
|
+
- Spec states RND changes ~16x more frequently than other waveforms
|
|
486
|
+
- Unclear if this is a fixed number of steps per cycle or a time-based rate
|
|
487
|
+
- Recommendation: TEST 6 will clarify
|
|
488
|
+
|
|
489
|
+
2. **HLF Mode Backward Behavior**
|
|
490
|
+
- Spec describes forward behavior clearly but backward is implied
|
|
491
|
+
- Expected behavior: halfway point reverses direction (subtract 0.5 instead of add)
|
|
492
|
+
- Recommendation: Verify with hardware
|
|
493
|
+
|
|
494
|
+
3. **Fade Timing During Stopped ONE/HLF**
|
|
495
|
+
- Spec shows code where fade continues after ONE stops
|
|
496
|
+
- Question: should fade be frozen once ONE reaches its endpoint?
|
|
497
|
+
- Current spec: fade continues (line 509 shows fadeMultiplier is maintained)
|
|
498
|
+
|
|
499
|
+
4. **HLD Mode and Modulation Destinations**
|
|
500
|
+
- HLD samples output at trigger time and holds it
|
|
501
|
+
- Spec doesn't clarify if held value updates when modulation destination changes
|
|
502
|
+
- Expected: held value is frozen; modulation to underlying LFO doesn't affect held output
|
|
503
|
+
|
|
504
|
+
5. **DEPTH Asymmetry (-64 to +63)**
|
|
505
|
+
- Spec mentions SPD, DEP, FADE all have this asymmetry
|
|
506
|
+
- No explanation of why hardware chose this design
|
|
507
|
+
- May affect certain calculations; recommend testing edge cases at -64 vs +63
|
|
508
|
+
|
|
509
|
+
### Recommendations
|
|
510
|
+
|
|
511
|
+
1. **Validate RND behavior** at MULT=2048 (TEST 6)
|
|
512
|
+
2. **Test HLF backward** with SPH != 0 (TEST 16)
|
|
513
|
+
3. **Verify FADE behavior** when ONE mode completes (TEST 8)
|
|
514
|
+
4. **Test HLD and cross-modulation** to confirm held value independence
|
|
515
|
+
5. **Test -64 edge cases** for DEP and FADE parameters
|
|
516
|
+
|
|
517
|
+
---
|
|
518
|
+
|
|
519
|
+
## Test Execution Checklist
|
|
520
|
+
|
|
521
|
+
Use this checklist to track test completion:
|
|
522
|
+
|
|
523
|
+
- [ ] TEST 1: Negative Speed Symmetry
|
|
524
|
+
- [ ] TEST 2: ONE Mode with Non-Zero Start Phase (Backward)
|
|
525
|
+
- [ ] TEST 3: Depth = 0 Behavior
|
|
526
|
+
- [ ] TEST 4: Negative Depth with Unipolar Waveform
|
|
527
|
+
- [ ] TEST 5: HLD Mode with Fade Reset on Trigger
|
|
528
|
+
- [ ] TEST 6: RND Waveform at Audio-Rate Speed
|
|
529
|
+
- [ ] TEST 7: HLF Mode Boundary Precision
|
|
530
|
+
- [ ] TEST 8: Maximum FADE IN with ONE Mode Completion
|
|
531
|
+
- [ ] TEST 9: Three-LFO Cross-Modulation Chain
|
|
532
|
+
- [ ] TEST 10: Slowest LFO Smooth Continuity
|
|
533
|
+
- [ ] TEST 11: Overlapping Fade Envelopes with Rapid Triggers
|
|
534
|
+
- [ ] TEST 12: Fade Timing Relative to Cycle
|
|
535
|
+
- [ ] TEST 13: Waveform Switch Mid-Cycle
|
|
536
|
+
- [ ] TEST 14: Unipolar Waveform Depth Scaling
|
|
537
|
+
- [ ] TEST 15: Fade Direction Switch Mid-Envelope
|
|
538
|
+
- [ ] TEST 16: HLF Mode with Non-Zero Start Phase
|
|
539
|
+
- [ ] TEST 17: ONE Mode Backward with Zero Start Phase
|
|
540
|
+
- [ ] TEST 18: Long-Running FRE Mode Floating-Point Stability
|
|
541
|
+
|
|
542
|
+
---
|
|
543
|
+
|
|
544
|
+
## Document Metadata
|
|
545
|
+
|
|
546
|
+
- **Created:** Based on DIGITAKT_II_LFO_SPEC.md and DIGITAKT_II_LFO_PRESETS.md
|
|
547
|
+
- **Test Cases:** 18 comprehensive edge-case scenarios
|
|
548
|
+
- **Coverage:** LFO parameters (WAVE, SPD, MULT, SPH, MODE, DEP, FADE)
|
|
549
|
+
- **Focus Areas:** Negative values, unipolar waveforms, cross-modulation, boundary conditions, fade behavior, mode interactions
|
|
550
|
+
- **Status:** Ready for implementation and execution
|
package/package.json
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "elektron-lfo",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Elektron LFO engine simulator implementation with CLI visualization",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"module": "src/index.ts",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"bin": {
|
|
9
|
+
"elektron-lfo": "./src/cli/index.ts"
|
|
10
|
+
},
|
|
11
|
+
"scripts": {
|
|
12
|
+
"dev": "bun run src/cli/index.ts",
|
|
13
|
+
"test": "bun test",
|
|
14
|
+
"test:watch": "bun test --watch",
|
|
15
|
+
"build": "bun build src/index.ts --outdir dist --target node",
|
|
16
|
+
"cli": "bun run src/cli/index.ts",
|
|
17
|
+
"typecheck": "tsc --noEmit"
|
|
18
|
+
},
|
|
19
|
+
"devDependencies": {
|
|
20
|
+
"@types/bun": "latest",
|
|
21
|
+
"typescript": "^5.0.0"
|
|
22
|
+
},
|
|
23
|
+
"engines": {
|
|
24
|
+
"bun": ">=1.0.0"
|
|
25
|
+
}
|
|
26
|
+
}
|