optimize and fix some stuff

This commit is contained in:
2026-04-29 13:55:07 +02:00
parent 00219d862a
commit 099d9b97e7
4 changed files with 47 additions and 82 deletions
+3 -4
View File
@@ -5,8 +5,7 @@ FM95 is a audio processor for FM, it does:
- Pre-Emphasis - Pre-Emphasis
- Low Pass Filtering - Low Pass Filtering
- AGC - AGC
- Stereo (Polar too) - Stereo
- SCA
- BS412 (mpx power limiter, simplest implementation ever) - BS412 (mpx power limiter, simplest implementation ever)
Supports these inputs: Supports these inputs:
@@ -36,11 +35,11 @@ Done!
## CPU Usage? ## CPU Usage?
Should run completly fine on a pi 5, fine on a pi 3b (30% cpu, 45% with lpf) Should run completly fine on a pi 5, fine on a pi 3b (~30% cpu)
## Other Apps ## Other Apps
FM95 also includes some other apps, such as chimer95 which generates GTS tones each half hour, and dcf95 which creates a DCF77 compatible signal, and vban95 now which is a buffered VBAN receiver. And now also SCA generation was moved to sca95 from fm95! FM95 also includes some other apps, such as chimer95 which generates GTS tones each half hour, and vban95 now which is a buffered VBAN receiver. And now also SCA generation was moved to sca95 from fm95!
## Usage of other projects ## Usage of other projects
-4
View File
@@ -35,10 +35,6 @@ Simple integer, set to 1 to output a sine wave at 400 Hz, which should correspon
Float value to set the master volume, 1 by default, set to 0 to mute MPX Float value to set the master volume, 1 by default, set to 0 to mute MPX
### audio_volume
Float value to set the audio volume, also 1 by default, set to 0 to mute audio itself, but stereo pilot and RDS should remain
### audio_preamp ### audio_preamp
Pre-amplification of the audio, before any filters, set to 1 by default, set to 0 to mute audio Pre-amplification of the audio, before any filters, set to 1 by default, set to 0 to mute audio
+9 -6
View File
@@ -5,7 +5,7 @@ void init_stereo_encoder(StereoEncoder* st, uint8_t stereo_ssb, uint8_t multipli
st->multiplier = multiplier; st->multiplier = multiplier;
st->osc = osc; st->osc = osc;
st->pilot_volume = pilot_volume; st->pilot_volume = pilot_volume;
st->audio_volume = audio_volume; st->audio_volume = audio_volume * 0.5f;
if(stereo_ssb) { if(stereo_ssb) {
init_delay_line(&st->delay_pilot, stereo_ssb*2); init_delay_line(&st->delay_pilot, stereo_ssb*2);
init_delay_line(&st->delay, stereo_ssb*2); init_delay_line(&st->delay, stereo_ssb*2);
@@ -15,7 +15,10 @@ void init_stereo_encoder(StereoEncoder* st, uint8_t stereo_ssb, uint8_t multipli
float stereo_encode(StereoEncoder* st, uint8_t enabled, float left, float right, float *audio) { float stereo_encode(StereoEncoder* st, uint8_t enabled, float left, float right, float *audio) {
float mid = (left+right) * 0.5f; float mid = (left+right) * 0.5f;
if(!enabled) return mid * st->audio_volume; if(!enabled) {
*audio = mid * 2.0f * st->audio_volume;
return 0.0f;
}
if(st->stereo_hilbert) mid = delay_line(&st->delay, mid); if(st->stereo_hilbert) mid = delay_line(&st->delay, mid);
@@ -33,12 +36,12 @@ float stereo_encode(StereoEncoder* st, uint8_t enabled, float left, float right,
} }
float signalx2 = get_oscillator_sin_multiplier_ni(st->osc, st->multiplier * 2.0f); float signalx2 = get_oscillator_sin_multiplier_ni(st->osc, st->multiplier * 2.0f);
*audio = (mid*st->audio_volume); *audio = mid * st->audio_volume;
if(st->stereo_hilbert) { if(st->stereo_hilbert) {
float stereo = (crealf(stereo_hilbert) * signalx2) - (cimagf(stereo_hilbert) * signalx2cos); float stereo = (crealf(stereo_hilbert) * signalx2) - (cimagf(stereo_hilbert) * signalx2cos);
*audio += (stereo * st->audio_volume); *audio += stereo * st->audio_volume;
} else *audio += ((side*signalx2) * st->audio_volume); } else *audio += (side*signalx2) * st->audio_volume;
return (signalx1*st->pilot_volume); return signalx1 * st->pilot_volume;
} }
void exit_stereo_encoder(StereoEncoder* st) { void exit_stereo_encoder(StereoEncoder* st) {
+35 -68
View File
@@ -18,19 +18,9 @@
#include "../io/audio.h" #include "../io/audio.h"
#define DEFAULT_PILOT_VOLUME 0.09f // 9%
#define DEFAULT_RDS_VOLUME 0.0475f // 4.75%
#define DEFAULT_RDS_VOLUME_STEP 0.9f // 90%, so RDS2 stream 4 is 90% of stream 3 which is 90% of stream 2, which again is 90% of stream 1...
static volatile sig_atomic_t to_run = 1; static volatile sig_atomic_t to_run = 1;
static volatile sig_atomic_t to_reload = 0; static volatile sig_atomic_t to_reload = 0;
static inline float soft_clip(float x) { return tanhf(x); }
static inline float soft_clip_drive(float x, float drive) {
return tanhf(x * drive) / tanhf(drive);
}
typedef struct { typedef struct {
bool rds_on; bool rds_on;
bool mpx_on; bool mpx_on;
@@ -59,7 +49,6 @@ typedef struct {
float mpx_deviation; float mpx_deviation;
float audio_deviation; float audio_deviation;
float master_volume; float master_volume;
float audio_volume;
float audio_preamp; float audio_preamp;
uint32_t sample_rate; uint32_t sample_rate;
@@ -104,10 +93,7 @@ typedef struct {
} FM95_SetupContext; } FM95_SetupContext;
bool compare_dvs(const FM95_DeviceNames *a, const FM95_DeviceNames *b) { bool compare_dvs(const FM95_DeviceNames *a, const FM95_DeviceNames *b) {
return strcmp(a->input, b->input) == 0 && return strcmp(a->input, b->input) == 0 && strcmp(a->output, b->output) == 0 && strcmp(a->mpx, b->mpx) == 0 && strcmp(a->rds, b->rds) == 0;
strcmp(a->output, b->output) == 0 &&
strcmp(a->mpx, b->mpx) == 0 &&
strcmp(a->rds, b->rds) == 0;
} }
float calculate_sharedaudio_volume(const FM95_Volumes volumes, const int rds_streams) { float calculate_sharedaudio_volume(const FM95_Volumes volumes, const int rds_streams) {
@@ -169,7 +155,7 @@ int run_fm95(const FM95_Config config, FM95_Runtime* runtime) {
else if(config.calibration == 3) sample *= (19000/config.mpx_deviation); else if(config.calibration == 3) sample *= (19000/config.mpx_deviation);
output[i] = sample*config.master_volume; output[i] = sample*config.master_volume;
} }
if((pulse_error = write_PulseOutputDevice(&runtime->output_device, output, sizeof(output)))) { // get output from the function and assign it into pulse_error, this comment to avoid confusion if((pulse_error = write_PulseOutputDevice(&runtime->output_device, output, sizeof(output)))) {
fprintf(stderr, "Error writing to output device: %s\n", pa_strerror(pulse_error)); fprintf(stderr, "Error writing to output device: %s\n", pa_strerror(pulse_error));
to_run = 0; to_run = 0;
break; break;
@@ -185,6 +171,8 @@ int run_fm95(const FM95_Config config, FM95_Runtime* runtime) {
bool mpx_on = config.options.mpx_on; bool mpx_on = config.options.mpx_on;
bool rds_on = config.options.rds_on; bool rds_on = config.options.rds_on;
float softclip_norm = config.volumes.makeup / tanhf(config.volumes.drive);
while (to_run) { while (to_run) {
if((pulse_error = read_PulseInputDevice(&runtime->input_device, audio_stereo_input, sizeof(audio_stereo_input)))) { // get output from the function and assign it into pulse_error, this comment to avoid confusion if((pulse_error = read_PulseInputDevice(&runtime->input_device, audio_stereo_input, sizeof(audio_stereo_input)))) { // get output from the function and assign it into pulse_error, this comment to avoid confusion
fprintf(stderr, "Error reading from input device: %s\n", pa_strerror(pulse_error)); fprintf(stderr, "Error reading from input device: %s\n", pa_strerror(pulse_error));
@@ -229,8 +217,8 @@ int run_fm95(const FM95_Config config, FM95_Runtime* runtime) {
mod_r = apply_preemphasis(&runtime->preemp_r, mod_r); mod_r = apply_preemphasis(&runtime->preemp_r, mod_r);
} }
mod_l = soft_clip_drive(mod_l, config.volumes.drive) * config.volumes.makeup; mod_l = tanhf(mod_l * config.volumes.drive) * softclip_norm;
mod_r = soft_clip_drive(mod_r, config.volumes.drive) * config.volumes.makeup; mod_r = tanhf(mod_r * config.volumes.drive) * softclip_norm;
mpx = stereo_encode(&runtime->stencode, config.stereo, mod_l, mod_r, &audio); mpx = stereo_encode(&runtime->stencode, config.stereo, mod_l, mod_r, &audio);
@@ -250,7 +238,7 @@ int run_fm95(const FM95_Config config, FM95_Runtime* runtime) {
mpx = bs412_compress(&runtime->bs412, audio, mpx+mpx_in[i]); mpx = bs412_compress(&runtime->bs412, audio, mpx+mpx_in[i]);
output[i] = soft_clip(mpx)*config.master_volume; // Ensure peak deviation of 75 khz (or the set deviation), assuming we're calibrated correctly output[i] = tanhf(mpx)*config.master_volume; // Ensure peak deviation of 75 khz (or the set deviation), assuming we're calibrated correctly
advance_oscillator(&runtime->osc); advance_oscillator(&runtime->osc);
} }
@@ -296,9 +284,8 @@ static int config_handler(void* user, const char* section, const char* name, con
#define MATCH(s, n) strcmp(section, s) == 0 && strcmp(name, n) == 0 #define MATCH(s, n) strcmp(section, s) == 0 && strcmp(name, n) == 0
if (MATCH("fm95", "stereo")) { if (MATCH("fm95", "stereo")) pconfig->stereo = atoi(value);
pconfig->stereo = atoi(value); else if (MATCH("devices", "input")) {
} else if (MATCH("devices", "input")) {
strncpy(dv->input, value, 63); strncpy(dv->input, value, 63);
dv->input[63] = '\0'; dv->input[63] = '\0';
} else if (MATCH("devices", "output")) { } else if (MATCH("devices", "output")) {
@@ -316,47 +303,26 @@ static int config_handler(void* user, const char* section, const char* name, con
printf("RDS Streams more than 4? Nuh uh\n"); printf("RDS Streams more than 4? Nuh uh\n");
return 0; return 0;
} }
} else if (MATCH("fm95", "preemphasis")) { } else if (MATCH("fm95", "preemphasis")) pconfig->preemphasis = atoi(value);
pconfig->preemphasis = atoi(value); else if (MATCH("fm95", "calibration")) pconfig->calibration = atoi(value);
} else if (MATCH("fm95", "calibration")) { else if (MATCH("fm95", "mpx_power")) pconfig->mpx_power = strtof(value, NULL);
pconfig->calibration = atoi(value); else if (MATCH("fm95", "mpx_deviation")) pconfig->mpx_deviation = strtof(value, NULL);
} else if (MATCH("fm95", "mpx_power")) { else if (MATCH("fm95", "master_volume")) pconfig->master_volume = strtof(value, NULL);
pconfig->mpx_power = strtof(value, NULL); else if (MATCH("fm95", "audio_preamp")) pconfig->audio_preamp = strtof(value, NULL);
} else if (MATCH("fm95", "mpx_deviation")) { else if (MATCH("fm95", "deviation")) pconfig->audio_deviation = strtof(value, NULL);
pconfig->mpx_deviation = strtof(value, NULL); else if(MATCH("fm95", "agc_target")) pconfig->agc_target = strtof(value, NULL);
} else if (MATCH("fm95", "master_volume")) { else if(MATCH("fm95", "agc_attack")) pconfig->agc_attack = strtof(value, NULL);
pconfig->master_volume = strtof(value, NULL); else if(MATCH("fm95", "agc_release")) pconfig->agc_release = strtof(value, NULL);
} else if (MATCH("fm95", "audio_volume")) { else if(MATCH("fm95", "agc_min")) pconfig->agc_min = strtof(value, NULL);
pconfig->audio_volume = strtof(value, NULL); else if(MATCH("fm95", "agc_max")) pconfig->agc_max = strtof(value, NULL);
} else if (MATCH("fm95", "audio_preamp")) { else if(MATCH("fm95", "bs412_attack")) pconfig->bs412_attack = strtof(value, NULL);
pconfig->audio_preamp = strtof(value, NULL); else if(MATCH("fm95", "bs412_release")) pconfig->bs412_release = strtof(value, NULL);
} else if (MATCH("fm95", "deviation")) { else if(MATCH("fm95", "bs412_max")) pconfig->bs412_max = strtof(value, NULL);
pconfig->audio_deviation = strtof(value, NULL); else if(MATCH("advanced", "lpf_order")) pconfig->lpf_order = atoi(value);
} else if(MATCH("fm95", "agc_target")) { else if(MATCH("advanced", "stereo_ssb")) pconfig->stereo_ssb = atoi(value);
pconfig->agc_target = strtof(value, NULL); else if(MATCH("advanced", "preemp_unity")) pconfig->preemp_unity_freq = strtof(value, NULL);
} else if(MATCH("fm95", "agc_attack")) { else if(MATCH("advanced", "sample_rate")) pconfig->sample_rate = atoi(value);
pconfig->agc_attack = strtof(value, NULL); else if(MATCH("advanced", "lpf_cutoff")) {
} else if(MATCH("fm95", "agc_release")) {
pconfig->agc_release = strtof(value, NULL);
} else if(MATCH("fm95", "agc_min")) {
pconfig->agc_min = strtof(value, NULL);
} else if(MATCH("fm95", "agc_max")) {
pconfig->agc_max = strtof(value, NULL);
} else if(MATCH("fm95", "bs412_attack")) {
pconfig->bs412_attack = strtof(value, NULL);
} else if(MATCH("fm95", "bs412_release")) {
pconfig->bs412_release = strtof(value, NULL);
} else if(MATCH("fm95", "bs412_max")) {
pconfig->bs412_max = strtof(value, NULL);
} else if(MATCH("advanced", "lpf_order")) {
pconfig->lpf_order = atoi(value);
} else if(MATCH("advanced", "stereo_ssb")) {
pconfig->stereo_ssb = atoi(value);
} else if(MATCH("advanced", "preemp_unity")) {
pconfig->preemp_unity_freq = strtof(value, NULL);
} else if(MATCH("advanced", "sample_rate")) {
pconfig->sample_rate = atoi(value);
} else if(MATCH("advanced", "lpf_cutoff")) {
pconfig->lpf_cutoff = strtof(value, NULL); pconfig->lpf_cutoff = strtof(value, NULL);
if(pconfig->lpf_cutoff > (pconfig->sample_rate * 0.5)) { if(pconfig->lpf_cutoff > (pconfig->sample_rate * 0.5)) {
pconfig->lpf_cutoff = (pconfig->sample_rate * 0.5); pconfig->lpf_cutoff = (pconfig->sample_rate * 0.5);
@@ -491,13 +457,13 @@ void init_runtime(FM95_Runtime* runtime, const FM95_Config config) {
} }
int main(int argc, char **argv) { int main(int argc, char **argv) {
printf("fm95 (an FM Processor by radio95) version 2.4\n"); printf("fm95 (an FM Processor by radio95) version 2.5\n");
FM95_Config config = { FM95_Config config = {
.volumes = { .volumes = {
.pilot = DEFAULT_PILOT_VOLUME, .pilot = 0.09f,
.rds = DEFAULT_RDS_VOLUME, .rds = 0.045f,
.rds_step = DEFAULT_RDS_VOLUME_STEP, .rds_step = 0.9f,
.headroom = 0.05f, .headroom = 0.05f,
.drive = 1.0f, .drive = 1.0f,
.makeup = 1.0f .makeup = 1.0f
@@ -513,7 +479,6 @@ int main(int argc, char **argv) {
.mpx_deviation = 75000.0f, // for BS412, this is what deviation does the compressor see as peak, so if i set here 150 khz, then the compressor will act as if it was two times louder .mpx_deviation = 75000.0f, // for BS412, this is what deviation does the compressor see as peak, so if i set here 150 khz, then the compressor will act as if it was two times louder
.audio_deviation = 75000.0f, // another way to set the volume .audio_deviation = 75000.0f, // another way to set the volume
.master_volume = 1.0f, // Volume of everything combined, for calibration .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 .audio_preamp = 1.0f, // Volume of the audio before the filters
.sample_rate = 192000, // Sample rate for this whole gizmo to run on .sample_rate = 192000, // Sample rate for this whole gizmo to run on
@@ -560,6 +525,8 @@ int main(int argc, char **argv) {
return 1; return 1;
} }
if (config.volumes.drive < 0.01f) config.volumes.drive = 0.01f;
config.master_volume *= config.audio_deviation/75000.0f; config.master_volume *= config.audio_deviation/75000.0f;
config.volumes.audio = calculate_sharedaudio_volume(config.volumes, config.rds_streams); config.volumes.audio = calculate_sharedaudio_volume(config.volumes, config.rds_streams);