0
1
mirror of https://github.com/radio95-rnt/fm95.git synced 2026-02-26 19:23:51 +01:00
This commit is contained in:
2025-08-02 12:41:04 +02:00
parent 55ecde6673
commit 13583c1728
2 changed files with 69 additions and 75 deletions

View File

@@ -41,7 +41,8 @@
"liquid.h": "c",
"queue": "c",
"thread": "c",
"vector": "c"
"vector": "c",
"stereo_encoder.h": "c"
},
"C_Cpp.errorSquiggles": "disabled"
}

View File

@@ -9,33 +9,16 @@
#define buffer_tlength_fragsize 12288
#define buffer_prebuf 8
#define DEFAULT_STEREO 1
#define DEFAULT_RDS_STREAMS 2
#define DEFAULT_CLIPPER_THRESHOLD 1.0f
#define DEFAULT_PREEMPHASIS_TAU 50e-6 // Europe, the freedomers use 75µs (75e-6)
#define DEFAULT_MPX_POWER 3.0f // dbr, this is for BS412, simplest bs412
#define DEFAULT_MPX_DEVIATION 75000.0f // for BS412
#define DEFAULT_DEVIATION 75000.0f // another way to set the volume
#include "../dsp/oscillator.h"
#include "../filter/iir.h"
#include "../modulation/stereo_encoder.h"
#include "../filter/bs412.h"
#include "../filter/gain_control.h"
#define DEFAULT_SAMPLE_RATE 192000
#define INPUT_DEVICE "FM_Audio.monitor"
#define OUTPUT_DEVICE "alsa_output.platform-soc_sound.stereo-fallback"
#define RDS_DEVICE "RDS.monitor"
#define MPX_DEVICE "FM_MPX.monitor"
#define BUFFER_SIZE 2048
#define BUFFER_SIZE 2048 // 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
#include "../io/audio.h"
#define DEFAULT_MASTER_VOLUME 1.0f // Volume of everything combined, for calibration
#define DEFAULT_MONO_VOLUME 0.45f // 45%
#define DEFAULT_PILOT_VOLUME 0.09f // 9%
#define DEFAULT_STEREO_VOLUME 0.3f // 30%
@@ -95,6 +78,13 @@ typedef struct
PulseInputDevice input_device, mpx_device, rds_device;
PulseOutputDevice output_device;
float* rds_in;
Oscillator osc;
iirfilt_rrrf lpf_l, lpf_r;
ResistorCapacitor preemp_l, preemp_r;
BS412Compressor bs412;
TiltCorrectionFilter tilter;
StereoEncoder stencode;
AGC agc;
} FM95_Runtime;
typedef struct {
@@ -138,15 +128,14 @@ int run_fm95(const FM95_Config config, FM95_Runtime* runtime) {
bool rds_on = (runtime->rds_device.initialized == 1);
if(config.calibration != 0) {
Oscillator osc;
init_oscillator(&osc, (config.calibration == 2) ? 60 : 400, config.sample_rate);
init_oscillator(&runtime->osc, (config.calibration == 2) ? 60 : 400, config.sample_rate);
int pulse_error;
float output[BUFFER_SIZE];
while(to_run) {
for (int i = 0; i < BUFFER_SIZE; i++) {
float sample = get_oscillator_sin_sample(&osc);
float sample = get_oscillator_sin_sample(&runtime->osc);
if(config.calibration == 2) sample = (sample > 0.0f) ? 1.0f : -1.0f; // Sine wave to square wave filter
output[i] = sample*config.master_volume;
}
@@ -160,32 +149,25 @@ int run_fm95(const FM95_Config config, FM95_Runtime* runtime) {
return 0;
}
Oscillator osc;
init_oscillator(&osc, (config.stereo == 2) ? 7812.5 : 4750, config.sample_rate);
init_oscillator(&runtime->osc, (config.stereo == 2) ? 7812.5 : 4750, config.sample_rate);
iirfilt_rrrf lpf_l, lpf_r;
if(config.lpf_cutoff != 0) {
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, 60.0f);
lpf_r = 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, 60.0f);
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, 60.0f);
runtime->lpf_r = 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, 60.0f);
}
ResistorCapacitor preemp_l, preemp_r;
if(config.preemphasis != 0) {
init_preemphasis(&preemp_l, config.preemphasis, config.sample_rate, config.preemp_unity_freq);
init_preemphasis(&preemp_r, config.preemphasis, config.sample_rate, config.preemp_unity_freq);
init_preemphasis(&runtime->preemp_l, config.preemphasis, config.sample_rate, config.preemp_unity_freq);
init_preemphasis(&runtime->preemp_r, config.preemphasis, config.sample_rate, config.preemp_unity_freq);
}
BS412Compressor bs412;
init_bs412(&bs412, config.mpx_deviation, config.mpx_power, config.bs412_attack, config.bs412_release, config.bs412_max, config.sample_rate);
init_bs412(&runtime->bs412, config.mpx_deviation, config.mpx_power, config.bs412_attack, config.bs412_release, config.bs412_max, config.sample_rate);
TiltCorrectionFilter tilter;
if(config.tilt != 0) tilt_init(&tilter, config.tilt);
if(config.tilt != 0) tilt_init(&runtime->tilter, config.tilt);
StereoEncoder stencode;
init_stereo_encoder(&stencode, 4.0f, &osc, (config.stereo == 2), config.volumes.mono, config.volumes.pilot, config.volumes.stereo);
init_stereo_encoder(&runtime->stencode, 4.0f, &runtime->osc, (config.stereo == 2), config.volumes.mono, config.volumes.pilot, config.volumes.stereo);
AGC agc;
if(config.agc_max != 0.0) initAGC(&agc, config.sample_rate, config.agc_target, config.agc_min, config.agc_max, config.agc_attack, config.agc_release);
if(config.agc_max != 0.0) initAGC(&runtime->agc, config.sample_rate, config.agc_target, config.agc_min, config.agc_max, config.agc_attack, config.agc_release);
int pulse_error;
@@ -221,25 +203,27 @@ int run_fm95(const FM95_Config config, FM95_Runtime* runtime) {
float r = audio_stereo_input[2*i+1]*config.audio_preamp;
if(config.agc_max != 0.0) {
float agc_gain = process_agc(&agc, 0.5f * (fabsf(l) + fabsf(r)));
float agc_gain = process_agc(&runtime->agc, 0.5f * (fabsf(l) + fabsf(r)));
l *= agc_gain;
r *= agc_gain;
}
if(config.lpf_cutoff != 0) {
iirfilt_rrrf_execute(lpf_l, l, &l);
iirfilt_rrrf_execute(lpf_r, r, &r);
iirfilt_rrrf_execute(runtime->lpf_l, l, &l);
iirfilt_rrrf_execute(runtime->lpf_r, r, &r);
}
if(config.preemphasis != 0) {
l = apply_preemphasis(&preemp_l, l);
r = apply_preemphasis(&preemp_r, r);
l = apply_preemphasis(&runtime->preemp_l, l);
r = apply_preemphasis(&runtime->preemp_r, r);
}
l = hard_clip(l*config.audio_volume, config.clipper_threshold);
r = hard_clip(r*config.audio_volume, config.clipper_threshold);
if(config.clipper_threshold != 0) {
l = hard_clip(l*config.audio_volume, config.clipper_threshold);
r = hard_clip(r*config.audio_volume, config.clipper_threshold);
}
mpx = stereo_encode(&stencode, config.stereo, l, r);
mpx = stereo_encode(&runtime->stencode, config.stereo, l, r);
if(rds_on && config.stereo != 2) { // disable rds on polar stereo
float rds_level = config.volumes.rds;
@@ -247,17 +231,17 @@ int run_fm95(const FM95_Config config, FM95_Runtime* runtime) {
uint8_t osc_stream = 12 + stream;
if(osc_stream == 13) osc_stream++;
mpx += (runtime->rds_in[config.rds_streams * i + stream] * get_oscillator_cos_multiplier_ni(&osc, osc_stream)) * rds_level;
mpx += (runtime->rds_in[config.rds_streams * i + stream] * get_oscillator_cos_multiplier_ni(&runtime->osc, osc_stream)) * rds_level;
rds_level *= config.volumes.rds_step; // Prepare level for the next stream
}
}
mpx = bs412_compress(&bs412, mpx+mpx_in[i]);
if(config.tilt != 0) mpx = tilt(&tilter, mpx);
mpx = bs412_compress(&runtime->bs412, mpx+mpx_in[i]);
if(config.tilt != 0) mpx = tilt(&runtime->tilter, mpx);
output[i] = hard_clip(mpx*config.master_volume, 1.0); // Ensure peak deviation of 75 khz (or the set deviation), assuming we're calibrated correctly
advance_oscillator(&osc);
advance_oscillator(&runtime->osc);
}
if((pulse_error = write_PulseOutputDevice(&runtime->output_device, output, sizeof(output)))) {
@@ -267,8 +251,8 @@ int run_fm95(const FM95_Config config, FM95_Runtime* runtime) {
}
}
if(config.lpf_cutoff != 0) {
iirfilt_rrrf_destroy(lpf_l);
iirfilt_rrrf_destroy(lpf_r);
iirfilt_rrrf_destroy(runtime->lpf_l);
iirfilt_rrrf_destroy(runtime->lpf_r);
}
return 0;
@@ -466,43 +450,43 @@ int main(int argc, char **argv) {
.rds = DEFAULT_RDS_VOLUME,
.rds_step = DEFAULT_RDS_VOLUME_STEP
},
.stereo = DEFAULT_STEREO,
.stereo = 1,
.rds_streams = DEFAULT_RDS_STREAMS,
.rds_streams = 1, // You have to match this with RDS95, otherwise may god have mercy on your RDS decoders
.clipper_threshold = DEFAULT_CLIPPER_THRESHOLD,
.preemphasis = DEFAULT_PREEMPHASIS_TAU,
.tilt = 0,
.calibration = 0,
.mpx_power = DEFAULT_MPX_POWER,
.mpx_deviation = DEFAULT_MPX_DEVIATION,
.audio_deviation = DEFAULT_DEVIATION,
.master_volume = DEFAULT_MASTER_VOLUME,
.audio_volume = 1.0f,
.audio_preamp = 1.0f,
.clipper_threshold = 1.0f, // At what level for the clipper to work, 1.0f, clips the audio at 1 volt peak to peak, so it will be always between -1 and 1
.preemphasis = 50e-6, // Europe, the "freedomers" use 75µs (75e-6)
.tilt = 0, // Off
.calibration = 0, // Off
.mpx_power = 3.0f, // dbr, this is for BS412, simplest bs412
.mpx_deviation = 75000.0f, // for BS412
.audio_deviation = 75000.0f, // another way to set the volume
.master_volume = 1.0f, // Volume of everything combined, for calibration
.audio_volume = 1.0f, // Volume of the audio, before stereo encoding, before clipper
.audio_preamp = 1.0f, // Volume of the audio before the filters
.sample_rate = DEFAULT_SAMPLE_RATE,
.sample_rate = 192000, // Sample rate for this whole gizmo to run on
.ini_config_path = DEFAULT_INI_PATH,
.lpf_order = 17,
.preemp_unity_freq = 15250.0f,
.lpf_order = 15, // how good the lpf is, usually no more than 18 is needed
.preemp_unity_freq = 15000.0f, // the preemphasis makes the highs louder, which for digital means no good, so instead of making the highs louder, make the lows quieter which gives the illusion of highs louder
.agc_target = 0.625f,
.agc_attack = 0.03f,
.agc_release = 0.225f,
.agc_min = 0.1f,
.agc_max = 1.75f,
.bs412_attack = 0.03f,
.bs412_release = 0.02,
.agc_max = 1.5f,
.bs412_attack = 0.05f,
.bs412_release = 0.025,
.bs412_max = 1.0f,
.lpf_cutoff = 15000,
.lpf_cutoff = 15000, // lpf cutoff, some run this at 15, because Big FM™ tells them to, but running this higher has no costs (unless you're running it above 18.5 khz), but no gains either
};
FM95_DeviceNames dv_names = {
.input = INPUT_DEVICE,
.output = OUTPUT_DEVICE,
.mpx = MPX_DEVICE,
.rds = RDS_DEVICE
.input = "\0",
.output = "\0",
.mpx = "\0",
.rds = "\0"
};
int err;
@@ -515,6 +499,15 @@ int main(int argc, char **argv) {
return err;
}
if(strlen(dv_names.input) == 0) {
printf("Please set the input device");
return 1;
}
if(strlen(dv_names.output) == 0) {
printf("Please set the output device");
return 1;
}
config.master_volume *= config.audio_deviation/75000.0f;
FM95_Runtime runtime;