elektron-lfo 1.0.12 → 1.0.13

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/dist/index.js CHANGED
@@ -283,15 +283,7 @@ function checkModeStop(config, state, previousPhase, currentPhase) {
283
283
  const isForward = config.speed >= 0;
284
284
  if (config.mode === "ONE") {
285
285
  if (state.cycleCount >= 1) {
286
- if (isForward) {
287
- if (startPhase === 0 || currentPhase >= startPhase) {
288
- return { shouldStop: true, cycleCompleted: true };
289
- }
290
- } else {
291
- if (startPhase === 0 || currentPhase <= startPhase) {
292
- return { shouldStop: true, cycleCompleted: true };
293
- }
294
- }
286
+ return { shouldStop: true, cycleCompleted: true };
295
287
  }
296
288
  } else if (config.mode === "HLF") {
297
289
  const halfPhase = (startPhase + 0.5) % 1;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "elektron-lfo",
3
- "version": "1.0.12",
3
+ "version": "1.0.13",
4
4
  "description": "Elektron LFO engine simulator implementation with CLI visualization",
5
5
  "main": "dist/index.js",
6
6
  "module": "src/index.ts",
@@ -112,23 +112,17 @@ export function checkModeStop(
112
112
  const isForward = config.speed >= 0;
113
113
 
114
114
  if (config.mode === 'ONE') {
115
- // ONE mode: Stop after completing one full cycle back to startPhase
116
- // For non-zero startPhase, we need to:
117
- // 1. Wait for phase to wrap (cycleCount >= 1)
118
- // 2. Then continue until we reach/pass the startPhase again
115
+ // ONE mode: Stop immediately when phase completes one wrap (cycleCount >= 1)
116
+ // Based on Digitakt II hardware testing (January 2025):
117
+ // - Phase runs from startPhase until it wraps (crosses 1.0→0.0 or 0.0→1.0)
118
+ // - Stops immediately on wrap, does NOT continue back to startPhase
119
+ // - This means non-zero startPhase values result in partial amplitude coverage:
120
+ // - Phase=0: full amplitude range (0→1→0, complete waveform)
121
+ // - Phase=32: full amplitude range (0.25→1→0, starts at peak)
122
+ // - Phase=64: half amplitude range (0.5→1→0, starts at middle)
123
+ // - Phase=96: half amplitude range (0.75→1→0, starts at trough)
119
124
  if (state.cycleCount >= 1) {
120
- if (isForward) {
121
- // Forward: stop when current phase reaches or passes startPhase after wrapping
122
- // Handle the case where startPhase is 0 (stop immediately on wrap)
123
- if (startPhase === 0 || currentPhase >= startPhase) {
124
- return { shouldStop: true, cycleCompleted: true };
125
- }
126
- } else {
127
- // Backward: stop when current phase reaches or goes below startPhase after wrapping
128
- if (startPhase === 0 || currentPhase <= startPhase) {
129
- return { shouldStop: true, cycleCompleted: true };
130
- }
131
- }
125
+ return { shouldStop: true, cycleCompleted: true };
132
126
  }
133
127
  } else if (config.mode === 'HLF') {
134
128
  // HLF mode: Stop after half cycle (0.5 phase distance from start)
@@ -241,62 +241,40 @@ describe('checkModeStop - ONE mode', () => {
241
241
  expect(result.shouldStop).toBe(true);
242
242
  });
243
243
 
244
- test('with non-zero startPhase, does NOT stop immediately after wrap - must reach startPhase', () => {
245
- // Bug fix: With startPhase 53 (~0.414), the LFO should:
246
- // 1. Start at 0.414
247
- // 2. Run to 1.0, wrap to 0.0 (cycleCount = 1)
248
- // 3. Continue from 0.0 to 0.414 (startPhase)
249
- // 4. THEN stop
244
+ test('with non-zero startPhase, stops immediately on wrap (Digitakt II verified)', () => {
245
+ // Hardware verified (January 2025): ONE mode stops immediately when phase wraps,
246
+ // regardless of startPhase. It does NOT continue back to startPhase.
247
+ // This means non-zero startPhase results in partial amplitude coverage:
248
+ // - Phase=64 (0.5): only covers half the waveform amplitude
249
+ // - Phase=96 (0.75): only covers quarter of phase distance
250
250
  const config = createConfig({ mode: 'ONE', speed: 16, startPhase: 53 });
251
251
  const startPhaseNormalized = 53 / 128; // ~0.414
252
252
 
253
- // After wrap, phase is 0.1, which is BEFORE startPhase (0.414)
254
- // Should NOT stop yet
253
+ // After wrap, cycleCount = 1, should stop IMMEDIATELY
255
254
  const stateAfterWrap = createState({
256
255
  hasTriggered: true,
257
256
  startPhaseNormalized,
258
257
  cycleCount: 1,
259
258
  });
260
- const resultBeforeStart = checkModeStop(config, stateAfterWrap, 0.05, 0.1);
261
- expect(resultBeforeStart.shouldStop).toBe(false);
262
-
263
- // Phase reaches 0.3, still before startPhase - should NOT stop
264
- const resultStillBefore = checkModeStop(config, stateAfterWrap, 0.2, 0.3);
265
- expect(resultStillBefore.shouldStop).toBe(false);
266
-
267
- // Phase crosses startPhase (0.414) - NOW should stop
268
- const resultAtStart = checkModeStop(config, stateAfterWrap, 0.4, 0.45);
269
- expect(resultAtStart.shouldStop).toBe(true);
270
- expect(resultAtStart.cycleCompleted).toBe(true);
259
+ const resultAfterWrap = checkModeStop(config, stateAfterWrap, 0.05, 0.1);
260
+ expect(resultAfterWrap.shouldStop).toBe(true);
261
+ expect(resultAfterWrap.cycleCompleted).toBe(true);
271
262
  });
272
263
 
273
- test('with non-zero startPhase (backward), continues after wrap until reaching startPhase', () => {
274
- // Backward direction with startPhase 53 (~0.414):
275
- // 1. Start at 0.414
276
- // 2. Run backward to 0.0, wrap to 1.0 (cycleCount = 1)
277
- // 3. Continue from 1.0 down to 0.414
278
- // 4. THEN stop
264
+ test('with non-zero startPhase (backward), stops immediately on wrap (Digitakt II verified)', () => {
265
+ // Hardware verified: backward direction also stops immediately on wrap
279
266
  const config = createConfig({ mode: 'ONE', speed: -16, startPhase: 53 });
280
267
  const startPhaseNormalized = 53 / 128; // ~0.414
281
268
 
282
- // After wrap going backward, phase is 0.9, which is ABOVE startPhase (0.414)
283
- // Should NOT stop yet
269
+ // After wrap going backward, cycleCount = 1, should stop IMMEDIATELY
284
270
  const stateAfterWrap = createState({
285
271
  hasTriggered: true,
286
272
  startPhaseNormalized,
287
273
  cycleCount: 1,
288
274
  });
289
275
  const resultAfterWrap = checkModeStop(config, stateAfterWrap, 0.95, 0.9);
290
- expect(resultAfterWrap.shouldStop).toBe(false);
291
-
292
- // Phase reaches 0.5, still above startPhase - should NOT stop
293
- const resultStillAbove = checkModeStop(config, stateAfterWrap, 0.6, 0.5);
294
- expect(resultStillAbove.shouldStop).toBe(false);
295
-
296
- // Phase crosses startPhase (0.414) going down - NOW should stop
297
- const resultAtStart = checkModeStop(config, stateAfterWrap, 0.45, 0.4);
298
- expect(resultAtStart.shouldStop).toBe(true);
299
- expect(resultAtStart.cycleCompleted).toBe(true);
276
+ expect(resultAfterWrap.shouldStop).toBe(true);
277
+ expect(resultAfterWrap.cycleCompleted).toBe(true);
300
278
  });
301
279
  });
302
280