Files
2026-02-05 00:37:55 -07:00

223 lines
6.7 KiB
C

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include "../src/modem.h"
#include "../src/packet.h"
#include "../src/channel.h"
/* ---- Minimal WAV writer ---- */
static void write_wav(const char *filename, const float *samples,
size_t num_samples, float sample_rate)
{
FILE *f = fopen(filename, "wb");
if (!f) {
fprintf(stderr, "Error: cannot open %s for writing\n", filename);
return;
}
uint32_t sr = (uint32_t)sample_rate;
uint16_t channels = 1;
uint16_t bits = 16;
uint32_t byte_rate = sr * channels * bits / 8;
uint16_t block_align = channels * bits / 8;
uint32_t data_size = (uint32_t)(num_samples * block_align);
uint32_t chunk_size = 36 + data_size;
/* RIFF header */
fwrite("RIFF", 1, 4, f);
fwrite(&chunk_size, 4, 1, f);
fwrite("WAVE", 1, 4, f);
/* fmt sub-chunk */
fwrite("fmt ", 1, 4, f);
uint32_t fmt_size = 16;
uint16_t audio_fmt = 1; /* PCM */
fwrite(&fmt_size, 4, 1, f);
fwrite(&audio_fmt, 2, 1, f);
fwrite(&channels, 2, 1, f);
fwrite(&sr, 4, 1, f);
fwrite(&byte_rate, 4, 1, f);
fwrite(&block_align, 2, 1, f);
fwrite(&bits, 2, 1, f);
/* data sub-chunk */
fwrite("data", 1, 4, f);
fwrite(&data_size, 4, 1, f);
for (size_t i = 0; i < num_samples; i++) {
float s = samples[i];
if (s > 1.0f) s = 1.0f;
if (s < -1.0f) s = -1.0f;
int16_t val = (int16_t)(s * 32767.0f);
fwrite(&val, 2, 1, f);
}
fclose(f);
printf("WAV written: %s (%zu samples, %.0f Hz)\n",
filename, num_samples, sample_rate);
}
/* ---- Hex dump helper ---- */
static void print_hex(const char *label, const uint8_t *data, size_t len)
{
printf("%s (%zu bytes):\n ", label, len);
for (size_t i = 0; i < len; i++) {
printf("%02X ", data[i]);
if ((i + 1) % 16 == 0 && i + 1 < len)
printf("\n ");
}
printf("\n");
}
/* ---- Main ---- */
int main(int argc, char *argv[])
{
const char *message = "Hello, Cat-Radio!";
float snr_db = 100.0f; /* effectively no noise */
int use_noise = 0;
int write_wav_file = 0;
/* Parse arguments */
for (int i = 1; i < argc; i++) {
if (strcmp(argv[i], "--snr") == 0 && i + 1 < argc) {
snr_db = (float)atof(argv[++i]);
use_noise = 1;
} else if (strcmp(argv[i], "--wav") == 0) {
write_wav_file = 1;
} else {
message = argv[i];
}
}
printf("=== CAT-Radio Simulator ===\n\n");
printf("Message: \"%s\"\n", message);
if (use_noise)
printf("Channel SNR: %.1f dB\n", snr_db);
else
printf("Channel: clean (no noise)\n");
printf("\n");
/* ---- Step 1: Packet encode ---- */
packet_t tx_pkt;
memset(&tx_pkt, 0, sizeof(tx_pkt));
tx_pkt.header.dst = 0xFF; /* broadcast */
tx_pkt.header.src = 0x01;
tx_pkt.header.seq = 0x00;
tx_pkt.header.flags = 0x00;
size_t msg_len = strlen(message);
if (msg_len > PACKET_MAX_PAYLOAD)
msg_len = PACKET_MAX_PAYLOAD;
memcpy(tx_pkt.payload, message, msg_len);
tx_pkt.payload_len = (uint8_t)msg_len;
uint8_t frame[PACKET_MAX_FRAME];
size_t frame_len = packet_encode(&tx_pkt, frame, sizeof(frame));
print_hex("Encoded frame", frame, frame_len);
printf("\n");
/* ---- Step 2: Modulate ---- */
modem_config_t cfg = modem_default_config();
size_t num_samples = modem_modulated_samples(&cfg, frame_len);
float *samples = (float *)malloc(num_samples * sizeof(float));
if (!samples) {
fprintf(stderr, "Error: cannot allocate sample buffer (%zu samples)\n",
num_samples);
return 1;
}
size_t produced = modem_modulate(&cfg, frame, frame_len, samples, num_samples);
printf("Modulated: %zu samples (%.3f seconds at %.0f Hz)\n\n",
produced, (float)produced / cfg.sample_rate, cfg.sample_rate);
/* ---- Optional: write WAV ---- */
if (write_wav_file)
write_wav("cat-radio-sim.wav", samples, produced, cfg.sample_rate);
/* ---- Step 3: Channel ---- */
if (use_noise) {
channel_add_noise(samples, produced, snr_db);
printf("Channel: added AWGN at %.1f dB SNR\n\n", snr_db);
}
/* ---- Step 4: Demodulate ---- */
uint8_t rx_frame[PACKET_MAX_FRAME];
size_t rx_len = modem_demodulate(&cfg, samples, produced,
rx_frame, sizeof(rx_frame));
free(samples);
printf("Demodulated: %zu bytes recovered\n", rx_len);
if (rx_len > 0)
print_hex("Recovered frame", rx_frame, rx_len);
printf("\n");
if (rx_len == 0) {
printf("RESULT: FAIL — no frame detected by demodulator\n");
return 1;
}
/* ---- Step 5: Packet decode ---- */
packet_t rx_pkt;
memset(&rx_pkt, 0, sizeof(rx_pkt));
int rc = packet_decode(rx_frame, rx_len, &rx_pkt);
if (rc != 0) {
printf("RESULT: FAIL — packet framing error\n");
return 1;
}
printf("Packet decode:\n");
printf(" CRC: %s\n", rx_pkt.crc_ok ? "OK" : "FAIL");
printf(" Dst: 0x%02X\n", rx_pkt.header.dst);
printf(" Src: 0x%02X\n", rx_pkt.header.src);
printf(" Seq: %u\n", rx_pkt.header.seq);
printf(" Flags: 0x%02X\n", rx_pkt.header.flags);
printf(" Payload: %u bytes\n", rx_pkt.payload_len);
if (rx_pkt.payload_len > 0) {
char recovered[PACKET_MAX_PAYLOAD + 1];
memcpy(recovered, rx_pkt.payload, rx_pkt.payload_len);
recovered[rx_pkt.payload_len] = '\0';
printf(" Message: \"%s\"\n", recovered);
}
printf("\n");
/* ---- Compare ---- */
int match = (rx_pkt.crc_ok &&
rx_pkt.payload_len == tx_pkt.payload_len &&
memcmp(rx_pkt.payload, tx_pkt.payload, tx_pkt.payload_len) == 0);
/* Bit error stats on raw frame (before packet decode) */
size_t cmp_len = rx_len < frame_len ? rx_len : frame_len;
/* Compare only the data portion (skip preamble+sync which aren't in rx_frame) */
size_t bit_errors = 0;
size_t total_bits = 0;
/* rx_frame corresponds to frame starting at byte 4 (after preamble+sync) */
for (size_t i = 0; i < cmp_len && (i + 4) < frame_len; i++) {
uint8_t diff = rx_frame[i] ^ frame[i + 4];
for (int b = 0; b < 8; b++)
if (diff & (1 << b))
bit_errors++;
total_bits += 8;
}
printf("Bit errors: %zu / %zu", bit_errors, total_bits);
if (total_bits > 0)
printf(" (BER: %.2e)", (double)bit_errors / total_bits);
printf("\n");
if (match)
printf("RESULT: PASS — message recovered successfully!\n");
else
printf("RESULT: FAIL — message mismatch or CRC error\n");
return match ? 0 : 1;
}