init commit
This commit is contained in:
+222
@@ -0,0 +1,222 @@
|
||||
#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;
|
||||
}
|
||||
Reference in New Issue
Block a user