wsjtx-lib 1.0.4 → 1.1.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.
@@ -1,483 +0,0 @@
1
- import {
2
- WSJTXError,
3
- WSJTXMode
4
- } from "./chunk-JMWITWT7.js";
5
-
6
- // src/index.ts
7
- import { createRequire } from "module";
8
- import { fileURLToPath } from "url";
9
- import path from "path";
10
- import fs from "fs";
11
- function getModuleContext() {
12
- try {
13
- if (typeof import.meta !== "undefined" && import.meta.url) {
14
- return {
15
- require: createRequire(import.meta.url),
16
- __filename: fileURLToPath(import.meta.url),
17
- __dirname: path.dirname(fileURLToPath(import.meta.url))
18
- };
19
- }
20
- } catch {
21
- }
22
- return {
23
- require: typeof require2 !== "undefined" ? require2 : createRequire("file://"),
24
- __filename: typeof __filename !== "undefined" ? __filename : "",
25
- __dirname: typeof __dirname !== "undefined" ? __dirname : ""
26
- };
27
- }
28
- var moduleContext = getModuleContext();
29
- var { require: require2, __filename, __dirname } = moduleContext;
30
- function findNativeModule() {
31
- const platform = process.platform;
32
- const arch = process.arch;
33
- const isPkgEnvironment = !!process.pkg || __dirname.includes("/snapshot/") || __dirname.includes("\\snapshot\\");
34
- if (isPkgEnvironment) {
35
- console.log("Detected pkg packaged environment");
36
- const execDir = path.dirname(process.execPath);
37
- const pkgPaths = [
38
- // 1. 与可执行文件同目录(最常见的打包方式)
39
- path.join(execDir, "wsjtx_lib_nodejs.node"),
40
- path.join(execDir, `wsjtx_lib_nodejs-${platform}-${arch}.node`),
41
- // 2. prebuilds 子目录(保持结构的打包方式)
42
- path.join(execDir, "prebuilds", `${platform}-${arch}`, "wsjtx_lib_nodejs.node"),
43
- // 3. 相对于可执行文件的其他可能位置
44
- path.join(execDir, "..", "prebuilds", `${platform}-${arch}`, "wsjtx_lib_nodejs.node"),
45
- path.join(execDir, "lib", "wsjtx_lib_nodejs.node"),
46
- path.join(execDir, "native", "wsjtx_lib_nodejs.node")
47
- ];
48
- console.log("PKG environment - searching for native module:");
49
- pkgPaths.forEach((p) => console.log(` - ${p}`));
50
- for (const modulePath of pkgPaths) {
51
- if (fs.existsSync(modulePath)) {
52
- console.log(`Found native module at: ${modulePath}`);
53
- return modulePath;
54
- }
55
- }
56
- const pathList2 = pkgPaths.map((p) => ` - ${p}`).join("\n");
57
- throw new Error(
58
- `Native module not found in pkg packaged environment for ${platform}-${arch}.
59
- Searched in:
60
- ${pathList2}
61
-
62
- Solutions:
63
- 1. Ensure the native module is included in your pkg configuration
64
- 2. Copy wsjtx_lib_nodejs.node to the same directory as your executable
65
- 3. Check if the native module was properly bundled during packaging`
66
- );
67
- }
68
- const moduleRoot = path.resolve(__dirname, "..");
69
- const possiblePaths = [
70
- // 1. Prebuilt binaries (npm packages) - highest priority
71
- path.join(moduleRoot, "prebuilds", `${platform}-${arch}`, "wsjtx_lib_nodejs.node"),
72
- // 2. GitHub Actions legacy format (for backward compatibility)
73
- path.join(moduleRoot, "prebuilds", `${platform}-latest-${arch}`, "wsjtx_lib_nodejs.node"),
74
- path.join(moduleRoot, "prebuilds", `ubuntu-latest-${arch}`, "wsjtx_lib_nodejs.node"),
75
- // Linux
76
- path.join(moduleRoot, "prebuilds", `macos-latest-${arch}`, "wsjtx_lib_nodejs.node"),
77
- // macOS
78
- path.join(moduleRoot, "prebuilds", `windows-latest-${arch}`, "wsjtx_lib_nodejs.node"),
79
- // Windows
80
- // 3. Local development builds - third priority
81
- path.join(moduleRoot, "build", "wsjtx_lib_nodejs.node"),
82
- path.join(moduleRoot, "build", "Release", "wsjtx_lib_nodejs.node")
83
- ];
84
- console.log("Normal environment - searching for native module:");
85
- possiblePaths.forEach((p) => console.log(` - ${p}`));
86
- for (const modulePath of possiblePaths) {
87
- if (fs.existsSync(modulePath)) {
88
- console.log(`Found native module at: ${modulePath}`);
89
- return modulePath;
90
- }
91
- }
92
- const pathList = possiblePaths.map((p) => ` - ${p}`).join("\n");
93
- throw new Error(
94
- `Native module not found for ${platform}-${arch}.
95
- Searched in:
96
- ${pathList}
97
-
98
- Solutions:
99
- 1. If you installed via npm, this may be a missing prebuilt binary
100
- 2. For development, run "npm run build" to compile the native module
101
- 3. Check if your platform/architecture is supported`
102
- );
103
- }
104
- var nativeModulePath = findNativeModule();
105
- var { WSJTXLib: NativeWSJTXLib } = require2(nativeModulePath);
106
- var WSJTXLib = class _WSJTXLib {
107
- native;
108
- config;
109
- /**
110
- * Create a new WSJTX library instance
111
- *
112
- * @param config Optional configuration options
113
- * @throws {WSJTXError} If the native library fails to initialize
114
- */
115
- constructor(config = {}) {
116
- this.config = {
117
- maxThreads: 4,
118
- debug: false,
119
- ...config
120
- };
121
- try {
122
- this.native = new NativeWSJTXLib();
123
- } catch (error) {
124
- throw new WSJTXError(
125
- `Failed to initialize WSJTX library: ${error instanceof Error ? error.message : String(error)}`,
126
- "INIT_ERROR"
127
- );
128
- }
129
- }
130
- /**
131
- * Decode digital radio signals from audio data
132
- *
133
- * This method processes audio samples and attempts to decode digital
134
- * messages using the specified protocol mode. The operation is performed
135
- * asynchronously to avoid blocking the Node.js event loop.
136
- *
137
- * @param mode The digital mode to use for decoding
138
- * @param audioData Audio samples (Float32Array or Int16Array)
139
- * @param frequency Center frequency in Hz
140
- * @param threads Number of threads to use (1-16, default: 4)
141
- * @returns Promise that resolves when decoding is complete
142
- *
143
- * @throws {WSJTXError} If parameters are invalid or decoding fails
144
- *
145
- * @example
146
- * ```typescript
147
- * const audioData = new Float32Array(48000 * 13); // 13 seconds
148
- * await lib.decode(WSJTXMode.FT8, audioData, 1500);
149
- * const messages = lib.pullMessages();
150
- * ```
151
- */
152
- async decode(mode, audioData, frequency, threads = this.config.maxThreads || 4) {
153
- this.validateMode(mode);
154
- this.validateFrequency(frequency);
155
- this.validateThreads(threads);
156
- this.validateAudioData(audioData);
157
- if (!this.isDecodingSupported(mode)) {
158
- throw new WSJTXError(`Decoding not supported for mode: ${WSJTXMode[mode]}`, "UNSUPPORTED_MODE");
159
- }
160
- return new Promise((resolve, reject) => {
161
- const callback = (error, result) => {
162
- if (error) {
163
- reject(new WSJTXError(error.message, "DECODE_ERROR"));
164
- } else {
165
- resolve({ success: result });
166
- }
167
- };
168
- try {
169
- this.native.decode(mode, audioData, frequency, threads, callback);
170
- } catch (error) {
171
- reject(new WSJTXError(
172
- `Decode operation failed: ${error instanceof Error ? error.message : String(error)}`,
173
- "DECODE_ERROR"
174
- ));
175
- }
176
- });
177
- }
178
- /**
179
- * Encode a message into audio waveform for transmission
180
- *
181
- * Generates the audio waveform that represents the specified message
182
- * using the given digital mode. The resulting audio can be fed to
183
- * a radio transmitter or audio interface.
184
- *
185
- * @param mode The digital mode to use for encoding
186
- * @param message The message text to encode (mode-specific format)
187
- * @param frequency Center frequency in Hz
188
- * @param threads Number of threads to use (1-16, default: 4)
189
- * @returns Promise that resolves with encoded audio data and actual message sent
190
- *
191
- * @throws {WSJTXError} If parameters are invalid or encoding fails
192
- *
193
- * @example
194
- * ```typescript
195
- * const result = await lib.encode(WSJTXMode.FT8, 'CQ DX K1ABC FN20', 1500);
196
- * console.log('Generated audio samples:', result.audioData.length);
197
- * console.log('Actual message sent:', result.messageSent);
198
- * ```
199
- */
200
- async encode(mode, message, frequency, threads = this.config.maxThreads || 4) {
201
- this.validateMode(mode);
202
- this.validateMessage(message);
203
- this.validateFrequency(frequency);
204
- this.validateThreads(threads);
205
- if (!this.isEncodingSupported(mode)) {
206
- throw new WSJTXError(`Encoding not supported for mode: ${WSJTXMode[mode]}`, "UNSUPPORTED_MODE");
207
- }
208
- return new Promise((resolve, reject) => {
209
- const callback = (error, result) => {
210
- if (error) {
211
- reject(new WSJTXError(error.message, "ENCODE_ERROR"));
212
- } else {
213
- resolve(result);
214
- }
215
- };
216
- try {
217
- this.native.encode(mode, message, frequency, threads, callback);
218
- } catch (error) {
219
- reject(new WSJTXError(
220
- `Encode operation failed: ${error instanceof Error ? error.message : String(error)}`,
221
- "ENCODE_ERROR"
222
- ));
223
- }
224
- });
225
- }
226
- /**
227
- * Decode WSPR signals from IQ data
228
- *
229
- * WSPR (Weak Signal Propagation Reporter) is a specialized protocol
230
- * for studying radio propagation. This method takes IQ (complex)
231
- * samples and attempts to decode WSPR transmissions.
232
- *
233
- * @param iqData Interleaved I,Q samples as Float32Array
234
- * @param options Decoder options (frequency, callsign, etc.)
235
- * @returns Promise that resolves with array of decoded WSPR results
236
- *
237
- * @throws {WSJTXError} If parameters are invalid or decoding fails
238
- *
239
- * @example
240
- * ```typescript
241
- * const iqData = new Float32Array(2 * 12000 * 111); // 2 minutes of IQ data
242
- * const options = {
243
- * dialFrequency: 14095600, // 20m WSPR frequency
244
- * callsign: 'K1ABC',
245
- * locator: 'FN20'
246
- * };
247
- * const results = await lib.decodeWSPR(iqData, options);
248
- * ```
249
- */
250
- async decodeWSPR(iqData, options = {}) {
251
- this.validateIQData(iqData);
252
- const defaultOptions = {
253
- dialFrequency: 14095600,
254
- // 20m WSPR frequency
255
- callsign: "",
256
- locator: "",
257
- quickMode: false,
258
- useHashTable: true,
259
- passes: 2,
260
- subtraction: true,
261
- ...options
262
- };
263
- return new Promise((resolve, reject) => {
264
- const callback = (error, results) => {
265
- if (error) {
266
- reject(new WSJTXError(error.message, "WSPR_DECODE_ERROR"));
267
- } else {
268
- resolve(results);
269
- }
270
- };
271
- try {
272
- this.native.decodeWSPR(iqData, defaultOptions, callback);
273
- } catch (error) {
274
- reject(new WSJTXError(
275
- `WSPR decode failed: ${error instanceof Error ? error.message : String(error)}`,
276
- "WSPR_DECODE_ERROR"
277
- ));
278
- }
279
- });
280
- }
281
- /**
282
- * Retrieve decoded messages from the internal queue
283
- *
284
- * Messages are added to an internal queue as they are decoded.
285
- * This method retrieves and removes all pending messages from the queue.
286
- *
287
- * @returns Array of decoded messages
288
- *
289
- * @example
290
- * ```typescript
291
- * const messages = lib.pullMessages();
292
- * messages.forEach(msg => {
293
- * console.log(`${msg.text} (SNR: ${msg.snr} dB, ΔT: ${msg.deltaTime}s)`);
294
- * });
295
- * ```
296
- */
297
- pullMessages() {
298
- try {
299
- return this.native.pullMessages();
300
- } catch (error) {
301
- throw new WSJTXError(
302
- `Failed to pull messages: ${error instanceof Error ? error.message : String(error)}`,
303
- "PULL_ERROR"
304
- );
305
- }
306
- }
307
- /**
308
- * Check if encoding is supported for a specific mode
309
- *
310
- * @param mode The mode to check
311
- * @returns True if encoding is supported
312
- */
313
- isEncodingSupported(mode) {
314
- return this.native.isEncodingSupported(mode);
315
- }
316
- /**
317
- * Check if decoding is supported for a specific mode
318
- *
319
- * @param mode The mode to check
320
- * @returns True if decoding is supported
321
- */
322
- isDecodingSupported(mode) {
323
- return this.native.isDecodingSupported(mode);
324
- }
325
- /**
326
- * Get the required sample rate for a specific mode
327
- *
328
- * @param mode The mode to query
329
- * @returns Sample rate in Hz
330
- */
331
- getSampleRate(mode) {
332
- return this.native.getSampleRate(mode);
333
- }
334
- /**
335
- * Get the transmission duration for a specific mode
336
- *
337
- * @param mode The mode to query
338
- * @returns Duration in seconds
339
- */
340
- getTransmissionDuration(mode) {
341
- return this.native.getTransmissionDuration(mode);
342
- }
343
- /**
344
- * Get capabilities for all supported modes
345
- *
346
- * @returns Array of mode capability information
347
- */
348
- getAllModeCapabilities() {
349
- const modes = Object.values(WSJTXMode).filter((v) => typeof v === "number");
350
- return modes.map((mode) => ({
351
- mode,
352
- encodingSupported: this.isEncodingSupported(mode),
353
- decodingSupported: this.isDecodingSupported(mode),
354
- sampleRate: this.getSampleRate(mode),
355
- duration: this.getTransmissionDuration(mode)
356
- }));
357
- }
358
- /**
359
- * Convert audio format between Float32Array and Int16Array
360
- *
361
- * @param audioData Input audio data
362
- * @param targetFormat Target format ('float32' or 'int16')
363
- * @returns Converted audio data
364
- */
365
- static convertAudioFormat(audioData, targetFormat) {
366
- if (targetFormat !== "float32" && targetFormat !== "int16") {
367
- throw new Error(`Invalid target format: ${targetFormat}. Must be 'float32' or 'int16'`);
368
- }
369
- if (targetFormat === "float32") {
370
- if (audioData instanceof Float32Array) {
371
- return audioData;
372
- }
373
- const result = new Float32Array(audioData.length);
374
- for (let i = 0; i < audioData.length; i++) {
375
- result[i] = audioData[i] / 32768;
376
- }
377
- return result;
378
- } else {
379
- if (audioData instanceof Int16Array) {
380
- return audioData;
381
- }
382
- const result = new Int16Array(audioData.length);
383
- for (let i = 0; i < audioData.length; i++) {
384
- result[i] = Math.max(-32768, Math.min(32767, Math.round(audioData[i] * 32768)));
385
- }
386
- return result;
387
- }
388
- }
389
- /**
390
- * Static convenience helper for async audio format conversion.
391
- */
392
- static async convertAudioFormatAsync(audioData, targetFormat) {
393
- const lib = new _WSJTXLib();
394
- return lib.convertAudioFormatAsync(audioData, targetFormat);
395
- }
396
- /**
397
- * Asynchronously convert audio data format without blocking the event loop
398
- *
399
- * Uses native thread pool to offload conversion.
400
- */
401
- async convertAudioFormatAsync(audioData, targetFormat) {
402
- if (targetFormat !== "float32" && targetFormat !== "int16") {
403
- throw new Error(`Invalid target format: ${targetFormat}. Must be 'float32' or 'int16'`);
404
- }
405
- this.validateAudioData(audioData);
406
- return new Promise((resolve, reject) => {
407
- const callback = (error, result) => {
408
- if (error) {
409
- reject(new WSJTXError(error.message ?? String(error), "CONVERT_ERROR"));
410
- } else {
411
- resolve(result);
412
- }
413
- };
414
- try {
415
- this.native.convertAudioAsync(audioData, targetFormat, callback);
416
- } catch (error) {
417
- reject(new WSJTXError(
418
- `Convert operation failed: ${error instanceof Error ? error.message : String(error)}`,
419
- "CONVERT_ERROR"
420
- ));
421
- }
422
- });
423
- }
424
- // Validation methods
425
- validateMode(mode) {
426
- if (!Object.values(WSJTXMode).includes(mode)) {
427
- throw new WSJTXError(`Invalid mode: ${mode}`, "INVALID_MODE");
428
- }
429
- }
430
- validateFrequency(frequency) {
431
- if (!Number.isInteger(frequency) || frequency < 0 || frequency > 3e7) {
432
- throw new WSJTXError(
433
- `Invalid frequency: ${frequency}. Must be between 0 and 30,000,000 Hz`,
434
- "INVALID_FREQUENCY"
435
- );
436
- }
437
- }
438
- validateThreads(threads) {
439
- if (!Number.isInteger(threads) || threads < 1 || threads > 16) {
440
- throw new WSJTXError(
441
- `Invalid thread count: ${threads}. Must be between 1 and 16`,
442
- "INVALID_THREADS"
443
- );
444
- }
445
- }
446
- validateMessage(message) {
447
- if (typeof message !== "string" || message.length === 0 || message.length > 22) {
448
- throw new WSJTXError(
449
- `Invalid message: "${message}". Must be 1-22 characters long`,
450
- "INVALID_MESSAGE"
451
- );
452
- }
453
- }
454
- validateAudioData(audioData) {
455
- if (!(audioData instanceof Float32Array) && !(audioData instanceof Int16Array)) {
456
- throw new WSJTXError(
457
- "Invalid audio data: must be Float32Array or Int16Array",
458
- "INVALID_AUDIO_DATA"
459
- );
460
- }
461
- if (audioData.length === 0) {
462
- throw new WSJTXError("Audio data cannot be empty", "INVALID_AUDIO_DATA");
463
- }
464
- }
465
- validateIQData(iqData) {
466
- if (!(iqData instanceof Float32Array)) {
467
- throw new WSJTXError(
468
- "Invalid IQ data: must be Float32Array with interleaved I,Q samples",
469
- "INVALID_IQ_DATA"
470
- );
471
- }
472
- if (iqData.length === 0 || iqData.length % 2 !== 0) {
473
- throw new WSJTXError(
474
- "IQ data length must be even (interleaved I,Q samples)",
475
- "INVALID_IQ_DATA"
476
- );
477
- }
478
- }
479
- };
480
-
481
- export {
482
- WSJTXLib
483
- };