@voicethere/agent 0.7.11 → 0.7.13

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.
@@ -8,6 +8,8 @@ const SHOWCASE_DEMOS = new Set<string>(["orbit", "soundboard", "proximity"]);
8
8
 
9
9
  export const MAX_SAY_TEXT_LENGTH = 200;
10
10
  export const MAX_XZ = 5;
11
+ export const ORBIT_SINE_MIN_FREQUENCY_HZ = 110;
12
+ export const ORBIT_SINE_MAX_FREQUENCY_HZ = 1760;
11
13
 
12
14
  export type ShowcaseJoinMessage = {
13
15
  type: "join";
@@ -22,6 +24,8 @@ export type ShowcaseOrbitSetMessage = {
22
24
  periodSec?: number;
23
25
  elevation?: number;
24
26
  paused?: boolean;
27
+ frequencyHz?: number;
28
+ sinePlaying?: boolean;
25
29
  };
26
30
 
27
31
  export type ShowcaseOrbitSayMessage = {
@@ -30,6 +34,13 @@ export type ShowcaseOrbitSayMessage = {
30
34
  text: string;
31
35
  };
32
36
 
37
+ export type ShowcaseOrbitPlaceMessage = {
38
+ type: "orbit";
39
+ action: "place";
40
+ x: number;
41
+ z: number;
42
+ };
43
+
33
44
  export type ShowcasePadMessage = {
34
45
  type: "pad";
35
46
  clipId: string;
@@ -49,6 +60,13 @@ export type ShowcasePadStatusMessage = {
49
60
  playId: string;
50
61
  };
51
62
 
63
+ export type ShowcasePadMoveMessage = {
64
+ type: "pad_move";
65
+ playId: string;
66
+ x: number;
67
+ z: number;
68
+ };
69
+
52
70
  export type ShowcaseMoveMessage = {
53
71
  type: "move";
54
72
  x: number;
@@ -74,9 +92,11 @@ export type ShowcaseInbound =
74
92
  | ShowcaseJoinMessage
75
93
  | ShowcaseOrbitSetMessage
76
94
  | ShowcaseOrbitSayMessage
95
+ | ShowcaseOrbitPlaceMessage
77
96
  | ShowcasePadMessage
78
97
  | ShowcasePadStopMessage
79
98
  | ShowcasePadStatusMessage
99
+ | ShowcasePadMoveMessage
80
100
  | ShowcaseMoveMessage
81
101
  | ShowcaseMutePeerMessage
82
102
  | ShowcaseLeaveMessage
@@ -98,6 +118,11 @@ export type ShowcaseOrbitPose = {
98
118
  z: number;
99
119
  };
100
120
 
121
+ export type ShowcaseOrbitSayStatus = {
122
+ type: "orbit_say_status";
123
+ status: "playing" | "ended";
124
+ };
125
+
101
126
  export type ShowcasePadProgress = {
102
127
  type: "pad_progress";
103
128
  playId: string;
@@ -127,6 +152,7 @@ export type ShowcaseError = {
127
152
  export type ShowcaseOutbound =
128
153
  | ShowcaseAck
129
154
  | ShowcaseOrbitPose
155
+ | ShowcaseOrbitSayStatus
130
156
  | ShowcasePadProgress
131
157
  | ShowcaseRoomState
132
158
  | ShowcaseRoomFull
@@ -144,6 +170,12 @@ function isInXzRange(value: number): boolean {
144
170
  return value >= -MAX_XZ && value <= MAX_XZ;
145
171
  }
146
172
 
173
+ function isOrbitSineFrequencyHz(value: number): boolean {
174
+ return (
175
+ value >= ORBIT_SINE_MIN_FREQUENCY_HZ && value <= ORBIT_SINE_MAX_FREQUENCY_HZ
176
+ );
177
+ }
178
+
147
179
  function isMixPlacement(value: unknown): value is MixPlacement {
148
180
  return (
149
181
  value === "center" ||
@@ -164,7 +196,9 @@ export function isShowcaseDemo(value: unknown): value is ShowcaseDemo {
164
196
  return typeof value === "string" && SHOWCASE_DEMOS.has(value);
165
197
  }
166
198
 
167
- export function isShowcaseInbound(message: unknown): message is ShowcaseInbound {
199
+ export function isShowcaseInbound(
200
+ message: unknown,
201
+ ): message is ShowcaseInbound {
168
202
  return parseShowcaseMessage(message) !== null;
169
203
  }
170
204
 
@@ -219,6 +253,19 @@ export function parseShowcaseMessage(message: unknown): ShowcaseInbound | null {
219
253
  if (set.paused !== undefined && typeof set.paused !== "boolean") {
220
254
  return null;
221
255
  }
256
+ if (
257
+ set.frequencyHz !== undefined &&
258
+ (!isFiniteNumber(set.frequencyHz) ||
259
+ !isOrbitSineFrequencyHz(set.frequencyHz))
260
+ ) {
261
+ return null;
262
+ }
263
+ if (
264
+ set.sinePlaying !== undefined &&
265
+ typeof set.sinePlaying !== "boolean"
266
+ ) {
267
+ return null;
268
+ }
222
269
  return {
223
270
  type: "orbit",
224
271
  action: "set",
@@ -226,8 +273,24 @@ export function parseShowcaseMessage(message: unknown): ShowcaseInbound | null {
226
273
  ...(set.periodSec !== undefined ? { periodSec: set.periodSec } : {}),
227
274
  ...(set.elevation !== undefined ? { elevation: set.elevation } : {}),
228
275
  ...(set.paused !== undefined ? { paused: set.paused } : {}),
276
+ ...(set.frequencyHz !== undefined
277
+ ? { frequencyHz: set.frequencyHz }
278
+ : {}),
279
+ ...(set.sinePlaying !== undefined
280
+ ? { sinePlaying: set.sinePlaying }
281
+ : {}),
229
282
  };
230
283
  }
284
+ if (orbit.action === "place") {
285
+ const place = message as ShowcaseOrbitPlaceMessage;
286
+ if (!isFiniteNumber(place.x) || !isInXzRange(place.x)) {
287
+ return null;
288
+ }
289
+ if (!isFiniteNumber(place.z) || !isInXzRange(place.z)) {
290
+ return null;
291
+ }
292
+ return { type: "orbit", action: "place", x: place.x, z: place.z };
293
+ }
231
294
  if (orbit.action === "say") {
232
295
  const say = message as ShowcaseOrbitSayMessage;
233
296
  if (
@@ -285,6 +348,24 @@ export function parseShowcaseMessage(message: unknown): ShowcaseInbound | null {
285
348
  }
286
349
  return { type: "pad_status", playId: status.playId.trim() };
287
350
  }
351
+ case "pad_move": {
352
+ const padMove = message as ShowcasePadMoveMessage;
353
+ if (!isNonEmptyString(padMove.playId)) {
354
+ return null;
355
+ }
356
+ if (!isFiniteNumber(padMove.x) || !isInXzRange(padMove.x)) {
357
+ return null;
358
+ }
359
+ if (!isFiniteNumber(padMove.z) || !isInXzRange(padMove.z)) {
360
+ return null;
361
+ }
362
+ return {
363
+ type: "pad_move",
364
+ playId: padMove.playId.trim(),
365
+ x: padMove.x,
366
+ z: padMove.z,
367
+ };
368
+ }
288
369
  case "move": {
289
370
  const move = message as ShowcaseMoveMessage;
290
371
  if (!isFiniteNumber(move.x) || !isInXzRange(move.x)) {
@@ -0,0 +1,66 @@
1
+ /** Inline orbit sine clip — 16 kHz mono s16le, decoded size under 64 KiB play cap. */
2
+
3
+ /** 2 s keeps loop-restart gaps infrequent; 16 kHz mono still fits under 64 KiB. */
4
+ export const ORBIT_SINE_DURATION_MS = 2000;
5
+ export const ORBIT_SINE_FREQUENCY_HZ = 440;
6
+ export const ORBIT_SINE_AMPLITUDE = 8000;
7
+ export const ORBIT_SINE_SAMPLE_RATE = 16_000;
8
+ export const ORBIT_SINE_INLINE_URL =
9
+ "https://example.com/inline-orbit-sine.wav";
10
+
11
+ /** Snap duration to an integer number of cycles so the clip loops at a zero crossing. */
12
+ export function orbitSineSampleCount(
13
+ durationMs = ORBIT_SINE_DURATION_MS,
14
+ frequencyHz = ORBIT_SINE_FREQUENCY_HZ,
15
+ sampleRate = ORBIT_SINE_SAMPLE_RATE,
16
+ ): number {
17
+ const targetCycles = (durationMs / 1000) * frequencyHz;
18
+ const cycles = Math.max(1, Math.floor(targetCycles));
19
+ return Math.round((cycles * sampleRate) / frequencyHz);
20
+ }
21
+
22
+ /** Duration in ms for a clip built with {@link orbitSineSampleCount}. */
23
+ export function orbitSineClipDurationMs(
24
+ durationMs = ORBIT_SINE_DURATION_MS,
25
+ frequencyHz = ORBIT_SINE_FREQUENCY_HZ,
26
+ sampleRate = ORBIT_SINE_SAMPLE_RATE,
27
+ ): number {
28
+ const numSamples = orbitSineSampleCount(durationMs, frequencyHz, sampleRate);
29
+ return (numSamples / sampleRate) * 1000;
30
+ }
31
+
32
+ /** Looping sine WAV as base64 for {@link play} inline bytes. */
33
+ export function buildOrbitSineInlineClipBase64(
34
+ durationMs = ORBIT_SINE_DURATION_MS,
35
+ frequencyHz = ORBIT_SINE_FREQUENCY_HZ,
36
+ amplitude = ORBIT_SINE_AMPLITUDE,
37
+ phaseRad = 0,
38
+ ): string {
39
+ const sampleRate = ORBIT_SINE_SAMPLE_RATE;
40
+ const channels = 1;
41
+ const bytesPerSample = 2;
42
+ const numSamples = orbitSineSampleCount(durationMs, frequencyHz, sampleRate);
43
+ const dataSize = numSamples * channels * bytesPerSample;
44
+ const buffer = Buffer.alloc(44 + dataSize);
45
+ buffer.write("RIFF", 0);
46
+ buffer.writeUInt32LE(36 + dataSize, 4);
47
+ buffer.write("WAVE", 8);
48
+ buffer.write("fmt ", 12);
49
+ buffer.writeUInt32LE(16, 16);
50
+ buffer.writeUInt16LE(1, 20);
51
+ buffer.writeUInt16LE(channels, 22);
52
+ buffer.writeUInt32LE(sampleRate, 24);
53
+ buffer.writeUInt32LE(sampleRate * channels * bytesPerSample, 28);
54
+ buffer.writeUInt16LE(channels * bytesPerSample, 32);
55
+ buffer.writeUInt16LE(16, 34);
56
+ buffer.write("data", 36);
57
+ buffer.writeUInt32LE(dataSize, 40);
58
+ for (let i = 0; i < numSamples; i++) {
59
+ const t = i / sampleRate;
60
+ const sample = Math.round(
61
+ amplitude * Math.sin(2 * Math.PI * frequencyHz * t + phaseRad),
62
+ );
63
+ buffer.writeInt16LE(sample, 44 + i * 2);
64
+ }
65
+ return buffer.toString("base64");
66
+ }