wsjtx-lib
Node.js C++ extension for WSJTX digital radio protocol library
A high-performance Node.js C++ extension for digital amateur radio protocols, providing TypeScript support and async/await interfaces for WSJTX library functionality.
- š High Performance: Native C++ implementation with multi-threading support
- š” Multiple Modes: Support for FT8, FT4, JT4, JT65, JT9, FST4, Q65, FST4W, and WSPR
- š§ TypeScript Support: Full TypeScript definitions and modern ES modules
- ā” Async/Await: Promise-based API for non-blocking operations
- šµ Audio Processing: Support for both Float32Array and Int16Array audio formats
- š Cross-Platform: Prebuilt binaries for Windows, macOS, and Linux
- š WSPR Decoding: Specialized support for WSPR IQ data processing
Mode | Encoding | Decoding | Sample Rate | Duration | Bandwidth |
---|---|---|---|---|---|
FT8 | ā | ā | 48 kHz | 12.6s | ~50 Hz |
FT4 | ā | ā | 48 kHz | 6.0s | ~80 Hz |
JT4 | ā | ā | 11.025 kHz | 47.1s | Variable |
JT65 | ā | ā | 11.025 kHz | 46.8s | ~180 Hz |
JT9 | ā | ā | 12 kHz | 49.0s | ~16 Hz |
FST4 | ā | ā | 12 kHz | 60.0s | Variable |
Q65 | ā | ā | 12 kHz | 60.0s | Variable |
FST4W | ā | ā | 12 kHz | 120.0s | Variable |
WSPR | ā | ā | 12 kHz | 110.6s | ~6 Hz |
The package includes prebuilt binaries for major platforms:
npm install wsjtx-lib
Supported platforms with prebuilt binaries:
- Linux x64
- macOS ARM64 (Apple Silicon)
- Windows x64
Only needed if prebuilt binaries are not available for your platform.
- Node.js 16+
- CMake 3.15+
- C++ compiler with C++17 support
- FFTW3 library (single precision)
- Boost libraries
- Fortran compiler (gfortran)
# Install dependencies using Homebrew
brew install cmake fftw boost gcc pkg-config
# Clone and build
git clone --recursive https://github.com/boybook/wsjtx_lib_nodejs.git
cd wsjtx_lib_nodejs
npm install
npm run build
# Install dependencies
sudo apt-get update
sudo apt-get install -y \
cmake \
build-essential \
gfortran \
libfftw3-dev \
libboost-all-dev \
pkg-config
# Clone and build
git clone --recursive https://github.com/boybook/wsjtx_lib_nodejs.git
cd wsjtx_lib_nodejs
npm install
npm run build
Use MSYS2/MinGW-w64 for best compatibility:
# Install MSYS2, then in MSYS2 MINGW64 terminal:
pacman -S --needed \
base-devel \
mingw-w64-x86_64-toolchain \
mingw-w64-x86_64-cmake \
mingw-w64-x86_64-pkg-config \
mingw-w64-x86_64-fftw \
mingw-w64-x86_64-boost \
mingw-w64-x86_64-gcc-fortran \
mingw-w64-x86_64-nodejs
# Clone and build
git clone --recursive https://github.com/boybook/wsjtx_lib_nodejs.git
cd wsjtx_lib_nodejs
npm install
npm run build
import { WSJTXLib, WSJTXMode } from 'wsjtx-lib';
async function example() {
// Create library instance
const lib = new WSJTXLib();
// Encode an FT8 message
const encodeResult = await lib.encode(
WSJTXMode.FT8,
'CQ DX BH1ABC OM88',
1000 // Audio frequency in Hz (typically 500-3000 Hz)
);
console.log(`Generated ${encodeResult.audioData.length} audio samples`);
console.log(`Message sent: "${encodeResult.messageSent}"`);
// Decode audio data (example with proper resampling for FT8)
const audioData = new Float32Array(48000 * 13); // 13 seconds at 48kHz
// ... fill audioData with actual audio samples ...
const decodeResult = await lib.decode(
WSJTXMode.FT8,
audioData,
1000 // Same audio frequency used for encoding
);
// Get decoded messages
const messages = lib.pullMessages();
messages.forEach(msg => {
console.log(`Decoded: "${msg.text}" (SNR: ${msg.snr} dB, ĪT: ${msg.deltaTime}s)`);
});
}
new WSJTXLib(config?: WSJTXConfig)
Creates a new WSJTX library instance.
Parameters:
-
config
(optional): Configuration options-
maxThreads
: Maximum number of threads (1-16, default: 4) -
debug
: Enable debug logging (default: false)
-
Decode digital radio signals from audio data.
Parameters:
-
mode
: WSJTXMode enum value -
audioData
: Float32Array or Int16Array of audio samples -
frequency
: Audio frequency in Hz (typically 500-3000 Hz) -
threads
: Number of threads to use (optional, default: 4)
Returns: Promise resolving to DecodeResult with success status
Note: For optimal FT8 decoding, audio may need resampling. See examples for details.
Encode a message into audio waveform for transmission.
Parameters:
-
mode
: WSJTXMode enum value -
message
: Message text to encode (1-22 characters) -
frequency
: Audio frequency in Hz (typically 500-3000 Hz) -
threads
: Number of threads to use (optional, default: 4)
Returns: Promise resolving to EncodeResult with audio data and actual message sent
Decode WSPR signals from IQ data.
Parameters:
-
iqData
: Float32Array of interleaved I,Q samples -
options
: WSPRDecodeOptions (optional)-
dialFrequency
: RF dial frequency in Hz (default: 14095600) -
callsign
: Station callsign -
locator
: Grid locator -
quickMode
: Enable quick decode mode (default: false) -
useHashTable
: Use hash table optimization (default: true) -
passes
: Number of decode passes (default: 2) -
subtraction
: Enable signal subtraction (default: true)
-
Returns: Promise resolving to array of WSPR decode results
Retrieve decoded messages from the internal queue.
Returns: Array of decoded messages
-
isEncodingSupported(mode): boolean
- Check if encoding is supported for a mode -
isDecodingSupported(mode): boolean
- Check if decoding is supported for a mode -
getSampleRate(mode): number
- Get required sample rate for a mode -
getTransmissionDuration(mode): number
- Get transmission duration for a mode -
getAllModeCapabilities(): ModeCapabilities[]
- Get capabilities for all modes
-
convertAudioFormat(audioData, targetFormat): AudioData
- Convert between Float32Array and Int16Array
enum WSJTXMode {
FT8 = 0,
FT4 = 1,
JT4 = 2,
JT65 = 3,
JT9 = 4,
FST4 = 5,
Q65 = 6,
FST4W = 7,
WSPR = 8
}
interface WSJTXMessage {
text: string; // Decoded message text
snr: number; // Signal-to-noise ratio in dB
deltaTime: number; // Time offset in seconds
deltaFrequency: number; // Frequency offset in Hz
}
interface EncodeResult {
audioData: Float32Array; // Generated audio waveform (48kHz sample rate)
messageSent: string; // Actual message encoded
}
interface WSPRResult {
frequency: number; // Signal frequency in Hz
sync: number; // Sync quality
snr: number; // Signal-to-noise ratio in dB
deltaTime: number; // Time offset in seconds
drift: number; // Frequency drift in Hz/minute
jitter: number; // Jitter metric
message: string; // Decoded message
callsign: string; // Decoded callsign
locator: string; // Decoded grid locator
power: string; // Decoded power in dBm
cycles: number; // Number of decode cycles
}
import { WSJTXLib, WSJTXMode } from 'wsjtx-lib';
import * as fs from 'fs';
import * as wav from 'wav';
async function ft8Example() {
const lib = new WSJTXLib();
const message = 'CQ DX BH1ABC OM88';
const audioFrequency = 1000;
// 1. Encode message
const encodeResult = await lib.encode(WSJTXMode.FT8, message, audioFrequency);
console.log(`Encoded: "${encodeResult.messageSent}"`);
// 2. Save as WAV file
const audioInt16 = new Int16Array(encodeResult.audioData.length);
for (let i = 0; i < encodeResult.audioData.length; i++) {
audioInt16[i] = Math.round(encodeResult.audioData[i] * 32767);
}
const writer = new wav.FileWriter('ft8_test.wav', {
channels: 1,
sampleRate: lib.getSampleRate(WSJTXMode.FT8),
bitDepth: 16
});
const buffer = Buffer.from(audioInt16.buffer);
writer.write(buffer);
writer.end();
// 3. Read back and decode
// Note: For optimal decode, you may need resampling
const resampled = resampleTo12kHz(encodeResult.audioData);
const audioForDecode = new Int16Array(resampled.length);
for (let i = 0; i < resampled.length; i++) {
audioForDecode[i] = Math.round(resampled[i] * 32767);
}
lib.pullMessages(); // Clear queue
const decodeResult = await lib.decode(WSJTXMode.FT8, audioForDecode, audioFrequency);
const messages = lib.pullMessages();
console.log(`Decoded ${messages.length} messages`);
}
// Helper function for resampling (48kHz -> 12kHz)
function resampleTo12kHz(audioData48k: Float32Array): Float32Array {
const audioData12k = new Float32Array(Math.floor(audioData48k.length / 4));
for (let i = 0; i < audioData12k.length; i++) {
audioData12k[i] = audioData48k[i * 4];
}
return audioData12k;
}
import { WSJTXLib } from 'wsjtx-lib';
async function wsprExample() {
const lib = new WSJTXLib();
// IQ data (interleaved I,Q samples)
const iqData = new Float32Array(2 * 12000 * 120); // 2 minutes of IQ data
// ... fill with actual IQ data from SDR ...
const options = {
dialFrequency: 14095600, // 20m WSPR frequency
callsign: 'BH1ABC',
locator: 'OM88',
quickMode: false,
passes: 2
};
const results = await lib.decodeWSPR(iqData, options);
console.log('WSPR Decode Results:');
results.forEach(result => {
console.log(`${result.callsign} ${result.locator} ${result.power}dBm (SNR: ${result.snr}dB)`);
});
}
import { WSJTXLib } from 'wsjtx-lib';
// Convert Float32Array to Int16Array
const floatData = new Float32Array([0.5, -0.5, 0.25, -0.25]);
const intData = WSJTXLib.convertAudioFormat(floatData, 'int16');
console.log(intData); // Int16Array [16384, -16384, 8192, -8192]
// Convert back to Float32Array
const backToFloat = WSJTXLib.convertAudioFormat(intData, 'float32');
console.log(backToFloat); // Float32Array [0.5, -0.5, 0.25, -0.25] (approximately)
import { WSJTXLib, WSJTXMode } from 'wsjtx-lib';
async function multipleMessages() {
const lib = new WSJTXLib();
const audioFrequency = 1000;
const messages = [
'CQ DX BH1ABC OM88', // CQ call
'BH1ABC BH2DEF +05', // Signal report
'BH2DEF BH1ABC R-12', // Report acknowledgment
'BH1ABC BH2DEF RRR', // Received acknowledgment
'BH2DEF BH1ABC 73' // End contact
];
for (const message of messages) {
const result = await lib.encode(WSJTXMode.FT8, message, audioFrequency);
console.log(`"${message}" -> "${result.messageSent}" (${result.audioData.length} samples)`);
}
}
The library throws WSJTXError
for all operation failures:
import { WSJTXError } from 'wsjtx-lib';
try {
await lib.decode(WSJTXMode.FT8, audioData, 1000);
} catch (error) {
if (error instanceof WSJTXError) {
console.error(`WSJTX Error [${error.code}]: ${error.message}`);
// Common error codes:
// - INVALID_MODE: Invalid mode parameter
// - INVALID_FREQUENCY: Invalid frequency parameter
// - INVALID_AUDIO_DATA: Invalid audio data format/size
// - INVALID_MESSAGE: Invalid message text
// - DECODE_ERROR: Decoding operation failed
// - ENCODE_ERROR: Encoding operation failed
} else {
console.error('Unexpected error:', error);
}
}
-
Audio Frequency: The
frequency
parameter is the audio tone frequency within your audio passband (typically 500-3000 Hz), not the RF frequency. -
Sample Rates: Different modes require different sample rates. Use
lib.getSampleRate(mode)
to get the correct rate. -
Audio Resampling: For optimal FT8 decoding, audio may need to be resampled from 48kHz to 12kHz. See examples for implementation.
-
Thread Safety: Each WSJTXLib instance should be used from a single thread. Create separate instances for concurrent operations.
-
Message Queue: The
pullMessages()
method clears the internal message queue. Call it regularly to avoid memory buildup.
For detailed build instructions when prebuilt binaries are not available, see BUILD.md.
# Clone with submodules
git clone --recursive https://github.com/boybook/wsjtx_lib_nodejs.git
cd wsjtx_lib_nodejs
# Install dependencies
npm install
# Build native module and TypeScript
npm run build
# Run tests
npm test
# Run comprehensive tests
npm run test:full
# Run examples
node examples/examples.js
wsjtx_lib_nodejs/
āāā src/ # TypeScript source files
āāā native/ # C++ wrapper code
āāā wsjtx_lib/ # Git submodule (wsjtx_lib library)
āāā test/ # Test files
āāā examples/ # Usage examples
āāā dist/ # Compiled TypeScript output
āāā prebuilds/ # Prebuilt binaries for distribution
āāā build/ # CMake build directory
-
npm run build
- Build both native module and TypeScript -
npm run build:native
- Build only the native C++ module -
npm run build:ts
- Build only TypeScript -
npm test
- Run basic tests (CI-friendly) -
npm run test:full
- Run comprehensive tests -
npm run clean
- Clean build artifacts -
npm run package
- Package prebuilt binaries for distribution
- Fork the repository
- Create a feature branch
- Make your changes
- Add tests for new functionality
- Ensure all tests pass
- Submit a pull request
This project is licensed under the GPL-3.0 License - see the LICENSE file for details.
- Based on the excellent wsjtx_lib library by PA0PHH
- WSJT-X development team for the original algorithms by K1JT
- Amateur radio community for protocol specifications