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-03-01 12:06:59 +01:00
parent 7d1ee4ae50
commit 8c9f6464b8
5 changed files with 33 additions and 183 deletions

View File

@@ -3,14 +3,13 @@ FM95 is a audio processor for FM, it does:
- Pre-Emphasis
- Low Pass Filter
- Stereo
- SSB Stereo
- Polar Stereo
- Polar SSB Stereo (huh)
- SCA
Supports 2 inputs:
- Audio (via Pulse)
- MPX (via Pulse)
- SCA (via Pulse)
and one output:
- MPX (via Pulse)

View File

@@ -77,45 +77,4 @@ float hard_clip(float sample, float threshold) {
} else {
return sample; // No clipping
}
}
void init_delay_line(DelayLine *delay_line, int max_delay) {
delay_line->buffer = (float *)calloc(max_delay, sizeof(float));
delay_line->size = max_delay;
delay_line->write_idx = 0;
delay_line->read_idx = 0;
delay_line->delay = 0;
}
void set_delay_line(DelayLine *delay_line, int new_delay) {
if (new_delay >= delay_line->size) {
new_delay = delay_line->size - 1;
}
if (new_delay < 0) {
new_delay = 0;
}
delay_line->delay = new_delay;
delay_line->read_idx = (delay_line->write_idx - new_delay + delay_line->size) % delay_line->size;
}
float delay_line(DelayLine *delay_line, float in) {
float out;
// Read the delayed sample
out = delay_line->buffer[delay_line->read_idx];
// Write the new sample
delay_line->buffer[delay_line->write_idx] = in;
// Update indices
delay_line->write_idx = (delay_line->write_idx + 1) % delay_line->size;
delay_line->read_idx = (delay_line->read_idx + 1) % delay_line->size;
return out;
}
void exit_delay_line(DelayLine *delay_line) {
free(delay_line->buffer);
delay_line->buffer = NULL;
}

View File

@@ -1,43 +0,0 @@
#include "hilbert.h"
void compute_hilbert_coeffs(float* coeffs, int taps) {
int mid = taps / 2;
for (int i = 0; i < taps; i++) {
if ((i - mid) % 2 == 0) {
coeffs[i] = 0.0f;
} else {
coeffs[i] = 2.0f / (M_PI * (i - mid));
}
}
}
// Initialize the Hilbert transformer
void init_hilbert(HilbertTransformer* filter) {
compute_hilbert_coeffs(filter->coeffs, HILBERT_TAPS);
memset(filter->delay, 0, sizeof(filter->delay));
filter->index = 0;
}
// Apply the Hilbert transformer
void apply_hilbert(HilbertTransformer* filter, float input, float* inphase, float* quadrature) {
// Insert the new sample into the circular buffer
filter->delay[filter->index] = input;
// Compute the in-phase and quadrature components
float i_sum = 0.0f; // In-phase (0-degree output)
float q_sum = 0.0f; // Quadrature (90-degree output)
int coeff_index = 0;
for (int i = filter->index; coeff_index < HILBERT_TAPS; coeff_index++) {
i_sum += filter->delay[i] * (coeff_index == HILBERT_TAPS / 2 ? 1.0f : 0.0f);
q_sum += filter->delay[i] * filter->coeffs[coeff_index];
i = (i > 0) ? i - 1 : HILBERT_TAPS - 1;
}
// Update the index for the next sample
filter->index = (filter->index + 1) % HILBERT_TAPS;
*inphase = i_sum;
*quadrature = q_sum;
}

View File

@@ -1,17 +0,0 @@
#pragma once
#include <stdlib.h>
#include <string.h>
#include "constants.h"
#include <math.h>
#define HILBERT_TAPS 95
typedef struct {
float coeffs[HILBERT_TAPS];
float delay[HILBERT_TAPS];
int index;
} HilbertTransformer;
void init_hilbert(HilbertTransformer* filter);
void apply_hilbert(HilbertTransformer* filter, float input, float* inphase, float* quadrature);

View File

