@mcptoolshop/claude-sfx 1.1.2 → 1.2.1

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/profiles.js CHANGED
@@ -2,251 +2,444 @@
2
2
  * Profile system — configurable sound palettes.
3
3
  * Each profile defines synthesis parameters for every verb + session sounds.
4
4
  * Profiles are JSON-serializable, so users can create their own.
5
+ *
6
+ * Sound design is rooted in A major pentatonic to ensure musical coherence.
5
7
  */
6
8
  import { readFileSync, existsSync } from "node:fs";
7
9
  import { join, dirname } from "node:path";
8
10
  import { fileURLToPath } from "node:url";
11
+ // --- A Major Pentatonic Scale (home key) ---
12
+ export const SCALE = {
13
+ A3: 220.00,
14
+ B3: 246.94,
15
+ Cs4: 277.18,
16
+ E4: 329.63,
17
+ Fs4: 369.99,
18
+ A4: 440.00,
19
+ B4: 493.88,
20
+ Cs5: 554.37,
21
+ E5: 659.25,
22
+ Fs5: 739.99,
23
+ A5: 880.00,
24
+ B5: 987.77,
25
+ };
26
+ /** Ordered scale degrees for modifier lookups (low to high). */
27
+ export const SCALE_DEGREES = [
28
+ SCALE.A3, SCALE.B3, SCALE.Cs4, SCALE.E4, SCALE.Fs4,
29
+ SCALE.A4, SCALE.B4, SCALE.Cs5, SCALE.E5, SCALE.Fs5,
30
+ SCALE.A5, SCALE.B5,
31
+ ];
32
+ /** Get the next lower scale degree. Returns freq × 0.9 as fallback. */
33
+ export function scaleStepDown(freq) {
34
+ for (let i = SCALE_DEGREES.length - 1; i >= 0; i--) {
35
+ if (SCALE_DEGREES[i] < freq - 1)
36
+ return SCALE_DEGREES[i];
37
+ }
38
+ return freq * 0.9;
39
+ }
40
+ /** Find the closest scale degree to a target frequency. */
41
+ export function findClosestScaleDegree(freq) {
42
+ let closest = SCALE_DEGREES[0];
43
+ let minDist = Math.abs(freq - closest);
44
+ for (const deg of SCALE_DEGREES) {
45
+ const dist = Math.abs(freq - deg);
46
+ if (dist < minDist) {
47
+ minDist = dist;
48
+ closest = deg;
49
+ }
50
+ }
51
+ return closest;
52
+ }
53
+ // --- Shared envelope templates ---
54
+ const ENV_INTAKE = { attack: 0.006, decay: 0.045, sustain: 0.18, release: 0.04 };
55
+ const ENV_TRANSFORM = { attack: 0.003, decay: 0.04, sustain: 0.12, release: 0.045 };
56
+ const ENV_COMMIT = { attack: 0.004, decay: 0.06, sustain: 0.20, release: 0.07 };
57
+ const ENV_COMMIT_SPARKLE = { attack: 0.003, decay: 0.04, sustain: 0.15, release: 0.06 };
58
+ const ENV_NAVIGATE = { attack: 0.002, decay: 0.035, sustain: 0.10, release: 0.055 };
59
+ const ENV_EXECUTE = { attack: 0.001, decay: 0.038, sustain: 0.08, release: 0.035 };
9
60
  // --- Built-in profiles ---
