mirror of
https://github.com/radio95-rnt/fm95.git
synced 2026-02-26 19:23:51 +01:00
things
This commit is contained in:
3
.vscode/settings.json
vendored
3
.vscode/settings.json
vendored
@@ -41,7 +41,8 @@
|
|||||||
"liquid.h": "c",
|
"liquid.h": "c",
|
||||||
"queue": "c",
|
"queue": "c",
|
||||||
"thread": "c",
|
"thread": "c",
|
||||||
"vector": "c"
|
"vector": "c",
|
||||||
|
"stereo_encoder.h": "c"
|
||||||
},
|
},
|
||||||
"C_Cpp.errorSquiggles": "disabled"
|
"C_Cpp.errorSquiggles": "disabled"
|
||||||
}
|
}
|
||||||
137
src/fm95.c
137
src/fm95.c
@@ -9,33 +9,16 @@
|
|||||||
#define buffer_tlength_fragsize 12288
|
#define buffer_tlength_fragsize 12288
|
||||||
#define buffer_prebuf 8
|
#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 "../dsp/oscillator.h"
|
||||||
#include "../filter/iir.h"
|
#include "../filter/iir.h"
|
||||||
#include "../modulation/stereo_encoder.h"
|
#include "../modulation/stereo_encoder.h"
|
||||||
#include "../filter/bs412.h"
|
#include "../filter/bs412.h"
|
||||||
#include "../filter/gain_control.h"
|
#include "../filter/gain_control.h"
|
||||||
|
|
||||||
#define DEFAULT_SAMPLE_RATE 192000
|
#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
|
||||||
|
|
||||||
#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
|
|
||||||
|
|
||||||
#include "../io/audio.h"
|
#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_MONO_VOLUME 0.45f // 45%
|
||||||
#define DEFAULT_PILOT_VOLUME 0.09f // 9%
|
#define DEFAULT_PILOT_VOLUME 0.09f // 9%
|
||||||
#define DEFAULT_STEREO_VOLUME 0.3f // 30%
|
#define DEFAULT_STEREO_VOLUME 0.3f // 30%
|
||||||
@@ -95,6 +78,13 @@ typedef struct
|
|||||||
PulseInputDevice input_device, mpx_device, rds_device;
|
PulseInputDevice input_device, mpx_device, rds_device;
|
||||||
PulseOutputDevice output_device;
|
PulseOutputDevice output_device;
|
||||||
float* rds_in;
|
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;
|
} FM95_Runtime;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
@@ -138,15 +128,14 @@ int run_fm95(const FM95_Config config, FM95_Runtime* runtime) {
|
|||||||
bool rds_on = (runtime->rds_device.initialized == 1);
|
bool rds_on = (runtime->rds_device.initialized == 1);
|
||||||
|
|
||||||
if(config.calibration != 0) {
|
if(config.calibration != 0) {
|
||||||
Oscillator osc;
|
init_oscillator(&runtime->osc, (config.calibration == 2) ? 60 : 400, config.sample_rate);
|
||||||
init_oscillator(&osc, (config.calibration == 2) ? 60 : 400, config.sample_rate);
|
|
||||||
|
|
||||||
int pulse_error;
|
int pulse_error;
|
||||||
float output[BUFFER_SIZE];
|
float output[BUFFER_SIZE];
|
||||||
|
|
||||||
while(to_run) {
|
while(to_run) {
|
||||||
for (int i = 0; i < BUFFER_SIZE; i++) {
|
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
|
if(config.calibration == 2) sample = (sample > 0.0f) ? 1.0f : -1.0f; // Sine wave to square wave filter
|
||||||
output[i] = sample*config.master_volume;
|
output[i] = sample*config.master_volume;
|
||||||
}
|
}
|
||||||
@@ -160,32 +149,25 @@ int run_fm95(const FM95_Config config, FM95_Runtime* runtime) {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
Oscillator osc;
|
init_oscillator(&runtime->osc, (config.stereo == 2) ? 7812.5 : 4750, config.sample_rate);
|
||||||
init_oscillator(&osc, (config.stereo == 2) ? 7812.5 : 4750, config.sample_rate);
|
|
||||||
|
|
||||||
iirfilt_rrrf lpf_l, lpf_r;
|
|
||||||
if(config.lpf_cutoff != 0) {
|
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);
|
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);
|
||||||
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_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) {
|
if(config.preemphasis != 0) {
|
||||||
init_preemphasis(&preemp_l, 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(&preemp_r, 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(&runtime->bs412, config.mpx_deviation, config.mpx_power, config.bs412_attack, config.bs412_release, config.bs412_max, config.sample_rate);
|
||||||
init_bs412(&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(&runtime->tilter, config.tilt);
|
||||||
if(config.tilt != 0) tilt_init(&tilter, config.tilt);
|
|
||||||
|
|
||||||
StereoEncoder stencode;
|
init_stereo_encoder(&runtime->stencode, 4.0f, &runtime->osc, (config.stereo == 2), config.volumes.mono, config.volumes.pilot, config.volumes.stereo);
|
||||||
init_stereo_encoder(&stencode, 4.0f, &osc, (config.stereo == 2), config.volumes.mono, config.volumes.pilot, config.volumes.stereo);
|
|
||||||
|
|
||||||
AGC agc;
|
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);
|
||||||
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);
|
|
||||||
|
|
||||||
int pulse_error;
|
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;
|
float r = audio_stereo_input[2*i+1]*config.audio_preamp;
|
||||||
|
|
||||||
if(config.agc_max != 0.0) {
|
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;
|
l *= agc_gain;
|
||||||
r *= agc_gain;
|
r *= agc_gain;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(config.lpf_cutoff != 0) {
|
if(config.lpf_cutoff != 0) {
|
||||||
iirfilt_rrrf_execute(lpf_l, l, &l);
|
iirfilt_rrrf_execute(runtime->lpf_l, l, &l);
|
||||||
iirfilt_rrrf_execute(lpf_r, r, &r);
|
iirfilt_rrrf_execute(runtime->lpf_r, r, &r);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(config.preemphasis != 0) {
|
if(config.preemphasis != 0) {
|
||||||
l = apply_preemphasis(&preemp_l, l);
|
l = apply_preemphasis(&runtime->preemp_l, l);
|
||||||
r = apply_preemphasis(&preemp_r, r);
|
r = apply_preemphasis(&runtime->preemp_r, r);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(config.clipper_threshold != 0) {
|
||||||
l = hard_clip(l*config.audio_volume, config.clipper_threshold);
|
l = hard_clip(l*config.audio_volume, config.clipper_threshold);
|
||||||
r = hard_clip(r*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
|
if(rds_on && config.stereo != 2) { // disable rds on polar stereo
|
||||||
float rds_level = config.volumes.rds;
|
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;
|
uint8_t osc_stream = 12 + stream;
|
||||||
if(osc_stream == 13) osc_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
|
rds_level *= config.volumes.rds_step; // Prepare level for the next stream
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
mpx = bs412_compress(&bs412, mpx+mpx_in[i]);
|
mpx = bs412_compress(&runtime->bs412, mpx+mpx_in[i]);
|
||||||
if(config.tilt != 0) mpx = tilt(&tilter, mpx);
|
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
|
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)))) {
|
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) {
|
if(config.lpf_cutoff != 0) {
|
||||||
iirfilt_rrrf_destroy(lpf_l);
|
iirfilt_rrrf_destroy(runtime->lpf_l);
|
||||||
iirfilt_rrrf_destroy(lpf_r);
|
iirfilt_rrrf_destroy(runtime->lpf_r);
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
@@ -466,43 +450,43 @@ int main(int argc, char **argv) {
|
|||||||
.rds = DEFAULT_RDS_VOLUME,
|
.rds = DEFAULT_RDS_VOLUME,
|
||||||
.rds_step = DEFAULT_RDS_VOLUME_STEP
|
.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,
|
.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 = DEFAULT_PREEMPHASIS_TAU,
|
.preemphasis = 50e-6, // Europe, the "freedomers" use 75µs (75e-6)
|
||||||
.tilt = 0,
|
.tilt = 0, // Off
|
||||||
.calibration = 0,
|
.calibration = 0, // Off
|
||||||
.mpx_power = DEFAULT_MPX_POWER,
|
.mpx_power = 3.0f, // dbr, this is for BS412, simplest bs412
|
||||||
.mpx_deviation = DEFAULT_MPX_DEVIATION,
|
.mpx_deviation = 75000.0f, // for BS412
|
||||||
.audio_deviation = DEFAULT_DEVIATION,
|
.audio_deviation = 75000.0f, // another way to set the volume
|
||||||
.master_volume = DEFAULT_MASTER_VOLUME,
|
.master_volume = 1.0f, // Volume of everything combined, for calibration
|
||||||
.audio_volume = 1.0f,
|
.audio_volume = 1.0f, // Volume of the audio, before stereo encoding, before clipper
|
||||||
.audio_preamp = 1.0f,
|
.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,
|
.ini_config_path = DEFAULT_INI_PATH,
|
||||||
|
|
||||||
.lpf_order = 17,
|
.lpf_order = 15, // how good the lpf is, usually no more than 18 is needed
|
||||||
.preemp_unity_freq = 15250.0f,
|
.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_target = 0.625f,
|
||||||
.agc_attack = 0.03f,
|
.agc_attack = 0.03f,
|
||||||
.agc_release = 0.225f,
|
.agc_release = 0.225f,
|
||||||
.agc_min = 0.1f,
|
.agc_min = 0.1f,
|
||||||
.agc_max = 1.75f,
|
.agc_max = 1.5f,
|
||||||
.bs412_attack = 0.03f,
|
.bs412_attack = 0.05f,
|
||||||
.bs412_release = 0.02,
|
.bs412_release = 0.025,
|
||||||
.bs412_max = 1.0f,
|
.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 = {
|
FM95_DeviceNames dv_names = {
|
||||||
.input = INPUT_DEVICE,
|
.input = "\0",
|
||||||
.output = OUTPUT_DEVICE,
|
.output = "\0",
|
||||||
.mpx = MPX_DEVICE,
|
.mpx = "\0",
|
||||||
.rds = RDS_DEVICE
|
.rds = "\0"
|
||||||
};
|
};
|
||||||
|
|
||||||
int err;
|
int err;
|
||||||
@@ -515,6 +499,15 @@ int main(int argc, char **argv) {
|
|||||||
return err;
|
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;
|
config.master_volume *= config.audio_deviation/75000.0f;
|
||||||
|
|
||||||
FM95_Runtime runtime;
|
FM95_Runtime runtime;
|
||||||
|
|||||||
Reference in New Issue
Block a user