tci-client-node 0.2.0 → 0.3.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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.3.0
4
+
5
+ - Add standard TCI IQ capability discovery and dialect-specific sample-rate lists.
6
+ - Track IQ sample-rate, receiver lifecycle, and DDS center-frequency state.
7
+ - Add owned IQ stream sessions with first-frame startup acknowledgement and sample-rate readback.
8
+ - Emit typed IQ frames and add interleaved I/Q decoding helpers.
9
+ - Extend the mock TCI server with standard IQ frames and lifecycle commands.
10
+
3
11
  ## 0.2.0
4
12
 
5
13
  - Add an extensible TCI transport, dialect registry, strict handshake, and protocol identity model.
package/README.md CHANGED
@@ -6,7 +6,7 @@ TCI is a WebSocket protocol: text commands are used for CAT-style radio control,
6
6
 
7
7
  ## Status
8
8
 
9
- `0.2.x` provides a dialect-aware client for application integrations:
9
+ `0.3.x` provides a dialect-aware client for application integrations:
10
10
 
11
11
  - Strict WebSocket and READY/startup handshake validation
12
12
  - Automatic ExpertSDR 1.4, 1.5-1.8, 1.9-2.0, AetherSDR, and Thetis dialect selection
@@ -14,10 +14,11 @@ TCI is a WebSocket protocol: text commands are used for CAT-style radio control,
14
14
  - Frequency, mode, PTT, tune, drive, split, and CW text/macros
15
15
  - RX and TX sensor state parsing
16
16
  - RX audio, TX audio, TX_CHRONO, and line-out stream frame parsing/building
17
+ - Standard IQ capability discovery, DDS tracking, sample-rate readback, and owned IQ stream sessions
17
18
  - Serial command queue with timeout, cancellation, and interleaved broadcast handling
18
19
  - Mock TCI server and fake WebSocket transport for integration tests
19
20
 
20
- Panadapter, IQ UI, skimmer, and spots APIs are intentionally out of scope for the first release, but the protocol layer is designed to be extended.
21
+ Panadapter rendering, skimmer DSP, and spots APIs remain application concerns; this package exposes the standard IQ transport without imposing a spectrum implementation.
21
22
 
22
23
  ## Install
23
24
 
@@ -67,6 +68,27 @@ await client.startAudio();
67
68
  await client.setPtt(true, { source: 'tci' });