61
+ const S = SCALE; // alias for readability
10
62
  const MINIMAL_PROFILE = {
11
63
  name: "minimal",
12
- description: "Tasteful UI tonessubtle, professional, daily-driver",
64
+ description: "A-major pentatonic motifsmusical, cohesive, daily-driver",
13
65
  verbs: {
66
+ // ── intake: light, inviting, curious ───────────────────────────
14
67
  intake: {
15
- type: "tone",
16
- waveform: "sine",
17
- frequency: 440,
18
- frequencyEnd: 620,
19
- duration: 0.15,
20
- envelope: { attack: 0.02, decay: 0.06, sustain: 0.4, release: 0.04 },
21
- gain: 0.6,
68
+ type: "motif",
69
+ gain: 0.55,
70
+ variants: [
71
+ // Variant A: A4 → C#5
72
+ { notes: [
73
+ { frequency: S.A4, duration: 0.055, offsetMs: 0, waveform: "sine", blendWaveform: "triangle", blendAmount: 0.25, envelope: ENV_INTAKE, gain: 1.0 },
74
+ { frequency: S.Cs5, duration: 0.075, offsetMs: 55, waveform: "sine", blendWaveform: "triangle", blendAmount: 0.25, envelope: ENV_INTAKE, gain: 1.0 },
75
+ ] },
76
+ // Variant B: E4 → A4
77
+ { notes: [
78
+ { frequency: S.E4, duration: 0.055, offsetMs: 0, waveform: "sine", blendWaveform: "triangle", blendAmount: 0.25, envelope: ENV_INTAKE, gain: 1.0 },
79
+ { frequency: S.A4, duration: 0.075, offsetMs: 55, waveform: "sine", blendWaveform: "triangle", blendAmount: 0.25, envelope: ENV_INTAKE, gain: 1.0 },
80
+ ] },
81
+ // Variant C: A4 → B4
82
+ { notes: [
83
+ { frequency: S.A4, duration: 0.055, offsetMs: 0, waveform: "sine", blendWaveform: "triangle", blendAmount: 0.25, envelope: ENV_INTAKE, gain: 1.0 },
84
+ { frequency: S.B4, duration: 0.075, offsetMs: 55, waveform: "sine", blendWaveform: "triangle", blendAmount: 0.25, envelope: ENV_INTAKE, gain: 1.0 },
85
+ ] },
86
+ ],
22
87
  },
88
+ // ── transform: active, clever, articulate ─────────────────────
23
89
  transform: {
24
- type: "tone",
25
- waveform: "sine",
26
- frequency: 520,
27
- duration: 0.1,
28
- envelope: { attack: 0.005, decay: 0.03, sustain: 0.2, release: 0.03 },
29
- gain: 0.55,
30
- fmRatio: 1.5,
31
- fmDepth: 30,
90
+ type: "motif",
91
+ gain: 0.52,
92
+ variants: [
93
+ // Variant A: C#5 → B4 → E5 (3-note)
94
+ { notes: [
95
+ { frequency: S.Cs5, duration: 0.042, offsetMs: 0, waveform: "sine", blendWaveform: "triangle", blendAmount: 0.24, envelope: ENV_TRANSFORM, gain: 1.0, fmRatio: 2.0, fmDepth: 12 },
96
+ { frequency: S.B4, duration: 0.036, offsetMs: 42, waveform: "sine", blendWaveform: "triangle", blendAmount: 0.24, envelope: ENV_TRANSFORM, gain: 1.0, fmRatio: 2.0, fmDepth: 10 },
97
+ { frequency: S.E5, duration: 0.067, offsetMs: 78, waveform: "sine", blendWaveform: "triangle", blendAmount: 0.24, envelope: ENV_TRANSFORM, gain: 1.0, fmRatio: 2.0, fmDepth: 8 },
98
+ ] },
99
+ // Variant B: B4 → C#5 → E5
100
+ { notes: [
101
+ { frequency: S.B4, duration: 0.042, offsetMs: 0, waveform: "sine", blendWaveform: "triangle", blendAmount: 0.24, envelope: ENV_TRANSFORM, gain: 1.0, fmRatio: 2.0, fmDepth: 12 },
102
+ { frequency: S.Cs5, duration: 0.036, offsetMs: 42, waveform: "sine", blendWaveform: "triangle", blendAmount: 0.24, envelope: ENV_TRANSFORM, gain: 1.0, fmRatio: 2.0, fmDepth: 10 },
103
+ { frequency: S.E5, duration: 0.067, offsetMs: 78, waveform: "sine", blendWaveform: "triangle", blendAmount: 0.24, envelope: ENV_TRANSFORM, gain: 1.0, fmRatio: 2.0, fmDepth: 8 },
104
+ ] },
105
+ // Variant C: C#5 → E5 (2-note)
106
+ { notes: [
107
+ { frequency: S.Cs5, duration: 0.058, offsetMs: 0, waveform: "sine", blendWaveform: "triangle", blendAmount: 0.24, envelope: ENV_TRANSFORM, gain: 1.0, fmRatio: 2.0, fmDepth: 12 },
108
+ { frequency: S.E5, duration: 0.087, offsetMs: 58, waveform: "sine", blendWaveform: "triangle", blendAmount: 0.24, envelope: ENV_TRANSFORM, gain: 1.0, fmRatio: 2.0, fmDepth: 8 },
109
+ ] },
110
+ ],
32
111
  },
112
+ // ── commit: satisfying, settled, rewarding ────────────────────
33
113
  commit: {
34
- type: "tone",
35
- waveform: "sine",
36
- frequency: 840,
37
- duration: 0.08,
38
- envelope: { attack: 0.005, decay: 0.03, sustain: 0.2, release: 0.03 },
39
- gain: 0.6,
114
+ type: "motif",
115
+ gain: 0.60,
116
+ variants: [
117
+ // Variant A: E4+A4 dyad → C#5 sparkle
118
+ { notes: [
119
+ { frequency: S.E4, duration: 0.110, offsetMs: 0, waveform: "triangle", blendWaveform: "sine", blendAmount: 0.39, envelope: ENV_COMMIT, gain: 0.9, harmonicGain: 0.10 },
120
+ { frequency: S.A4, duration: 0.110, offsetMs: 0, waveform: "triangle", blendWaveform: "sine", blendAmount: 0.39, envelope: ENV_COMMIT, gain: 0.9, harmonicGain: 0.10 },
121
+ { frequency: S.Cs5, duration: 0.085, offsetMs: 55, waveform: "triangle", blendWaveform: "sine", blendAmount: 0.39, envelope: ENV_COMMIT_SPARKLE, gain: 0.7, harmonicGain: 0.10 },
122
+ ] },
123
+ // Variant B: A4+C#5 → E5 sparkle
124
+ { notes: [
125
+ { frequency: S.A4, duration: 0.110, offsetMs: 0, waveform: "triangle", blendWaveform: "sine", blendAmount: 0.39, envelope: ENV_COMMIT, gain: 0.9, harmonicGain: 0.10 },
126
+ { frequency: S.Cs5, duration: 0.110, offsetMs: 0, waveform: "triangle", blendWaveform: "sine", blendAmount: 0.39, envelope: ENV_COMMIT, gain: 0.9, harmonicGain: 0.10 },
127
+ { frequency: S.E5, duration: 0.085, offsetMs: 55, waveform: "triangle", blendWaveform: "sine", blendAmount: 0.39, envelope: ENV_COMMIT_SPARKLE, gain: 0.7, harmonicGain: 0.10 },
128
+ ] },
129
+ // Variant C: E4+B4 → C#5 sparkle
130
+ { notes: [
131
+ { frequency: S.E4, duration: 0.110, offsetMs: 0, waveform: "triangle", blendWaveform: "sine", blendAmount: 0.39, envelope: ENV_COMMIT, gain: 0.9, harmonicGain: 0.10 },
132
+ { frequency: S.B4, duration: 0.110, offsetMs: 0, waveform: "triangle", blendWaveform: "sine", blendAmount: 0.39, envelope: ENV_COMMIT, gain: 0.9, harmonicGain: 0.10 },
133
+ { frequency: S.Cs5, duration: 0.085, offsetMs: 55, waveform: "triangle", blendWaveform: "sine", blendAmount: 0.39, envelope: ENV_COMMIT_SPARKLE, gain: 0.7, harmonicGain: 0.10 },
134
+ ] },
135
+ ],
40
136
  },
137
+ // ── navigate: precise, clean, directional ─────────────────────
41
138
  navigate: {
42
- type: "tone",
43
- waveform: "sine",
44
- frequency: 1050,
45
- duration: 0.2,
46
- envelope: { attack: 0.003, decay: 0.15, sustain: 0.0, release: 0.05 },
47
- gain: 0.5,
139
+ type: "motif",
140
+ gain: 0.50,
141
+ variants: [
142
+ // Variant A: B4 → E5
143
+ { notes: [
144
+ { frequency: S.B4, duration: 0.045, offsetMs: 0, waveform: "triangle", blendWaveform: "sine", blendAmount: 0.33, envelope: ENV_NAVIGATE, gain: 1.0, harmonicGain: 0.10 },
145
+ { frequency: S.E5, duration: 0.095, offsetMs: 39, waveform: "triangle", blendWaveform: "sine", blendAmount: 0.33, envelope: ENV_NAVIGATE, gain: 1.0, harmonicGain: 0.10 },
146
+ ] },
147
+ // Variant B: A4 → E5
148
+ { notes: [
149
+ { frequency: S.A4, duration: 0.045, offsetMs: 0, waveform: "triangle", blendWaveform: "sine", blendAmount: 0.33, envelope: ENV_NAVIGATE, gain: 1.0, harmonicGain: 0.10 },
150
+ { frequency: S.E5, duration: 0.095, offsetMs: 39, waveform: "triangle", blendWaveform: "sine", blendAmount: 0.33, envelope: ENV_NAVIGATE, gain: 1.0, harmonicGain: 0.10 },
151
+ ] },
152
+ // Variant C: B4 → C#5
153
+ { notes: [
154
+ { frequency: S.B4, duration: 0.045, offsetMs: 0, waveform: "triangle", blendWaveform: "sine", blendAmount: 0.33, envelope: ENV_NAVIGATE, gain: 1.0, harmonicGain: 0.10 },
155
+ { frequency: S.Cs5, duration: 0.095, offsetMs: 39, waveform: "triangle", blendWaveform: "sine", blendAmount: 0.33, envelope: ENV_NAVIGATE, gain: 1.0, harmonicGain: 0.10 },
156
+ ] },
157
+ ],
48
158
  },
159
+ // ── execute: grounded, tactile, percussive ────────────────────
49
160
  execute: {
50
- type: "tone",
51
- waveform: "sine",
52
- frequency: 620,
53
- duration: 0.1,
54
- envelope: { attack: 0.005, decay: 0.03, sustain: 0.2, release: 0.03 },
55
- gain: 0.5,
56
- noiseBurst: { duration: 0.04, gain: 0.25 },
161
+ type: "motif",
162
+ gain: 0.58,
163
+ variants: [
164
+ // Variant A: A3 body + noise click
165
+ {
166
+ notes: [
167
+ { frequency: S.A3, duration: 0.095, offsetMs: 15, waveform: "sine", blendWaveform: "triangle", blendAmount: 0.29, envelope: ENV_EXECUTE, gain: 1.0 },
168
+ ],
169
+ noiseTransient: { freqStart: 1200, freqEnd: 1800, bandwidth: 1.2, duration: 0.015, gain: 0.35 },
170
+ },
171
+ // Variant B: E4 body + noise click
172
+ {
173
+ notes: [
174
+ { frequency: S.E4, duration: 0.095, offsetMs: 15, waveform: "sine", blendWaveform: "triangle", blendAmount: 0.29, envelope: ENV_EXECUTE, gain: 1.0 },
175
+ ],
176
+ noiseTransient: { freqStart: 1200, freqEnd: 1800, bandwidth: 1.2, duration: 0.015, gain: 0.35 },
177
+ },
178
+ // Variant C: A3 → E4 quick rise + noise click
179
+ {
180
+ notes: [
181
+ { frequency: S.A3, duration: 0.045, offsetMs: 15, waveform: "sine", blendWaveform: "triangle", blendAmount: 0.29, envelope: ENV_EXECUTE, gain: 1.0 },
182
+ { frequency: S.E4, duration: 0.050, offsetMs: 60, waveform: "sine", blendWaveform: "triangle", blendAmount: 0.29, envelope: ENV_EXECUTE, gain: 1.0 },
183
+ ],
184
+ noiseTransient: { freqStart: 1200, freqEnd: 1800, bandwidth: 1.2, duration: 0.015, gain: 0.35 },
185
+ },
186
+ ],
57
187
  },
188
+ // ── move: airy displacement ───────────────────────────────────
58
189
  move: {
59
190
  type: "whoosh",
60
- duration: 0.25,
61
- freqUp: [600, 2800],
62
- freqDown: [2800, 600],
63
- bandwidth: 0.8,
64
- envelope: { attack: 0.04, decay: 0.08, sustain: 0.5, release: 0.1 },
65
- gain: 0.6,
191
+ duration: 0.24,
192
+ freqUp: [700, 2400],
193
+ freqDown: [2400, 700],
194
+ bandwidth: 0.7,
195
+ envelope: { attack: 0.01, decay: 0.11, sustain: 0.10, release: 0.06 },
196
+ gain: 0.52,
197
+ anchorVariants: [
198
+ // Variant A: E4 → A4
199
+ { notes: [
200
+ { frequency: S.E4, duration: 0.18, offsetMs: 20, waveform: "sine", blendWaveform: "triangle", blendAmount: 0.30, envelope: { attack: 0.015, decay: 0.08, sustain: 0.12, release: 0.05 }, gain: 0.45 },
201
+ { frequency: S.A4, duration: 0.14, offsetMs: 100, waveform: "sine", blendWaveform: "triangle", blendAmount: 0.30, envelope: { attack: 0.01, decay: 0.06, sustain: 0.10, release: 0.04 }, gain: 0.40 },
202
+ ] },
203
+ // Variant B: A4 → C#5
204
+ { notes: [
205
+ { frequency: S.A4, duration: 0.18, offsetMs: 20, waveform: "sine", blendWaveform: "triangle", blendAmount: 0.30, envelope: { attack: 0.015, decay: 0.08, sustain: 0.12, release: 0.05 }, gain: 0.45 },
206
+ { frequency: S.Cs5, duration: 0.14, offsetMs: 100, waveform: "sine", blendWaveform: "triangle", blendAmount: 0.30, envelope: { attack: 0.01, decay: 0.06, sustain: 0.10, release: 0.04 }, gain: 0.40 },
207
+ ] },
208
+ // Variant C: E4 sustained
209
+ { notes: [
210
+ { frequency: S.E4, duration: 0.22, offsetMs: 20, waveform: "sine", blendWaveform: "triangle", blendAmount: 0.30, envelope: { attack: 0.015, decay: 0.10, sustain: 0.12, release: 0.06 }, gain: 0.45 },
211
+ ] },
212
+ ],
66
213
  },
214
+ // ── sync: broader system motion ───────────────────────────────
67
215
  sync: {
68
216
  type: "whoosh",
69
- duration: 0.32,
70
- freqUp: [400, 3200],
71
- freqDown: [3200, 400],
72
- bandwidth: 0.6,
73
- envelope: { attack: 0.05, decay: 0.1, sustain: 0.45, release: 0.12 },
74
- gain: 0.55,
75
- tonalAnchor: {
76
- freqUp: [350, 700],
77
- freqDown: [700, 350],
78
- envelope: { attack: 0.03, decay: 0.1, sustain: 0.15, release: 0.08 },
79
- gain: 0.2,
80
- },
217
+ duration: 0.30,
218
+ freqUp: [500, 2600],
219
+ freqDown: [2600, 500],
220
+ bandwidth: 0.65,
221
+ envelope: { attack: 0.008, decay: 0.12, sustain: 0.16, release: 0.09 },
222
+ gain: 0.58,
223
+ anchorVariants: [
224
+ // Variant A: A3+E4 anchor → C#5 confirmation
225
+ { notes: [
226
+ { frequency: S.A3, duration: 0.16, offsetMs: 0, waveform: "sine", blendWaveform: "triangle", blendAmount: 0.30, envelope: { attack: 0.01, decay: 0.08, sustain: 0.14, release: 0.06 }, gain: 0.50 },
227
+ { frequency: S.E4, duration: 0.16, offsetMs: 0, waveform: "sine", blendWaveform: "triangle", blendAmount: 0.30, envelope: { attack: 0.01, decay: 0.08, sustain: 0.14, release: 0.06 }, gain: 0.45 },
228
+ { frequency: S.Cs5, duration: 0.09, offsetMs: 160, waveform: "sine", blendWaveform: "triangle", blendAmount: 0.25, envelope: { attack: 0.003, decay: 0.04, sustain: 0.10, release: 0.04 }, gain: 0.35 },
229
+ ] },
230
+ // Variant B: E4+A4 anchor → C#5 confirmation
231
+ { notes: [
232
+ { frequency: S.E4, duration: 0.16, offsetMs: 0, waveform: "sine", blendWaveform: "triangle", blendAmount: 0.30, envelope: { attack: 0.01, decay: 0.08, sustain: 0.14, release: 0.06 }, gain: 0.50 },
233
+ { frequency: S.A4, duration: 0.16, offsetMs: 0, waveform: "sine", blendWaveform: "triangle", blendAmount: 0.30, envelope: { attack: 0.01, decay: 0.08, sustain: 0.14, release: 0.06 }, gain: 0.45 },
234
+ { frequency: S.Cs5, duration: 0.09, offsetMs: 160, waveform: "sine", blendWaveform: "triangle", blendAmount: 0.25, envelope: { attack: 0.003, decay: 0.04, sustain: 0.10, release: 0.04 }, gain: 0.35 },
235
+ ] },
236
+ // Variant C: A3+E4 sustained (no confirmation)
237
+ { notes: [
238
+ { frequency: S.A3, duration: 0.20, offsetMs: 0, waveform: "sine", blendWaveform: "triangle", blendAmount: 0.30, envelope: { attack: 0.01, decay: 0.10, sustain: 0.14, release: 0.07 }, gain: 0.50 },
239
+ { frequency: S.E4, duration: 0.20, offsetMs: 0, waveform: "sine", blendWaveform: "triangle", blendAmount: 0.30, envelope: { attack: 0.01, decay: 0.10, sustain: 0.14, release: 0.07 }, gain: 0.45 },
240
+ ] },
241
+ ],
81
242
  },
82
243
  },
244
+ // ── Session sounds (A major pentatonic grammar) ─────────────────
83
245
  sessionStart: {
84
246
  tone1: {
85
247
  waveform: "sine",
86
- frequency: 523,
248
+ blendWaveform: "triangle",
249
+ blendAmount: 0.25,
250
+ frequency: S.A4,
87
251
  duration: 0.18,
88
- envelope: { attack: 0.01, decay: 0.08, sustain: 0.3, release: 0.06 },
89
- gain: 0.5,
90
- harmonicGain: 0.1,
252
+ envelope: { attack: 0.01, decay: 0.08, sustain: 0.25, release: 0.06 },
253
+ gain: 0.60,
254
+ harmonicGain: 0.08,
91
255
  },
92
256
  tone2: {
93
257
  waveform: "sine",
94
- frequency: 659,
258
+ blendWaveform: "triangle",
259
+ blendAmount: 0.25,
260
+ frequency: S.Cs5,
95
261
  duration: 0.22,
96
- envelope: { attack: 0.01, decay: 0.1, sustain: 0.3, release: 0.08 },
97
- gain: 0.45,
98
- harmonicGain: 0.1,
262
+ envelope: { attack: 0.01, decay: 0.10, sustain: 0.25, release: 0.08 },
263
+ gain: 0.55,
264
+ harmonicGain: 0.08,
99
265
  },
100
- staggerSeconds: 0.07,
266
+ staggerSeconds: 0.065,
101
267
  },
102
268
  sessionEnd: {
103
269
  tone1: {
104
270
  waveform: "sine",
105
- frequency: 659,
271
+ blendWaveform: "triangle",
272
+ blendAmount: 0.25,
273
+ frequency: S.Cs5,
106
274
  duration: 0.15,
107
- envelope: { attack: 0.01, decay: 0.08, sustain: 0.2, release: 0.04 },
108
- gain: 0.45,
275
+ envelope: { attack: 0.01, decay: 0.08, sustain: 0.18, release: 0.04 },
276
+ gain: 0.55,
109
277
  },
110
278
  tone2: {
111
279
  waveform: "sine",
112
- frequency: 523,
280
+ blendWaveform: "triangle",
281
+ blendAmount: 0.25,
282
+ frequency: S.A4,
113
283
  duration: 0.25,
114
- envelope: { attack: 0.01, decay: 0.12, sustain: 0.2, release: 0.1 },
115
- gain: 0.4,
284
+ envelope: { attack: 0.01, decay: 0.12, sustain: 0.18, release: 0.10 },
285
+ gain: 0.50,
116
286
  },
117
- staggerSeconds: 0.08,
287
+ staggerSeconds: 0.075,
118
288
  },
119
289
  ambient: {
120
- droneFreq: 150,
290
+ droneFreq: S.A3,
121
291
  droneWaveform: "sine",
122
- droneGain: 0.12,
292
+ droneGain: 0.25,
123
293
  chunkDuration: 2.0,
124
- resolveNote1: 523,
125
- resolveNote2: 659,
294
+ resolveNote1: S.A4,
295
+ resolveNote2: S.E5,
126
296
  resolveWaveform: "sine",
127
- resolveDuration: 0.15,
128
- resolveGain: 0.45,
297
+ resolveDuration: 0.16,
298
+ resolveGain: 0.55,
129
299
  },
130
300
  };
