diff --git a/README.md b/README.md index 0b31552..fc60d09 100644 --- a/README.md +++ b/README.md @@ -5,8 +5,7 @@ FM95 is a audio processor for FM, it does: - Pre-Emphasis - Low Pass Filtering - AGC -- Stereo (Polar too) -- SCA +- Stereo - BS412 (mpx power limiter, simplest implementation ever) Supports these inputs: @@ -36,11 +35,11 @@ Done! ## 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 -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 diff --git a/fm95.md b/fm95.md index cd09fa9..9179e81 100644 --- a/fm95.md +++ b/fm95.md @@ -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 -### 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 Pre-amplification of the audio, before any filters, set to 1 by default, set to 0 to mute audio diff --git a/modulation/stereo_encoder.c b/modulation/stereo_encoder.c index b1bd300..38898b9 100644 --- a/modulation/stereo_encoder.c +++ b/modulation/stereo_encoder.c @@ -5,7 +5,7 @@ void init_stereo_encoder(StereoEncoder* st, uint8_t stereo_ssb, uint8_t multipli st->multiplier = multiplier; st->osc = osc; st->pilot_volume = pilot_volume; - st->audio_volume = audio_volume; + st->audio_volume = audio_volume * 0.5f; if(stereo_ssb) { init_delay_line(&st->delay_pilot, 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 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); @@ -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); - *audio = (mid*st->audio_volume); + *audio = mid * st->audio_volume; if(st->stereo_hilbert) { float stereo = (crealf(stereo_hilbert) * signalx2) - (cimagf(stereo_hilbert) * signalx2cos); - *audio += (stereo * st->audio_volume); - } else *audio += ((side*signalx2) * st->audio_volume); - return (signalx1*st->pilot_volume); + *audio += stereo * st->audio_volume; + } else *audio += (side*signalx2) * st->audio_volume; + return signalx1 * st->pilot_volume; } void exit_stereo_encoder(StereoEncoder* st) { diff --git a/src/fm95.c b/src/fm95.c index 131de10..61be2cb 100644 --- a/src/fm95.c +++ b/src/fm95.c @@ -18,19 +18,9 @@ #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_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 { bool rds_on; bool mpx_on; @@ -59,7 +49,6 @@ typedef struct { float mpx_deviation; float audio_deviation; float master_volume; - float audio_volume; float audio_preamp; uint32_t sample_rate; @@ -104,10 +93,7 @@ typedef struct { } FM95_SetupContext; bool compare_dvs(const FM95_DeviceNames *a, const FM95_DeviceNames *b) { - 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; + 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; } 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); 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)); to_run = 0; break; @@ -185,6 +171,8 @@ int run_fm95(const FM95_Config config, FM95_Runtime* runtime) { bool mpx_on = config.options.mpx_on; bool rds_on = config.options.rds_on; + float softclip_norm = config.volumes.makeup / tanhf(config.volumes.drive); + 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 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_l = soft_clip_drive(mod_l, config.volumes.drive) * config.volumes.makeup; - mod_r = soft_clip_drive(mod_r, config.volumes.drive) * config.volumes.makeup; + mod_l = tanhf(mod_l * config.volumes.drive) * softclip_norm; + mod_r = tanhf(mod_r * config.volumes.drive) * softclip_norm; 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]); - 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); } @@ -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 - if (MATCH("fm95", "stereo")) { - pconfig->stereo = atoi(value); - } else if (MATCH("devices", "input")) { + if (MATCH("fm95", "stereo")) pconfig->stereo = atoi(value); + else if (MATCH("devices", "input")) { strncpy(dv->input, value, 63); dv->input[63] = '\0'; } 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"); return 0; } - } else if (MATCH("fm95", "preemphasis")) { - pconfig->preemphasis = atoi(value); - } else if (MATCH("fm95", "calibration")) { - pconfig->calibration = atoi(value); - } else if (MATCH("fm95", "mpx_power")) { - pconfig->mpx_power = strtof(value, NULL); - } else if (MATCH("fm95", "mpx_deviation")) { - pconfig->mpx_deviation = strtof(value, NULL); - } else if (MATCH("fm95", "master_volume")) { - pconfig->master_volume = strtof(value, NULL); - } else if (MATCH("fm95", "audio_volume")) { - pconfig->audio_volume = strtof(value, NULL); - } else if (MATCH("fm95", "audio_preamp")) { - pconfig->audio_preamp = strtof(value, NULL); - } else if (MATCH("fm95", "deviation")) { - pconfig->audio_deviation = strtof(value, NULL); - } else if(MATCH("fm95", "agc_target")) { - pconfig->agc_target = strtof(value, NULL); - } else if(MATCH("fm95", "agc_attack")) { - pconfig->agc_attack = strtof(value, NULL); - } 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")) { + } else if (MATCH("fm95", "preemphasis")) pconfig->preemphasis = atoi(value); + else if (MATCH("fm95", "calibration")) pconfig->calibration = atoi(value); + else if (MATCH("fm95", "mpx_power")) pconfig->mpx_power = strtof(value, NULL); + else if (MATCH("fm95", "mpx_deviation")) pconfig->mpx_deviation = strtof(value, NULL); + else if (MATCH("fm95", "master_volume")) pconfig->master_volume = strtof(value, NULL); + else if (MATCH("fm95", "audio_preamp")) pconfig->audio_preamp = strtof(value, NULL); + else if (MATCH("fm95", "deviation")) pconfig->audio_deviation = strtof(value, NULL); + else if(MATCH("fm95", "agc_target")) pconfig->agc_target = strtof(value, NULL); + else if(MATCH("fm95", "agc_attack")) pconfig->agc_attack = strtof(value, NULL); + 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); if(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) { - 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 = { .volumes = { - .pilot = DEFAULT_PILOT_VOLUME, - .rds = DEFAULT_RDS_VOLUME, - .rds_step = DEFAULT_RDS_VOLUME_STEP, + .pilot = 0.09f, + .rds = 0.045f, + .rds_step = 0.9f, .headroom = 0.05f, .drive = 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 .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 = 192000, // Sample rate for this whole gizmo to run on @@ -560,6 +525,8 @@ int main(int argc, char **argv) { return 1; } + if (config.volumes.drive < 0.01f) config.volumes.drive = 0.01f; + config.master_volume *= config.audio_deviation/75000.0f; config.volumes.audio = calculate_sharedaudio_volume(config.volumes, config.rds_streams);