mirror of
https://github.com/radio95-rnt/fm95.git
synced 2026-02-26 19:23:51 +01:00
update
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
112
src/fm95.c
112
src/fm95.c
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user