301
+ // --- Retro profile: same motifs, square waveform, snappier envelopes ---
302
+ const ENV_RETRO_SHORT = { attack: 0.002, decay: 0.03, sustain: 0.15, release: 0.02 };
303
+ const ENV_RETRO_MED = { attack: 0.003, decay: 0.035, sustain: 0.12, release: 0.025 };
304
+ const ENV_RETRO_LONG = { attack: 0.004, decay: 0.04, sustain: 0.18, release: 0.04 };
305
+ function sqNote(freq, duration, offsetMs, env, gain = 1.0, opts = {}) {
306
+ return { frequency: freq, duration, offsetMs, waveform: "square", envelope: env, gain, ...opts };
307
+ }
131
308
  const RETRO_PROFILE = {
132
309
  name: "retro",
133
- description: "8-bit micro-chirps — fun but controlled",
310
+ description: "8-bit pentatonic chirps — fun, crisp, controlled",
134
311
  verbs: {
135
312
  intake: {
136
- type: "tone",
137
- waveform: "square",
138
- frequency: 480,
139
- frequencyEnd: 720,
140
- duration: 0.1,
141
- envelope: { attack: 0.005, decay: 0.04, sustain: 0.3, release: 0.02 },
142
- gain: 0.4,
313
+ type: "motif",
314
+ gain: 0.14,
315
+ variants: [
316
+ { notes: [sqNote(S.A4, 0.045, 0, ENV_RETRO_SHORT), sqNote(S.Cs5, 0.060, 45, ENV_RETRO_SHORT)] },
317
+ { notes: [sqNote(S.E4, 0.045, 0, ENV_RETRO_SHORT), sqNote(S.A4, 0.060, 45, ENV_RETRO_SHORT)] },
318
+ { notes: [sqNote(S.A4, 0.045, 0, ENV_RETRO_SHORT), sqNote(S.B4, 0.060, 45, ENV_RETRO_SHORT)] },
319
+ ],
143
320
  },
144
321
  transform: {
145
- type: "tone",
146
- waveform: "square",
147
- frequency: 560,
148
- duration: 0.07,
149
- envelope: { attack: 0.003, decay: 0.03, sustain: 0.15, release: 0.02 },
150
- gain: 0.38,
151
- fmRatio: 2,
152
- fmDepth: 50,
322
+ type: "motif",
323
+ gain: 0.13,
324
+ variants: [
325
+ { notes: [
326
+ sqNote(S.Cs5, 0.035, 0, ENV_RETRO_SHORT, 1.0, { fmRatio: 2.0, fmDepth: 18 }),
327
+ sqNote(S.B4, 0.030, 35, ENV_RETRO_SHORT, 1.0, { fmRatio: 2.0, fmDepth: 15 }),
328
+ sqNote(S.E5, 0.055, 65, ENV_RETRO_SHORT, 1.0, { fmRatio: 2.0, fmDepth: 12 }),
329
+ ] },
330
+ { notes: [
331
+ sqNote(S.B4, 0.035, 0, ENV_RETRO_SHORT, 1.0, { fmRatio: 2.0, fmDepth: 18 }),
332
+ sqNote(S.Cs5, 0.030, 35, ENV_RETRO_SHORT, 1.0, { fmRatio: 2.0, fmDepth: 15 }),
333
+ sqNote(S.E5, 0.055, 65, ENV_RETRO_SHORT, 1.0, { fmRatio: 2.0, fmDepth: 12 }),
334
+ ] },
335
+ { notes: [
336
+ sqNote(S.Cs5, 0.048, 0, ENV_RETRO_SHORT, 1.0, { fmRatio: 2.0, fmDepth: 18 }),
337
+ sqNote(S.E5, 0.072, 48, ENV_RETRO_SHORT, 1.0, { fmRatio: 2.0, fmDepth: 12 }),
338
+ ] },
339
+ ],
153
340
  },
154
341
  commit: {
155
- type: "tone",
156
- waveform: "square",
157
- frequency: 880,
158
- duration: 0.06,
159
- envelope: { attack: 0.002, decay: 0.02, sustain: 0.1, release: 0.02 },
160
- gain: 0.42,
342
+ type: "motif",
343
+ gain: 0.16,
344
+ variants: [
345
+ { notes: [
346
+ sqNote(S.E4, 0.090, 0, ENV_RETRO_LONG, 0.85, { harmonicGain: 0.08 }),
347
+ sqNote(S.A4, 0.090, 0, ENV_RETRO_LONG, 0.85, { harmonicGain: 0.08 }),
348
+ sqNote(S.Cs5, 0.070, 45, ENV_RETRO_MED, 0.65, { harmonicGain: 0.08 }),
349
+ ] },
350
+ { notes: [
351
+ sqNote(S.A4, 0.090, 0, ENV_RETRO_LONG, 0.85, { harmonicGain: 0.08 }),
352
+ sqNote(S.Cs5, 0.090, 0, ENV_RETRO_LONG, 0.85, { harmonicGain: 0.08 }),
353
+ sqNote(S.E5, 0.070, 45, ENV_RETRO_MED, 0.65, { harmonicGain: 0.08 }),
354
+ ] },
355
+ { notes: [
356
+ sqNote(S.E4, 0.090, 0, ENV_RETRO_LONG, 0.85, { harmonicGain: 0.08 }),
357
+ sqNote(S.B4, 0.090, 0, ENV_RETRO_LONG, 0.85, { harmonicGain: 0.08 }),
358
+ sqNote(S.Cs5, 0.070, 45, ENV_RETRO_MED, 0.65, { harmonicGain: 0.08 }),
359
+ ] },
360
+ ],
161
361
  },
162
362
  navigate: {
163
- type: "tone",
164
- waveform: "square",
165
- frequency: 1100,
166
- duration: 0.12,
167
- envelope: { attack: 0.002, decay: 0.08, sustain: 0.0, release: 0.03 },
168
- gain: 0.38,
363
+ type: "motif",
364
+ gain: 0.12,
365
+ variants: [
366
+ { notes: [sqNote(S.B4, 0.038, 0, ENV_RETRO_SHORT, 1.0, { harmonicGain: 0.08 }), sqNote(S.E5, 0.078, 32, ENV_RETRO_SHORT, 1.0, { harmonicGain: 0.08 })] },
367
+ { notes: [sqNote(S.A4, 0.038, 0, ENV_RETRO_SHORT, 1.0, { harmonicGain: 0.08 }), sqNote(S.E5, 0.078, 32, ENV_RETRO_SHORT, 1.0, { harmonicGain: 0.08 })] },
368
+ { notes: [sqNote(S.B4, 0.038, 0, ENV_RETRO_SHORT, 1.0, { harmonicGain: 0.08 }), sqNote(S.Cs5, 0.078, 32, ENV_RETRO_SHORT, 1.0, { harmonicGain: 0.08 })] },
369
+ ],
169
370
  },
170
371
  execute: {
171
- type: "tone",
172
- waveform: "square",
173
- frequency: 660,
174
- duration: 0.08,
175
- envelope: { attack: 0.002, decay: 0.03, sustain: 0.15, release: 0.02 },
176
- gain: 0.38,
177
- noiseBurst: { duration: 0.03, gain: 0.3 },
372
+ type: "motif",
373
+ gain: 0.15,
374
+ variants: [
375
+ { notes: [sqNote(S.A3, 0.078, 12, ENV_RETRO_SHORT)], noiseTransient: { freqStart: 1200, freqEnd: 1800, bandwidth: 1.0, duration: 0.012, gain: 0.40 } },
376
+ { notes: [sqNote(S.E4, 0.078, 12, ENV_RETRO_SHORT)], noiseTransient: { freqStart: 1200, freqEnd: 1800, bandwidth: 1.0, duration: 0.012, gain: 0.40 } },
377
+ { notes: [sqNote(S.A3, 0.038, 12, ENV_RETRO_SHORT), sqNote(S.E4, 0.042, 50, ENV_RETRO_SHORT)], noiseTransient: { freqStart: 1200, freqEnd: 1800, bandwidth: 1.0, duration: 0.012, gain: 0.40 } },
378
+ ],
178
379
  },
179
380
  move: {
180
381
  type: "whoosh",
181
- duration: 0.18,
182
- freqUp: [800, 3500],
183
- freqDown: [3500, 800],
184
- bandwidth: 1.0,
185
- envelope: { attack: 0.02, decay: 0.06, sustain: 0.4, release: 0.06 },
186
- gain: 0.45,
382
+ duration: 0.19,
383
+ freqUp: [800, 2800],
384
+ freqDown: [2800, 800],
385
+ bandwidth: 0.9,
386
+ envelope: { attack: 0.008, decay: 0.08, sustain: 0.08, release: 0.04 },
387
+ gain: 0.14,
388
+ anchorVariants: [
389
+ { notes: [sqNote(S.E4, 0.15, 16, { attack: 0.01, decay: 0.06, sustain: 0.10, release: 0.04 }, 0.40), sqNote(S.A4, 0.11, 80, { attack: 0.008, decay: 0.05, sustain: 0.08, release: 0.03 }, 0.35)] },
390
+ { notes: [sqNote(S.A4, 0.15, 16, { attack: 0.01, decay: 0.06, sustain: 0.10, release: 0.04 }, 0.40), sqNote(S.Cs5, 0.11, 80, { attack: 0.008, decay: 0.05, sustain: 0.08, release: 0.03 }, 0.35)] },
391
+ { notes: [sqNote(S.E4, 0.18, 16, { attack: 0.01, decay: 0.08, sustain: 0.10, release: 0.04 }, 0.40)] },
392
+ ],
187
393
  },
188
394
  sync: {
189
395
  type: "whoosh",
190
- duration: 0.22,
191
- freqUp: [500, 4000],
192
- freqDown: [4000, 500],
193
- bandwidth: 0.8,
194
- envelope: { attack: 0.03, decay: 0.06, sustain: 0.35, release: 0.08 },
195
- gain: 0.42,
196
- tonalAnchor: {
197
- freqUp: [400, 800],
198
- freqDown: [800, 400],
199
- envelope: { attack: 0.02, decay: 0.06, sustain: 0.1, release: 0.05 },
200
- gain: 0.18,
201
- },
396
+ duration: 0.24,
397
+ freqUp: [600, 3000],
398
+ freqDown: [3000, 600],
399
+ bandwidth: 0.75,
400
+ envelope: { attack: 0.006, decay: 0.09, sustain: 0.12, release: 0.06 },
401
+ gain: 0.15,
402
+ anchorVariants: [
403
+ { notes: [
404
+ sqNote(S.A3, 0.13, 0, { attack: 0.008, decay: 0.06, sustain: 0.12, release: 0.04 }, 0.45),
405
+ sqNote(S.E4, 0.13, 0, { attack: 0.008, decay: 0.06, sustain: 0.12, release: 0.04 }, 0.40),
406
+ sqNote(S.Cs5, 0.07, 130, { attack: 0.002, decay: 0.03, sustain: 0.08, release: 0.03 }, 0.30),
407
+ ] },
408
+ { notes: [
409
+ sqNote(S.E4, 0.13, 0, { attack: 0.008, decay: 0.06, sustain: 0.12, release: 0.04 }, 0.45),
410
+ sqNote(S.A4, 0.13, 0, { attack: 0.008, decay: 0.06, sustain: 0.12, release: 0.04 }, 0.40),
411
+ sqNote(S.Cs5, 0.07, 130, { attack: 0.002, decay: 0.03, sustain: 0.08, release: 0.03 }, 0.30),
412
+ ] },
413
+ { notes: [
414
+ sqNote(S.A3, 0.16, 0, { attack: 0.008, decay: 0.08, sustain: 0.12, release: 0.05 }, 0.45),
415
+ sqNote(S.E4, 0.16, 0, { attack: 0.008, decay: 0.08, sustain: 0.12, release: 0.05 }, 0.40),
416
+ ] },
417
+ ],
202
418
  },
203
419
  },
204
420
  sessionStart: {
205
- tone1: {
206
- waveform: "square",
207
- frequency: 523,
208
- duration: 0.1,
209
- envelope: { attack: 0.005, decay: 0.04, sustain: 0.2, release: 0.03 },
210
- gain: 0.4,
211
- },
212
- tone2: {
213
- waveform: "square",
214
- frequency: 659,
215
- duration: 0.12,
216
- envelope: { attack: 0.005, decay: 0.05, sustain: 0.2, release: 0.04 },
217
- gain: 0.38,
218
- },
219
- staggerSeconds: 0.06,
421
+ tone1: { waveform: "square", frequency: S.A4, duration: 0.10, envelope: { attack: 0.003, decay: 0.04, sustain: 0.18, release: 0.02 }, gain: 0.15 },
422
+ tone2: { waveform: "square", frequency: S.Cs5, duration: 0.12, envelope: { attack: 0.003, decay: 0.05, sustain: 0.18, release: 0.03 }, gain: 0.14 },
423
+ staggerSeconds: 0.055,
220
424
  },
221
425
  sessionEnd: {
222
- tone1: {
223
- waveform: "square",
224
- frequency: 659,
225
- duration: 0.08,
226
- envelope: { attack: 0.005, decay: 0.04, sustain: 0.15, release: 0.02 },
227
- gain: 0.38,
228
- },
229
- tone2: {
230
- waveform: "square",
231
- frequency: 523,
232
- duration: 0.14,
233
- envelope: { attack: 0.005, decay: 0.06, sustain: 0.15, release: 0.05 },
234
- gain: 0.35,
235
- },
236
- staggerSeconds: 0.06,
426
+ tone1: { waveform: "square", frequency: S.Cs5, duration: 0.08, envelope: { attack: 0.003, decay: 0.04, sustain: 0.12, release: 0.02 }, gain: 0.14 },
427
+ tone2: { waveform: "square", frequency: S.A4, duration: 0.14, envelope: { attack: 0.003, decay: 0.06, sustain: 0.12, release: 0.04 }, gain: 0.12 },
428
+ staggerSeconds: 0.055,
237
429
  },
238
430
  ambient: {
239
- droneFreq: 120,
431
+ droneFreq: S.A3,
240
432
  droneWaveform: "square",
241
- droneGain: 0.08,
433
+ droneGain: 0.06,
242
434
  chunkDuration: 2.0,
243
- resolveNote1: 523,
244
- resolveNote2: 784,
435
+ resolveNote1: S.A4,
436
+ resolveNote2: S.E5,
245
437
  resolveWaveform: "square",
246
- resolveDuration: 0.1,
247
- resolveGain: 0.35,
438
+ resolveDuration: 0.10,
439
+ resolveGain: 0.14,
248
440
  },
249
441
  };
