@hraness/dawg 0.5.0 → 0.6.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.
Files changed (88) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/DAWG.md +269 -13
  3. package/README.md +4 -4
  4. package/core/diff.ts +4 -0
  5. package/core/fx.ts +437 -2
  6. package/core/granular.ts +528 -0
  7. package/core/instruments.ts +281 -0
  8. package/core/keys.ts +386 -0
  9. package/core/resonators.ts +574 -0
  10. package/core/score.ts +181 -6
  11. package/core/sdk/eval-child.ts +2 -0
  12. package/core/sdk/print.ts +169 -13
  13. package/core/sdk/sync-instruments.ts +58 -0
  14. package/core/sdk/v1.ts +1114 -21
  15. package/core/sections.ts +34 -8
  16. package/core/strings.ts +845 -0
  17. package/guides/effects.md +3 -1
  18. package/guides/sounds.md +4 -0
  19. package/guides/tempo.md +1 -0
  20. package/package.json +1 -1
  21. package/src/agent/agent.ts +7 -0
  22. package/src/agent/granular-tools.ts +138 -0
  23. package/src/agent/models.ts +4 -4
  24. package/src/agent/ops.ts +39 -2
  25. package/src/agent/preview-tool.ts +6 -1
  26. package/src/agent/tools.ts +538 -14
  27. package/src/agent/xcb-agent.ts +7 -0
  28. package/src/audio/arrange.ts +21 -3
  29. package/src/audio/dsp/bank.ts +233 -0
  30. package/src/audio/dsp/fft.ts +6 -0
  31. package/src/audio/dsp/filters.ts +57 -0
  32. package/src/audio/dsp/interp.ts +112 -0
  33. package/src/audio/dsp/modal.ts +684 -0
  34. package/src/audio/dsp/onset.ts +197 -0
  35. package/src/audio/dsp/oversample.ts +202 -0
  36. package/src/audio/dsp/rng.ts +37 -0
  37. package/src/audio/dsp/shape.ts +81 -0
  38. package/src/audio/dsp/stft.ts +73 -0
  39. package/src/audio/dsp/window.ts +77 -0
  40. package/src/audio/effects/chain.ts +6 -1
  41. package/src/audio/effects/rig/cab.ts +99 -0
  42. package/src/audio/effects/rig/filters.ts +152 -0
  43. package/src/audio/effects/rig/gate.ts +39 -0
  44. package/src/audio/effects/rig/head.ts +487 -0
  45. package/src/audio/effects/rig/index.ts +170 -0
  46. package/src/audio/effects/rig/section.ts +78 -0
  47. package/src/audio/effects/rig/stomp.ts +238 -0
  48. package/src/audio/engine.ts +10 -1
  49. package/src/audio/fit.ts +402 -0
  50. package/src/audio/granular.ts +664 -0
  51. package/src/audio/instrument-check.ts +133 -0
  52. package/src/audio/instruments.ts +111 -0
  53. package/src/audio/keys/dsp.ts +323 -0
  54. package/src/audio/keys/engine.ts +361 -0
  55. package/src/audio/keys/piano.ts +432 -0
  56. package/src/audio/live-worker.ts +54 -0
  57. package/src/audio/live.ts +231 -12
  58. package/src/audio/loudness.ts +65 -20
  59. package/src/audio/preview.ts +2 -0
  60. package/src/audio/resonators.ts +287 -0
  61. package/src/audio/sampler.ts +163 -17
  62. package/src/audio/samples.ts +30 -7
  63. package/src/audio/strings/body.ts +250 -0
  64. package/src/audio/strings/engine.ts +369 -0
  65. package/src/audio/strings/loop.ts +119 -0
  66. package/src/audio/strings/measure.test-helpers.ts +198 -0
  67. package/src/audio/strings/pluck.ts +354 -0
  68. package/src/audio/warp.ts +61 -0
  69. package/src/audio/wav.ts +78 -6
  70. package/src/commands/expression.ts +9 -2
  71. package/src/commands/fit.ts +135 -0
  72. package/src/commands/fx.ts +31 -0
  73. package/src/commands/granular.ts +427 -0
  74. package/src/commands/help.ts +74 -3
  75. package/src/commands/keys.ts +353 -0
  76. package/src/commands/modal.ts +247 -0
  77. package/src/commands/rig.ts +260 -0
  78. package/src/commands/sample.ts +3 -0
  79. package/src/commands/string.ts +175 -0
  80. package/src/commands/time.ts +4 -1
  81. package/src/main.ts +210 -8
  82. package/src/project/check.ts +13 -0
  83. package/src/render.ts +33 -4
  84. package/src/tui/audition.ts +2 -2
  85. package/src/tui/granular-menu.ts +278 -0
  86. package/src/tui/menu.ts +332 -9
  87. package/src/tui/modal-menu.ts +118 -0
  88. package/src/tui/play-session.ts +45 -2
