From fdab99d1faa89ef9963387157975338c4a710fd1 Mon Sep 17 00:00:00 2001 From: KubaPro010 Date: Sat, 11 Jul 2026 12:45:50 +0200 Subject: [PATCH] rds input via ipc - experimental (hence it is on 61.75 - the only unused frequncy --- include/bit_ring.h | 37 +++++++++++++++++++++++++++++++++++ lib/ipc.c | 15 ++++++--------- lib/oscillator.c | 7 +++++-- lib/oscillator.h | 3 ++- src/fm95.c | 48 ++++++++++++++++++++++++++++++++++++++++------ 5 files changed, 92 insertions(+), 18 deletions(-) create mode 100644 include/bit_ring.h diff --git a/include/bit_ring.h b/include/bit_ring.h new file mode 100644 index 0000000..76c9175 --- /dev/null +++ b/include/bit_ring.h @@ -0,0 +1,37 @@ +// bit_ring.h +#include +#include + +typedef struct { + uint8_t *bits; // one bit stored per byte (0/1) — throughput is tiny, simplicity wins + size_t capacity; + _Atomic size_t head, tail; +} bit_ring_t; + +static inline void bit_ring_init(bit_ring_t *r, size_t capacity) { + r->bits = calloc(capacity, 1); + r->capacity = capacity; + atomic_store(&r->head, 0); + atomic_store(&r->tail, 0); +} + +static inline size_t bit_ring_write(bit_ring_t *r, const uint8_t *bits, size_t n) { + size_t head = atomic_load_explicit(&r->head, memory_order_relaxed); + size_t tail = atomic_load_explicit(&r->tail, memory_order_acquire); + size_t free_space = r->capacity - (head - tail); + size_t to_write = n < free_space ? n : free_space; + for (size_t i = 0; i < to_write; i++) + r->bits[(head + i) % r->capacity] = bits[i]; + atomic_store_explicit(&r->head, head + to_write, memory_order_release); + return to_write; +} + +// returns 1 if a bit was available, 0 on underrun +static inline int bit_ring_read1(bit_ring_t *r, uint8_t *out) { + size_t tail = atomic_load_explicit(&r->tail, memory_order_relaxed); + size_t head = atomic_load_explicit(&r->head, memory_order_acquire); + if (head == tail) return 0; + *out = r->bits[tail % r->capacity]; + atomic_store_explicit(&r->tail, tail + 1, memory_order_release); + return 1; +} \ No newline at end of file diff --git a/lib/ipc.c b/lib/ipc.c index ab3f413..d3498d8 100644 --- a/lib/ipc.c +++ b/lib/ipc.c @@ -6,8 +6,7 @@ #include #include -static int make_server_socket(const char *path) -{ +static int make_server_socket(const char *path) { int fd = socket(AF_UNIX, SOCK_STREAM, 0); if (fd < 0) { perror("ipc: socket"); return -1; } @@ -63,13 +62,11 @@ static void *listener_thread(void *arg) { return NULL; } -int create_ipc(ipc_ctx_t *ctx, ipc_client_fn handler, - const char *socket_path, void *user_data) -{ - ctx->handler = handler; - ctx->user_data = user_data; - ctx->running = 1; - ctx->server_fd = make_server_socket(socket_path); +int create_ipc(ipc_ctx_t *ctx, ipc_client_fn handler, const char *socket_path, void *user_data) { + ctx->handler = handler; + ctx->user_data = user_data; + ctx->running = 1; + ctx->server_fd = make_server_socket(socket_path); ctx->socket_path = strdup(socket_path); if (ctx->server_fd < 0) return -1; diff --git a/lib/oscillator.c b/lib/oscillator.c index 674b1d2..d211cd7 100644 --- a/lib/oscillator.c +++ b/lib/oscillator.c @@ -30,7 +30,10 @@ float get_oscillator_cos_multiplier_ni(Oscillator *osc, float multiplier) { return cosf(osc->phase * multiplier); } -inline void advance_oscillator(Oscillator *osc) { +inline bool advance_oscillator(Oscillator *osc) { osc->phase += osc->phase_increment; - if (osc->phase >= M_2PI) osc->phase -= M_2PI; + if (osc->phase >= M_2PI) { + osc->phase -= M_2PI; + return true; + } return false; } \ No newline at end of file diff --git a/lib/oscillator.h b/lib/oscillator.h index 778c98d..b50774b 100644 --- a/lib/oscillator.h +++ b/lib/oscillator.h @@ -1,6 +1,7 @@ #pragma once #include "constants.h" +#include #include typedef struct { @@ -15,4 +16,4 @@ float get_oscillator_sin_sample(Oscillator *osc); float get_oscillator_cos_sample(Oscillator *osc); float get_oscillator_sin_multiplier_ni(Oscillator *osc, float multiplier); float get_oscillator_cos_multiplier_ni(Oscillator *osc, float multiplier); -void advance_oscillator(Oscillator *osc); \ No newline at end of file +bool advance_oscillator(Oscillator *osc); \ No newline at end of file diff --git a/src/fm95.c b/src/fm95.c index c8c8727..20fd5fb 100644 --- a/src/fm95.c +++ b/src/fm95.c @@ -13,6 +13,7 @@ #include "stereo_encoder.h" #include "bs412.h" #include "gain_control.h" +#include "bit_ring.h" #define BUFFER_SIZE 4998 // This defines how many samples to process at a time, because the loop here is this: get signal -> process signal -> output signal, and when we get signal we actually get BUFFER_SIZE of them @@ -83,6 +84,10 @@ typedef struct { StereoEncoder stencode; AGC agc; delay_line_t rds_delays[4]; + bit_ring_t rds_bitring[1]; + double rds_phase[1]; + float rds_symbol[1]; + uint8_t rds_last_bit[1]; } FM95_Runtime; typedef struct { @@ -142,6 +147,8 @@ void cleanup_runtime(FM95_Runtime* runtime, const FM95_Config config) { if(runtime->lpf_r != NULL) iirfilt_rrrf_destroy(runtime->lpf_r); runtime->lpf_l = runtime->lpf_r = NULL; } exit_stereo_encoder(&runtime->stencode); + + free(runtime->rds_bitring[0].bits); } void cleanup_audio_runtime(FM95_Runtime *rt, const FM95_Options options) { @@ -208,6 +215,7 @@ int run_fm95(FM95_Config* config, FM95_Runtime* runtime, FM95_RunResult* result) } for(uint16_t i = 0; i < BUFFER_SIZE; i++) { + bool cycle = advance_oscillator(&runtime->osc); bool do_result = (i == BUFFER_SIZE - 1); float mpx = 0.0f; @@ -251,7 +259,7 @@ int run_fm95(FM95_Config* config, FM95_Runtime* runtime, FM95_RunResult* result) uint8_t osc_stream = 12 + stream; if(osc_stream >= 13) osc_stream++; - float carrier = get_oscillator_cos_multiplier_ni(&runtime->osc, osc_stream); + float carrier = get_oscillator_cos_multiplier_ni(&runtime->osc, osc_stream * 4.0f); if(config->stereo_ssb) carrier = delay_line(&runtime->rds_delays[stream], carrier); mpx += (runtime->rds_in[config->rds_streams * i + stream] * carrier) * rds_level; @@ -259,12 +267,24 @@ int run_fm95(FM95_Config* config, FM95_Runtime* runtime, FM95_RunResult* result) } } + { + if (cycle) { + uint8_t bit; + if (bit_ring_read1(&runtime->rds_bitring[0], &bit)) { + runtime->rds_last_bit[0] = bit; + } + runtime->rds_symbol[0] = runtime->rds_last_bit[0] ? 1.0f : -1.0f; + } + + float clock = get_oscillator_cos_multiplier_ni(&runtime->osc, 1.0f); + float carrier = get_oscillator_cos_multiplier_ni(&runtime->osc, 52.0f); + mpx += runtime->rds_symbol[0] * clock * carrier * config->volumes.rds; + } + mpx = bs412_compress(&runtime->bs412, audio, mpx+mpx_in[i], &temp_result.mpx_power); temp_result.bs412_gain = runtime->bs412.gain; - mpx = tanhf(mpx); - output[i] = mpx*config->master_volume; // Ensure peak deviation of 75 khz (or the set deviation), assuming we're calibrated correctly - advance_oscillator(&runtime->osc); + output[i] = tanhf(mpx)*config->master_volume; // Ensure peak deviation of 75 khz (or the set deviation), assuming we're calibrated correctly } memcpy(result, &temp_result, sizeof(FM95_RunResult)); _pulse_output; } @@ -432,7 +452,7 @@ void init_runtime(FM95_Runtime* runtime, const FM95_Config config) { init_oscillator(&runtime->osc, (config.calibration == 2) ? 60 : ((config.calibration == 1) ? 400 : 19000), config.sample_rate); return; } - else init_oscillator(&runtime->osc, 4750, config.sample_rate); + else init_oscillator(&runtime->osc, 1187.5f, config.sample_rate); if(config.lpf_cutoff != 0) { runtime->lpf_l = iirfilt_rrrf_create_prototype(LIQUID_IIRDES_CHEBY2, LIQUID_IIRDES_LOWPASS, LIQUID_IIRDES_SOS, config.lpf_order, (config.lpf_cutoff/config.sample_rate), 0.0f, 1.0f, 40.0f); @@ -451,7 +471,7 @@ void init_runtime(FM95_Runtime* runtime, const FM95_Config config) { if(runtime->bs412.init == true && (runtime->bs412.sample_rate == config.sample_rate)) { reinit_bs412(&runtime->bs412, config.mpx_deviation, config.mpx_power, config.bs412_attack, config.bs412_release, config.bs412_max, config.bs412_gate, config.bs412_knee, config.bs412_strenght); } else init_bs412(&runtime->bs412, config.mpx_deviation, config.mpx_power, config.bs412_attack, config.bs412_release, config.bs412_max, config.bs412_gate, config.bs412_knee, config.bs412_strenght, config.sample_rate); - init_stereo_encoder(&runtime->stencode, config.stereo_ssb, 4.0f, &runtime->osc, config.volumes.audio, config.volumes.pilot); + init_stereo_encoder(&runtime->stencode, config.stereo_ssb, 16.0f, &runtime->osc, config.volumes.audio, config.volumes.pilot); float last_gain = 0.0f; if(config.agc_max != 0.0) { @@ -460,6 +480,11 @@ void init_runtime(FM95_Runtime* runtime, const FM95_Config config) { initAGC(&runtime->agc, config.sample_rate, config.agc_target, config.agc_min, config.agc_max, config.agc_attack, config.agc_release); runtime->agc.currentGain = last_gain; } + + bit_ring_init(&runtime->rds_bitring[0], 2048); + runtime->rds_phase[0] = 0.0; + runtime->rds_symbol[0] = -1.0f; + runtime->rds_last_bit[0] = 0; } #define BUF_SIZE 256 @@ -577,6 +602,17 @@ static void *handle_client(ipc_client_arg_t *arg) { data->runtime->bs412.strenght = val; reply = 0; break; + case 112: { + 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; + } + bit_ring_write(&data->runtime->rds_bitring[0], unpacked, nbits); + reply = 0xff; + break; + } case 0xfe: // Fetch config send(fd, data->config, sizeof(FM95_Config), 0);