442
+ // --- Profile registry ---
250
443
  const BUILTIN_PROFILES = {
251
444
  minimal: MINIMAL_PROFILE,
252
445
  retro: RETRO_PROFILE,
@@ -264,7 +457,6 @@ export function listBuiltinProfiles() {
264
457
  export function loadProfileFromFile(filePath) {
265
458
  const raw = readFileSync(filePath, "utf-8");
266
459
  const parsed = JSON.parse(raw);
267
- // Basic validation
268
460
  if (!parsed.name || !parsed.verbs || !parsed.sessionStart || !parsed.sessionEnd) {
269
461
  throw new Error(`Invalid profile: missing required fields in ${filePath}`);
270
462
  }
@@ -277,18 +469,15 @@ export function loadProfileFromFile(filePath) {
277
469
  * 3. Treat as absolute/relative file path
278
470
  */
279
471
  export function resolveProfile(nameOrPath) {
280
- // Built-in?
281
472
  const builtin = getBuiltinProfile(nameOrPath);
282
473
  if (builtin)
283
474
  return builtin;
284
- // Check profiles/ directory relative to this package
285
475
  const __dirname = dirname(fileURLToPath(import.meta.url));
286
476
  const profilesDir = join(__dirname, "..", "profiles");
287
477
  const inProfilesDir = join(profilesDir, `${nameOrPath}.json`);
288
478
  if (existsSync(inProfilesDir)) {
289
479
  return loadProfileFromFile(inProfilesDir);
290
480
  }
291
- // Treat as file path
292
481
  if (existsSync(nameOrPath)) {
293
482
  return loadProfileFromFile(nameOrPath);
294
483
  }