#include #include #include #include #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; }