68
69
  ```
69
70
 
71
+ ## IQ Streams
72
+
73
+ ```ts
74
+ import { decodeInterleavedIq } from 'tci-client-node';
75
+
76
+ const capabilities = client.getIqCapabilities();
77
+ if (capabilities.supported) {
78
+ const iq = await client.openIqStream({ receiver: 0, sampleRate: 96_000 });
79
+ iq.on('frame', (event) => {
80
+ const interleaved = decodeInterleavedIq(event.frame);
81
+ console.log(event.centerFrequency, event.sampleRate, interleaved.length / 2);
82
+ });
83
+
84
+ const applied = await iq.setSampleRate(48_000);
85
+ console.log(applied.requested, applied.applied);
86
+ await iq.close();
87
+ }
88
+ ```
89
+
90
+ `openIqStream()` resolves on the first valid IQ frame rather than relying on an `IQ_START` echo, because some compatible servers intentionally do not echo that command. The returned session owns the subscription and sends best-effort `IQ_STOP` when closed.
91
+
70
92
  `connect()` resolves only after a valid `READY;` initialization sequence. A WebSocket that opens but does not provide enough TCI initialization evidence is rejected. Use `dialect: 'thetis-2.0'` or another dialect ID only when an incomplete server cannot be identified automatically.
71
93
 
72
94
  ## Dialects
@@ -137,10 +159,8 @@ The package is built with `tsup` and publishes ESM, CommonJS, and declaration fi
137
159
  Releases are published by GitHub Actions when a `v*` tag is pushed. The tag must
138
160
  match `package.json` exactly, for example `v0.1.0` for version `0.1.0`.
139
161
 
140
- The workflow mirrors the `icom-wlan-node` release shape: install with `npm ci`,
141
- typecheck, build, test, verify the package contents, and publish to npm using
142
- the `NPM_TOKEN` repository secret. Provenance is enabled through npm's
143
- `publishConfig`.
162
+ The workflow installs with `npm ci`, typechecks, builds, tests, verifies package
163
+ contents, and publishes through npm Trusted Publishing (OIDC) with provenance.
144
164
 
145
165
  ## References
146
166
 
@@ -25,6 +25,7 @@ __export(audio_exports, {
25
25
  TciStreamType: () => TciStreamType,
26
26
  buildStreamFrame: () => buildStreamFrame,
27
27
  buildTxAudioFrame: () => buildTxAudioFrame,
28
+ decodeInterleavedIq: () => decodeInterleavedIq,
28
29
  deinterleaveChannels: () => deinterleaveChannels,
29
30
  float32ToPcm16: () => float32ToPcm16,
30
31
  mixToMono: () => mixToMono,
@@ -269,6 +270,15 @@ function payloadToFloat32(frameOrPayload, sampleType) {
269
270
  }
270
271
  return output;
271
272
  }
273
+ function decodeInterleavedIq(frame) {
274
+ if (frame.streamType !== 0 /* IQ_STREAM */) {
275
+ throw new TciError("invalid-frame", "Expected a TCI IQ stream frame");
276
+ }
277
+ if (frame.channels !== 2 || frame.sampleCount % 2 !== 0) {
278
+ throw new TciError("invalid-frame", `TCI IQ frame must contain interleaved I/Q pairs, got ${frame.channels} channels`);
279
+ }
280
+ return payloadToFloat32(frame);
281
+ }
272
282
  function samplesToPayload(samples, sampleType) {
273
283
  const type = normalizeSampleType(sampleType);
274
284
  const bytes = sampleTypeBytes(type);
@@ -371,6 +381,7 @@ function writeInt24(view, offset, value) {
371
381
  TciStreamType,
372
382
  buildStreamFrame,
373
383
  buildTxAudioFrame,
384
+ decodeInterleavedIq,
374
385
  deinterleaveChannels,
375
386
  float32ToPcm16,
376
387
  mixToMono,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/audio/index.ts","../../src/errors.ts","../../src/audio/streamFrame.ts"],"sourcesContent":["export * from './streamFrame.js';\n","export type TciErrorCode =\n | 'connect-timeout'\n | 'handshake-timeout'\n | 'invalid-handshake'\n | 'unknown-dialect'\n | 'command-timeout'\n | 'not-connected'\n | 'disconnected'\n | 'protocol-error'\n | 'invalid-frame'\n | 'cancelled';\n\nexport class TciError extends Error {\n readonly code: TciErrorCode;\n readonly details?: unknown;\n\n constructor(code: TciErrorCode, message: string, details?: unknown) {\n super(message);\n this.name = 'TciError';\n this.code = code;\n this.details = details;\n }\n}\n\nexport function toTciError(error: unknown, fallbackCode: TciErrorCode = 'protocol-error'): TciError {\n if (error instanceof TciError) {\n return error;\n }\n if (error instanceof Error) {\n return new TciError(fallbackCode, error.message, error);\n }\n return new TciError(fallbackCode, String(error), error);\n}\n","import { TciError } from '../errors.js';\nimport type { TciStreamLengthSemantics } from '../dialect/types.js';\n\nexport const TCI_STREAM_HEADER_BYTES = 16 * 4;\n\nexport enum TciStreamType {\n IQ_STREAM = 0,\n RX_AUDIO_STREAM = 1,\n TX_AUDIO_STREAM = 2,\n TX_CHRONO = 3,\n LINEOUT_STREAM = 4,\n}\n\nexport enum TciSampleType {\n INT16 = 0,\n INT24 = 1,\n INT32 = 2,\n FLOAT32 = 3,\n}\n\nexport type TciSampleTypeName = 'int16' | 'int24' | 'int32' | 'float32';\n\nexport interface TciStreamFrame {\n receiver: number;\n sampleRate: number;\n sampleType: TciSampleType;\n codec: number;\n crc: number;\n /** Byte length of the payload following the 64-byte TCI stream header. */\n payloadLength: number;\n streamType: TciStreamType;\n channels: number;\n reserved: number[];\n payload: Buffer;\n /** Raw Stream.length value from the wire header. */\n headerSampleCount: number;\n /** Canonical number of scalar sample values across all channels. */\n sampleCount: number;\n /** Number of sample frames, each containing `channels` scalar values. */\n frameCount: number;\n lengthSemantics: Exclude<TciStreamLengthSemantics, 'auto'>;\n}\n\nexport interface ParseStreamFrameOptions {\n lengthSemantics?: TciStreamLengthSemantics;\n negotiatedChannels?: number;\n}\n\nexport interface BuildStreamFrameOptions {\n receiver?: number;\n sampleRate: number;\n sampleType: TciSampleType | TciSampleTypeName;\n streamType: TciStreamType;\n channels: number;\n payload?: Buffer | Uint8Array | ArrayBuffer | ArrayBufferView;\n samples?: Float32Array | readonly number[];\n /** Explicit Stream.length value, used for header-only frames such as TX_CHRONO. */\n sampleCount?: number;\n lengthSemantics?: TciStreamLengthSemantics;\n codec?: number;\n crc?: number;\n reserved?: readonly number[];\n}\n\nexport interface BuildTxAudioFrameOptions extends Omit<BuildStreamFrameOptions, 'streamType'> {\n receiver?: number;\n}\n\nexport function parseStreamFrame(input: Buffer | ArrayBuffer | ArrayBufferView, options: ParseStreamFrameOptions = {}): TciStreamFrame {\n const buffer = toBuffer(input);\n if (buffer.byteLength < TCI_STREAM_HEADER_BYTES) {\n throw new TciError('invalid-frame', `TCI stream frame is shorter than ${TCI_STREAM_HEADER_BYTES} bytes`);\n }\n\n const view = new DataView(buffer.buffer, buffer.byteOffset, buffer.byteLength);\n const header = Array.from({ length: 16 }, (_, index) => view.getUint32(index * 4, true));\n const sampleType = normalizeSampleType(header[2]);\n const streamType = normalizeStreamType(header[6]);\n let channels = header[7] || options.negotiatedChannels || 0;\n const bytesPerSample = sampleTypeBytes(sampleType);\n const headerSampleCount = header[5];\n const actualPayloadLength = buffer.byteLength - TCI_STREAM_HEADER_BYTES;\n if (channels <= 0) {\n if (streamType === TciStreamType.TX_CHRONO && actualPayloadLength === 0) {\n channels = 1;\n } else {\n const inferredChannels = headerSampleCount > 0 ? actualPayloadLength / headerSampleCount / bytesPerSample : 1;\n if (!Number.isInteger(inferredChannels) || inferredChannels <= 0) {\n throw new TciError('invalid-frame', `Invalid TCI channel count: ${channels}`);\n }\n channels = inferredChannels;\n }\n }\n const payloadLength = actualPayloadLength;\n const alignedFrameBytes = bytesPerSample * channels;\n if (payloadLength % alignedFrameBytes !== 0) {\n throw new TciError('invalid-frame', 'TCI payload length is not aligned to sample type and channel count');\n }\n\n const actualScalarCount = payloadLength / bytesPerSample;\n const requestedSemantics = options.lengthSemantics ?? 'auto';\n const lengthSemantics = resolveLengthSemantics(\n requestedSemantics,\n streamType,\n headerSampleCount,\n actualScalarCount,\n channels,\n );\n const sampleCount = lengthSemantics === 'per-channel' ? headerSampleCount * channels : headerSampleCount;\n\n if (streamType !== TciStreamType.TX_CHRONO) {\n const expectedPayloadLength = sampleCount * bytesPerSample;\n if (payloadLength !== expectedPayloadLength) {\n throw new TciError(\n 'invalid-frame',\n `TCI stream frame length mismatch: header says ${headerSampleCount} samples using ${lengthSemantics} semantics (${expectedPayloadLength} payload bytes), got ${payloadLength}`,\n );\n }\n }\n\n if (sampleCount % channels !== 0) {\n throw new TciError('invalid-frame', `TCI scalar sample count ${sampleCount} is not divisible by ${channels} channels`);\n }\n\n return {\n receiver: header[0],\n sampleRate: header[1],\n sampleType,\n codec: header[3],\n crc: header[4],\n payloadLength,\n streamType,\n channels,\n reserved: header.slice(8),\n payload: buffer.subarray(TCI_STREAM_HEADER_BYTES),\n headerSampleCount,\n sampleCount,\n frameCount: sampleCount / channels,\n lengthSemantics,\n };\n}\n\nexport function buildStreamFrame(options: BuildStreamFrameOptions): Buffer {\n const sampleType = normalizeSampleType(options.sampleType);\n const payload = options.payload ? toBuffer(options.payload) : samplesToPayload(options.samples ?? [], sampleType);\n const channels = options.channels;\n if (channels <= 0) {\n throw new TciError('invalid-frame', `Invalid TCI channel count: ${channels}`);\n }\n const bytesPerSample = sampleTypeBytes(sampleType);\n if (payload.byteLength % (bytesPerSample * channels) !== 0) {\n throw new TciError('invalid-frame', 'TCI payload length is not aligned to sample type and channel count');\n }\n const derivedSampleCount = payload.byteLength / bytesPerSample;\n const sampleCount = options.sampleCount ?? derivedSampleCount;\n if (!Number.isInteger(sampleCount) || sampleCount < 0) {\n throw new TciError('invalid-frame', `Invalid TCI sample count: ${sampleCount}`);\n }\n if (payload.byteLength > 0 && sampleCount !== derivedSampleCount) {\n throw new TciError('invalid-frame', `Explicit scalar sample count ${sampleCount} does not match payload count ${derivedSampleCount}`);\n }\n if (sampleCount % channels !== 0) {\n throw new TciError('invalid-frame', `TCI scalar sample count ${sampleCount} is not divisible by ${channels} channels`);\n }\n const lengthSemantics = options.lengthSemantics === 'per-channel' ? 'per-channel' : 'scalar';\n const headerSampleCount = lengthSemantics === 'per-channel' ? sampleCount / channels : sampleCount;\n\n const frame = Buffer.alloc(TCI_STREAM_HEADER_BYTES + payload.byteLength);\n const view = new DataView(frame.buffer, frame.byteOffset, frame.byteLength);\n const reserved = options.reserved ?? [];\n const header = [\n options.receiver ?? 0,\n options.sampleRate,\n sampleType,\n options.codec ?? 0,\n options.crc ?? 0,\n headerSampleCount,\n options.streamType,\n channels,\n ...Array.from({ length: 8 }, (_, index) => reserved[index] ?? 0),\n ];\n header.forEach((value, index) => view.setUint32(index * 4, value >>> 0, true));\n payload.copy(frame, TCI_STREAM_HEADER_BYTES);\n return frame;\n}\n\nfunction resolveLengthSemantics(\n requested: TciStreamLengthSemantics,\n streamType: TciStreamType,\n headerSampleCount: number,\n actualScalarCount: number,\n channels: number,\n): Exclude<TciStreamLengthSemantics, 'auto'> {\n if (requested !== 'auto') return requested;\n if (streamType === TciStreamType.TX_CHRONO && actualScalarCount === 0) return 'scalar';\n if (actualScalarCount === headerSampleCount) return 'scalar';\n if (actualScalarCount === headerSampleCount * channels) return 'per-channel';\n return 'scalar';\n}\n\nexport function buildTxAudioFrame(options: BuildTxAudioFrameOptions): Buffer {\n return buildStreamFrame({ ...options, streamType: TciStreamType.TX_AUDIO_STREAM });\n}\n\nexport function sampleTypeBytes(sampleType: TciSampleType | TciSampleTypeName): number {\n switch (normalizeSampleType(sampleType)) {\n case TciSampleType.INT16:\n return 2;\n case TciSampleType.INT24:\n return 3;\n case TciSampleType.INT32:\n case TciSampleType.FLOAT32:\n return 4;\n default:\n throw new TciError('invalid-frame', `Unsupported TCI sample type: ${sampleType}`);\n }\n}\n\nexport function sampleTypeName(sampleType: TciSampleType): TciSampleTypeName {\n switch (sampleType) {\n case TciSampleType.INT16:\n return 'int16';\n case TciSampleType.INT24:\n return 'int24';\n case TciSampleType.INT32:\n return 'int32';\n case TciSampleType.FLOAT32:\n return 'float32';\n default:\n throw new TciError('invalid-frame', `Unsupported TCI sample type: ${sampleType}`);\n }\n}\n\nexport function normalizeSampleType(sampleType: TciSampleType | TciSampleTypeName | number): TciSampleType {\n if (typeof sampleType === 'string') {\n switch (sampleType.toLowerCase()) {\n case 'int16':\n return TciSampleType.INT16;\n case 'int24':\n return TciSampleType.INT24;\n case 'int32':\n return TciSampleType.INT32;\n case 'float32':\n return TciSampleType.FLOAT32;\n default:\n throw new TciError('invalid-frame', `Unsupported TCI sample type: ${sampleType}`);\n }\n }\n if (sampleType >= TciSampleType.INT16 && sampleType <= TciSampleType.FLOAT32) {\n return sampleType as TciSampleType;\n }\n throw new TciError('invalid-frame', `Unsupported TCI sample type: ${sampleType}`);\n}\n\nexport function normalizeStreamType(streamType: TciStreamType | number): TciStreamType {\n if (streamType >= TciStreamType.IQ_STREAM && streamType <= TciStreamType.LINEOUT_STREAM) {\n return streamType as TciStreamType;\n }\n throw new TciError('invalid-frame', `Unsupported TCI stream type: ${streamType}`);\n}\n\nexport function payloadToFloat32(frameOrPayload: TciStreamFrame | Buffer | Uint8Array, sampleType?: TciSampleType | TciSampleTypeName): Float32Array {\n const payload = isFrame(frameOrPayload) ? frameOrPayload.payload : toBuffer(frameOrPayload);\n const type = isFrame(frameOrPayload) ? frameOrPayload.sampleType : normalizeSampleType(sampleType ?? TciSampleType.FLOAT32);\n const bytes = sampleTypeBytes(type);\n if (payload.byteLength % bytes !== 0) {\n throw new TciError('invalid-frame', 'Payload length is not aligned to sample type');\n }\n\n const output = new Float32Array(payload.byteLength / bytes);\n const view = new DataView(payload.buffer, payload.byteOffset, payload.byteLength);\n for (let i = 0; i < output.length; i += 1) {\n const offset = i * bytes;\n switch (type) {\n case TciSampleType.INT16:\n output[i] = view.getInt16(offset, true) / 32768;\n break;\n case TciSampleType.INT24:\n output[i] = readInt24(view, offset) / 8388608;\n break;\n case TciSampleType.INT32:\n output[i] = view.getInt32(offset, true) / 2147483648;\n break;\n case TciSampleType.FLOAT32:\n output[i] = view.getFloat32(offset, true);\n break;\n }\n }\n return output;\n}\n\nexport function samplesToPayload(samples: Float32Array | readonly number[], sampleType: TciSampleType | TciSampleTypeName): Buffer {\n const type = normalizeSampleType(sampleType);\n const bytes = sampleTypeBytes(type);\n const payload = Buffer.alloc(samples.length * bytes);\n const view = new DataView(payload.buffer, payload.byteOffset, payload.byteLength);\n for (let i = 0; i < samples.length; i += 1) {\n const value = clampSample(samples[i] ?? 0);\n const offset = i * bytes;\n switch (type) {\n case TciSampleType.INT16:\n view.setInt16(offset, Math.round(value * 32767), true);\n break;\n case TciSampleType.INT24:\n writeInt24(view, offset, Math.round(value * 8388607));\n break;\n case TciSampleType.INT32:\n view.setInt32(offset, Math.round(value * 2147483647), true);\n break;\n case TciSampleType.FLOAT32:\n view.setFloat32(offset, value, true);\n break;\n }\n }\n return payload;\n}\n\nexport function pcm16ToFloat32(input: Buffer | Uint8Array | Int16Array): Float32Array {\n if (input instanceof Int16Array) {\n const output = new Float32Array(input.length);\n for (let i = 0; i < input.length; i += 1) {\n output[i] = input[i] / 32768;\n }\n return output;\n }\n return payloadToFloat32(toBuffer(input), TciSampleType.INT16);\n}\n\nexport function float32ToPcm16(samples: Float32Array | readonly number[]): Buffer {\n return samplesToPayload(samples, TciSampleType.INT16);\n}\n\nexport function deinterleaveChannels(samples: Float32Array, channels: number): Float32Array[] {\n if (channels <= 0 || samples.length % channels !== 0) {\n throw new TciError('invalid-frame', 'Cannot deinterleave samples with invalid channel count');\n }\n const frames = samples.length / channels;\n const outputs = Array.from({ length: channels }, () => new Float32Array(frames));\n for (let frame = 0; frame < frames; frame += 1) {\n for (let channel = 0; channel < channels; channel += 1) {\n outputs[channel][frame] = samples[frame * channels + channel];\n }\n }\n return outputs;\n}\n\nexport function mixToMono(samples: Float32Array, channels: number): Float32Array {\n if (channels === 1) {\n return samples;\n }\n const separated = deinterleaveChannels(samples, channels);\n const mono = new Float32Array(separated[0]?.length ?? 0);\n for (const channel of separated) {\n for (let i = 0; i < mono.length; i += 1) {\n mono[i] += channel[i] / channels;\n }\n }\n return mono;\n}\n\nfunction toBuffer(input: Buffer | Uint8Array | ArrayBuffer | ArrayBufferView): Buffer {\n if (Buffer.isBuffer(input)) {\n return input;\n }\n if (input instanceof ArrayBuffer) {\n return Buffer.from(input);\n }\n if (ArrayBuffer.isView(input)) {\n return Buffer.from(input.buffer, input.byteOffset, input.byteLength);\n }\n return Buffer.from(input);\n}\n\nfunction isFrame(value: unknown): value is TciStreamFrame {\n return Boolean(value && typeof value === 'object' && 'payload' in value && 'sampleType' in value);\n}\n\nfunction clampSample(value: number): number {\n if (!Number.isFinite(value)) {\n return 0;\n }\n return Math.max(-1, Math.min(1, value));\n}\n\nfunction readInt24(view: DataView, offset: number): number {\n const value = view.getUint8(offset) | (view.getUint8(offset + 1) << 8) | (view.getUint8(offset + 2) << 16);\n return value & 0x800000 ? value | 0xff000000 : value;\n}\n\nfunction writeInt24(view: DataView, offset: number, value: number): void {\n const clamped = Math.max(-8388608, Math.min(8388607, value));\n view.setUint8(offset, clamped & 0xff);\n view.setUint8(offset + 1, (clamped >> 8) & 0xff);\n view.setUint8(offset + 2, (clamped >> 16) & 0xff);\n}\n"],"mappings":"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","names":["TciStreamType","TciSampleType"]}
1
+ {"version":3,"sources":["../../src/audio/index.ts","../../src/errors.ts","../../src/audio/streamFrame.ts"],"sourcesContent":["export * from './streamFrame.js';\n","export type TciErrorCode =\n | 'connect-timeout'\n | 'handshake-timeout'\n | 'invalid-handshake'\n | 'unknown-dialect'\n | 'command-timeout'\n | 'not-connected'\n | 'disconnected'\n | 'protocol-error'\n | 'invalid-frame'\n | 'cancelled';\n\nexport class TciError extends Error {\n readonly code: TciErrorCode;\n readonly details?: unknown;\n\n constructor(code: TciErrorCode, message: string, details?: unknown) {\n super(message);\n this.name = 'TciError';\n this.code = code;\n this.details = details;\n }\n}\n\nexport function toTciError(error: unknown, fallbackCode: TciErrorCode = 'protocol-error'): TciError {\n if (error instanceof TciError) {\n return error;\n }\n if (error instanceof Error) {\n return new TciError(fallbackCode, error.message, error);\n }\n return new TciError(fallbackCode, String(error), error);\n}\n","import { TciError } from '../errors.js';\nimport type { TciStreamLengthSemantics } from '../dialect/types.js';\n\nexport const TCI_STREAM_HEADER_BYTES = 16 * 4;\n\nexport enum TciStreamType {\n IQ_STREAM = 0,\n RX_AUDIO_STREAM = 1,\n TX_AUDIO_STREAM = 2,\n TX_CHRONO = 3,\n LINEOUT_STREAM = 4,\n}\n\nexport enum TciSampleType {\n INT16 = 0,\n INT24 = 1,\n INT32 = 2,\n FLOAT32 = 3,\n}\n\nexport type TciSampleTypeName = 'int16' | 'int24' | 'int32' | 'float32';\n\nexport interface TciStreamFrame {\n receiver: number;\n sampleRate: number;\n sampleType: TciSampleType;\n codec: number;\n crc: number;\n /** Byte length of the payload following the 64-byte TCI stream header. */\n payloadLength: number;\n streamType: TciStreamType;\n channels: number;\n reserved: number[];\n payload: Buffer;\n /** Raw Stream.length value from the wire header. */\n headerSampleCount: number;\n /** Canonical number of scalar sample values across all channels. */\n sampleCount: number;\n /** Number of sample frames, each containing `channels` scalar values. */\n frameCount: number;\n lengthSemantics: Exclude<TciStreamLengthSemantics, 'auto'>;\n}\n\nexport interface ParseStreamFrameOptions {\n lengthSemantics?: TciStreamLengthSemantics;\n negotiatedChannels?: number;\n}\n\nexport interface BuildStreamFrameOptions {\n receiver?: number;\n sampleRate: number;\n sampleType: TciSampleType | TciSampleTypeName;\n streamType: TciStreamType;\n channels: number;\n payload?: Buffer | Uint8Array | ArrayBuffer | ArrayBufferView;\n samples?: Float32Array | readonly number[];\n /** Explicit Stream.length value, used for header-only frames such as TX_CHRONO. */\n sampleCount?: number;\n lengthSemantics?: TciStreamLengthSemantics;\n codec?: number;\n crc?: number;\n reserved?: readonly number[];\n}\n\nexport interface BuildTxAudioFrameOptions extends Omit<BuildStreamFrameOptions, 'streamType'> {\n receiver?: number;\n}\n\nexport function parseStreamFrame(input: Buffer | ArrayBuffer | ArrayBufferView, options: ParseStreamFrameOptions = {}): TciStreamFrame {\n const buffer = toBuffer(input);\n if (buffer.byteLength < TCI_STREAM_HEADER_BYTES) {\n throw new TciError('invalid-frame', `TCI stream frame is shorter than ${TCI_STREAM_HEADER_BYTES} bytes`);\n }\n\n const view = new DataView(buffer.buffer, buffer.byteOffset, buffer.byteLength);\n const header = Array.from({ length: 16 }, (_, index) => view.getUint32(index * 4, true));\n const sampleType = normalizeSampleType(header[2]);\n const streamType = normalizeStreamType(header[6]);\n let channels = header[7] || options.negotiatedChannels || 0;\n const bytesPerSample = sampleTypeBytes(sampleType);\n const headerSampleCount = header[5];\n const actualPayloadLength = buffer.byteLength - TCI_STREAM_HEADER_BYTES;\n if (channels <= 0) {\n if (streamType === TciStreamType.TX_CHRONO && actualPayloadLength === 0) {\n channels = 1;\n } else {\n const inferredChannels = headerSampleCount > 0 ? actualPayloadLength / headerSampleCount / bytesPerSample : 1;\n if (!Number.isInteger(inferredChannels) || inferredChannels <= 0) {\n throw new TciError('invalid-frame', `Invalid TCI channel count: ${channels}`);\n }\n channels = inferredChannels;\n }\n }\n const payloadLength = actualPayloadLength;\n const alignedFrameBytes = bytesPerSample * channels;\n if (payloadLength % alignedFrameBytes !== 0) {\n throw new TciError('invalid-frame', 'TCI payload length is not aligned to sample type and channel count');\n }\n\n const actualScalarCount = payloadLength / bytesPerSample;\n const requestedSemantics = options.lengthSemantics ?? 'auto';\n const lengthSemantics = resolveLengthSemantics(\n requestedSemantics,\n streamType,\n headerSampleCount,\n actualScalarCount,\n channels,\n );\n const sampleCount = lengthSemantics === 'per-channel' ? headerSampleCount * channels : headerSampleCount;\n\n if (streamType !== TciStreamType.TX_CHRONO) {\n const expectedPayloadLength = sampleCount * bytesPerSample;\n if (payloadLength !== expectedPayloadLength) {\n throw new TciError(\n 'invalid-frame',\n `TCI stream frame length mismatch: header says ${headerSampleCount} samples using ${lengthSemantics} semantics (${expectedPayloadLength} payload bytes), got ${payloadLength}`,\n );\n }\n }\n\n if (sampleCount % channels !== 0) {\n throw new TciError('invalid-frame', `TCI scalar sample count ${sampleCount} is not divisible by ${channels} channels`);\n }\n\n return {\n receiver: header[0],\n sampleRate: header[1],\n sampleType,\n codec: header[3],\n crc: header[4],\n payloadLength,\n streamType,\n channels,\n reserved: header.slice(8),\n payload: buffer.subarray(TCI_STREAM_HEADER_BYTES),\n headerSampleCount,\n sampleCount,\n frameCount: sampleCount / channels,\n lengthSemantics,\n };\n}\n\nexport function buildStreamFrame(options: BuildStreamFrameOptions): Buffer {\n const sampleType = normalizeSampleType(options.sampleType);\n const payload = options.payload ? toBuffer(options.payload) : samplesToPayload(options.samples ?? [], sampleType);\n const channels = options.channels;\n if (channels <= 0) {\n throw new TciError('invalid-frame', `Invalid TCI channel count: ${channels}`);\n }\n const bytesPerSample = sampleTypeBytes(sampleType);\n if (payload.byteLength % (bytesPerSample * channels) !== 0) {\n throw new TciError('invalid-frame', 'TCI payload length is not aligned to sample type and channel count');\n }\n const derivedSampleCount = payload.byteLength / bytesPerSample;\n const sampleCount = options.sampleCount ?? derivedSampleCount;\n if (!Number.isInteger(sampleCount) || sampleCount < 0) {\n throw new TciError('invalid-frame', `Invalid TCI sample count: ${sampleCount}`);\n }\n if (payload.byteLength > 0 && sampleCount !== derivedSampleCount) {\n throw new TciError('invalid-frame', `Explicit scalar sample count ${sampleCount} does not match payload count ${derivedSampleCount}`);\n }\n if (sampleCount % channels !== 0) {\n throw new TciError('invalid-frame', `TCI scalar sample count ${sampleCount} is not divisible by ${channels} channels`);\n }\n const lengthSemantics = options.lengthSemantics === 'per-channel' ? 'per-channel' : 'scalar';\n const headerSampleCount = lengthSemantics === 'per-channel' ? sampleCount / channels : sampleCount;\n\n const frame = Buffer.alloc(TCI_STREAM_HEADER_BYTES + payload.byteLength);\n const view = new DataView(frame.buffer, frame.byteOffset, frame.byteLength);\n const reserved = options.reserved ?? [];\n const header = [\n options.receiver ?? 0,\n options.sampleRate,\n sampleType,\n options.codec ?? 0,\n options.crc ?? 0,\n headerSampleCount,\n options.streamType,\n channels,\n ...Array.from({ length: 8 }, (_, index) => reserved[index] ?? 0),\n ];\n header.forEach((value, index) => view.setUint32(index * 4, value >>> 0, true));\n payload.copy(frame, TCI_STREAM_HEADER_BYTES);\n return frame;\n}\n\nfunction resolveLengthSemantics(\n requested: TciStreamLengthSemantics,\n streamType: TciStreamType,\n headerSampleCount: number,\n actualScalarCount: number,\n channels: number,\n): Exclude<TciStreamLengthSemantics, 'auto'> {\n if (requested !== 'auto') return requested;\n if (streamType === TciStreamType.TX_CHRONO && actualScalarCount === 0) return 'scalar';\n if (actualScalarCount === headerSampleCount) return 'scalar';\n if (actualScalarCount === headerSampleCount * channels) return 'per-channel';\n return 'scalar';\n}\n\nexport function buildTxAudioFrame(options: BuildTxAudioFrameOptions): Buffer {\n return buildStreamFrame({ ...options, streamType: TciStreamType.TX_AUDIO_STREAM });\n}\n\nexport function sampleTypeBytes(sampleType: TciSampleType | TciSampleTypeName): number {\n switch (normalizeSampleType(sampleType)) {\n case TciSampleType.INT16:\n return 2;\n case TciSampleType.INT24:\n return 3;\n case TciSampleType.INT32:\n case TciSampleType.FLOAT32:\n return 4;\n default:\n throw new TciError('invalid-frame', `Unsupported TCI sample type: ${sampleType}`);\n }\n}\n\nexport function sampleTypeName(sampleType: TciSampleType): TciSampleTypeName {\n switch (sampleType) {\n case TciSampleType.INT16:\n return 'int16';\n case TciSampleType.INT24:\n return 'int24';\n case TciSampleType.INT32:\n return 'int32';\n case TciSampleType.FLOAT32:\n return 'float32';\n default:\n throw new TciError('invalid-frame', `Unsupported TCI sample type: ${sampleType}`);\n }\n}\n\nexport function normalizeSampleType(sampleType: TciSampleType | TciSampleTypeName | number): TciSampleType {\n if (typeof sampleType === 'string') {\n switch (sampleType.toLowerCase()) {\n case 'int16':\n return TciSampleType.INT16;\n case 'int24':\n return TciSampleType.INT24;\n case 'int32':\n return TciSampleType.INT32;\n case 'float32':\n return TciSampleType.FLOAT32;\n default:\n throw new TciError('invalid-frame', `Unsupported TCI sample type: ${sampleType}`);\n }\n }\n if (sampleType >= TciSampleType.INT16 && sampleType <= TciSampleType.FLOAT32) {\n return sampleType as TciSampleType;\n }\n throw new TciError('invalid-frame', `Unsupported TCI sample type: ${sampleType}`);\n}\n\nexport function normalizeStreamType(streamType: TciStreamType | number): TciStreamType {\n if (streamType >= TciStreamType.IQ_STREAM && streamType <= TciStreamType.LINEOUT_STREAM) {\n return streamType as TciStreamType;\n }\n throw new TciError('invalid-frame', `Unsupported TCI stream type: ${streamType}`);\n}\n\nexport function payloadToFloat32(frameOrPayload: TciStreamFrame | Buffer | Uint8Array, sampleType?: TciSampleType | TciSampleTypeName): Float32Array {\n const payload = isFrame(frameOrPayload) ? frameOrPayload.payload : toBuffer(frameOrPayload);\n const type = isFrame(frameOrPayload) ? frameOrPayload.sampleType : normalizeSampleType(sampleType ?? TciSampleType.FLOAT32);\n const bytes = sampleTypeBytes(type);\n if (payload.byteLength % bytes !== 0) {\n throw new TciError('invalid-frame', 'Payload length is not aligned to sample type');\n }\n\n const output = new Float32Array(payload.byteLength / bytes);\n const view = new DataView(payload.buffer, payload.byteOffset, payload.byteLength);\n for (let i = 0; i < output.length; i += 1) {\n const offset = i * bytes;\n switch (type) {\n case TciSampleType.INT16:\n output[i] = view.getInt16(offset, true) / 32768;\n break;\n case TciSampleType.INT24:\n output[i] = readInt24(view, offset) / 8388608;\n break;\n case TciSampleType.INT32:\n output[i] = view.getInt32(offset, true) / 2147483648;\n break;\n case TciSampleType.FLOAT32:\n output[i] = view.getFloat32(offset, true);\n break;\n }\n }\n return output;\n}\n\nexport function decodeInterleavedIq(frame: TciStreamFrame): Float32Array {\n if (frame.streamType !== TciStreamType.IQ_STREAM) {\n throw new TciError('invalid-frame', 'Expected a TCI IQ stream frame');\n }\n if (frame.channels !== 2 || frame.sampleCount % 2 !== 0) {\n throw new TciError('invalid-frame', `TCI IQ frame must contain interleaved I/Q pairs, got ${frame.channels} channels`);\n }\n return payloadToFloat32(frame);\n}\n\nexport function samplesToPayload(samples: Float32Array | readonly number[], sampleType: TciSampleType | TciSampleTypeName): Buffer {\n const type = normalizeSampleType(sampleType);\n const bytes = sampleTypeBytes(type);\n const payload = Buffer.alloc(samples.length * bytes);\n const view = new DataView(payload.buffer, payload.byteOffset, payload.byteLength);\n for (let i = 0; i < samples.length; i += 1) {\n const value = clampSample(samples[i] ?? 0);\n const offset = i * bytes;\n switch (type) {\n case TciSampleType.INT16:\n view.setInt16(offset, Math.round(value * 32767), true);\n break;\n case TciSampleType.INT24:\n writeInt24(view, offset, Math.round(value * 8388607));\n break;\n case TciSampleType.INT32:\n view.setInt32(offset, Math.round(value * 2147483647), true);\n break;\n case TciSampleType.FLOAT32:\n view.setFloat32(offset, value, true);\n break;\n }\n }\n return payload;\n}\n\nexport function pcm16ToFloat32(input: Buffer | Uint8Array | Int16Array): Float32Array {\n if (input instanceof Int16Array) {\n const output = new Float32Array(input.length);\n for (let i = 0; i < input.length; i += 1) {\n output[i] = input[i] / 32768;\n }\n return output;\n }\n return payloadToFloat32(toBuffer(input), TciSampleType.INT16);\n}\n\nexport function float32ToPcm16(samples: Float32Array | readonly number[]): Buffer {\n return samplesToPayload(samples, TciSampleType.INT16);\n}\n\nexport function deinterleaveChannels(samples: Float32Array, channels: number): Float32Array[] {\n if (channels <= 0 || samples.length % channels !== 0) {\n throw new TciError('invalid-frame', 'Cannot deinterleave samples with invalid channel count');\n }\n const frames = samples.length / channels;\n const outputs = Array.from({ length: channels }, () => new Float32Array(frames));\n for (let frame = 0; frame < frames; frame += 1) {\n for (let channel = 0; channel < channels; channel += 1) {\n outputs[channel][frame] = samples[frame * channels + channel];\n }\n }\n return outputs;\n}\n\nexport function mixToMono(samples: Float32Array, channels: number): Float32Array {\n if (channels === 1) {\n return samples;\n }\n const separated = deinterleaveChannels(samples, channels);\n const mono = new Float32Array(separated[0]?.length ?? 0);\n for (const channel of separated) {\n for (let i = 0; i < mono.length; i += 1) {\n mono[i] += channel[i] / channels;\n }\n }\n return mono;\n}\n\nfunction toBuffer(input: Buffer | Uint8Array | ArrayBuffer | ArrayBufferView): Buffer {\n if (Buffer.isBuffer(input)) {\n return input;\n }\n if (input instanceof ArrayBuffer) {\n return Buffer.from(input);\n }\n if (ArrayBuffer.isView(input)) {\n return Buffer.from(input.buffer, input.byteOffset, input.byteLength);\n }\n return Buffer.from(input);\n}\n\nfunction isFrame(value: unknown): value is TciStreamFrame {\n return Boolean(value && typeof value === 'object' && 'payload' in value && 'sampleType' in value);\n}\n\nfunction clampSample(value: number): number {\n if (!Number.isFinite(value)) {\n return 0;\n }\n return Math.max(-1, Math.min(1, value));\n}\n\nfunction readInt24(view: DataView, offset: number): number {\n const value = view.getUint8(offset) | (view.getUint8(offset + 1) << 8) | (view.getUint8(offset + 2) << 16);\n return value & 0x800000 ? value | 0xff000000 : value;\n}\n\nfunction writeInt24(view: DataView, offset: number, value: number): void {\n const clamped = Math.max(-8388608, Math.min(8388607, value));\n view.setUint8(offset, clamped & 0xff);\n view.setUint8(offset + 1, (clamped >> 8) & 0xff);\n view.setUint8(offset + 2, (clamped >> 16) & 0xff);\n}\n"],"mappings":"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","names":["TciStreamType","TciSampleType"]}
@@ -1,4 +1,4 @@
1
- import { j as TciStreamLengthSemantics } from '../types-xRotf9gW.cjs';
1
+ import { j as TciStreamLengthSemantics } from '../types-1YXGwrgI.cjs';
2
2
  import '../text-BwCWY1k1.cjs';
3
3
 
4
4
  declare const TCI_STREAM_HEADER_BYTES: number;
@@ -66,10 +66,11 @@ declare function sampleTypeName(sampleType: TciSampleType): TciSampleTypeName;
66
66
  declare function normalizeSampleType(sampleType: TciSampleType | TciSampleTypeName | number): TciSampleType;
67
67
  declare function normalizeStreamType(streamType: TciStreamType | number): TciStreamType;
68
68
  declare function payloadToFloat32(frameOrPayload: TciStreamFrame | Buffer | Uint8Array, sampleType?: TciSampleType | TciSampleTypeName): Float32Array;
69
+ declare function decodeInterleavedIq(frame: TciStreamFrame): Float32Array;
69
70
  declare function samplesToPayload(samples: Float32Array | readonly number[], sampleType: TciSampleType | TciSampleTypeName): Buffer;
70
71
  declare function pcm16ToFloat32(input: Buffer | Uint8Array | Int16Array): Float32Array;
71
72
  declare function float32ToPcm16(samples: Float32Array | readonly number[]): Buffer;
72
73
  declare function deinterleaveChannels(samples: Float32Array, channels: number): Float32Array[];
73
74
  declare function mixToMono(samples: Float32Array, channels: number): Float32Array;
74
75
 
75
- export { type BuildStreamFrameOptions, type BuildTxAudioFrameOptions, type ParseStreamFrameOptions, TCI_STREAM_HEADER_BYTES, TciSampleType, type TciSampleTypeName, type TciStreamFrame, TciStreamType, buildStreamFrame, buildTxAudioFrame, deinterleaveChannels, float32ToPcm16, mixToMono, normalizeSampleType, normalizeStreamType, parseStreamFrame, payloadToFloat32, pcm16ToFloat32, sampleTypeBytes, sampleTypeName, samplesToPayload };
76
+ export { type BuildStreamFrameOptions, type BuildTxAudioFrameOptions, type ParseStreamFrameOptions, TCI_STREAM_HEADER_BYTES, TciSampleType, type TciSampleTypeName, type TciStreamFrame, TciStreamType, buildStreamFrame, buildTxAudioFrame, decodeInterleavedIq, deinterleaveChannels, float32ToPcm16, mixToMono, normalizeSampleType, normalizeStreamType, parseStreamFrame, payloadToFloat32, pcm16ToFloat32, sampleTypeBytes, sampleTypeName, samplesToPayload };
@@ -1,4 +1,4 @@
1
- import { j as TciStreamLengthSemantics } from '../types-9seY9Th-.js';
1
+ import { j as TciStreamLengthSemantics } from '../types-BtPh4Dbd.js';
2
2
  import '../text-BwCWY1k1.js';
3
3
 
4
4
  declare const TCI_STREAM_HEADER_BYTES: number;
@@ -66,10 +66,11 @@ declare function sampleTypeName(sampleType: TciSampleType): TciSampleTypeName;
66
66
  declare function normalizeSampleType(sampleType: TciSampleType | TciSampleTypeName | number): TciSampleType;
67
67
  declare function normalizeStreamType(streamType: TciStreamType | number): TciStreamType;
68
68
  declare function payloadToFloat32(frameOrPayload: TciStreamFrame | Buffer | Uint8Array, sampleType?: TciSampleType | TciSampleTypeName): Float32Array;
69
+ declare function decodeInterleavedIq(frame: TciStreamFrame): Float32Array;
69
70
  declare function samplesToPayload(samples: Float32Array | readonly number[], sampleType: TciSampleType | TciSampleTypeName): Buffer;
70
71
  declare function pcm16ToFloat32(input: Buffer | Uint8Array | Int16Array): Float32Array;
71
72
  declare function float32ToPcm16(samples: Float32Array | readonly number[]): Buffer;
72
73
  declare function deinterleaveChannels(samples: Float32Array, channels: number): Float32Array[];
73
74
  declare function mixToMono(samples: Float32Array, channels: number): Float32Array;
74
75
 
75
- export { type BuildStreamFrameOptions, type BuildTxAudioFrameOptions, type ParseStreamFrameOptions, TCI_STREAM_HEADER_BYTES, TciSampleType, type TciSampleTypeName, type TciStreamFrame, TciStreamType, buildStreamFrame, buildTxAudioFrame, deinterleaveChannels, float32ToPcm16, mixToMono, normalizeSampleType, normalizeStreamType, parseStreamFrame, payloadToFloat32, pcm16ToFloat32, sampleTypeBytes, sampleTypeName, samplesToPayload };
76
+ export { type BuildStreamFrameOptions, type BuildTxAudioFrameOptions, type ParseStreamFrameOptions, TCI_STREAM_HEADER_BYTES, TciSampleType, type TciSampleTypeName, type TciStreamFrame, TciStreamType, buildStreamFrame, buildTxAudioFrame, decodeInterleavedIq, deinterleaveChannels, float32ToPcm16, mixToMono, normalizeSampleType, normalizeStreamType, parseStreamFrame, payloadToFloat32, pcm16ToFloat32, sampleTypeBytes, sampleTypeName, samplesToPayload };
@@ -228,6 +228,15 @@ function payloadToFloat32(frameOrPayload, sampleType) {
228
228
  }
229
229
  return output;
230
230
  }
231
+ function decodeInterleavedIq(frame) {
232
+ if (frame.streamType !== 0 /* IQ_STREAM */) {
233
+ throw new TciError("invalid-frame", "Expected a TCI IQ stream frame");
234
+ }
235
+ if (frame.channels !== 2 || frame.sampleCount % 2 !== 0) {
236
+ throw new TciError("invalid-frame", `TCI IQ frame must contain interleaved I/Q pairs, got ${frame.channels} channels`);
237
+ }
238
+ return payloadToFloat32(frame);
239
+ }
231
240
  function samplesToPayload(samples, sampleType) {
232
241
  const type = normalizeSampleType(sampleType);
233
242
  const bytes = sampleTypeBytes(type);
@@ -329,6 +338,7 @@ export {
329
338
  TciStreamType,
330
339
  buildStreamFrame,
331
340
  buildTxAudioFrame,
341
+ decodeInterleavedIq,
332
342
  deinterleaveChannels,
333
343
  float32ToPcm16,
334
344
  mixToMono,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/errors.ts","../../src/audio/streamFrame.ts"],"sourcesContent":["export type TciErrorCode =\n | 'connect-timeout'\n | 'handshake-timeout'\n | 'invalid-handshake'\n | 'unknown-dialect'\n | 'command-timeout'\n | 'not-connected'\n | 'disconnected'\n | 'protocol-error'\n | 'invalid-frame'\n | 'cancelled';\n\nexport class TciError extends Error {\n readonly code: TciErrorCode;\n readonly details?: unknown;\n\n constructor(code: TciErrorCode, message: string, details?: unknown) {\n super(message);\n this.name = 'TciError';\n this.code = code;\n this.details = details;\n }\n}\n\nexport function toTciError(error: unknown, fallbackCode: TciErrorCode = 'protocol-error'): TciError {\n if (error instanceof TciError) {\n return error;\n }\n if (error instanceof Error) {\n return new TciError(fallbackCode, error.message, error);\n }\n return new TciError(fallbackCode, String(error), error);\n}\n","import { TciError } from '../errors.js';\nimport type { TciStreamLengthSemantics } from '../dialect/types.js';\n\nexport const TCI_STREAM_HEADER_BYTES = 16 * 4;\n\nexport enum TciStreamType {\n IQ_STREAM = 0,\n RX_AUDIO_STREAM = 1,\n TX_AUDIO_STREAM = 2,\n TX_CHRONO = 3,\n LINEOUT_STREAM = 4,\n}\n\nexport enum TciSampleType {\n INT16 = 0,\n INT24 = 1,\n INT32 = 2,\n FLOAT32 = 3,\n}\n\nexport type TciSampleTypeName = 'int16' | 'int24' | 'int32' | 'float32';\n\nexport interface TciStreamFrame {\n receiver: number;\n sampleRate: number;\n sampleType: TciSampleType;\n codec: number;\n crc: number;\n /** Byte length of the payload following the 64-byte TCI stream header. */\n payloadLength: number;\n streamType: TciStreamType;\n channels: number;\n reserved: number[];\n payload: Buffer;\n /** Raw Stream.length value from the wire header. */\n headerSampleCount: number;\n /** Canonical number of scalar sample values across all channels. */\n sampleCount: number;\n /** Number of sample frames, each containing `channels` scalar values. */\n frameCount: number;\n lengthSemantics: Exclude<TciStreamLengthSemantics, 'auto'>;\n}\n\nexport interface ParseStreamFrameOptions {\n lengthSemantics?: TciStreamLengthSemantics;\n negotiatedChannels?: number;\n}\n\nexport interface BuildStreamFrameOptions {\n receiver?: number;\n sampleRate: number;\n sampleType: TciSampleType | TciSampleTypeName;\n streamType: TciStreamType;\n channels: number;\n payload?: Buffer | Uint8Array | ArrayBuffer | ArrayBufferView;\n samples?: Float32Array | readonly number[];\n /** Explicit Stream.length value, used for header-only frames such as TX_CHRONO. */\n sampleCount?: number;\n lengthSemantics?: TciStreamLengthSemantics;\n codec?: number;\n crc?: number;\n reserved?: readonly number[];\n}\n\nexport interface BuildTxAudioFrameOptions extends Omit<BuildStreamFrameOptions, 'streamType'> {\n receiver?: number;\n}\n\nexport function parseStreamFrame(input: Buffer | ArrayBuffer | ArrayBufferView, options: ParseStreamFrameOptions = {}): TciStreamFrame {\n const buffer = toBuffer(input);\n if (buffer.byteLength < TCI_STREAM_HEADER_BYTES) {\n throw new TciError('invalid-frame', `TCI stream frame is shorter than ${TCI_STREAM_HEADER_BYTES} bytes`);\n }\n\n const view = new DataView(buffer.buffer, buffer.byteOffset, buffer.byteLength);\n const header = Array.from({ length: 16 }, (_, index) => view.getUint32(index * 4, true));\n const sampleType = normalizeSampleType(header[2]);\n const streamType = normalizeStreamType(header[6]);\n let channels = header[7] || options.negotiatedChannels || 0;\n const bytesPerSample = sampleTypeBytes(sampleType);\n const headerSampleCount = header[5];\n const actualPayloadLength = buffer.byteLength - TCI_STREAM_HEADER_BYTES;\n if (channels <= 0) {\n if (streamType === TciStreamType.TX_CHRONO && actualPayloadLength === 0) {\n channels = 1;\n } else {\n const inferredChannels = headerSampleCount > 0 ? actualPayloadLength / headerSampleCount / bytesPerSample : 1;\n if (!Number.isInteger(inferredChannels) || inferredChannels <= 0) {\n throw new TciError('invalid-frame', `Invalid TCI channel count: ${channels}`);\n }\n channels = inferredChannels;\n }\n }\n const payloadLength = actualPayloadLength;\n const alignedFrameBytes = bytesPerSample * channels;\n if (payloadLength % alignedFrameBytes !== 0) {\n throw new TciError('invalid-frame', 'TCI payload length is not aligned to sample type and channel count');\n }\n\n const actualScalarCount = payloadLength / bytesPerSample;\n const requestedSemantics = options.lengthSemantics ?? 'auto';\n const lengthSemantics = resolveLengthSemantics(\n requestedSemantics,\n streamType,\n headerSampleCount,\n actualScalarCount,\n channels,\n );\n const sampleCount = lengthSemantics === 'per-channel' ? headerSampleCount * channels : headerSampleCount;\n\n if (streamType !== TciStreamType.TX_CHRONO) {\n const expectedPayloadLength = sampleCount * bytesPerSample;\n if (payloadLength !== expectedPayloadLength) {\n throw new TciError(\n 'invalid-frame',\n `TCI stream frame length mismatch: header says ${headerSampleCount} samples using ${lengthSemantics} semantics (${expectedPayloadLength} payload bytes), got ${payloadLength}`,\n );\n }\n }\n\n if (sampleCount % channels !== 0) {\n throw new TciError('invalid-frame', `TCI scalar sample count ${sampleCount} is not divisible by ${channels} channels`);\n }\n\n return {\n receiver: header[0],\n sampleRate: header[1],\n sampleType,\n codec: header[3],\n crc: header[4],\n payloadLength,\n streamType,\n channels,\n reserved: header.slice(8),\n payload: buffer.subarray(TCI_STREAM_HEADER_BYTES),\n headerSampleCount,\n sampleCount,\n frameCount: sampleCount / channels,\n lengthSemantics,\n };\n}\n\nexport function buildStreamFrame(options: BuildStreamFrameOptions): Buffer {\n const sampleType = normalizeSampleType(options.sampleType);\n const payload = options.payload ? toBuffer(options.payload) : samplesToPayload(options.samples ?? [], sampleType);\n const channels = options.channels;\n if (channels <= 0) {\n throw new TciError('invalid-frame', `Invalid TCI channel count: ${channels}`);\n }\n const bytesPerSample = sampleTypeBytes(sampleType);\n if (payload.byteLength % (bytesPerSample * channels) !== 0) {\n throw new TciError('invalid-frame', 'TCI payload length is not aligned to sample type and channel count');\n }\n const derivedSampleCount = payload.byteLength / bytesPerSample;\n const sampleCount = options.sampleCount ?? derivedSampleCount;\n if (!Number.isInteger(sampleCount) || sampleCount < 0) {\n throw new TciError('invalid-frame', `Invalid TCI sample count: ${sampleCount}`);\n }\n if (payload.byteLength > 0 && sampleCount !== derivedSampleCount) {\n throw new TciError('invalid-frame', `Explicit scalar sample count ${sampleCount} does not match payload count ${derivedSampleCount}`);\n }\n if (sampleCount % channels !== 0) {\n throw new TciError('invalid-frame', `TCI scalar sample count ${sampleCount} is not divisible by ${channels} channels`);\n }\n const lengthSemantics = options.lengthSemantics === 'per-channel' ? 'per-channel' : 'scalar';\n const headerSampleCount = lengthSemantics === 'per-channel' ? sampleCount / channels : sampleCount;\n\n const frame = Buffer.alloc(TCI_STREAM_HEADER_BYTES + payload.byteLength);\n const view = new DataView(frame.buffer, frame.byteOffset, frame.byteLength);\n const reserved = options.reserved ?? [];\n const header = [\n options.receiver ?? 0,\n options.sampleRate,\n sampleType,\n options.codec ?? 0,\n options.crc ?? 0,\n headerSampleCount,\n options.streamType,\n channels,\n ...Array.from({ length: 8 }, (_, index) => reserved[index] ?? 0),\n ];\n header.forEach((value, index) => view.setUint32(index * 4, value >>> 0, true));\n payload.copy(frame, TCI_STREAM_HEADER_BYTES);\n return frame;\n}\n\nfunction resolveLengthSemantics(\n requested: TciStreamLengthSemantics,\n streamType: TciStreamType,\n headerSampleCount: number,\n actualScalarCount: number,\n channels: number,\n): Exclude<TciStreamLengthSemantics, 'auto'> {\n if (requested !== 'auto') return requested;\n if (streamType === TciStreamType.TX_CHRONO && actualScalarCount === 0) return 'scalar';\n if (actualScalarCount === headerSampleCount) return 'scalar';\n if (actualScalarCount === headerSampleCount * channels) return 'per-channel';\n return 'scalar';\n}\n\nexport function buildTxAudioFrame(options: BuildTxAudioFrameOptions): Buffer {\n return buildStreamFrame({ ...options, streamType: TciStreamType.TX_AUDIO_STREAM });\n}\n\nexport function sampleTypeBytes(sampleType: TciSampleType | TciSampleTypeName): number {\n switch (normalizeSampleType(sampleType)) {\n case TciSampleType.INT16:\n return 2;\n case TciSampleType.INT24:\n return 3;\n case TciSampleType.INT32:\n case TciSampleType.FLOAT32:\n return 4;\n default:\n throw new TciError('invalid-frame', `Unsupported TCI sample type: ${sampleType}`);\n }\n}\n\nexport function sampleTypeName(sampleType: TciSampleType): TciSampleTypeName {\n switch (sampleType) {\n case TciSampleType.INT16:\n return 'int16';\n case TciSampleType.INT24:\n return 'int24';\n case TciSampleType.INT32:\n return 'int32';\n case TciSampleType.FLOAT32:\n return 'float32';\n default:\n throw new TciError('invalid-frame', `Unsupported TCI sample type: ${sampleType}`);\n }\n}\n\nexport function normalizeSampleType(sampleType: TciSampleType | TciSampleTypeName | number): TciSampleType {\n if (typeof sampleType === 'string') {\n switch (sampleType.toLowerCase()) {\n case 'int16':\n return TciSampleType.INT16;\n case 'int24':\n return TciSampleType.INT24;\n case 'int32':\n return TciSampleType.INT32;\n case 'float32':\n return TciSampleType.FLOAT32;\n default:\n throw new TciError('invalid-frame', `Unsupported TCI sample type: ${sampleType}`);\n }\n }\n if (sampleType >= TciSampleType.INT16 && sampleType <= TciSampleType.FLOAT32) {\n return sampleType as TciSampleType;\n }\n throw new TciError('invalid-frame', `Unsupported TCI sample type: ${sampleType}`);\n}\n\nexport function normalizeStreamType(streamType: TciStreamType | number): TciStreamType {\n if (streamType >= TciStreamType.IQ_STREAM && streamType <= TciStreamType.LINEOUT_STREAM) {\n return streamType as TciStreamType;\n }\n throw new TciError('invalid-frame', `Unsupported TCI stream type: ${streamType}`);\n}\n\nexport function payloadToFloat32(frameOrPayload: TciStreamFrame | Buffer | Uint8Array, sampleType?: TciSampleType | TciSampleTypeName): Float32Array {\n const payload = isFrame(frameOrPayload) ? frameOrPayload.payload : toBuffer(frameOrPayload);\n const type = isFrame(frameOrPayload) ? frameOrPayload.sampleType : normalizeSampleType(sampleType ?? TciSampleType.FLOAT32);\n const bytes = sampleTypeBytes(type);\n if (payload.byteLength % bytes !== 0) {\n throw new TciError('invalid-frame', 'Payload length is not aligned to sample type');\n }\n\n const output = new Float32Array(payload.byteLength / bytes);\n const view = new DataView(payload.buffer, payload.byteOffset, payload.byteLength);\n for (let i = 0; i < output.length; i += 1) {\n const offset = i * bytes;\n switch (type) {\n case TciSampleType.INT16:\n output[i] = view.getInt16(offset, true) / 32768;\n break;\n case TciSampleType.INT24:\n output[i] = readInt24(view, offset) / 8388608;\n break;\n case TciSampleType.INT32:\n output[i] = view.getInt32(offset, true) / 2147483648;\n break;\n case TciSampleType.FLOAT32:\n output[i] = view.getFloat32(offset, true);\n break;\n }\n }\n return output;\n}\n\nexport function samplesToPayload(samples: Float32Array | readonly number[], sampleType: TciSampleType | TciSampleTypeName): Buffer {\n const type = normalizeSampleType(sampleType);\n const bytes = sampleTypeBytes(type);\n const payload = Buffer.alloc(samples.length * bytes);\n const view = new DataView(payload.buffer, payload.byteOffset, payload.byteLength);\n for (let i = 0; i < samples.length; i += 1) {\n const value = clampSample(samples[i] ?? 0);\n const offset = i * bytes;\n switch (type) {\n case TciSampleType.INT16:\n view.setInt16(offset, Math.round(value * 32767), true);\n break;\n case TciSampleType.INT24:\n writeInt24(view, offset, Math.round(value * 8388607));\n break;\n case TciSampleType.INT32:\n view.setInt32(offset, Math.round(value * 2147483647), true);\n break;\n case TciSampleType.FLOAT32:\n view.setFloat32(offset, value, true);\n break;\n }\n }\n return payload;\n}\n\nexport function pcm16ToFloat32(input: Buffer | Uint8Array | Int16Array): Float32Array {\n if (input instanceof Int16Array) {\n const output = new Float32Array(input.length);\n for (let i = 0; i < input.length; i += 1) {\n output[i] = input[i] / 32768;\n }\n return output;\n }\n return payloadToFloat32(toBuffer(input), TciSampleType.INT16);\n}\n\nexport function float32ToPcm16(samples: Float32Array | readonly number[]): Buffer {\n return samplesToPayload(samples, TciSampleType.INT16);\n}\n\nexport function deinterleaveChannels(samples: Float32Array, channels: number): Float32Array[] {\n if (channels <= 0 || samples.length % channels !== 0) {\n throw new TciError('invalid-frame', 'Cannot deinterleave samples with invalid channel count');\n }\n const frames = samples.length / channels;\n const outputs = Array.from({ length: channels }, () => new Float32Array(frames));\n for (let frame = 0; frame < frames; frame += 1) {\n for (let channel = 0; channel < channels; channel += 1) {\n outputs[channel][frame] = samples[frame * channels + channel];\n }\n }\n return outputs;\n}\n\nexport function mixToMono(samples: Float32Array, channels: number): Float32Array {\n if (channels === 1) {\n return samples;\n }\n const separated = deinterleaveChannels(samples, channels);\n const mono = new Float32Array(separated[0]?.length ?? 0);\n for (const channel of separated) {\n for (let i = 0; i < mono.length; i += 1) {\n mono[i] += channel[i] / channels;\n }\n }\n return mono;\n}\n\nfunction toBuffer(input: Buffer | Uint8Array | ArrayBuffer | ArrayBufferView): Buffer {\n if (Buffer.isBuffer(input)) {\n return input;\n }\n if (input instanceof ArrayBuffer) {\n return Buffer.from(input);\n }\n if (ArrayBuffer.isView(input)) {\n return Buffer.from(input.buffer, input.byteOffset, input.byteLength);\n }\n return Buffer.from(input);\n}\n\nfunction isFrame(value: unknown): value is TciStreamFrame {\n return Boolean(value && typeof value === 'object' && 'payload' in value && 'sampleType' in value);\n}\n\nfunction clampSample(value: number): number {\n if (!Number.isFinite(value)) {\n return 0;\n }\n return Math.max(-1, Math.min(1, value));\n}\n\nfunction readInt24(view: DataView, offset: number): number {\n const value = view.getUint8(offset) | (view.getUint8(offset + 1) << 8) | (view.getUint8(offset + 2) << 16);\n return value & 0x800000 ? value | 0xff000000 : value;\n}\n\nfunction writeInt24(view: DataView, offset: number, value: number): void {\n const clamped = Math.max(-8388608, Math.min(8388607, value));\n view.setUint8(offset, clamped & 0xff);\n view.setUint8(offset + 1, (clamped >> 8) & 0xff);\n view.setUint8(offset + 2, (clamped >> 16) & 0xff);\n}\n"],"mappings":"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","names":["TciStreamType","TciSampleType"]}
1
+ {"version":3,"sources":["../../src/errors.ts","../../src/audio/streamFrame.ts"],"sourcesContent":["export type TciErrorCode =\n | 'connect-timeout'\n | 'handshake-timeout'\n | 'invalid-handshake'\n | 'unknown-dialect'\n | 'command-timeout'\n | 'not-connected'\n | 'disconnected'\n | 'protocol-error'\n | 'invalid-frame'\n | 'cancelled';\n\nexport class TciError extends Error {\n readonly code: TciErrorCode;\n readonly details?: unknown;\n\n constructor(code: TciErrorCode, message: string, details?: unknown) {\n super(message);\n this.name = 'TciError';\n this.code = code;\n this.details = details;\n }\n}\n\nexport function toTciError(error: unknown, fallbackCode: TciErrorCode = 'protocol-error'): TciError {\n if (error instanceof TciError) {\n return error;\n }\n if (error instanceof Error) {\n return new TciError(fallbackCode, error.message, error);\n }\n return new TciError(fallbackCode, String(error), error);\n}\n","import { TciError } from '../errors.js';\nimport type { TciStreamLengthSemantics } from '../dialect/types.js';\n\nexport const TCI_STREAM_HEADER_BYTES = 16 * 4;\n\nexport enum TciStreamType {\n IQ_STREAM = 0,\n RX_AUDIO_STREAM = 1,\n TX_AUDIO_STREAM = 2,\n TX_CHRONO = 3,\n LINEOUT_STREAM = 4,\n}\n\nexport enum TciSampleType {\n INT16 = 0,\n INT24 = 1,\n INT32 = 2,\n FLOAT32 = 3,\n}\n\nexport type TciSampleTypeName = 'int16' | 'int24' | 'int32' | 'float32';\n\nexport interface TciStreamFrame {\n receiver: number;\n sampleRate: number;\n sampleType: TciSampleType;\n codec: number;\n crc: number;\n /** Byte length of the payload following the 64-byte TCI stream header. */\n payloadLength: number;\n streamType: TciStreamType;\n channels: number;\n reserved: number[];\n payload: Buffer;\n /** Raw Stream.length value from the wire header. */\n headerSampleCount: number;\n /** Canonical number of scalar sample values across all channels. */\n sampleCount: number;\n /** Number of sample frames, each containing `channels` scalar values. */\n frameCount: number;\n lengthSemantics: Exclude<TciStreamLengthSemantics, 'auto'>;\n}\n\nexport interface ParseStreamFrameOptions {\n lengthSemantics?: TciStreamLengthSemantics;\n negotiatedChannels?: number;\n}\n\nexport interface BuildStreamFrameOptions {\n receiver?: number;\n sampleRate: number;\n sampleType: TciSampleType | TciSampleTypeName;\n streamType: TciStreamType;\n channels: number;\n payload?: Buffer | Uint8Array | ArrayBuffer | ArrayBufferView;\n samples?: Float32Array | readonly number[];\n /** Explicit Stream.length value, used for header-only frames such as TX_CHRONO. */\n sampleCount?: number;\n lengthSemantics?: TciStreamLengthSemantics;\n codec?: number;\n crc?: number;\n reserved?: readonly number[];\n}\n\nexport interface BuildTxAudioFrameOptions extends Omit<BuildStreamFrameOptions, 'streamType'> {\n receiver?: number;\n}\n\nexport function parseStreamFrame(input: Buffer | ArrayBuffer | ArrayBufferView, options: ParseStreamFrameOptions = {}): TciStreamFrame {\n const buffer = toBuffer(input);\n if (buffer.byteLength < TCI_STREAM_HEADER_BYTES) {\n throw new TciError('invalid-frame', `TCI stream frame is shorter than ${TCI_STREAM_HEADER_BYTES} bytes`);\n }\n\n const view = new DataView(buffer.buffer, buffer.byteOffset, buffer.byteLength);\n const header = Array.from({ length: 16 }, (_, index) => view.getUint32(index * 4, true));\n const sampleType = normalizeSampleType(header[2]);\n const streamType = normalizeStreamType(header[6]);\n let channels = header[7] || options.negotiatedChannels || 0;\n const bytesPerSample = sampleTypeBytes(sampleType);\n const headerSampleCount = header[5];\n const actualPayloadLength = buffer.byteLength - TCI_STREAM_HEADER_BYTES;\n if (channels <= 0) {\n if (streamType === TciStreamType.TX_CHRONO && actualPayloadLength === 0) {\n channels = 1;\n } else {\n const inferredChannels = headerSampleCount > 0 ? actualPayloadLength / headerSampleCount / bytesPerSample : 1;\n if (!Number.isInteger(inferredChannels) || inferredChannels <= 0) {\n throw new TciError('invalid-frame', `Invalid TCI channel count: ${channels}`);\n }\n channels = inferredChannels;\n }\n }\n const payloadLength = actualPayloadLength;\n const alignedFrameBytes = bytesPerSample * channels;\n if (payloadLength % alignedFrameBytes !== 0) {\n throw new TciError('invalid-frame', 'TCI payload length is not aligned to sample type and channel count');\n }\n\n const actualScalarCount = payloadLength / bytesPerSample;\n const requestedSemantics = options.lengthSemantics ?? 'auto';\n const lengthSemantics = resolveLengthSemantics(\n requestedSemantics,\n streamType,\n headerSampleCount,\n actualScalarCount,\n channels,\n );\n const sampleCount = lengthSemantics === 'per-channel' ? headerSampleCount * channels : headerSampleCount;\n\n if (streamType !== TciStreamType.TX_CHRONO) {\n const expectedPayloadLength = sampleCount * bytesPerSample;\n if (payloadLength !== expectedPayloadLength) {\n throw new TciError(\n 'invalid-frame',\n `TCI stream frame length mismatch: header says ${headerSampleCount} samples using ${lengthSemantics} semantics (${expectedPayloadLength} payload bytes), got ${payloadLength}`,\n );\n }\n }\n\n if (sampleCount % channels !== 0) {\n throw new TciError('invalid-frame', `TCI scalar sample count ${sampleCount} is not divisible by ${channels} channels`);\n }\n\n return {\n receiver: header[0],\n sampleRate: header[1],\n sampleType,\n codec: header[3],\n crc: header[4],\n payloadLength,\n streamType,\n channels,\n reserved: header.slice(8),\n payload: buffer.subarray(TCI_STREAM_HEADER_BYTES),\n headerSampleCount,\n sampleCount,\n frameCount: sampleCount / channels,\n lengthSemantics,\n };\n}\n\nexport function buildStreamFrame(options: BuildStreamFrameOptions): Buffer {\n const sampleType = normalizeSampleType(options.sampleType);\n const payload = options.payload ? toBuffer(options.payload) : samplesToPayload(options.samples ?? [], sampleType);\n const channels = options.channels;\n if (channels <= 0) {\n throw new TciError('invalid-frame', `Invalid TCI channel count: ${channels}`);\n }\n const bytesPerSample = sampleTypeBytes(sampleType);\n if (payload.byteLength % (bytesPerSample * channels) !== 0) {\n throw new TciError('invalid-frame', 'TCI payload length is not aligned to sample type and channel count');\n }\n const derivedSampleCount = payload.byteLength / bytesPerSample;\n const sampleCount = options.sampleCount ?? derivedSampleCount;\n if (!Number.isInteger(sampleCount) || sampleCount < 0) {\n throw new TciError('invalid-frame', `Invalid TCI sample count: ${sampleCount}`);\n }\n if (payload.byteLength > 0 && sampleCount !== derivedSampleCount) {\n throw new TciError('invalid-frame', `Explicit scalar sample count ${sampleCount} does not match payload count ${derivedSampleCount}`);\n }\n if (sampleCount % channels !== 0) {\n throw new TciError('invalid-frame', `TCI scalar sample count ${sampleCount} is not divisible by ${channels} channels`);\n }\n const lengthSemantics = options.lengthSemantics === 'per-channel' ? 'per-channel' : 'scalar';\n const headerSampleCount = lengthSemantics === 'per-channel' ? sampleCount / channels : sampleCount;\n\n const frame = Buffer.alloc(TCI_STREAM_HEADER_BYTES + payload.byteLength);\n const view = new DataView(frame.buffer, frame.byteOffset, frame.byteLength);\n const reserved = options.reserved ?? [];\n const header = [\n options.receiver ?? 0,\n options.sampleRate,\n sampleType,\n options.codec ?? 0,\n options.crc ?? 0,\n headerSampleCount,\n options.streamType,\n channels,\n ...Array.from({ length: 8 }, (_, index) => reserved[index] ?? 0),\n ];\n header.forEach((value, index) => view.setUint32(index * 4, value >>> 0, true));\n payload.copy(frame, TCI_STREAM_HEADER_BYTES);\n return frame;\n}\n\nfunction resolveLengthSemantics(\n requested: TciStreamLengthSemantics,\n streamType: TciStreamType,\n headerSampleCount: number,\n actualScalarCount: number,\n channels: number,\n): Exclude<TciStreamLengthSemantics, 'auto'> {\n if (requested !== 'auto') return requested;\n if (streamType === TciStreamType.TX_CHRONO && actualScalarCount === 0) return 'scalar';\n if (actualScalarCount === headerSampleCount) return 'scalar';\n if (actualScalarCount === headerSampleCount * channels) return 'per-channel';\n return 'scalar';\n}\n\nexport function buildTxAudioFrame(options: BuildTxAudioFrameOptions): Buffer {\n return buildStreamFrame({ ...options, streamType: TciStreamType.TX_AUDIO_STREAM });\n}\n\nexport function sampleTypeBytes(sampleType: TciSampleType | TciSampleTypeName): number {\n switch (normalizeSampleType(sampleType)) {\n case TciSampleType.INT16:\n return 2;\n case TciSampleType.INT24:\n return 3;\n case TciSampleType.INT32:\n case TciSampleType.FLOAT32:\n return 4;\n default:\n throw new TciError('invalid-frame', `Unsupported TCI sample type: ${sampleType}`);\n }\n}\n\nexport function sampleTypeName(sampleType: TciSampleType): TciSampleTypeName {\n switch (sampleType) {\n case TciSampleType.INT16:\n return 'int16';\n case TciSampleType.INT24:\n return 'int24';\n case TciSampleType.INT32:\n return 'int32';\n case TciSampleType.FLOAT32:\n return 'float32';\n default:\n throw new TciError('invalid-frame', `Unsupported TCI sample type: ${sampleType}`);\n }\n}\n\nexport function normalizeSampleType(sampleType: TciSampleType | TciSampleTypeName | number): TciSampleType {\n if (typeof sampleType === 'string') {\n switch (sampleType.toLowerCase()) {\n case 'int16':\n return TciSampleType.INT16;\n case 'int24':\n return TciSampleType.INT24;\n case 'int32':\n return TciSampleType.INT32;\n case 'float32':\n return TciSampleType.FLOAT32;\n default:\n throw new TciError('invalid-frame', `Unsupported TCI sample type: ${sampleType}`);\n }\n }\n if (sampleType >= TciSampleType.INT16 && sampleType <= TciSampleType.FLOAT32) {\n return sampleType as TciSampleType;\n }\n throw new TciError('invalid-frame', `Unsupported TCI sample type: ${sampleType}`);\n}\n\nexport function normalizeStreamType(streamType: TciStreamType | number): TciStreamType {\n if (streamType >= TciStreamType.IQ_STREAM && streamType <= TciStreamType.LINEOUT_STREAM) {\n return streamType as TciStreamType;\n }\n throw new TciError('invalid-frame', `Unsupported TCI stream type: ${streamType}`);\n}\n\nexport function payloadToFloat32(frameOrPayload: TciStreamFrame | Buffer | Uint8Array, sampleType?: TciSampleType | TciSampleTypeName): Float32Array {\n const payload = isFrame(frameOrPayload) ? frameOrPayload.payload : toBuffer(frameOrPayload);\n const type = isFrame(frameOrPayload) ? frameOrPayload.sampleType : normalizeSampleType(sampleType ?? TciSampleType.FLOAT32);\n const bytes = sampleTypeBytes(type);\n if (payload.byteLength % bytes !== 0) {\n throw new TciError('invalid-frame', 'Payload length is not aligned to sample type');\n }\n\n const output = new Float32Array(payload.byteLength / bytes);\n const view = new DataView(payload.buffer, payload.byteOffset, payload.byteLength);\n for (let i = 0; i < output.length; i += 1) {\n const offset = i * bytes;\n switch (type) {\n case TciSampleType.INT16:\n output[i] = view.getInt16(offset, true) / 32768;\n break;\n case TciSampleType.INT24:\n output[i] = readInt24(view, offset) / 8388608;\n break;\n case TciSampleType.INT32:\n output[i] = view.getInt32(offset, true) / 2147483648;\n break;\n case TciSampleType.FLOAT32:\n output[i] = view.getFloat32(offset, true);\n break;\n }\n }\n return output;\n}\n\nexport function decodeInterleavedIq(frame: TciStreamFrame): Float32Array {\n if (frame.streamType !== TciStreamType.IQ_STREAM) {\n throw new TciError('invalid-frame', 'Expected a TCI IQ stream frame');\n }\n if (frame.channels !== 2 || frame.sampleCount % 2 !== 0) {\n throw new TciError('invalid-frame', `TCI IQ frame must contain interleaved I/Q pairs, got ${frame.channels} channels`);\n }\n return payloadToFloat32(frame);\n}\n\nexport function samplesToPayload(samples: Float32Array | readonly number[], sampleType: TciSampleType | TciSampleTypeName): Buffer {\n const type = normalizeSampleType(sampleType);\n const bytes = sampleTypeBytes(type);\n const payload = Buffer.alloc(samples.length * bytes);\n const view = new DataView(payload.buffer, payload.byteOffset, payload.byteLength);\n for (let i = 0; i < samples.length; i += 1) {\n const value = clampSample(samples[i] ?? 0);\n const offset = i * bytes;\n switch (type) {\n case TciSampleType.INT16:\n view.setInt16(offset, Math.round(value * 32767), true);\n break;\n case TciSampleType.INT24:\n writeInt24(view, offset, Math.round(value * 8388607));\n break;\n case TciSampleType.INT32:\n view.setInt32(offset, Math.round(value * 2147483647), true);\n break;\n case TciSampleType.FLOAT32:\n view.setFloat32(offset, value, true);\n break;\n }\n }\n return payload;\n}\n\nexport function pcm16ToFloat32(input: Buffer | Uint8Array | Int16Array): Float32Array {\n if (input instanceof Int16Array) {\n const output = new Float32Array(input.length);\n for (let i = 0; i < input.length; i += 1) {\n output[i] = input[i] / 32768;\n }\n return output;\n }\n return payloadToFloat32(toBuffer(input), TciSampleType.INT16);\n}\n\nexport function float32ToPcm16(samples: Float32Array | readonly number[]): Buffer {\n return samplesToPayload(samples, TciSampleType.INT16);\n}\n\nexport function deinterleaveChannels(samples: Float32Array, channels: number): Float32Array[] {\n if (channels <= 0 || samples.length % channels !== 0) {\n throw new TciError('invalid-frame', 'Cannot deinterleave samples with invalid channel count');\n }\n const frames = samples.length / channels;\n const outputs = Array.from({ length: channels }, () => new Float32Array(frames));\n for (let frame = 0; frame < frames; frame += 1) {\n for (let channel = 0; channel < channels; channel += 1) {\n outputs[channel][frame] = samples[frame * channels + channel];\n }\n }\n return outputs;\n}\n\nexport function mixToMono(samples: Float32Array, channels: number): Float32Array {\n if (channels === 1) {\n return samples;\n }\n const separated = deinterleaveChannels(samples, channels);\n const mono = new Float32Array(separated[0]?.length ?? 0);\n for (const channel of separated) {\n for (let i = 0; i < mono.length; i += 1) {\n mono[i] += channel[i] / channels;\n }\n }\n return mono;\n}\n\nfunction toBuffer(input: Buffer | Uint8Array | ArrayBuffer | ArrayBufferView): Buffer {\n if (Buffer.isBuffer(input)) {\n return input;\n }\n if (input instanceof ArrayBuffer) {\n return Buffer.from(input);\n }\n if (ArrayBuffer.isView(input)) {\n return Buffer.from(input.buffer, input.byteOffset, input.byteLength);\n }\n return Buffer.from(input);\n}\n\nfunction isFrame(value: unknown): value is TciStreamFrame {\n return Boolean(value && typeof value === 'object' && 'payload' in value && 'sampleType' in value);\n}\n\nfunction clampSample(value: number): number {\n if (!Number.isFinite(value)) {\n return 0;\n }\n return Math.max(-1, Math.min(1, value));\n}\n\nfunction readInt24(view: DataView, offset: number): number {\n const value = view.getUint8(offset) | (view.getUint8(offset + 1) << 8) | (view.getUint8(offset + 2) << 16);\n return value & 0x800000 ? value | 0xff000000 : value;\n}\n\nfunction writeInt24(view: DataView, offset: number, value: number): void {\n const clamped = Math.max(-8388608, Math.min(8388607, value));\n view.setUint8(offset, clamped & 0xff);\n view.setUint8(offset + 1, (clamped >> 8) & 0xff);\n view.setUint8(offset + 2, (clamped >> 16) & 0xff);\n}\n"],"mappings":"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","names":["TciStreamType","TciSampleType"]}
@@ -43,6 +43,8 @@ var StandardTciDialect = class {
43
43
  streamLengthSemantics;
44
44
  supportsStreamChannels;
45
45
  supportsTxAudioSource;
46
+ supportsIqStream;
47
+ iqSampleRates;
46
48
  driveHasTrx;
47
49
  detector;
48
50
  resolver;
@@ -52,6 +54,8 @@ var StandardTciDialect = class {
52
54
  this.streamLengthSemantics = options.streamLengthSemantics;
53
55
  this.supportsStreamChannels = options.supportsStreamChannels;
54
56
  this.supportsTxAudioSource = options.supportsTxAudioSource;
57
+ this.supportsIqStream = options.supportsIqStream;
58
+ this.iqSampleRates = [...options.iqSampleRates];
55
59
  this.driveHasTrx = options.driveHasTrx;
56
60
  this.detector = options.detect;
57
61
  this.resolver = options.resolve;
@@ -99,6 +103,8 @@ var expertSdr14Dialect = new StandardTciDialect({
99
103
  streamLengthSemantics: "per-channel",
100
104
  supportsStreamChannels: false,
101
105
  supportsTxAudioSource: false,
106
+ supportsIqStream: true,
107
+ iqSampleRates: [48e3, 96e3, 192e3, 384e3],
102
108
  driveHasTrx: false,
103
109
  detect: (context) => {
104
110
  const parsed = version(context);
@@ -112,6 +118,8 @@ var expertSdrLegacyDialect = new StandardTciDialect({
112
118
  streamLengthSemantics: "per-channel",
113
119
  supportsStreamChannels: false,
114
120
  supportsTxAudioSource: false,
121
+ supportsIqStream: true,
122
+ iqSampleRates: [48e3, 96e3, 192e3, 384e3],
115
123
  driveHasTrx: true,
116
124
  detect: (context) => {
117
125
  const parsed = version(context);
@@ -125,6 +133,8 @@ var expertSdrModernDialect = new StandardTciDialect({
125
133
  streamLengthSemantics: "scalar",
126
134
  supportsStreamChannels: true,
127
135
  supportsTxAudioSource: true,
136
+ supportsIqStream: true,
137
+ iqSampleRates: [48e3, 96e3, 192e3, 384e3],
128
138
  driveHasTrx: true,
129
139
  detect: (context) => {
130
140
  const parsed = version(context);
@@ -143,6 +153,8 @@ var thetisDialect = new StandardTciDialect({
143
153
  streamLengthSemantics: "scalar",
144
154
  supportsStreamChannels: true,
145
155
  supportsTxAudioSource: true,
156
+ supportsIqStream: true,
157
+ iqSampleRates: [48e3, 96e3, 192e3, 384e3],
146
158
  driveHasTrx: true,
147
159
  detect: (context) => {
148
160
  const evidence = [];
@@ -169,6 +181,8 @@ var aetherSdrDialect = new StandardTciDialect({
169
181
  streamLengthSemantics: "scalar",
170
182
  supportsStreamChannels: true,
171
183
  supportsTxAudioSource: true,
184
+ supportsIqStream: true,
185
+ iqSampleRates: [24e3, 48e3, 96e3, 192e3],
172
186
  driveHasTrx: true,
173
187
  detect: (context) => {
174
188
  if (!/^aethersdr$/i.test(context.identity.device ?? "")) return { score: 0, evidence: [] };
@@ -188,6 +202,8 @@ var genericObservedDialect = new StandardTciDialect({
188
202
  streamLengthSemantics: "auto",
189
203
  supportsStreamChannels: true,
190
204
  supportsTxAudioSource: true,
205
+ supportsIqStream: false,
206
+ iqSampleRates: [],
191
207
  driveHasTrx: true,
192
208
  detect: (context) => ({
193
209
  score: context.commandNames.has("ready") ? 10 : 0,
@@ -203,6 +219,8 @@ var genericObservedDialect = new StandardTciDialect({
203
219
  streamLengthSemantics: "auto",
204
220
  supportsStreamChannels: context.commandNames.has("audio_stream_channels"),
205
221
  supportsTxAudioSource: compareTciVersion(version(context) ?? [0], [2, 0]) >= 0,
222
+ supportsIqStream: context.commandNames.has("iq_samplerate"),
223
+ iqSampleRates: context.commandNames.has("iq_samplerate") ? [48e3] : [],
206
224
  driveHasTrx,
207
225
  detect: genericObservedDialect.detect.bind(genericObservedDialect)
208
226
  });
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/dialect/index.ts","../../src/dialect/builtins.ts","../../src/errors.ts","../../src/dialect/registry.ts","../../src/dialect/handshake.ts"],"sourcesContent":["export * from './types.js';\nexport * from './builtins.js';\nexport * from './registry.js';\nexport * from './handshake.js';\n","import type {\n TciDialect,\n TciDialectDetectionContext,\n TciDialectScore,\n TciDriveState,\n TciStreamLengthSemantics,\n} from './types.js';\n\ntype VersionTuple = readonly number[];\n\ninterface StandardDialectOptions {\n id: TciDialect['id'];\n label: string;\n streamLengthSemantics: TciStreamLengthSemantics;\n supportsStreamChannels: boolean;\n supportsTxAudioSource: boolean;\n driveHasTrx: boolean;\n detect: (context: TciDialectDetectionContext) => TciDialectScore;\n resolve?: (context: TciDialectDetectionContext) => TciDialect;\n}\n\nclass StandardTciDialect implements TciDialect {\n readonly id: TciDialect['id'];\n readonly label: string;\n readonly streamLengthSemantics: TciStreamLengthSemantics;\n readonly supportsStreamChannels: boolean;\n readonly supportsTxAudioSource: boolean;\n private readonly driveHasTrx: boolean;\n private readonly detector: StandardDialectOptions['detect'];\n private readonly resolver?: StandardDialectOptions['resolve'];\n\n constructor(options: StandardDialectOptions) {\n this.id = options.id;\n this.label = options.label;\n this.streamLengthSemantics = options.streamLengthSemantics;\n this.supportsStreamChannels = options.supportsStreamChannels;\n this.supportsTxAudioSource = options.supportsTxAudioSource;\n this.driveHasTrx = options.driveHasTrx;\n this.detector = options.detect;\n this.resolver = options.resolve;\n }\n\n detect(context: TciDialectDetectionContext): TciDialectScore {\n return this.detector(context);\n }\n\n resolve(context: TciDialectDetectionContext): TciDialect {\n return this.resolver?.(context) ?? this;\n }\n\n buildDriveSetArgs(trx: number, value: number): readonly unknown[] {\n return this.driveHasTrx ? [trx, value] : [value];\n }\n\n buildDriveReadArgs(trx: number): readonly unknown[] {\n return this.driveHasTrx ? [trx] : [];\n }\n\n parseDrive(args: readonly string[], defaultTrx: number): TciDriveState | undefined {\n return parseDriveState(args, defaultTrx, this.driveHasTrx);\n }\n\n buildTuneDriveSetArgs(trx: number, value: number): readonly unknown[] {\n return this.driveHasTrx ? [trx, value] : [value];\n }\n\n buildTuneDriveReadArgs(trx: number): readonly unknown[] {\n return this.driveHasTrx ? [trx] : [];\n }\n\n parseTuneDrive(args: readonly string[], defaultTrx: number): TciDriveState | undefined {\n return parseDriveState(args, defaultTrx, this.driveHasTrx);\n }\n}\n\nfunction parseDriveState(args: readonly string[], defaultTrx: number, hasTrx: boolean): TciDriveState | undefined {\n const trx = hasTrx ? Number(args[0]) : defaultTrx;\n const value = Number(args[hasTrx ? 1 : 0]);\n if (!Number.isInteger(trx) || !Number.isFinite(value)) return undefined;\n return { trx, value };\n}\n\nfunction version(context: TciDialectDetectionContext): VersionTuple | undefined {\n return parseTciVersion(context.identity.protocolVersion);\n}\n\nfunction programIncludes(context: TciDialectDetectionContext, value: string): boolean {\n return context.identity.programName?.toLowerCase().includes(value) ?? false;\n}\n\nexport const expertSdr14Dialect: TciDialect = new StandardTciDialect({\n id: 'expertsdr-1.4', label: 'ExpertSDR / TCI 1.4', streamLengthSemantics: 'per-channel',\n supportsStreamChannels: false, supportsTxAudioSource: false, driveHasTrx: false,\n detect: (context) => {\n const parsed = version(context);\n if (!parsed || compareTciVersion(parsed, [1, 4]) > 0) return { score: 0, evidence: [] };\n return { score: 80 + (programIncludes(context, 'expert') ? 10 : 0), evidence: [`protocol ${formatVersion(parsed)} <= 1.4`] };\n },\n});\n\nexport const expertSdrLegacyDialect: TciDialect = new StandardTciDialect({\n id: 'expertsdr-1.5-1.8', label: 'ExpertSDR / TCI 1.5-1.8', streamLengthSemantics: 'per-channel',\n supportsStreamChannels: false, supportsTxAudioSource: false, driveHasTrx: true,\n detect: (context) => {\n const parsed = version(context);\n if (!parsed || compareTciVersion(parsed, [1, 5]) < 0 || compareTciVersion(parsed, [1, 9]) >= 0) return { score: 0, evidence: [] };\n return { score: 80 + (programIncludes(context, 'expert') ? 10 : 0), evidence: [`protocol ${formatVersion(parsed)} is in 1.5-1.8`] };\n },\n});\n\nexport const expertSdrModernDialect: TciDialect = new StandardTciDialect({\n id: 'expertsdr-1.9-2.0', label: 'ExpertSDR / TCI 1.9-2.0', streamLengthSemantics: 'scalar',\n supportsStreamChannels: true, supportsTxAudioSource: true, driveHasTrx: true,\n detect: (context) => {\n const parsed = version(context);\n if (!parsed || compareTciVersion(parsed, [1, 9]) < 0) return { score: 0, evidence: [] };\n const future = compareTciVersion(parsed, [2, 0]) > 0;\n return {\n score: 70 + (programIncludes(context, 'expert') ? 15 : 0),\n evidence: [`protocol ${formatVersion(parsed)} uses modern stream negotiation`],\n warnings: future ? [`Unknown future TCI version ${formatVersion(parsed)}; using the modern dialect`] : [],\n };\n },\n});\n\nexport const thetisDialect: TciDialect = new StandardTciDialect({\n id: 'thetis-2.0', label: 'Thetis / TCI 2.0', streamLengthSemantics: 'scalar',\n supportsStreamChannels: true, supportsTxAudioSource: true, driveHasTrx: true,\n detect: (context) => {\n const evidence: string[] = [];\n let score = 0;\n if (programIncludes(context, 'thetis')) { score += 120; evidence.push('PROTOCOL program is Thetis'); }\n const observed = ['tx_frequency_ex', 'tx_profiles_ex', 'tx_profile_ex', 'calibration_ex']\n .filter((name) => context.commandNames.has(name));\n if (observed.length > 0) { score += 100; evidence.push(`Thetis extension commands: ${observed.join(', ')}`); }\n if (/anan|hermes|orion|saturn/i.test(context.identity.device ?? '')) {\n score += 30;\n evidence.push(`Thetis-family device: ${context.identity.device}`);\n }\n return { score, evidence };\n },\n});\n\nexport const aetherSdrDialect: TciDialect = new StandardTciDialect({\n id: 'aethersdr-1.5', label: 'AetherSDR / TCI 1.5 hybrid', streamLengthSemantics: 'scalar',\n supportsStreamChannels: true, supportsTxAudioSource: true, driveHasTrx: true,\n detect: (context) => {\n if (!/^aethersdr$/i.test(context.identity.device ?? '')) return { score: 0, evidence: [] };\n const evidence = [`AetherSDR device identity: ${context.identity.device}`];\n const modernAudioCommands = ['audio_stream_sample_type', 'audio_stream_channels', 'audio_stream_samples']\n .filter((name) => context.commandNames.has(name));\n if (modernAudioCommands.length > 0) evidence.push(`Modern audio negotiation: ${modernAudioCommands.join(', ')}`);\n return {\n score: 150,\n evidence,\n warnings: context.identity.protocolVersion === '1.5'\n ? ['AetherSDR reports TCI 1.5 but uses modern scalar audio stream semantics']\n : [],\n };\n },\n});\n\nexport const genericObservedDialect: TciDialect = new StandardTciDialect({\n id: 'generic-observed', label: 'Generic observed TCI', streamLengthSemantics: 'auto',\n supportsStreamChannels: true, supportsTxAudioSource: true, driveHasTrx: true,\n detect: (context) => ({\n score: context.commandNames.has('ready') ? 10 : 0,\n evidence: ['No vendor-specific match; using observed command shapes'],\n warnings: ['Dialect identity is uncertain'],\n }),\n resolve: (context) => {\n const drive = [...context.commands].reverse().find((command) => command.name === 'drive');\n const driveHasTrx = (drive?.args.length ?? 0) >= 2;\n return new StandardTciDialect({\n id: 'generic-observed',\n label: 'Generic observed TCI',\n streamLengthSemantics: 'auto',\n supportsStreamChannels: context.commandNames.has('audio_stream_channels'),\n supportsTxAudioSource: compareTciVersion(version(context) ?? [0], [2, 0]) >= 0,\n driveHasTrx,\n detect: genericObservedDialect.detect.bind(genericObservedDialect),\n });\n },\n});\n\nexport const builtInDialects: readonly TciDialect[] = [\n aetherSdrDialect, thetisDialect, expertSdr14Dialect, expertSdrLegacyDialect, expertSdrModernDialect, genericObservedDialect,\n];\n\nexport function parseTciVersion(value: string | undefined): VersionTuple | undefined {\n if (!value) return undefined;\n const match = value.trim().match(/^(\\d+)(?:\\.(\\d+))?(?:\\.(\\d+))?/);\n if (!match) return undefined;\n return match.slice(1).filter((part): part is string => part !== undefined).map(Number);\n}\n\nexport function compareTciVersion(left: VersionTuple, right: VersionTuple): number {\n const length = Math.max(left.length, right.length);\n for (let index = 0; index < length; index += 1) {\n const difference = (left[index] ?? 0) - (right[index] ?? 0);\n if (difference !== 0) return difference;\n }\n return 0;\n}\n\nfunction formatVersion(value: VersionTuple): string { return value.join('.'); }\n","export type TciErrorCode =\n | 'connect-timeout'\n | 'handshake-timeout'\n | 'invalid-handshake'\n | 'unknown-dialect'\n | 'command-timeout'\n | 'not-connected'\n | 'disconnected'\n | 'protocol-error'\n | 'invalid-frame'\n | 'cancelled';\n\nexport class TciError extends Error {\n readonly code: TciErrorCode;\n readonly details?: unknown;\n\n constructor(code: TciErrorCode, message: string, details?: unknown) {\n super(message);\n this.name = 'TciError';\n this.code = code;\n this.details = details;\n }\n}\n\nexport function toTciError(error: unknown, fallbackCode: TciErrorCode = 'protocol-error'): TciError {\n if (error instanceof TciError) {\n return error;\n }\n if (error instanceof Error) {\n return new TciError(fallbackCode, error.message, error);\n }\n return new TciError(fallbackCode, String(error), error);\n}\n","import { TciError } from '../errors.js';\nimport { builtInDialects } from './builtins.js';\nimport type {\n TciDialect,\n TciDialectDetection,\n TciDialectDetectionContext,\n TciDialectId,\n TciDialectSelection,\n} from './types.js';\n\nexport class TciDialectRegistry {\n private readonly dialects = new Map<TciDialectId, TciDialect>();\n\n constructor(dialects: readonly TciDialect[] = builtInDialects) {\n for (const dialect of dialects) this.register(dialect);\n }\n\n register(dialect: TciDialect): void { this.dialects.set(dialect.id, dialect); }\n get(id: TciDialectId): TciDialect | undefined { return this.dialects.get(id); }\n list(): TciDialect[] { return [...this.dialects.values()]; }\n\n select(context: TciDialectDetectionContext, selection: TciDialectSelection = 'auto'): TciDialectDetection {\n if (typeof selection === 'object') {\n return { dialect: selection, confidence: 'manual', evidence: ['Custom dialect supplied by caller'], warnings: [] };\n }\n if (selection !== 'auto') {\n const dialect = this.get(selection);\n if (!dialect) throw new TciError('unknown-dialect', `Unknown TCI dialect: ${selection}`);\n return { dialect: dialect.resolve?.(context) ?? dialect, confidence: 'manual', evidence: [`Dialect ${selection} selected by caller`], warnings: [] };\n }\n\n const candidates = this.list()\n .map((dialect) => ({ dialect, result: dialect.detect(context) }))\n .sort((left, right) => right.result.score - left.result.score);\n const selected = candidates[0];\n if (!selected || selected.result.score <= 0) {\n throw new TciError('unknown-dialect', 'Unable to identify the TCI server dialect');\n }\n return {\n dialect: selected.dialect.resolve?.(context) ?? selected.dialect,\n confidence: selected.result.score >= 100 ? 'high' : selected.result.score >= 70 ? 'medium' : 'low',\n evidence: selected.result.evidence,\n warnings: selected.result.warnings ?? [],\n };\n }\n}\n\nexport const defaultTciDialectRegistry = new TciDialectRegistry();\n","import { TciError } from '../errors.js';\nimport type { TciCommand } from '../protocol/text.js';\nimport type { TciProtocolIdentity } from './types.js';\n\nconst IDENTITY_COMMANDS = new Set(['protocol', 'device', 'trx_count', 'channels_count', 'channel_count']);\nconst STATE_COMMANDS = new Set(['vfo', 'modulation', 'modulations_list', 'trx', 'drive']);\n\nexport function parseProtocolIdentity(commands: readonly TciCommand[]): TciProtocolIdentity {\n const protocol = [...commands].reverse().find((command) => command.name === 'protocol');\n const device = [...commands].reverse().find((command) => command.name === 'device');\n const rawProtocolArgs = protocol?.args ?? [];\n const firstLooksLikeVersion = /^\\d+(?:\\.\\d+){0,2}/.test(rawProtocolArgs[0] ?? '');\n return {\n programName: firstLooksLikeVersion ? undefined : rawProtocolArgs[0],\n protocolVersion: firstLooksLikeVersion ? rawProtocolArgs[0] : rawProtocolArgs[1],\n rawProtocolArgs: [...rawProtocolArgs],\n device: device?.args.join(','),\n };\n}\n\nexport function assertValidTciHandshake(commands: readonly TciCommand[]): void {\n const names = new Set(commands.map((command) => command.name));\n if (!names.has('ready')) throw new TciError('handshake-timeout', 'TCI READY was not received');\n const categories = [\n [...IDENTITY_COMMANDS].some((name) => names.has(name)),\n [...STATE_COMMANDS].some((name) => names.has(name)),\n ].filter(Boolean).length;\n const identitySignals = [...IDENTITY_COMMANDS].filter((name) => names.has(name)).length;\n if (categories < 2 && identitySignals < 2) {\n throw new TciError('invalid-handshake', 'WebSocket opened but did not provide enough TCI initialization evidence');\n }\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/dialect/index.ts","../../src/dialect/builtins.ts","../../src/errors.ts","../../src/dialect/registry.ts","../../src/dialect/handshake.ts"],"sourcesContent":["export * from './types.js';\nexport * from './builtins.js';\nexport * from './registry.js';\nexport * from './handshake.js';\n","import type {\n TciDialect,\n TciDialectDetectionContext,\n TciDialectScore,\n TciDriveState,\n TciStreamLengthSemantics,\n} from './types.js';\n\ntype VersionTuple = readonly number[];\n\ninterface StandardDialectOptions {\n id: TciDialect['id'];\n label: string;\n streamLengthSemantics: TciStreamLengthSemantics;\n supportsStreamChannels: boolean;\n supportsTxAudioSource: boolean;\n supportsIqStream: boolean;\n iqSampleRates: readonly number[];\n driveHasTrx: boolean;\n detect: (context: TciDialectDetectionContext) => TciDialectScore;\n resolve?: (context: TciDialectDetectionContext) => TciDialect;\n}\n\nclass StandardTciDialect implements TciDialect {\n readonly id: TciDialect['id'];\n readonly label: string;\n readonly streamLengthSemantics: TciStreamLengthSemantics;\n readonly supportsStreamChannels: boolean;\n readonly supportsTxAudioSource: boolean;\n readonly supportsIqStream: boolean;\n readonly iqSampleRates: readonly number[];\n private readonly driveHasTrx: boolean;\n private readonly detector: StandardDialectOptions['detect'];\n private readonly resolver?: StandardDialectOptions['resolve'];\n\n constructor(options: StandardDialectOptions) {\n this.id = options.id;\n this.label = options.label;\n this.streamLengthSemantics = options.streamLengthSemantics;\n this.supportsStreamChannels = options.supportsStreamChannels;\n this.supportsTxAudioSource = options.supportsTxAudioSource;\n this.supportsIqStream = options.supportsIqStream;\n this.iqSampleRates = [...options.iqSampleRates];\n this.driveHasTrx = options.driveHasTrx;\n this.detector = options.detect;\n this.resolver = options.resolve;\n }\n\n detect(context: TciDialectDetectionContext): TciDialectScore {\n return this.detector(context);\n }\n\n resolve(context: TciDialectDetectionContext): TciDialect {\n return this.resolver?.(context) ?? this;\n }\n\n buildDriveSetArgs(trx: number, value: number): readonly unknown[] {\n return this.driveHasTrx ? [trx, value] : [value];\n }\n\n buildDriveReadArgs(trx: number): readonly unknown[] {\n return this.driveHasTrx ? [trx] : [];\n }\n\n parseDrive(args: readonly string[], defaultTrx: number): TciDriveState | undefined {\n return parseDriveState(args, defaultTrx, this.driveHasTrx);\n }\n\n buildTuneDriveSetArgs(trx: number, value: number): readonly unknown[] {\n return this.driveHasTrx ? [trx, value] : [value];\n }\n\n buildTuneDriveReadArgs(trx: number): readonly unknown[] {\n return this.driveHasTrx ? [trx] : [];\n }\n\n parseTuneDrive(args: readonly string[], defaultTrx: number): TciDriveState | undefined {\n return parseDriveState(args, defaultTrx, this.driveHasTrx);\n }\n}\n\nfunction parseDriveState(args: readonly string[], defaultTrx: number, hasTrx: boolean): TciDriveState | undefined {\n const trx = hasTrx ? Number(args[0]) : defaultTrx;\n const value = Number(args[hasTrx ? 1 : 0]);\n if (!Number.isInteger(trx) || !Number.isFinite(value)) return undefined;\n return { trx, value };\n}\n\nfunction version(context: TciDialectDetectionContext): VersionTuple | undefined {\n return parseTciVersion(context.identity.protocolVersion);\n}\n\nfunction programIncludes(context: TciDialectDetectionContext, value: string): boolean {\n return context.identity.programName?.toLowerCase().includes(value) ?? false;\n}\n\nexport const expertSdr14Dialect: TciDialect = new StandardTciDialect({\n id: 'expertsdr-1.4', label: 'ExpertSDR / TCI 1.4', streamLengthSemantics: 'per-channel',\n supportsStreamChannels: false, supportsTxAudioSource: false, supportsIqStream: true,\n iqSampleRates: [48_000, 96_000, 192_000, 384_000], driveHasTrx: false,\n detect: (context) => {\n const parsed = version(context);\n if (!parsed || compareTciVersion(parsed, [1, 4]) > 0) return { score: 0, evidence: [] };\n return { score: 80 + (programIncludes(context, 'expert') ? 10 : 0), evidence: [`protocol ${formatVersion(parsed)} <= 1.4`] };\n },\n});\n\nexport const expertSdrLegacyDialect: TciDialect = new StandardTciDialect({\n id: 'expertsdr-1.5-1.8', label: 'ExpertSDR / TCI 1.5-1.8', streamLengthSemantics: 'per-channel',\n supportsStreamChannels: false, supportsTxAudioSource: false, supportsIqStream: true,\n iqSampleRates: [48_000, 96_000, 192_000, 384_000], driveHasTrx: true,\n detect: (context) => {\n const parsed = version(context);\n if (!parsed || compareTciVersion(parsed, [1, 5]) < 0 || compareTciVersion(parsed, [1, 9]) >= 0) return { score: 0, evidence: [] };\n return { score: 80 + (programIncludes(context, 'expert') ? 10 : 0), evidence: [`protocol ${formatVersion(parsed)} is in 1.5-1.8`] };\n },\n});\n\nexport const expertSdrModernDialect: TciDialect = new StandardTciDialect({\n id: 'expertsdr-1.9-2.0', label: 'ExpertSDR / TCI 1.9-2.0', streamLengthSemantics: 'scalar',\n supportsStreamChannels: true, supportsTxAudioSource: true, supportsIqStream: true,\n iqSampleRates: [48_000, 96_000, 192_000, 384_000], driveHasTrx: true,\n detect: (context) => {\n const parsed = version(context);\n if (!parsed || compareTciVersion(parsed, [1, 9]) < 0) return { score: 0, evidence: [] };\n const future = compareTciVersion(parsed, [2, 0]) > 0;\n return {\n score: 70 + (programIncludes(context, 'expert') ? 15 : 0),\n evidence: [`protocol ${formatVersion(parsed)} uses modern stream negotiation`],\n warnings: future ? [`Unknown future TCI version ${formatVersion(parsed)}; using the modern dialect`] : [],\n };\n },\n});\n\nexport const thetisDialect: TciDialect = new StandardTciDialect({\n id: 'thetis-2.0', label: 'Thetis / TCI 2.0', streamLengthSemantics: 'scalar',\n supportsStreamChannels: true, supportsTxAudioSource: true, supportsIqStream: true,\n iqSampleRates: [48_000, 96_000, 192_000, 384_000], driveHasTrx: true,\n detect: (context) => {\n const evidence: string[] = [];\n let score = 0;\n if (programIncludes(context, 'thetis')) { score += 120; evidence.push('PROTOCOL program is Thetis'); }\n const observed = ['tx_frequency_ex', 'tx_profiles_ex', 'tx_profile_ex', 'calibration_ex']\n .filter((name) => context.commandNames.has(name));\n if (observed.length > 0) { score += 100; evidence.push(`Thetis extension commands: ${observed.join(', ')}`); }\n if (/anan|hermes|orion|saturn/i.test(context.identity.device ?? '')) {\n score += 30;\n evidence.push(`Thetis-family device: ${context.identity.device}`);\n }\n return { score, evidence };\n },\n});\n\nexport const aetherSdrDialect: TciDialect = new StandardTciDialect({\n id: 'aethersdr-1.5', label: 'AetherSDR / TCI 1.5 hybrid', streamLengthSemantics: 'scalar',\n supportsStreamChannels: true, supportsTxAudioSource: true, supportsIqStream: true,\n iqSampleRates: [24_000, 48_000, 96_000, 192_000], driveHasTrx: true,\n detect: (context) => {\n if (!/^aethersdr$/i.test(context.identity.device ?? '')) return { score: 0, evidence: [] };\n const evidence = [`AetherSDR device identity: ${context.identity.device}`];\n const modernAudioCommands = ['audio_stream_sample_type', 'audio_stream_channels', 'audio_stream_samples']\n .filter((name) => context.commandNames.has(name));\n if (modernAudioCommands.length > 0) evidence.push(`Modern audio negotiation: ${modernAudioCommands.join(', ')}`);\n return {\n score: 150,\n evidence,\n warnings: context.identity.protocolVersion === '1.5'\n ? ['AetherSDR reports TCI 1.5 but uses modern scalar audio stream semantics']\n : [],\n };\n },\n});\n\nexport const genericObservedDialect: TciDialect = new StandardTciDialect({\n id: 'generic-observed', label: 'Generic observed TCI', streamLengthSemantics: 'auto',\n supportsStreamChannels: true, supportsTxAudioSource: true, supportsIqStream: false,\n iqSampleRates: [], driveHasTrx: true,\n detect: (context) => ({\n score: context.commandNames.has('ready') ? 10 : 0,\n evidence: ['No vendor-specific match; using observed command shapes'],\n warnings: ['Dialect identity is uncertain'],\n }),\n resolve: (context) => {\n const drive = [...context.commands].reverse().find((command) => command.name === 'drive');\n const driveHasTrx = (drive?.args.length ?? 0) >= 2;\n return new StandardTciDialect({\n id: 'generic-observed',\n label: 'Generic observed TCI',\n streamLengthSemantics: 'auto',\n supportsStreamChannels: context.commandNames.has('audio_stream_channels'),\n supportsTxAudioSource: compareTciVersion(version(context) ?? [0], [2, 0]) >= 0,\n supportsIqStream: context.commandNames.has('iq_samplerate'),\n iqSampleRates: context.commandNames.has('iq_samplerate') ? [48_000] : [],\n driveHasTrx,\n detect: genericObservedDialect.detect.bind(genericObservedDialect),\n });\n },\n});\n\nexport const builtInDialects: readonly TciDialect[] = [\n aetherSdrDialect, thetisDialect, expertSdr14Dialect, expertSdrLegacyDialect, expertSdrModernDialect, genericObservedDialect,\n];\n\nexport function parseTciVersion(value: string | undefined): VersionTuple | undefined {\n if (!value) return undefined;\n const match = value.trim().match(/^(\\d+)(?:\\.(\\d+))?(?:\\.(\\d+))?/);\n if (!match) return undefined;\n return match.slice(1).filter((part): part is string => part !== undefined).map(Number);\n}\n\nexport function compareTciVersion(left: VersionTuple, right: VersionTuple): number {\n const length = Math.max(left.length, right.length);\n for (let index = 0; index < length; index += 1) {\n const difference = (left[index] ?? 0) - (right[index] ?? 0);\n if (difference !== 0) return difference;\n }\n return 0;\n}\n\nfunction formatVersion(value: VersionTuple): string { return value.join('.'); }\n","export type TciErrorCode =\n | 'connect-timeout'\n | 'handshake-timeout'\n | 'invalid-handshake'\n | 'unknown-dialect'\n | 'command-timeout'\n | 'not-connected'\n | 'disconnected'\n | 'protocol-error'\n | 'invalid-frame'\n | 'cancelled';\n\nexport class TciError extends Error {\n readonly code: TciErrorCode;\n readonly details?: unknown;\n\n constructor(code: TciErrorCode, message: string, details?: unknown) {\n super(message);\n this.name = 'TciError';\n this.code = code;\n this.details = details;\n }\n}\n\nexport function toTciError(error: unknown, fallbackCode: TciErrorCode = 'protocol-error'): TciError {\n if (error instanceof TciError) {\n return error;\n }\n if (error instanceof Error) {\n return new TciError(fallbackCode, error.message, error);\n }\n return new TciError(fallbackCode, String(error), error);\n}\n","import { TciError } from '../errors.js';\nimport { builtInDialects } from './builtins.js';\nimport type {\n TciDialect,\n TciDialectDetection,\n TciDialectDetectionContext,\n TciDialectId,\n TciDialectSelection,\n} from './types.js';\n\nexport class TciDialectRegistry {\n private readonly dialects = new Map<TciDialectId, TciDialect>();\n\n constructor(dialects: readonly TciDialect[] = builtInDialects) {\n for (const dialect of dialects) this.register(dialect);\n }\n\n register(dialect: TciDialect): void { this.dialects.set(dialect.id, dialect); }\n get(id: TciDialectId): TciDialect | undefined { return this.dialects.get(id); }\n list(): TciDialect[] { return [...this.dialects.values()]; }\n\n select(context: TciDialectDetectionContext, selection: TciDialectSelection = 'auto'): TciDialectDetection {\n if (typeof selection === 'object') {\n return { dialect: selection, confidence: 'manual', evidence: ['Custom dialect supplied by caller'], warnings: [] };\n }\n if (selection !== 'auto') {\n const dialect = this.get(selection);\n if (!dialect) throw new TciError('unknown-dialect', `Unknown TCI dialect: ${selection}`);\n return { dialect: dialect.resolve?.(context) ?? dialect, confidence: 'manual', evidence: [`Dialect ${selection} selected by caller`], warnings: [] };\n }\n\n const candidates = this.list()\n .map((dialect) => ({ dialect, result: dialect.detect(context) }))\n .sort((left, right) => right.result.score - left.result.score);\n const selected = candidates[0];\n if (!selected || selected.result.score <= 0) {\n throw new TciError('unknown-dialect', 'Unable to identify the TCI server dialect');\n }\n return {\n dialect: selected.dialect.resolve?.(context) ?? selected.dialect,\n confidence: selected.result.score >= 100 ? 'high' : selected.result.score >= 70 ? 'medium' : 'low',\n evidence: selected.result.evidence,\n warnings: selected.result.warnings ?? [],\n };\n }\n}\n\nexport const defaultTciDialectRegistry = new TciDialectRegistry();\n","import { TciError } from '../errors.js';\nimport type { TciCommand } from '../protocol/text.js';\nimport type { TciProtocolIdentity } from './types.js';\n\nconst IDENTITY_COMMANDS = new Set(['protocol', 'device', 'trx_count', 'channels_count', 'channel_count']);\nconst STATE_COMMANDS = new Set(['vfo', 'modulation', 'modulations_list', 'trx', 'drive']);\n\nexport function parseProtocolIdentity(commands: readonly TciCommand[]): TciProtocolIdentity {\n const protocol = [...commands].reverse().find((command) => command.name === 'protocol');\n const device = [...commands].reverse().find((command) => command.name === 'device');\n const rawProtocolArgs = protocol?.args ?? [];\n const firstLooksLikeVersion = /^\\d+(?:\\.\\d+){0,2}/.test(rawProtocolArgs[0] ?? '');\n return {\n programName: firstLooksLikeVersion ? undefined : rawProtocolArgs[0],\n protocolVersion: firstLooksLikeVersion ? rawProtocolArgs[0] : rawProtocolArgs[1],\n rawProtocolArgs: [...rawProtocolArgs],\n device: device?.args.join(','),\n };\n}\n\nexport function assertValidTciHandshake(commands: readonly TciCommand[]): void {\n const names = new Set(commands.map((command) => command.name));\n if (!names.has('ready')) throw new TciError('handshake-timeout', 'TCI READY was not received');\n const categories = [\n [...IDENTITY_COMMANDS].some((name) => names.has(name)),\n [...STATE_COMMANDS].some((name) => names.has(name)),\n ].filter(Boolean).length;\n const identitySignals = [...IDENTITY_COMMANDS].filter((name) => names.has(name)).length;\n if (categories < 2 && identitySignals < 2) {\n throw new TciError('invalid-handshake', 'WebSocket opened but did not provide enough TCI initialization evidence');\n }\n}\n"],"mappings":"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","names":[]}
@@ -1,5 +1,5 @@
1
- import { d as TciDialect, T as TciDialectId, f as TciDialectDetectionContext, b as TciDialectSelection, e as TciDialectDetection, i as TciProtocolIdentity } from '../types-xRotf9gW.cjs';
2
- export { B as BuiltInTciDialectId, g as TciDialectScore, h as TciDriveState, a as TciHandshakeResult, j as TciStreamLengthSemantics, c as TciWriteResult } from '../types-xRotf9gW.cjs';
1
+ import { d as TciDialect, a as TciDialectId, f as TciDialectDetectionContext, c as TciDialectSelection, e as TciDialectDetection, i as TciProtocolIdentity } from '../types-1YXGwrgI.cjs';
2
+ export { B as BuiltInTciDialectId, g as TciDialectScore, h as TciDriveState, b as TciHandshakeResult, j as TciStreamLengthSemantics, T as TciWriteResult } from '../types-1YXGwrgI.cjs';
3
3
  import { T as TciCommand } from '../text-BwCWY1k1.cjs';
4
4
 
5
5
  type VersionTuple = readonly number[];