@@ -0,0 +1,99 @@
1
+ /**
2
+ * Speaker cabinets (`fx.cab`): a parametric speaker, box and microphone
3
+ * model from cheap biquads (no impulse response, no setup cost). Ported
4
+ * from the 0.6 guitar prototype; `8x10`, `1x15` and `di` are dawg
5
+ * additions in the same model.
6
+ */
7
+ import { Rbj, clamp } from "./filters.ts";
8
+
9
+ export const CAB_TYPES = [
10
+ "1x12",
11
+ "2x12",
12
+ "4x12",
13
+ "1x10",
14
+ "open",
15
+ "8x10",
16
+ "1x15",
17
+ "di",
18
+ ] as const;
19
+ export type CabType = (typeof CAB_TYPES)[number];
20
+
21
+ type CabModel = Readonly<{
22
+ hp: number;
23
+ /** Low resonance: Hz, Q, dB. */
24
+ low: readonly [number, number, number];
25
+ peaks: readonly (readonly [number, number, number])[];
26
+ /** Speaker roll-off (two biquads, Butterworth 4th order); 0 = none. */
27
+ lp: number;
28
+ }>;
29
+
30
+ const CABS: Record<CabType, CabModel> = {
31
+ "1x12": {
32
+ hp: 75,
33
+ low: [115, 1.4, 3],
34
+ peaks: [
35
+ [2100, 2, 3],
36
+ [3600, 3, 2],
37
+ ],
38
+ lp: 5200,
39
+ },
40
+ "2x12": {
41
+ hp: 70,
42
+ low: [105, 1.3, 3.5],
43
+ peaks: [
44
+ [1200, 1.5, -2],
45
+ [2800, 2.2, 4.5],
46
+ ],
47
+ lp: 6200,
48
+ },
49
+ "4x12": {
50
+ hp: 65,
51
+ low: [95, 1.2, 5],
52
+ peaks: [
53
+ [700, 1, -2.5],
54
+ [2400, 2, 4],
55
+ [3900, 3, 2],
56
+ ],
57
+ lp: 4700,
58
+ },
59
+ "1x10": { hp: 120, low: [160, 1.5, 2], peaks: [[1500, 1.5, 3]], lp: 4300 },
60
+ open: { hp: 90, low: [120, 0.9, 1], peaks: [[2500, 1.2, 2]], lp: 7000 },
61
+ // Bass cabinets: deep, rolled off early (10s brighter than a 15).
62
+ "8x10": {
63
+ hp: 45,
64
+ low: [80, 1.0, 3],
65
+ peaks: [
66
+ [800, 1.2, -2],
67
+ [2500, 2, 3],
68
+ ],
69
+ lp: 4500,
70
+ },
71
+ "1x15": {
72
+ hp: 40,
73
+ low: [70, 1.2, 4],
74
+ peaks: [
75
+ [1000, 1, -1.5],
76
+ [2000, 1.5, 2],
77
+ ],
78
+ lp: 3500,
79
+ },
80
+ // Direct in: no speaker, only a subsonic high-pass.
81
+ di: { hp: 20, low: [100, 0.7, 0], peaks: [], lp: 0 },
82
+ };
83
+
84
+ /** The cab's filter chain at `sr` for mic position `mic` (0..1). */
85
+ export function cabFilters(type: string, mic: number, sr: number): Rbj[] {
86
+ const m = CABS[type as CabType] ?? CABS["2x12"];
87
+ if (m.lp === 0) return [new Rbj("hpf", m.hp, 0.707, sr)];
88
+ const edge = clamp(mic, 0, 1);
89
+ return [
90
+ new Rbj("hpf", m.hp, 0.707, sr),
91
+ new Rbj("peak", m.low[0], m.low[1], sr, m.low[2]),
92
+ ...m.peaks.map(
93
+ ([f, q, db]) => new Rbj("peak", f, q, sr, db * (1 - 0.6 * edge)),
94
+ ),
95
+ new Rbj("highshelf", 3000, 0.7, sr, 2 - 8 * edge),
96
+ new Rbj("lpf", m.lp * (1 - 0.3 * edge), 0.54, sr),
97
+ new Rbj("lpf", m.lp * (1 - 0.3 * edge), 1.31, sr),
98
+ ];
99
+ }
@@ -0,0 +1,152 @@
1
+ /**
2
+ * Filters for the guitar rig: the full RBJ cookbook biquad (peaks and
3
+ * shelves, which the effects chain's `Biquad` does not offer) and a
4
+ * one-pole high-pass. Ported from the 0.6 guitar prototype; a new class so
5
+ * the shared `Biquad` is never retuned.
6
+ */
7
+
8
+ export function clamp(x: number, lo: number, hi: number): number {
9
+ return x < lo ? lo : x > hi ? hi : x;
10
+ }
11
+
12
+ export function dbToGain(db: number): number {
13
+ return 10 ** (db / 20);
14
+ }
15
+
16
+ export type RbjType = "lpf" | "hpf" | "peak" | "lowshelf" | "highshelf";
17
+
18
+ /** RBJ Audio EQ Cookbook biquad, transposed direct form II. */
19
+ export class Rbj {
20
+ b0 = 1;
21
+ b1 = 0;
22
+ b2 = 0;
23
+ a1 = 0;
24
+ a2 = 0;
25
+ z1 = 0;
26
+ z2 = 0;
27
+
28
+ constructor(type: RbjType, hz: number, q: number, sr: number, db = 0) {
29
+ this.set(type, hz, q, sr, db);
30
+ }
31
+
32
+ set(type: RbjType, hz: number, q: number, sr: number, db = 0): void {
33
+ const w = (2 * Math.PI * clamp(hz, 10, sr * 0.49)) / sr;
34
+ const cw = Math.cos(w);
35
+ const alpha = Math.sin(w) / (2 * q);
36
+ const A = 10 ** (db / 40);
37
+ let b0: number, b1: number, b2: number, a0: number, a1: number, a2: number;
38
+ switch (type) {
39
+ case "lpf":
40
+ b0 = (1 - cw) / 2;
41
+ b1 = 1 - cw;
42
+ b2 = b0;
43
+ a0 = 1 + alpha;
44
+ a1 = -2 * cw;
45
+ a2 = 1 - alpha;
46
+ break;
47
+ case "hpf":
48
+ b0 = (1 + cw) / 2;
49
+ b1 = -(1 + cw);
50
+ b2 = b0;
51
+ a0 = 1 + alpha;
52
+ a1 = -2 * cw;
53
+ a2 = 1 - alpha;
54
+ break;
55
+ case "peak":
56
+ b0 = 1 + alpha * A;
57
+ b1 = -2 * cw;
58
+ b2 = 1 - alpha * A;
59
+ a0 = 1 + alpha / A;
60
+ a1 = -2 * cw;
61
+ a2 = 1 - alpha / A;
62
+ break;
63
+ case "lowshelf": {
64
+ const s = 2 * Math.sqrt(A) * alpha;
65
+ b0 = A * (A + 1 - (A - 1) * cw + s);
66
+ b1 = 2 * A * (A - 1 - (A + 1) * cw);
67
+ b2 = A * (A + 1 - (A - 1) * cw - s);
68
+ a0 = A + 1 + (A - 1) * cw + s;
69
+ a1 = -2 * (A - 1 + (A + 1) * cw);
70
+ a2 = A + 1 + (A - 1) * cw - s;
71
+ break;
72
+ }
73
+ case "highshelf": {
74
+ const s = 2 * Math.sqrt(A) * alpha;
75
+ b0 = A * (A + 1 + (A - 1) * cw + s);
76
+ b1 = -2 * A * (A - 1 + (A + 1) * cw);
77
+ b2 = A * (A + 1 + (A - 1) * cw - s);
78
+ a0 = A + 1 - (A - 1) * cw + s;
79
+ a1 = 2 * (A - 1 - (A + 1) * cw);
80
+ a2 = A + 1 - (A - 1) * cw - s;
81
+ break;
82
+ }
83
+ }
84
+ this.b0 = b0 / a0;
85
+ this.b1 = b1 / a0;
86
+ this.b2 = b2 / a0;
87
+ this.a1 = a1 / a0;
88
+ this.a2 = a2 / a0;
89
+ }
90
+
91
+ process(x: number): number {
92
+ const y = this.b0 * x + this.z1;
93
+ this.z1 = this.b1 * x - this.a1 * y + this.z2;
94
+ this.z2 = this.b2 * x - this.a2 * y;
95
+ return y;
96
+ }
97
+ }
98
+
99
+ /** One-pole low-pass y = x + p (y - x). */
100
+ export class Lp1 {
101
+ p = 0;
102
+ y = 0;
103
+
104
+ constructor(hz: number, sr: number) {
105
+ this.set(hz, sr);
106
+ }
107
+
108
+ set(hz: number, sr: number): void {
109
+ this.p = Math.exp((-2 * Math.PI * clamp(hz, 1, sr * 0.49)) / sr);
110
+ }
111
+
112
+ process(x: number): number {
113
+ this.y = x + this.p * (this.y - x);
114
+ return this.y;
115
+ }
116
+ }
117
+
118
+ /** One-pole high-pass (leaky differentiator). */
119
+ export class Hp1 {
120
+ p = 0;
121
+ x1 = 0;
122
+ y1 = 0;
123
+
124
+ constructor(hz: number, sr: number) {
125
+ this.p = Math.exp((-2 * Math.PI * clamp(hz, 1, sr * 0.49)) / sr);
126
+ }
127
+
128
+ process(x: number): number {
129
+ const y = ((1 + this.p) / 2) * (x - this.x1) + this.p * this.y1;
130
+ this.x1 = x;
131
+ this.y1 = y;
132
+ return y;
133
+ }
134
+ }
135
+
136
+ /** DC blocker y = x - x1 + R y1. */
137
+ export class Dc {
138
+ r: number;
139
+ x1 = 0;
140
+ y1 = 0;
141
+
142
+ constructor(sr: number, hz: number) {
143
+ this.r = Math.exp((-2 * Math.PI * hz) / sr);
144
+ }
145
+
146
+ process(x: number): number {
147
+ const y = x - this.x1 + this.r * this.y1;
148
+ this.x1 = x;
149
+ this.y1 = y;
150
+ return y;
151
+ }
152
+ }
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Noise gate before the amp (`fx.head.gate`): a peak follower with
3
+ * hysteresis (opens at the threshold, closes 6 dB below it), a 50 ms hold,
4
+ * 1 ms attack and 80 ms release, so a high-gain head does not amplify the
5
+ * hiss and string noise between notes.
6
+ */
7
+ export const GATE_HYSTERESIS_DB = 6;
8
+
9
+ export function applyGate(
10
+ buffer: Float64Array,
11
+ thresholdDb: number,
12
+ sampleRate: number,
13
+ ): void {
14
+ const open = 10 ** (thresholdDb / 20);
15
+ const close = 10 ** ((thresholdDb - GATE_HYSTERESIS_DB) / 20);
16
+ const peakRelease = Math.exp(-1 / (0.01 * sampleRate));
17
+ const attack = 1 - Math.exp(-1 / (0.001 * sampleRate));
18
+ const release = 1 - Math.exp(-1 / (0.08 * sampleRate));
19
+ const holdSamples = Math.round(0.05 * sampleRate);
20
+ let peak = 0;
21
+ let gain = 0;
22
+ let isOpen = false;
23
+ let hold = 0;
24
+ for (let i = 0; i < buffer.length; i += 1) {
25
+ const x = buffer[i]!;
26
+ const level = Math.abs(x);
27
+ peak = level > peak ? level : peak * peakRelease;
28
+ if (peak >= open) {
29
+ isOpen = true;
30
+ hold = holdSamples;
31
+ } else if (peak < close) {
32
+ if (hold > 0) hold -= 1;
33
+ else isOpen = false;
34
+ }
35
+ const target = isOpen ? 1 : 0;
36
+ gain += (target - gain) * (target > gain ? attack : release);
37
+ buffer[i] = x * gain;
38
+ }
39
+ }