@@ -8,8 +8,7 @@
#define DEFAULT_STEREO 1
#define DEFAULT_STEREO_POLAR 0
#define DEFAULT_STEREO_SSB 0
#define DEFAULT_CLIPPER_THRESHOLD 1.0f
#define DEFAULT_CLIPPER_THRESHOLD 2.0f
#define DEFAULT_SCA_FREQUENCY 67000.0f
#define DEFAULT_SCA_DEVIATION 7000.0f
#define DEFAULT_SCA_CLIPPER_THRESHOLD 1.0f // Full deviation, if you set this to 0.5 then you may as well set the deviation to 3.5k
@@ -20,27 +19,27 @@
#include "../lib/constants.h"
#include "../lib/oscillator.h"
#include "../lib/filters.h"
#include "../lib/hilbert.h"
#include "../lib/fm_modulator.h"
#define SAMPLE_RATE 192000
#define INPUT_DEVICE "FM_Audio.monitor"
#define OUTPUT_DEVICE "alsa_output.pci-0000_00_14.2.analog-stereo"
#define OUTPUT_DEVICE "alsa_output.platform-soc_sound.stereo-fallback"
#define MPX_DEVICE "FM_MPX.monitor"
// #define SCA_DEVICE ""
#define BUFFER_SIZE 768
#define BUFFER_SIZE 1024
#include <pulse/simple.h>
#include <pulse/error.h>
#define MASTER_VOLUME 1.0f // Volume of everything combined
#define MASTER_VOLUME 1.0f // Volume of everything combined, for calibration
#define AUDIO_VOLUME 2.0f // Audio volume, before clipper
#define MONO_VOLUME 0.45f // L+R Signal
#define PILOT_VOLUME 0.09f // 19 KHz Pilot
#define STEREO_VOLUME 0.45f // L-R signal
#define SCA_VOLUME 0.1f
#define MPX_VOLUME 1.0f
#define STEREO_VOLUME 0.45f // L-R signal, should be same as MONO
#define SCA_VOLUME 0.1f // FM SCA signal, 10%
#define MPX_VOLUME 1.0f // Passtrough
#define LPF_CUTOFF 15000 // Should't need to be changed
@@ -61,7 +60,7 @@ static void stop(int signum) {
}
void show_version() {
printf("fm95 (an FM Processor by radio95) version 1.1\n");
printf("fm95 (an FM Processor by radio95) version 1.2\n");
}
void show_help(char *name) {
printf(
@@ -78,11 +77,10 @@ void show_help(char *name) {
" -c,--clipper Override the clipper threshold [default: %.2f]\n"
" -P,--polar Force Polar Stereo (does not take effect with -m%s)\n"
" -g,--ge Force Zenith/GE stereo (does not take effect with -m%s)\n"
" -S,--ssb Force SSB [default: %d]\n"
" -D,--dsb Force DSB [default: %d]\n"
" -R,--preemp Override preemphasis [default: %.2f µs]\n"
" -V,--calibrate Enable Calibration mode [default: off]\n"
" -A,--master_vol Set master volume [default: %.3f]\n"
" -v,--audio_vol Set audio volume [default: %.3f]\n"
,name
,DEFAULT_STEREO^1
,DEFAULT_STEREO
@@ -104,10 +102,9 @@ void show_help(char *name) {
,DEFAULT_CLIPPER_THRESHOLD
,(DEFAULT_STEREO_POLAR == 1) ? ", default" : ""
,(DEFAULT_STEREO_POLAR == 1) ? "" : ", default"
,DEFAULT_STEREO_SSB
,DEFAULT_STEREO_SSB^1
,DEFAULT_PREEMPHASIS_TAU/0.000001
,MASTER_VOLUME
,AUDIO_VOLUME
);
}
@@ -121,7 +118,6 @@ int main(int argc, char **argv) {
float clipper_threshold = DEFAULT_CLIPPER_THRESHOLD;
int stereo = DEFAULT_STEREO;
int polar_stereo = DEFAULT_STEREO_POLAR;
int ssb = DEFAULT_STEREO_SSB;
float sca_frequency = DEFAULT_SCA_FREQUENCY;
float sca_deviation = DEFAULT_SCA_DEVIATION;
@@ -143,10 +139,11 @@ int main(int argc, char **argv) {
int calibration_mode = 0;
float master_volume = MASTER_VOLUME;
float audio_volume = AUDIO_VOLUME;
// #region Parse Arguments
int opt;
const char *short_opt = "msi:o:apM:C:f:F:L:c:l:PgSDR:VA:hv";
const char *short_opt = "msi:o:apM:C:f:F:L:c:l:PgSDR:VA:v:h";
struct option long_opt[] =
{
{"mono", no_argument, NULL, 'm'},
@@ -161,14 +158,12 @@ int main(int argc, char **argv) {
{"clipper", required_argument, NULL, 'c'},
{"polar", no_argument, NULL, 'P'},
{"ge", no_argument, NULL, 'g'},
{"ssb", no_argument, NULL, 'S'},
{"dsb", no_argument, NULL, 'D'},
{"preemp", required_argument, NULL, 'R'},
{"calibrate", no_argument, NULL, 'V'},
{"master_vol", no_argument, NULL, 'A'},
{"master_vol", required_argument, NULL, 'A'},
{"audio_vol", required_argument, NULL, 'v'},
{"help", no_argument, NULL, 'h'},
{"version", no_argument, NULL, 'v'},
{0, 0, 0, 0}
};
@@ -210,12 +205,6 @@ int main(int argc, char **argv) {
case 'g': //GE
polar_stereo = 0;
break;
case 'S': //SSB
ssb = 1;
break;
case 'D': //DSB
ssb = 0;
break;
case 'R': // Preemp
preemphasis_tau = strtof(optarg, NULL)*0.000001;
break;
@@ -225,9 +214,9 @@ int main(int argc, char **argv) {
case 'A': // Master vol
master_volume = strtof(optarg, NULL);
break;
case 'v': // Version
show_version();
return 0;
case 'v': // Audio Volume
audio_volume = strtof(optarg, NULL);
break;
case 'h':
show_help(argv[0]);
return 1;
@@ -372,27 +361,18 @@ int main(int argc, char **argv) {
// #region Setup Filters/Modulaltors/Oscillators
Oscillator osc;
if(polar_stereo) {
init_oscillator(&osc, 31250.0, SAMPLE_RATE); // The stereo carrier itself, the stereo carrier in polar is modulated directly on 31.25 khz with a bit of a carrier
} else {
init_oscillator(&osc, 19000.0, SAMPLE_RATE); // Pilot, it's there to indicate stereo and as a refrence signal with the stereo carrier
}
init_oscillator(&osc, polar_stereo ? 31250.0 : 19000, SAMPLE_RATE); // Pilot, it's there to indicate stereo and as a refrence signal with the stereo carrier
FMModulator sca_mod;
init_fm_modulator(&sca_mod, sca_frequency, sca_deviation, SAMPLE_RATE);
HilbertTransformer hilbert; // An Hilbert shifts a signal in quadrature, generating the I/Q data
init_hilbert(&hilbert);
DelayLine monoDelay; // Hilbert introduces a delay, this should be here to sync the mono with stereo to a sample
init_delay_line(&monoDelay, (HILBERT_TAPS-1)/2);
ResistorCapacitor preemp_l, preemp_r;
init_preemphasis(&preemp_l, preemphasis_tau, SAMPLE_RATE);
init_preemphasis(&preemp_r, preemphasis_tau, SAMPLE_RATE);
BiquadFilter lpf_l, lpf_r;
init_lpf(&lpf_l, LPF_CUTOFF, 1.0f, SAMPLE_RATE);
init_lpf(&lpf_r, LPF_CUTOFF, 1.0f, SAMPLE_RATE);
init_lpf(&lpf_l, LPF_CUTOFF, 1.25f, SAMPLE_RATE);
init_lpf(&lpf_r, LPF_CUTOFF, 1.25f, SAMPLE_RATE);
// #endregion
signal(SIGINT, stop);
@@ -435,53 +415,26 @@ int main(int argc, char **argv) {
float ready_l = apply_frequency_filter(&lpf_l, l_in);
float ready_r = apply_frequency_filter(&lpf_r, r_in);
ready_l = apply_preemphasis(&preemp_l, ready_l);
ready_r = apply_preemphasis(&preemp_r, ready_r);
ready_l = hard_clip(ready_l, clipper_threshold);
ready_r = hard_clip(ready_r, clipper_threshold);
ready_l = apply_preemphasis(&preemp_l, ready_l)*2;
ready_r = apply_preemphasis(&preemp_r, ready_r)*2;
ready_l = hard_clip(ready_l*audio_volume, clipper_threshold);
ready_r = hard_clip(ready_r*audio_volume, clipper_threshold);
float mono = (ready_l + ready_r) / 2.0f; // Stereo to Mono
output[i] = mono*MONO_VOLUME;
if(stereo == 1) {
float stereo = (ready_l - ready_r) / 2.0f; // Also Stereo to Mono but a bit diffrent
float stereo_i, stereo_q;
if(ssb) {
// Compute hilbert here
apply_hilbert(&hilbert, stereo, &stereo_i, &stereo_q);
mono = delay_line(&monoDelay, mono); // Delay Mono
}
float stereo = (ready_l - ready_r) / 2.0f; // Also Stereo to Mono but a bit diffrent
float stereo_carrier = get_oscillator_sin_multiplier_ni(&osc, polar_stereo ? 1 : 2);
if(polar_stereo) {
float stereo_carrier = get_oscillator_sin_multiplier_ni(&osc, 1);
if(ssb) {
float stereo_carrier_cos = get_oscillator_cos_multiplier_ni(&osc, 1); // Get Carrier Q of I/Q
output[i] = mono*MONO_VOLUME +
((stereo_i+0.2)*stereo_carrier_cos+(stereo_q+0.2)*stereo_carrier)*STEREO_VOLUME;
} else {
float stereo_carrier = get_oscillator_sin_multiplier_ni(&osc, 1);
output[i] = mono*MONO_VOLUME +
((stereo+0.2)*stereo_carrier)*STEREO_VOLUME;
}
output[i] += ((stereo+0.2)*stereo_carrier)*STEREO_VOLUME;
} else {
float stereo_carrier = get_oscillator_sin_multiplier_ni(&osc, 2); // Get stereo carrier via multiplication
float pilot = get_oscillator_sin_multiplier_ni(&osc, 1);
if(ssb) {
float stereo_carrier_cos = get_oscillator_cos_multiplier_ni(&osc, 2); // Get Carrier Q of I/Q
output[i] = mono*MONO_VOLUME +
pilot*PILOT_VOLUME +
(stereo_i*stereo_carrier_cos+stereo_q*stereo_carrier)*STEREO_VOLUME;
} else {
output[i] = mono*MONO_VOLUME +
pilot*PILOT_VOLUME +
(stereo*stereo_carrier)*STEREO_VOLUME;
}
output[i] += pilot*PILOT_VOLUME +
(stereo*stereo_carrier)*STEREO_VOLUME;
}
advance_oscillator(&osc);
} else {
output[i] = mono*MONO_VOLUME; // Only Mono
}
if(strlen(audio_mpx_device) != 0) output[i] += current_mpx_in*MPX_VOLUME;
if(strlen(audio_mpx_device) != 0) output[i] += hard_clip(current_mpx_in, 1.0f)*MPX_VOLUME;
if(strlen(audio_sca_device) != 0) output[i] += modulate_fm(&sca_mod, hard_clip(current_sca_in, sca_clipper_threshold))*SCA_VOLUME;
output[i] *= master_volume;
}
@@ -497,6 +450,5 @@ int main(int argc, char **argv) {
if(strlen(audio_mpx_device) != 0) pa_simple_free(mpx_device);
if(strlen(audio_sca_device) != 0) pa_simple_free(sca_device);
pa_simple_free(output_device);
exit_delay_line(&monoDelay);
return 0;
}