elektron-lfo 1.0.7 → 1.0.9

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
@@ -66,7 +66,7 @@ function generateSquare(phase) {
66
66
  return phase < 0.5 ? 1 : -1;
67
67
  }
68
68
  function generateSawtooth(phase) {
69
- return 1 - phase * 2;
69
+ return phase * 2 - 1;
70
70
  }
71
71
  function generateExponential(phase) {
72
72
  const k = 3;
@@ -122,7 +122,7 @@ function generateWaveform(waveform, phase, state) {
122
122
  }
123
123
  }
124
124
  function isUnipolar(waveform) {
125
- return waveform === "EXP" || waveform === "RMP";
125
+ return waveform === "RMP" || waveform === "EXP";
126
126
  }
127
127
  function getWaveformRange(waveform) {
128
128
  if (isUnipolar(waveform)) {
@@ -524,6 +524,9 @@ class LFO {
524
524
  stop() {
525
525
  this.state.isRunning = false;
526
526
  }
527
+ resetTiming() {
528
+ this.lastUpdateTime = 0;
529
+ }
527
530
  }
528
531
  export {
529
532
  updateFade,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "elektron-lfo",
3
- "version": "1.0.7",
3
+ "version": "1.0.9",
4
4
  "description": "Elektron LFO engine simulator implementation with CLI visualization",
5
5
  "main": "dist/index.js",
6
6
  "module": "src/index.ts",
package/src/engine/lfo.ts CHANGED
@@ -289,9 +289,17 @@ export class LFO {
289
289
  }
290
290
 
291
291
  /**
292
- * Stop the LFO
292
+ * Stop the LFO (pause, keeps current position)
293
293
  */
294
294
  stop(): void {
295
295
  this.state.isRunning = false;
296
296
  }
297
+
298
+ /**
299
+ * Reset timing so the next update() call treats it as the first call (deltaMs = 0).
300
+ * Use this when resuming from pause or starting transport to avoid large phase jumps.
301
+ */
302
+ resetTiming(): void {
303
+ this.lastUpdateTime = 0;
304
+ }
297
305
  }
@@ -1,9 +1,8 @@
1
1
  /**
2
2
  * Waveform generators for Elektron Digitakt II LFO
3
3
  *
4
- * Waveform types:
5
- * - Bipolar (-1 to +1): TRI, SIN, SQR, SAW, RND
6
- * - Unipolar (0 to +1): EXP, RMP
4
+ * Most waveforms are bipolar (-1 to +1).
5
+ * RMP and EXP are unipolar (0 to +1) - depth parameter handles negative modulation.
7
6
  */
8
7
 
9
8
  import type { Waveform, LFOState } from './types';
@@ -40,29 +39,27 @@ export function generateSquare(phase: number): number {
40
39
 
41
40
  /**
42
41
  * Sawtooth waveform - Bipolar
43
- * Linear fall from +1 to -1 (with positive depth)
42
+ * Linear rise from -1 to +1 (matches Digitakt II behavior)
44
43
  */
45
44
  export function generateSawtooth(phase: number): number {
46
- return 1 - phase * 2;
45
+ return phase * 2 - 1;
47
46
  }
48
47
 
49
48
  /**
50
49
  * Exponential waveform - Unipolar (0 to +1)
51
- * Decaying curve from 1 to 0 (matches Digitakt II behavior)
50
+ * Decaying curve from +1 to 0 (matches Digitakt II behavior)
52
51
  * Fast initial decay, slowing toward the end (true exponential decay shape)
53
52
  */
54
53
  export function generateExponential(phase: number): number {
55
54
  const k = 3; // Decay rate - controls steepness of decay
56
- // True exponential decay: starts at 1, decays rapidly then slows
57
- // Formula: (exp(-phase * k) - exp(-k)) / (1 - exp(-k))
58
- // This normalizes to exactly [1, 0] range
55
+ // True exponential decay normalized to [1, 0]
59
56
  const expK = Math.exp(-k);
60
57
  return (Math.exp(-phase * k) - expK) / (1 - expK);
61
58
  }
62
59
 
63
60
  /**
64
61
  * Ramp waveform - Unipolar (0 to +1)
65
- * Linear rise from 0 to +1
62
+ * Linear rise from 0 to +1 (matches Digitakt II behavior)
66
63
  */
67
64
  export function generateRamp(phase: number): number {
68
65
  return phase;
@@ -141,9 +138,10 @@ export function generateWaveform(
141
138
 
142
139
  /**
143
140
  * Check if a waveform is unipolar (0 to +1) vs bipolar (-1 to +1)
141
+ * RMP and EXP are unipolar - depth parameter handles negative modulation
144
142
  */
145
143
  export function isUnipolar(waveform: Waveform): boolean {
146
- return waveform === 'EXP' || waveform === 'RMP';
144
+ return waveform === 'RMP' || waveform === 'EXP';
147
145
  }
148
146
 
149
147
  /**
@@ -123,22 +123,22 @@ describe('Square Waveform', () => {
123
123
  });
124
124
 
125
125
  describe('Sawtooth Waveform', () => {
126
- test('starts at +1 at phase 0', () => {
127
- expect(generateSawtooth(0)).toBe(1);
126
+ test('starts at -1 at phase 0', () => {
127
+ expect(generateSawtooth(0)).toBe(-1);
128
128
  });
129
129
 
130
130
  test('is 0 at phase 0.5', () => {
131
131
  expect(generateSawtooth(0.5)).toBe(0);
132
132
  });
133
133
 
134
- test('approaches -1 at phase ~1', () => {
135
- expect(generateSawtooth(1)).toBe(-1);
136
- expect(generateSawtooth(0.999)).toBeCloseTo(-1, 2);
134
+ test('approaches +1 at phase ~1', () => {
135
+ expect(generateSawtooth(1)).toBe(1);
136
+ expect(generateSawtooth(0.999)).toBeCloseTo(1, 2);
137
137
  });
138
138
 
139
- test('is bipolar and linear (falling)', () => {
140
- expect(generateSawtooth(0.25)).toBeCloseTo(0.5, 5);
141
- expect(generateSawtooth(0.75)).toBeCloseTo(-0.5, 5);
139
+ test('is bipolar and linear (rising)', () => {
140
+ expect(generateSawtooth(0.25)).toBeCloseTo(-0.5, 5);
141
+ expect(generateSawtooth(0.75)).toBeCloseTo(0.5, 5);
142
142
  });
143
143
  });
144
144