From 8a4cbb9b5fa3929179453cbfdac570374b8722b4 Mon Sep 17 00:00:00 2001 From: KubaPro010 Date: Sat, 11 Jul 2026 22:07:33 +0200 Subject: [PATCH] jdsca tests --- lib/qpsk.c | 66 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ lib/qpsk.h | 22 ++++++++++++++++++ src/fm95.c | 54 ++++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 140 insertions(+), 2 deletions(-) create mode 100644 lib/qpsk.c create mode 100644 lib/qpsk.h diff --git a/lib/qpsk.c b/lib/qpsk.c new file mode 100644 index 0000000..004f245 --- /dev/null +++ b/lib/qpsk.c @@ -0,0 +1,66 @@ +#include "qpsk.h" +#include +#include +#include + +#define OQPSK_RRC_DELAY 6 // filter delay in symbols (liquid's "m" param) + +static void oqpsk_next_dibit(OQPSKMod *m, float *i, float *q) { + uint8_t b0 = m->last_bits[0], b1 = m->last_bits[1], bit; + if (bit_ring_read1(m->bitring, &bit)) b0 = bit; + if (bit_ring_read1(m->bitring, &bit)) b1 = bit; + m->last_bits[0] = b0; + m->last_bits[1] = b1; + *i = b0 ? -1.0f : 1.0f; + *q = b1 ? -1.0f : 1.0f; +} + +void oqpsk_init(OQPSKMod *m, float if_freq, float sample_rate, float symbol_rate, float alpha, bit_ring_t *bitring) { + memset(m, 0, sizeof(*m)); + init_oscillator(&m->carrier, if_freq, sample_rate); + + m->sps = (int)roundf(sample_rate / symbol_rate); + if (m->sps < 2) m->sps = 2; + if (m->sps % 2 != 0) m->sps += 1; // must be even for the half-symbol Q delay + + m->fir_i = firfilt_rrrf_create_rnyquist(LIQUID_FIRFILT_RRC, m->sps, OQPSK_RRC_DELAY, alpha, 0.0f); + m->fir_q = firfilt_rrrf_create_rnyquist(LIQUID_FIRFILT_RRC, m->sps, OQPSK_RRC_DELAY, alpha, 0.0f); + + m->q_delay_len = m->sps / 2; + m->q_delay = calloc(m->q_delay_len, sizeof(float)); + m->q_delay_pos = 0; + m->symbol_clock = 0; + m->sym_i = m->sym_q = 1.0f; + m->bitring = bitring; +} + +float oqpsk_process(OQPSKMod *m) { + float impulse_i = 0.0f, impulse_q = 0.0f; + + if (m->symbol_clock == 0) { + oqpsk_next_dibit(m, &m->sym_i, &m->sym_q); + impulse_i = m->sym_i * (float)m->sps; + impulse_q = m->sym_q * (float)m->sps; + } + m->symbol_clock = (m->symbol_clock + 1) % m->sps; + + float filt_i, filt_q_raw; + firfilt_rrrf_push(m->fir_i, impulse_i); + firfilt_rrrf_execute(m->fir_i, &filt_i); + firfilt_rrrf_push(m->fir_q, impulse_q); + firfilt_rrrf_execute(m->fir_q, &filt_q_raw); + + float filt_q = m->q_delay[m->q_delay_pos]; + m->q_delay[m->q_delay_pos] = filt_q_raw; + m->q_delay_pos = (m->q_delay_pos + 1) % m->q_delay_len; + + float c = get_oscillator_cos_sample(&m->carrier); + float s = get_oscillator_sin_sample(&m->carrier); + return filt_i * c - filt_q * s; +} + +void oqpsk_destroy(OQPSKMod *m) { + firfilt_rrrf_destroy(m->fir_i); + firfilt_rrrf_destroy(m->fir_q); + free(m->q_delay); +} \ No newline at end of file diff --git a/lib/qpsk.h b/lib/qpsk.h new file mode 100644 index 0000000..cc1d521 --- /dev/null +++ b/lib/qpsk.h @@ -0,0 +1,22 @@ +#pragma once + +#include +#include "oscillator.h" +#include "bit_ring.h" + +typedef struct { + Oscillator carrier; // IF carrier (e.g. 3 kHz) + firfilt_rrrf fir_i, fir_q; // liquid RRC matched-filter shapers + float *q_delay; + int q_delay_len; + int q_delay_pos; + int sps; // samples per symbol + int symbol_clock; + float sym_i, sym_q; + bit_ring_t *bitring; + uint8_t last_bits[2]; // hold last symbol if bitring runs dry +} OQPSKMod; + +void oqpsk_init(OQPSKMod *m, float if_freq, float sample_rate, float symbol_rate, float alpha, bit_ring_t *bitring); +float oqpsk_process(OQPSKMod *m); +void oqpsk_destroy(OQPSKMod *m); \ No newline at end of file diff --git a/src/fm95.c b/src/fm95.c index abd940d..751a564 100644 --- a/src/fm95.c +++ b/src/fm95.c @@ -3,6 +3,17 @@ #include "ini.h" #include +// #define JDSCA // jontio's jdsca - for fun + +#ifdef JDSCA +#define JDSCA_BITRATE 2400.0f // bits/sec +#define JDSCA_IF_FREQ 3000.0f // OQPSK IF carrier, Hz +#define JDSCA_ALPHA 0.75f // RRC excess bandwidth +#define JDSCA_BUFFER_BITS 40000 // bit_ring capacity +#define JDSCA_SUBCARRIER_MULT 64.0f // 64 * 1187.5 Hz = 76 kHz +#define JDSCA_LEVEL 0.045f // injection level, tune by ear/scope +#endif + #define DEFAULT_INI_PATH "/etc/fm95/fm95.conf" #define buffer_maxlength 99960 @@ -86,6 +97,10 @@ typedef struct { float rds_symbol[4]; uint8_t rds_last_bit[4]; iirfilt_rrrf rds_filter[4]; +#ifdef JDSCA + OQPSKMod mod; + bit_ring_t jsca_bitring; +#endif } FM95_Runtime; typedef struct { @@ -145,8 +160,15 @@ void cleanup_runtime(FM95_Runtime* runtime, const FM95_Config config) { runtime->lpf_l = runtime->lpf_r = NULL; } exit_stereo_encoder(&runtime->stencode); - for(int i = 0; i < 4; i++) free(runtime->rds_bitring[i].bits); - for(int i = 0; i < 4; i++) iirfilt_rrrf_destroy(runtime->rds_filter[i]); + for(int i = 0; i < 4; i++) { + free(runtime->rds_bitring[i].bits); + iirfilt_rrrf_destroy(runtime->rds_filter[i]); + } + +#ifdef JDSCA + free(runtime->jsca_bitring.bits); + oqpsk_destroy(&runtime->mod); +#endif } void cleanup_audio_runtime(FM95_Runtime *rt, const FM95_Options options) { @@ -266,6 +288,14 @@ int run_fm95(FM95_Config* config, FM95_Runtime* runtime, FM95_RunResult* result) } } +#ifdef JDSCA + { + float jsca_baseband = oqpsk_process(&runtime->mod); + float jsca_carrier = get_oscillator_cos_multiplier_ni(&runtime->osc, JDSCA_SUBCARRIER_MULT); + mpx += jsca_baseband * jsca_carrier * JDSCA_LEVEL; + } +#endif + mpx = bs412_compress(&runtime->bs412, audio, mpx+mpx_in[i], &temp_result.mpx_power); temp_result.bs412_gain = runtime->bs412.gain; @@ -454,6 +484,12 @@ void init_runtime(FM95_Runtime* runtime, const FM95_Config config) { runtime->rds_filter[i] = iirfilt_rrrf_create_prototype(LIQUID_IIRDES_CHEBY2, LIQUID_IIRDES_LOWPASS, LIQUID_IIRDES_SOS, 8, (2400.0f/config.sample_rate), 0.0f, 1.0f, 40.0f); } + +#ifdef JDSCA + bit_ring_init(&runtime->jsca_bitring, JDSCA_BUFFER_BITS); + oqpsk_init(&runtime->mod, JDSCA_IF_FREQ, (float)config.sample_rate, + JDSCA_BITRATE / 2.0f, JDSCA_ALPHA, &runtime->jsca_bitring); +#endif } #define BUF_SIZE 256 @@ -589,6 +625,20 @@ static void *handle_client(ipc_client_arg_t *arg) { reply = (written < nbits) ? 2 : 0; break; } +#ifdef JDSCA + case 0xfd: { + uint8_t unpacked[8 * (BUF_SIZE - 1)]; + size_t nbits = 0; + for (ssize_t i = 1; i < n; i++) + for (int b = 7; b >= 0; b--) + unpacked[nbits++] = (buf[i] >> b) & 1; + size_t written = bit_ring_write(&data->runtime->jsca_bitring, unpacked, nbits); + if (written < nbits) + fprintf(stderr, "jsca bitring overrun: dropped %zu of %zu bits\n", nbits - written, nbits); + reply = (written < nbits) ? 2 : 0; + break; + } +#endif case 0xfe: // Fetch config send(fd, data->config, sizeof(FM95_Config), 0);