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.
@@ -0,0 +1,345 @@
1
+ import { describe, test, expect } from 'bun:test';
2
+ import {
3
+ handleTrigger,
4
+ checkModeStop,
5
+ requiresTriggerToStart,
6
+ resetsPhaseOnTrigger,
7
+ resetsFadeOnTrigger,
8
+ } from '../src/engine/triggers';
9
+ import type { LFOConfig, LFOState } from '../src/engine/types';
10
+ import { DEFAULT_CONFIG } from '../src/engine/types';
11
+
12
+ // Helper to create config with defaults
13
+ function createConfig(overrides: Partial<LFOConfig>): LFOConfig {
14
+ return { ...DEFAULT_CONFIG, ...overrides };
15
+ }
16
+
17
+ // Helper to create state with defaults
18
+ function createState(overrides: Partial<LFOState> = {}): LFOState {
19
+ return {
20
+ phase: 0.5,
21
+ output: 0.5,
22
+ rawOutput: 0.5,
23
+ isRunning: true,
24
+ fadeMultiplier: 1,
25
+ fadeProgress: 0.5,
26
+ randomValue: 0.5,
27
+ previousPhase: 0.4,
28
+ heldOutput: 0,
29
+ startPhaseNormalized: 0,
30
+ cycleCount: 2,
31
+ triggerCount: 0,
32
+ hasTriggered: false,
33
+ randomStep: 8,
34
+ ...overrides,
35
+ };
36
+ }
37
+
38
+ describe('handleTrigger - FRE mode', () => {
39
+ test('does not change phase', () => {
40
+ const config = createConfig({ mode: 'FRE' });
41
+ const state = createState({ phase: 0.75 });
42
+
43
+ const newState = handleTrigger(config, state, 0.5);
44
+ expect(newState.phase).toBe(0.75);
45
+ });
46
+
47
+ test('does not reset fade', () => {
48
+ const config = createConfig({ mode: 'FRE', fade: -32 });
49
+ const state = createState({ fadeProgress: 0.8, fadeMultiplier: 0.8 });
50
+
51
+ const newState = handleTrigger(config, state, 0.5);
52
+ expect(newState.fadeProgress).toBe(0.8);
53
+ expect(newState.fadeMultiplier).toBe(0.8);
54
+ });
55
+
56
+ test('increments trigger count', () => {
57
+ const config = createConfig({ mode: 'FRE' });
58
+ const state = createState({ triggerCount: 5 });
59
+
60
+ const newState = handleTrigger(config, state, 0.5);
61
+ expect(newState.triggerCount).toBe(6);
62
+ });
63
+ });
64
+
65
+ describe('handleTrigger - TRG mode', () => {
66
+ test('resets phase to start phase', () => {
67
+ const config = createConfig({ mode: 'TRG', startPhase: 32 });
68
+ const state = createState({ phase: 0.75, startPhaseNormalized: 0.25 });
69
+
70
+ const newState = handleTrigger(config, state, 0.5);
71
+ expect(newState.phase).toBe(0.25);
72
+ });
73
+
74
+ test('resets fade for fade in', () => {
75
+ const config = createConfig({ mode: 'TRG', fade: -32 });
76
+ const state = createState({ fadeProgress: 0.8, fadeMultiplier: 0.8 });
77
+
78
+ const newState = handleTrigger(config, state, 0.5);
79
+ expect(newState.fadeProgress).toBe(0);
80
+ expect(newState.fadeMultiplier).toBe(0); // Fade in starts at 0
81
+ });
82
+
83
+ test('resets fade for fade out', () => {
84
+ const config = createConfig({ mode: 'TRG', fade: 32 });
85
+ const state = createState({ fadeProgress: 0.8, fadeMultiplier: 0.2 });
86
+
87
+ const newState = handleTrigger(config, state, 0.5);
88
+ expect(newState.fadeProgress).toBe(0);
89
+ expect(newState.fadeMultiplier).toBe(1); // Fade out starts at 1
90
+ });
91
+
92
+ test('resets cycle count', () => {
93
+ const config = createConfig({ mode: 'TRG' });
94
+ const state = createState({ cycleCount: 10 });
95
+
96
+ const newState = handleTrigger(config, state, 0.5);
97
+ expect(newState.cycleCount).toBe(0);
98
+ });
99
+
100
+ test('generates new random value for RND waveform', () => {
101
+ const config = createConfig({ mode: 'TRG', waveform: 'RND' });
102
+ const state = createState({ randomValue: 0.5 });
103
+
104
+ const newState = handleTrigger(config, state, 0.5);
105
+ // Can't guarantee different value, but randomStep should be reset
106
+ expect(newState.randomStep).toBeDefined();
107
+ });
108
+ });
109
+
110
+ describe('handleTrigger - HLD mode', () => {
111
+ test('holds current output value', () => {
112
+ const config = createConfig({ mode: 'HLD' });
113
+ const state = createState();
114
+
115
+ const newState = handleTrigger(config, state, 0.75);
116
+ expect(newState.heldOutput).toBe(0.75);
117
+ });
118
+
119
+ test('does not reset phase', () => {
120
+ const config = createConfig({ mode: 'HLD' });
121
+ const state = createState({ phase: 0.6 });
122
+
123
+ const newState = handleTrigger(config, state, 0.5);
124
+ expect(newState.phase).toBe(0.6);
125
+ });
126
+
127
+ test('resets fade', () => {
128
+ const config = createConfig({ mode: 'HLD', fade: -32 });
129
+ const state = createState({ fadeProgress: 0.8, fadeMultiplier: 0.8 });
130
+
131
+ const newState = handleTrigger(config, state, 0.5);
132
+ expect(newState.fadeProgress).toBe(0);
133
+ expect(newState.fadeMultiplier).toBe(0);
134
+ });
135
+ });
136
+
137
+ describe('handleTrigger - ONE mode', () => {
138
+ test('resets phase to start phase', () => {
139
+ const config = createConfig({ mode: 'ONE', startPhase: 64 });
140
+ const state = createState({ phase: 0.75, startPhaseNormalized: 0.5 });
141
+
142
+ const newState = handleTrigger(config, state, 0.5);
143
+ expect(newState.phase).toBe(0.5);
144
+ });
145
+
146
+ test('starts running', () => {
147
+ const config = createConfig({ mode: 'ONE' });
148
+ const state = createState({ isRunning: false });
149
+
150
+ const newState = handleTrigger(config, state, 0.5);
151
+ expect(newState.isRunning).toBe(true);
152
+ });
153
+
154
+ test('sets hasTriggered flag', () => {
155
+ const config = createConfig({ mode: 'ONE' });
156
+ const state = createState({ hasTriggered: false });
157
+
158
+ const newState = handleTrigger(config, state, 0.5);
159
+ expect(newState.hasTriggered).toBe(true);
160
+ });
161
+
162
+ test('resets fade and cycle count', () => {
163
+ const config = createConfig({ mode: 'ONE', fade: -32 });
164
+ const state = createState({ fadeProgress: 0.8, cycleCount: 5 });
165
+
166
+ const newState = handleTrigger(config, state, 0.5);
167
+ expect(newState.fadeProgress).toBe(0);
168
+ expect(newState.cycleCount).toBe(0);
169
+ });
170
+ });
171
+
172
+ describe('handleTrigger - HLF mode', () => {
173
+ test('resets phase to start phase', () => {
174
+ const config = createConfig({ mode: 'HLF', startPhase: 32 });
175
+ const state = createState({ phase: 0.8, startPhaseNormalized: 0.25 });
176
+
177
+ const newState = handleTrigger(config, state, 0.5);
178
+ expect(newState.phase).toBe(0.25);
179
+ });
180
+
181
+ test('starts running and sets hasTriggered', () => {
182
+ const config = createConfig({ mode: 'HLF' });
183
+ const state = createState({ isRunning: false, hasTriggered: false });
184
+
185
+ const newState = handleTrigger(config, state, 0.5);
186
+ expect(newState.isRunning).toBe(true);
187
+ expect(newState.hasTriggered).toBe(true);
188
+ });
189
+ });
190
+
191
+ describe('checkModeStop - ONE mode', () => {
192
+ test('stops after completing one cycle (forward)', () => {
193
+ const config = createConfig({ mode: 'ONE', speed: 16 });
194
+ const state = createState({
195
+ hasTriggered: true,
196
+ startPhaseNormalized: 0,
197
+ cycleCount: 1,
198
+ });
199
+
200
+ // Phase wrapping from near 1 to near 0
201
+ const result = checkModeStop(config, state, 0.95, 0.05);
202
+ expect(result.shouldStop).toBe(true);
203
+ expect(result.cycleCompleted).toBe(true);
204
+ });
205
+
206
+ test('does not stop before cycle completes', () => {
207
+ const config = createConfig({ mode: 'ONE', speed: 16 });
208
+ const state = createState({
209
+ hasTriggered: true,
210
+ startPhaseNormalized: 0,
211
+ cycleCount: 0,
212
+ });
213
+
214
+ const result = checkModeStop(config, state, 0.4, 0.5);
215
+ expect(result.shouldStop).toBe(false);
216
+ });
217
+
218
+ test('stops when phase crosses start phase (with non-zero start)', () => {
219
+ const config = createConfig({ mode: 'ONE', speed: 16, startPhase: 64 });
220
+ const state = createState({
221
+ hasTriggered: true,
222
+ startPhaseNormalized: 0.5,
223
+ cycleCount: 0,
224
+ });
225
+
226
+ // Going from just before 0.5 (after wrapping) back to 0.5
227
+ const result = checkModeStop(config, state, 0.6, 0.4);
228
+ expect(result.shouldStop).toBe(false); // Haven't completed a full cycle yet
229
+ });
230
+
231
+ test('stops for negative speed (backward)', () => {
232
+ const config = createConfig({ mode: 'ONE', speed: -16 });
233
+ const state = createState({
234
+ hasTriggered: true,
235
+ startPhaseNormalized: 0,
236
+ cycleCount: 1,
237
+ });
238
+
239
+ // Phase wrapping from near 0 to near 1 (backward direction)
240
+ const result = checkModeStop(config, state, 0.05, 0.95);
241
+ expect(result.shouldStop).toBe(true);
242
+ });
243
+ });
244
+
245
+ describe('checkModeStop - HLF mode', () => {
246
+ test('stops at half cycle (forward, start at 0)', () => {
247
+ const config = createConfig({ mode: 'HLF', speed: 16 });
248
+ const state = createState({
249
+ hasTriggered: true,
250
+ startPhaseNormalized: 0,
251
+ });
252
+
253
+ // Crossing phase 0.5
254
+ const result = checkModeStop(config, state, 0.45, 0.55);
255
+ expect(result.shouldStop).toBe(true);
256
+ });
257
+
258
+ test('stops at half cycle (start at 0.25)', () => {
259
+ const config = createConfig({ mode: 'HLF', speed: 16, startPhase: 32 });
260
+ const state = createState({
261
+ hasTriggered: true,
262
+ startPhaseNormalized: 0.25, // 32/128
263
+ });
264
+
265
+ // Half point is 0.25 + 0.5 = 0.75
266
+ const result = checkModeStop(config, state, 0.7, 0.8);
267
+ expect(result.shouldStop).toBe(true);
268
+ });
269
+
270
+ test('does not stop before half cycle', () => {
271
+ const config = createConfig({ mode: 'HLF', speed: 16 });
272
+ const state = createState({
273
+ hasTriggered: true,
274
+ startPhaseNormalized: 0,
275
+ });
276
+
277
+ const result = checkModeStop(config, state, 0.2, 0.3);
278
+ expect(result.shouldStop).toBe(false);
279
+ });
280
+ });
281
+
282
+ describe('checkModeStop - other modes', () => {
283
+ test('FRE mode never stops', () => {
284
+ const config = createConfig({ mode: 'FRE' });
285
+ const state = createState({ hasTriggered: true });
286
+
287
+ const result = checkModeStop(config, state, 0.9, 0.1);
288
+ expect(result.shouldStop).toBe(false);
289
+ });
290
+
291
+ test('TRG mode never stops', () => {
292
+ const config = createConfig({ mode: 'TRG' });
293
+ const state = createState({ hasTriggered: true });
294
+
295
+ const result = checkModeStop(config, state, 0.9, 0.1);
296
+ expect(result.shouldStop).toBe(false);
297
+ });
298
+
299
+ test('HLD mode never stops', () => {
300
+ const config = createConfig({ mode: 'HLD' });
301
+ const state = createState({ hasTriggered: true });
302
+
303
+ const result = checkModeStop(config, state, 0.9, 0.1);
304
+ expect(result.shouldStop).toBe(false);
305
+ });
306
+ });
307
+
308
+ describe('requiresTriggerToStart', () => {
309
+ test('ONE and HLF require trigger', () => {
310
+ expect(requiresTriggerToStart('ONE')).toBe(true);
311
+ expect(requiresTriggerToStart('HLF')).toBe(true);
312
+ });
313
+
314
+ test('other modes do not require trigger', () => {
315
+ expect(requiresTriggerToStart('FRE')).toBe(false);
316
+ expect(requiresTriggerToStart('TRG')).toBe(false);
317
+ expect(requiresTriggerToStart('HLD')).toBe(false);
318
+ });
319
+ });
320
+
321
+ describe('resetsPhaseOnTrigger', () => {
322
+ test('TRG, ONE, HLF reset phase', () => {
323
+ expect(resetsPhaseOnTrigger('TRG')).toBe(true);
324
+ expect(resetsPhaseOnTrigger('ONE')).toBe(true);
325
+ expect(resetsPhaseOnTrigger('HLF')).toBe(true);
326
+ });
327
+
328
+ test('FRE and HLD do not reset phase', () => {
329
+ expect(resetsPhaseOnTrigger('FRE')).toBe(false);
330
+ expect(resetsPhaseOnTrigger('HLD')).toBe(false);
331
+ });
332
+ });
333
+
334
+ describe('resetsFadeOnTrigger', () => {
335
+ test('all modes except FRE reset fade', () => {
336
+ expect(resetsFadeOnTrigger('TRG')).toBe(true);
337
+ expect(resetsFadeOnTrigger('ONE')).toBe(true);
338
+ expect(resetsFadeOnTrigger('HLF')).toBe(true);
339
+ expect(resetsFadeOnTrigger('HLD')).toBe(true);
340
+ });
341
+
342
+ test('FRE does not reset fade', () => {
343
+ expect(resetsFadeOnTrigger('FRE')).toBe(false);
344
+ });
345
+ });
@@ -0,0 +1,273 @@
1
+ import { describe, test, expect } from 'bun:test';
2
+ import {
3
+ generateTriangle,
4
+ generateSine,
5
+ generateSquare,
6
+ generateSawtooth,
7
+ generateExponential,
8
+ generateRamp,
9
+ generateRandom,
10
+ generateWaveform,
11
+ isUnipolar,
12
+ getWaveformRange,
13
+ } from '../src/engine/waveforms';
14
+ import type { LFOState } from '../src/engine/types';
15
+ import { createInitialState } from '../src/engine/types';
16
+
17
+ // Helper to create a mock state for random waveform testing
18
+ function createMockState(overrides: Partial<LFOState> = {}): LFOState {
19
+ return {
20
+ phase: 0,
21
+ output: 0,
22
+ rawOutput: 0,
23
+ isRunning: true,
24
+ fadeMultiplier: 1,
25
+ fadeProgress: 0,
26
+ randomValue: 0.5,
27
+ previousPhase: 0,
28
+ heldOutput: 0,
29
+ startPhaseNormalized: 0,
30
+ cycleCount: 0,
31
+ triggerCount: 0,
32
+ hasTriggered: false,
33
+ randomStep: 0,
34
+ ...overrides,
35
+ };
36
+ }
37
+
38
+ describe('Triangle Waveform', () => {
39
+ test('starts at 0 at phase 0', () => {
40
+ expect(generateTriangle(0)).toBeCloseTo(0, 5);
41
+ });
42
+
43
+ test('peaks at +1 at phase 0.25', () => {
44
+ expect(generateTriangle(0.25)).toBeCloseTo(1, 5);
45
+ });
46
+
47
+ test('returns to 0 at phase 0.5', () => {
48
+ expect(generateTriangle(0.5)).toBeCloseTo(0, 5);
49
+ });
50
+
51
+ test('troughs at -1 at phase 0.75', () => {
52
+ expect(generateTriangle(0.75)).toBeCloseTo(-1, 5);
53
+ });
54
+
55
+ test('returns to near 0 at phase ~1', () => {
56
+ expect(generateTriangle(0.999)).toBeCloseTo(0, 1);
57
+ });
58
+
59
+ test('is bipolar (range -1 to +1)', () => {
60
+ const samples = Array.from({ length: 100 }, (_, i) => generateTriangle(i / 100));
61
+ expect(Math.min(...samples)).toBeGreaterThanOrEqual(-1);
62
+ expect(Math.max(...samples)).toBeLessThanOrEqual(1);
63
+ expect(Math.min(...samples)).toBeLessThan(0); // Has negative values
64
+ expect(Math.max(...samples)).toBeGreaterThan(0); // Has positive values
65
+ });
66
+ });
67
+
68
+ describe('Sine Waveform', () => {
69
+ test('starts at 0 at phase 0', () => {
70
+ expect(generateSine(0)).toBeCloseTo(0, 5);
71
+ });
72
+
73
+ test('peaks at +1 at phase 0.25', () => {
74
+ expect(generateSine(0.25)).toBeCloseTo(1, 5);
75
+ });
76
+
77
+ test('returns to 0 at phase 0.5', () => {
78
+ expect(generateSine(0.5)).toBeCloseTo(0, 5);
79
+ });
80
+
81
+ test('troughs at -1 at phase 0.75', () => {
82
+ expect(generateSine(0.75)).toBeCloseTo(-1, 5);
83
+ });
84
+
85
+ test('returns to 0 at phase 1', () => {
86
+ expect(generateSine(1)).toBeCloseTo(0, 5);
87
+ });
88
+
89
+ test('is bipolar (range -1 to +1)', () => {
90
+ const samples = Array.from({ length: 100 }, (_, i) => generateSine(i / 100));
91
+ expect(Math.min(...samples)).toBeGreaterThanOrEqual(-1);
92
+ expect(Math.max(...samples)).toBeLessThanOrEqual(1);
93
+ });
94
+ });
95
+
96
+ describe('Square Waveform', () => {
97
+ test('is +1 at phase 0', () => {
98
+ expect(generateSquare(0)).toBe(1);
99
+ });
100
+
101
+ test('is +1 just before phase 0.5', () => {
102
+ expect(generateSquare(0.49)).toBe(1);
103
+ });
104
+
105
+ test('is -1 at phase 0.5', () => {
106
+ expect(generateSquare(0.5)).toBe(-1);
107
+ });
108
+
109
+ test('is -1 at phase 0.75', () => {
110
+ expect(generateSquare(0.75)).toBe(-1);
111
+ });
112
+
113
+ test('is -1 just before phase 1', () => {
114
+ expect(generateSquare(0.99)).toBe(-1);
115
+ });
116
+
117
+ test('is bipolar (only +1 and -1 values)', () => {
118
+ const samples = Array.from({ length: 100 }, (_, i) => generateSquare(i / 100));
119
+ samples.forEach(s => {
120
+ expect(s === 1 || s === -1).toBe(true);
121
+ });
122
+ });
123
+ });
124
+
125
+ describe('Sawtooth Waveform', () => {
126
+ test('starts at -1 at phase 0', () => {
127
+ expect(generateSawtooth(0)).toBe(-1);
128
+ });
129
+
130
+ test('is 0 at phase 0.5', () => {
131
+ expect(generateSawtooth(0.5)).toBe(0);
132
+ });
133
+
134
+ test('approaches +1 at phase ~1', () => {
135
+ expect(generateSawtooth(1)).toBe(1);
136
+ expect(generateSawtooth(0.999)).toBeCloseTo(1, 2);
137
+ });
138
+
139
+ test('is bipolar and linear', () => {
140
+ expect(generateSawtooth(0.25)).toBeCloseTo(-0.5, 5);
141
+ expect(generateSawtooth(0.75)).toBeCloseTo(0.5, 5);
142
+ });
143
+ });
144
+
145
+ describe('Exponential Waveform', () => {
146
+ test('starts at 0 at phase 0', () => {
147
+ expect(generateExponential(0)).toBeCloseTo(0, 5);
148
+ });
149
+
150
+ test('ends at 1 at phase 1', () => {
151
+ expect(generateExponential(1)).toBeCloseTo(1, 5);
152
+ });
153
+
154
+ test('is unipolar (0 to +1)', () => {
155
+ const samples = Array.from({ length: 100 }, (_, i) => generateExponential(i / 100));
156
+ expect(Math.min(...samples)).toBeGreaterThanOrEqual(0);
157
+ expect(Math.max(...samples)).toBeLessThanOrEqual(1);
158
+ });
159
+
160
+ test('has accelerating (exponential) curve shape', () => {
161
+ // At phase 0.5, should be less than 0.5 (curve bends up)
162
+ const midValue = generateExponential(0.5);
163
+ expect(midValue).toBeLessThan(0.5);
164
+ });
165
+ });
166
+
167
+ describe('Ramp Waveform', () => {
168
+ test('starts at 1 at phase 0', () => {
169
+ expect(generateRamp(0)).toBe(1);
170
+ });
171
+
172
+ test('ends at 0 at phase 1', () => {
173
+ expect(generateRamp(1)).toBe(0);
174
+ });
175
+
176
+ test('is 0.5 at phase 0.5', () => {
177
+ expect(generateRamp(0.5)).toBe(0.5);
178
+ });
179
+
180
+ test('is unipolar (0 to +1)', () => {
181
+ const samples = Array.from({ length: 100 }, (_, i) => generateRamp(i / 100));
182
+ expect(Math.min(...samples)).toBeGreaterThanOrEqual(0);
183
+ expect(Math.max(...samples)).toBeLessThanOrEqual(1);
184
+ });
185
+
186
+ test('is linear', () => {
187
+ expect(generateRamp(0.25)).toBe(0.75);
188
+ expect(generateRamp(0.75)).toBe(0.25);
189
+ });
190
+ });
191
+
192
+ describe('Random Waveform', () => {
193
+ test('returns current random value when step unchanged', () => {
194
+ const state = createMockState({ randomValue: 0.5, randomStep: 0 });
195
+ const result = generateRandom(0.01, state); // Still in step 0
196
+ expect(result.value).toBe(0.5);
197
+ expect(result.newRandomStep).toBe(0);
198
+ });
199
+
200
+ test('generates new random value on step change', () => {
201
+ const state = createMockState({ randomValue: 0.5, randomStep: 0 });
202
+ const result = generateRandom(0.1, state); // Step 1 (16 steps per cycle)
203
+ expect(result.newRandomStep).toBe(1);
204
+ expect(result.newRandomValue).not.toBe(0.5); // New random value (with high probability)
205
+ });
206
+
207
+ test('has 16 steps per cycle', () => {
208
+ const stepPhases = [0, 0.0625, 0.125, 0.1875, 0.25];
209
+ const expectedSteps = [0, 1, 2, 3, 4];
210
+
211
+ stepPhases.forEach((phase, i) => {
212
+ expect(Math.floor(phase * 16)).toBe(expectedSteps[i]);
213
+ });
214
+ });
215
+
216
+ test('generates values in bipolar range (-1 to +1)', () => {
217
+ const state = createMockState({ randomStep: -1 }); // Force new value generation
218
+ const values: number[] = [];
219
+
220
+ for (let i = 0; i < 100; i++) {
221
+ const result = generateRandom(i / 100, state);
222
+ values.push(result.value);
223
+ state.randomStep = result.newRandomStep;
224
+ state.randomValue = result.newRandomValue;
225
+ }
226
+
227
+ expect(Math.min(...values)).toBeGreaterThanOrEqual(-1);
228
+ expect(Math.max(...values)).toBeLessThanOrEqual(1);
229
+ });
230
+ });
231
+
232
+ describe('generateWaveform', () => {
233
+ test('routes to correct waveform generator', () => {
234
+ const state = createMockState();
235
+
236
+ expect(generateWaveform('TRI', 0.25, state).value).toBeCloseTo(1, 5);
237
+ expect(generateWaveform('SIN', 0.25, state).value).toBeCloseTo(1, 5);
238
+ expect(generateWaveform('SQR', 0.25, state).value).toBe(1);
239
+ expect(generateWaveform('SAW', 0.5, state).value).toBe(0);
240
+ expect(generateWaveform('EXP', 1, state).value).toBeCloseTo(1, 5);
241
+ expect(generateWaveform('RMP', 0, state).value).toBe(1);
242
+ });
243
+ });
244
+
245
+ describe('isUnipolar', () => {
246
+ test('identifies unipolar waveforms', () => {
247
+ expect(isUnipolar('EXP')).toBe(true);
248
+ expect(isUnipolar('RMP')).toBe(true);
249
+ });
250
+
251
+ test('identifies bipolar waveforms', () => {
252
+ expect(isUnipolar('TRI')).toBe(false);
253
+ expect(isUnipolar('SIN')).toBe(false);
254
+ expect(isUnipolar('SQR')).toBe(false);
255
+ expect(isUnipolar('SAW')).toBe(false);
256
+ expect(isUnipolar('RND')).toBe(false);
257
+ });
258
+ });
259
+
260
+ describe('getWaveformRange', () => {
261
+ test('returns correct range for unipolar waveforms', () => {
262
+ expect(getWaveformRange('EXP')).toEqual({ min: 0, max: 1 });
263
+ expect(getWaveformRange('RMP')).toEqual({ min: 0, max: 1 });
264
+ });
265
+
266
+ test('returns correct range for bipolar waveforms', () => {
267
+ expect(getWaveformRange('TRI')).toEqual({ min: -1, max: 1 });
268
+ expect(getWaveformRange('SIN')).toEqual({ min: -1, max: 1 });
269
+ expect(getWaveformRange('SQR')).toEqual({ min: -1, max: 1 });
270
+ expect(getWaveformRange('SAW')).toEqual({ min: -1, max: 1 });
271
+ expect(getWaveformRange('RND')).toEqual({ min: -1, max: 1 });
272
+ });
273
+ });
package/tsconfig.json ADDED
@@ -0,0 +1,13 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "ESNext",
5
+ "moduleResolution": "bundler",
6
+ "lib": ["ES2022"],
7
+ "strict": true,
8
+ "noEmit": true,
9
+ "esModuleInterop": true,
10
+ "types": ["bun-types"]
11
+ },
12
+ "include": ["src/**/*", "tests/**/*"]
13
+ }