@wenathlan/debonair 2.0.89 → 2.0.93

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wenathlan/debonair",
3
- "version": "2.0.89",
3
+ "version": "2.0.93",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "https://github.com/wenathlan/devthink"
@@ -37,7 +37,8 @@
37
37
  "build": "vite build",
38
38
  "start": "vite preview --host",
39
39
  "preview": "vite preview --host",
40
- "check": "tsc --noEmit"
40
+ "check": "tsc --noEmit",
41
+ "test": "vitest run"
41
42
  },
42
43
  "dependencies": {
43
44
  "@hono/node-server": "^2.1.3",
@@ -1,10 +1,10 @@
1
1
  // # beatgrid.test — honest unit tests for the beat grid (tempo detection, beat
2
- // tracking, quantize/swing, tempo map integration), runnable with the node
3
- // built-in runner (no dependencies, no install):
4
- // node --test tests/beatgrid.test.ts
2
+ // tracking, quantize/swing, tempo map integration), runnable with the
3
+ // vitest runner:
4
+ // pnpm test
5
5
  // The fixtures synthesise a 120 BPM click track and piecewise tempo curves.
6
6
  import assert from "node:assert/strict";
7
- import { describe, it } from "node:test";
7
+ import { describe, it } from "vitest";
8
8
  import {
9
9
  beatCount,
10
10
  beatsElapsed,
@@ -1,10 +1,10 @@
1
1
  // # ducking.test — honest unit tests for the sidechain ducking logic (level
2
2
  // envelope, activity regions, per-sample follower gain, keyframes), runnable
3
- // with the node built-in runner (no dependencies, no install):
4
- // node --test tests/ducking.test.ts
3
+ // with the vitest runner:
4
+ // pnpm test
5
5
  // The fixtures synthesise a 1 kHz voice-like trigger with two speech bursts.
6
6
  import assert from "node:assert/strict";
7
- import { describe, it } from "node:test";
7
+ import { describe, it } from "vitest";
8
8
  import {
9
9
  activityRegions,
10
10
  autoDuckRegions,
@@ -1,11 +1,11 @@
1
1
  // # loudness.test — honest unit tests for the loudness meter (K-weighting,
2
- // gating, LRA, peaks, normalisation gain), runnable with the node built-in
3
- // runner (no dependencies, no install):
4
- // node --test tests/loudness.test.ts
2
+ // gating, LRA, peaks, normalisation gain), runnable with the vitest
3
+ // runner:
4
+ // pnpm test
5
5
  // The fixtures synthesise a 1 kHz sine at known amplitudes; every expected
6
6
  // number is derived from the meter itself, never from a lookup table.
7
7
  import assert from "node:assert/strict";
8
- import { describe, it } from "node:test";
8
+ import { describe, it } from "vitest";
9
9
  import {
10
10
  analyzeLoudness,
11
11
  applyGain,
@@ -1,9 +1,9 @@
1
1
  // # mixergraph.test — honest unit tests for the mixing graph model, runnable
2
- // with the node built-in runner (no dependencies, no install):
3
- // node --test tests/mixergraph.test.ts
2
+ // with the vitest runner:
3
+ // pnpm test
4
4
  // The desk fixtures reuse the real mixer strips the site seed serves.
5
5
  import assert from "node:assert/strict";
6
- import { describe, it } from "node:test";
6
+ import { describe, it } from "vitest";
7
7
  import {
8
8
  assertAcyclic,
9
9
  assertGraph,
@@ -12,10 +12,10 @@ import {
12
12
  effectiveGains,
13
13
  graphFromStrips,
14
14
  linearToDb,
15
+ type MixerChannel,
15
16
  MixerGraphError,
16
17
  patchChannel,
17
18
  resolvePaths,
18
- type MixerChannel,
19
19
  } from "../mixergraph.ts";
20
20
  import { seedMixerStrips } from "../seed.ts";
21
21
 
@@ -29,34 +29,46 @@ describe("mixer graph construction", () => {
29
29
 
30
30
  it("refuses a desk without a master row", () => {
31
31
  const orphans = seedMixerStrips.map((strip) => ({ ...strip, master: false }));
32
- assert.throws(() => graphFromStrips(orphans), (error: unknown) => {
33
- assert.ok(error instanceof MixerGraphError);
34
- assert.equal(error.code, "missing-master");
35
- return true;
36
- });
32
+ assert.throws(
33
+ () => graphFromStrips(orphans),
34
+ (error: unknown) => {
35
+ assert.ok(error instanceof MixerGraphError);
36
+ assert.equal(error.code, "missing-master");
37
+ return true;
38
+ },
39
+ );
37
40
  assert.throws(() => graphFromStrips([]), MixerGraphError);
38
41
  });
39
42
 
40
43
  it("validates duplicate ids, unknown sends and non finite gains", () => {
41
44
  const graph = graphFromStrips(seedMixerStrips);
42
45
  const duplicated: MixerChannel = { id: "Drums", name: "Drums", gaindb: 0, mute: false, solo: false, sends: [] };
43
- assert.throws(() => assertGraph({ ...graph, channels: [...graph.channels, duplicated] }), (error: unknown) => {
44
- assert.ok(error instanceof MixerGraphError);
45
- assert.equal(error.code, "duplicate-channel");
46
- return true;
47
- });
46
+ assert.throws(
47
+ () => assertGraph({ ...graph, channels: [...graph.channels, duplicated] }),
48
+ (error: unknown) => {
49
+ assert.ok(error instanceof MixerGraphError);
50
+ assert.equal(error.code, "duplicate-channel");
51
+ return true;
52
+ },
53
+ );
48
54
  const withbadsend = patchChannel(graph, "Drums", { sends: [{ targetid: "ghost", gaindb: -3 }] });
49
- assert.throws(() => assertGraph(withbadsend), (error: unknown) => {
50
- assert.ok(error instanceof MixerGraphError);
51
- assert.equal(error.code, "unknown-send");
52
- return true;
53
- });
55
+ assert.throws(
56
+ () => assertGraph(withbadsend),
57
+ (error: unknown) => {
58
+ assert.ok(error instanceof MixerGraphError);
59
+ assert.equal(error.code, "unknown-send");
60
+ return true;
61
+ },
62
+ );
54
63
  const withnangain = patchChannel(graph, "Drums", { gaindb: Number.NaN });
55
- assert.throws(() => assertGraph(withnangain), (error: unknown) => {
56
- assert.ok(error instanceof MixerGraphError);
57
- assert.equal(error.code, "bad-gain");
58
- return true;
59
- });
64
+ assert.throws(
65
+ () => assertGraph(withnangain),
66
+ (error: unknown) => {
67
+ assert.ok(error instanceof MixerGraphError);
68
+ assert.equal(error.code, "bad-gain");
69
+ return true;
70
+ },
71
+ );
60
72
  });
61
73
  });
62
74
 
@@ -64,17 +76,18 @@ describe("mixer graph cycles", () => {
64
76
  const graph = graphFromStrips(seedMixerStrips);
65
77
 
66
78
  it("rejects a send loop between two strips", () => {
67
- const looped = patchChannel(
68
- patchChannel(graph, "Drums", { sends: [{ targetid: "Bass", gaindb: -3 }] }),
69
- "Bass",
70
- { sends: [{ targetid: "Drums", gaindb: -3 }] },
71
- );
72
- assert.throws(() => assertAcyclic(looped), (error: unknown) => {
73
- assert.ok(error instanceof MixerGraphError);
74
- assert.equal(error.code, "cycle");
75
- assert.equal(error.channelid, "Drums");
76
- return true;
79
+ const looped = patchChannel(patchChannel(graph, "Drums", { sends: [{ targetid: "Bass", gaindb: -3 }] }), "Bass", {
80
+ sends: [{ targetid: "Drums", gaindb: -3 }],
77
81
  });
82
+ assert.throws(
83
+ () => assertAcyclic(looped),
84
+ (error: unknown) => {
85
+ assert.ok(error instanceof MixerGraphError);
86
+ assert.equal(error.code, "cycle");
87
+ assert.equal(error.channelid, "Drums");
88
+ return true;
89
+ },
90
+ );
78
91
  });
79
92
 
80
93
  it("rejects a strip that sends to itself", () => {
@@ -94,7 +107,10 @@ describe("mixer graph gains", () => {
94
107
  assert.ok(Math.abs(dbToLinear(-6) - 0.501) < 0.001);
95
108
  assert.equal(dbToLinear(Number.NEGATIVE_INFINITY), 0);
96
109
  assert.equal(linearToDb(1), 0);
97
- assert.ok(Math.abs(linearToDb(dbToLinear(-6)) - -6) < 1e-9, `expected the -6 dB roundtrip to hold, got ${linearToDb(dbToLinear(-6))}`);
110
+ assert.ok(
111
+ Math.abs(linearToDb(dbToLinear(-6)) - -6) < 1e-9,
112
+ `expected the -6 dB roundtrip to hold, got ${linearToDb(dbToLinear(-6))}`,
113
+ );
98
114
  assert.equal(linearToDb(0), Number.NEGATIVE_INFINITY);
99
115
  });
100
116
 
@@ -139,7 +155,9 @@ describe("mixer graph routing paths", () => {
139
155
  });
140
156
 
141
157
  it("resolves the send path with the send and master gains chained", () => {
142
- const graph = patchChannel(graphFromStrips(seedMixerStrips), "Drums", { sends: [{ targetid: "Master", gaindb: -6 }] });
158
+ const graph = patchChannel(graphFromStrips(seedMixerStrips), "Drums", {
159
+ sends: [{ targetid: "Master", gaindb: -6 }],
160
+ });
143
161
  const paths = resolvePaths(graph, "Drums");
144
162
  assert.equal(paths.length, 2);
145
163
  assert.ok(Math.abs(paths[0].gaindb - -6) < 1e-9); // direct: -4 + -2
@@ -156,10 +174,13 @@ describe("mixer graph routing paths", () => {
156
174
 
157
175
  it("refuses an unknown channel", () => {
158
176
  const graph = graphFromStrips(seedMixerStrips);
159
- assert.throws(() => resolvePaths(graph, "ghost"), (error: unknown) => {
160
- assert.ok(error instanceof MixerGraphError);
161
- assert.equal(error.code, "unknown-channel");
162
- return true;
163
- });
177
+ assert.throws(
178
+ () => resolvePaths(graph, "ghost"),
179
+ (error: unknown) => {
180
+ assert.ok(error instanceof MixerGraphError);
181
+ assert.equal(error.code, "unknown-channel");
182
+ return true;
183
+ },
184
+ );
164
185
  });
165
186
  });
@@ -1,10 +1,10 @@
1
1
  // # remixplan.test — honest unit tests for the remix planner (beat similarity,
2
- // joint scoring, piece plans, crossfade gains), runnable with the node
3
- // built-in runner (no dependencies, no install):
4
- // node --test tests/remixplan.test.ts
2
+ // joint scoring, piece plans, crossfade gains), runnable with the
3
+ // vitest runner:
4
+ // pnpm test
5
5
  // The fixtures synthesise a 32-beat loop whose features repeat every 3 beats.
6
6
  import assert from "node:assert/strict";
7
- import { describe, it } from "node:test";
7
+ import { describe, it } from "vitest";
8
8
  import {
9
9
  beatSimilarity,
10
10
  bestTargetBeat,
@@ -1,13 +1,12 @@
1
1
  // # speechalign.test — honest unit tests for the speech alignment logic (RMS
2
2
  // framing, silence threshold, phrase segmentation, grid fitting with stretch
3
- // limits), runnable with the node built-in runner (no dependencies, no
4
- // install):
5
- // node --test tests/speechalign.test.ts
3
+ // limits), runnable with the vitest runner:
4
+ // pnpm test
6
5
  // The fixture synthesises a 3 s "voice": two sine phrases (0.2–0.8 s and
7
6
  // 1.2–1.7 s) with 50 ms fades over digital silence, so every phrase boundary
8
7
  // the logic reports is checkable by hand.
9
8
  import assert from "node:assert/strict";
10
- import { describe, it } from "node:test";
9
+ import { describe, it } from "vitest";
11
10
  import {
12
11
  alignSpeech,
13
12
  beatGridTimes,
@@ -1,9 +1,9 @@
1
1
  // # timelinemodel.test — honest unit tests for the arrangement timeline
2
- // model (snap, overlap, split, trim, move), runnable with the node runner:
3
- // node --test tests/timelinemodel.test.ts
2
+ // model (snap, overlap, split, trim, move), runnable with the vitest runner:
3
+ // pnpm test
4
4
  // The clip fixtures mirror the shapes the studio timeline serves.
5
5
  import assert from "node:assert/strict";
6
- import { describe, it } from "node:test";
6
+ import { describe, it } from "vitest";
7
7
  import {
8
8
  activeAt,
9
9
  clipsOverlap,
@@ -12,11 +12,11 @@ import {
12
12
  snapClip,
13
13
  snapToGrid,
14
14
  splitClip,
15
+ type TimelineClip,
15
16
  TimelineModelError,
16
17
  totalDuration,
17
18
  trimClip,
18
19
  validatePlacement,
19
- type TimelineClip,
20
20
  } from "../timelinemodel.ts";
21
21
 
22
22
  const GRID = { stepseconds: 0.25 };
@@ -46,11 +46,14 @@ describe("timeline grid snap", () => {
46
46
 
47
47
  it("refuses a bad time or a bad grid", () => {
48
48
  assert.throws(() => snapToGrid(-1, GRID), TimelineModelError);
49
- assert.throws(() => snapToGrid(1, { stepseconds: 0 }), (error: unknown) => {
50
- assert.ok(error instanceof TimelineModelError);
51
- assert.equal(error.code, "bad-grid");
52
- return true;
53
- });
49
+ assert.throws(
50
+ () => snapToGrid(1, { stepseconds: 0 }),
51
+ (error: unknown) => {
52
+ assert.ok(error instanceof TimelineModelError);
53
+ assert.equal(error.code, "bad-grid");
54
+ return true;
55
+ },
56
+ );
54
57
  });
55
58
  });
56
59
 
@@ -67,12 +70,15 @@ describe("timeline overlap", () => {
67
70
  it("validates the placement and reports the offending pair", () => {
68
71
  const arrangement = [clip("a", "Drums", 0, 2), clip("b", "Bass", 0, 4)];
69
72
  assert.doesNotThrow(() => validatePlacement(clip("c", "Bass", 5, 1), arrangement));
70
- assert.throws(() => validatePlacement(clip("c", "Drums", 1, 2), arrangement), (error: unknown) => {
71
- assert.ok(error instanceof TimelineModelError);
72
- assert.equal(error.code, "overlap");
73
- assert.equal(error.clipid, "c");
74
- return true;
75
- });
73
+ assert.throws(
74
+ () => validatePlacement(clip("c", "Drums", 1, 2), arrangement),
75
+ (error: unknown) => {
76
+ assert.ok(error instanceof TimelineModelError);
77
+ assert.equal(error.code, "overlap");
78
+ assert.equal(error.clipid, "c");
79
+ return true;
80
+ },
81
+ );
76
82
  });
77
83
 
78
84
  it("skips the clip itself and honors the allowoverlap escape hatch", () => {
@@ -96,12 +102,15 @@ describe("timeline split", () => {
96
102
 
97
103
  it("refuses a split on the edges or outside the span", () => {
98
104
  const target = clip("c1", "Bass", 2, 1.5);
99
- assert.throws(() => splitClip(target, 2), (error: unknown) => {
100
- assert.ok(error instanceof TimelineModelError);
101
- assert.equal(error.code, "split-outside");
102
- assert.equal(error.clipid, "c1");
103
- return true;
104
- });
105
+ assert.throws(
106
+ () => splitClip(target, 2),
107
+ (error: unknown) => {
108
+ assert.ok(error instanceof TimelineModelError);
109
+ assert.equal(error.code, "split-outside");
110
+ assert.equal(error.clipid, "c1");
111
+ return true;
112
+ },
113
+ );
105
114
  assert.throws(() => splitClip(target, 3.5), TimelineModelError);
106
115
  assert.throws(() => splitClip(target, 9), TimelineModelError);
107
116
  });
@@ -122,11 +131,14 @@ describe("timeline trim", () => {
122
131
 
123
132
  it("refuses a trim that leaves the span or breaks the minimum duration", () => {
124
133
  const target = clip("c1", "Drums", 2, 1.5);
125
- assert.throws(() => trimClip(target, "start", 1.9), (error: unknown) => {
126
- assert.ok(error instanceof TimelineModelError);
127
- assert.equal(error.code, "trim-invalid");
128
- return true;
129
- });
134
+ assert.throws(
135
+ () => trimClip(target, "start", 1.9),
136
+ (error: unknown) => {
137
+ assert.ok(error instanceof TimelineModelError);
138
+ assert.equal(error.code, "trim-invalid");
139
+ return true;
140
+ },
141
+ );
130
142
  assert.throws(() => trimClip(target, "start", 3.5 - MINIMUMDURATION + 0.01), TimelineModelError);
131
143
  assert.throws(() => trimClip(target, "end", 2 + MINIMUMDURATION - 0.01), TimelineModelError);
132
144
  assert.throws(() => trimClip(target, "end", 9), TimelineModelError);
@@ -147,11 +159,14 @@ describe("timeline move", () => {
147
159
  });
148
160
 
149
161
  it("rejects the landing on an occupied spot", () => {
150
- assert.throws(() => moveClip(clip("d", "Drums", 2, 1), 1.5, arrangement), (error: unknown) => {
151
- assert.ok(error instanceof TimelineModelError);
152
- assert.equal(error.code, "overlap");
153
- return true;
154
- });
162
+ assert.throws(
163
+ () => moveClip(clip("d", "Drums", 2, 1), 1.5, arrangement),
164
+ (error: unknown) => {
165
+ assert.ok(error instanceof TimelineModelError);
166
+ assert.equal(error.code, "overlap");
167
+ return true;
168
+ },
169
+ );
155
170
  });
156
171
 
157
172
  it("snaps the landing when a grid is passed and moves across tracks", () => {
@@ -166,8 +181,14 @@ describe("timeline queries", () => {
166
181
  const arrangement = [clip("a", "Drums", 0, 1), clip("b", "Bass", 0.5, 2), clip("c", "Lead", 5, 1)];
167
182
 
168
183
  it("answers the clips playing at one instant", () => {
169
- assert.deepEqual(activeAt(arrangement, 0.75).map((row) => row.id), ["a", "b"]);
170
- assert.deepEqual(activeAt(arrangement, 1).map((row) => row.id), ["b"]);
184
+ assert.deepEqual(
185
+ activeAt(arrangement, 0.75).map((row) => row.id),
186
+ ["a", "b"],
187
+ );
188
+ assert.deepEqual(
189
+ activeAt(arrangement, 1).map((row) => row.id),
190
+ ["b"],
191
+ );
171
192
  assert.deepEqual(activeAt(arrangement, 10), []);
172
193
  });
173
194