0
1
mirror of https://github.com/radio95-rnt/fm95.git synced 2026-02-26 19:23:51 +01:00

generate the 66.5 inside the pll via the pilot

This commit is contained in:
2025-03-23 16:28:22 +01:00
parent a654700eba
commit 228cb355ac
10 changed files with 1056 additions and 1053 deletions

View File

@@ -1,5 +1,5 @@
{ {
"port": 13452, "port": 13452,
"time": 1742717063787, "time": 1742742025054,
"version": "0.0.3" "version": "0.0.3"
} }

View File

@@ -1,55 +1,59 @@
#include "filters.h" #include "filters.h"
void init_preemphasis(ResistorCapacitor *filter, float tau, float sample_rate) { void init_preemphasis(ResistorCapacitor *filter, float tau, float sample_rate) {
filter->prev_sample = 0.0f; filter->prev_sample = 0.0f;
filter->alpha = exp(-1 / (tau*sample_rate)); filter->alpha = exp(-1 / (tau*sample_rate));
} }
float apply_preemphasis(ResistorCapacitor *filter, float sample) { float apply_preemphasis(ResistorCapacitor *filter, float sample) {
float out = sample-filter->alpha*filter->prev_sample; float out = sample-filter->alpha*filter->prev_sample;
filter->prev_sample = sample; filter->prev_sample = sample;
return out; return out;
} }
float hard_clip(float sample, float threshold) { float hard_clip(float sample, float threshold) {
return fmaxf(-threshold, fminf(threshold, sample)); return fmaxf(-threshold, fminf(threshold, sample));
} }
void init_pll(PLL *pll, float output_freq, float reference_freq, float loop_filter_bandwidth, int quadrature_mode, int sample_rate) { void init_pll(PLL *pll, int interpolation, int decimation, float freq, float loop_filter_bandwidth, int quadrature_mode, int sample_rate) {
pll->phase = 0.0f; pll->phase = 0.0f;
pll->freq = output_freq; pll->freq = freq;
pll->ref_freq = reference_freq; pll->loop_filter_state = 0.0f;
pll->loop_filter_state = 0.0f; pll->kp = M_2PI * loop_filter_bandwidth;
pll->kp = 2.0f * M_PI * loop_filter_bandwidth; pll->ki = 0.25f * pll->kp * pll->kp;
pll->ki = 0.25f * pll->kp * pll->kp; pll->sample_rate = sample_rate;
pll->sample_rate = sample_rate; pll->quadrature_mode = quadrature_mode;
pll->quadrature_mode = quadrature_mode; pll->last_output = 0.0f;
pll->interpolation = interpolation;
pll->decimation = decimation;
} }
float apply_pll(PLL *pll, float ref_sample, float input_sample) { float apply_pll(PLL *pll, float ref_sample) {
float phase_error; float phase_error;
float output = sinf(pll->phase); float vco_phase = pll->phase;
if(pll->quadrature_mode) vco_phase += M_PI/2.0f;
if (pll->quadrature_mode) {
output = sinf(pll->phase + (M_PI / 2.0f)); float vco_output = sinf(pll->phase);
} if (pll->quadrature_mode) vco_output = sinf(pll->phase + (M_PI / 2.0f)); // 90 degrees
phase_error = ref_sample * input_sample; phase_error = ref_sample * pll->last_output;
pll->loop_filter_state += pll->ki * phase_error / pll->sample_rate; pll->loop_filter_state += pll->ki * phase_error / pll->sample_rate;
float loop_output = pll->loop_filter_state + pll->kp * phase_error; float loop_output = pll->loop_filter_state + pll->kp * phase_error;
float freq_adjustment = loop_output / (2.0f * M_PI); float freq_adjustment = loop_output / M_2PI;
float instantaneous_freq = pll->freq + freq_adjustment; float instantaneous_freq = pll->freq + freq_adjustment;
pll->phase += 2.0f * M_PI * instantaneous_freq / pll->sample_rate; pll->phase += M_2PI * instantaneous_freq / pll->sample_rate;
while (pll->phase >= 2.0f * M_PI) { while (pll->phase >= M_2PI) {
pll->phase -= 2.0f * M_PI; pll->phase -= M_2PI;
} }
while (pll->phase < 0.0f) { while (pll->phase < 0.0f) {
pll->phase += 2.0f * M_PI; pll->phase += M_2PI;
} }
return output; pll->last_output = sinf((vco_phase*pll->interpolation)/pll->decimation);
return pll->last_output;
} }

View File

@@ -7,8 +7,8 @@
typedef struct typedef struct
{ {
float alpha; float alpha;
float prev_sample; float prev_sample;
} ResistorCapacitor; } ResistorCapacitor;
void init_preemphasis(ResistorCapacitor *filter, float tau, float sample_rate); void init_preemphasis(ResistorCapacitor *filter, float tau, float sample_rate);
@@ -17,14 +17,16 @@ float apply_preemphasis(ResistorCapacitor *filter, float sample);
float hard_clip(float sample, float threshold); float hard_clip(float sample, float threshold);
typedef struct { typedef struct {
float phase; float phase;
float freq; float freq;
float ref_freq; float loop_filter_state;
float loop_filter_state; float kp;
float kp; float ki;
float ki; float last_output;
int sample_rate; int interpolation;
int quadrature_mode; int decimation;
int sample_rate;
int quadrature_mode;
} PLL; } PLL;
void init_pll(PLL *pll, float output_freq, float refrence_freq, float loop_filter_bandwidth, int quadrature_mode, int sample_rate); void init_pll(PLL *pll, int interpolation, int decimation, float freq, float loop_filter_bandwidth, int quadrature_mode, int sample_rate);
float apply_pll(PLL *pll, float ref_sample, float input_sample); float apply_pll(PLL *pll, float ref_sample);

View File

@@ -1,16 +1,16 @@
#include "fm_modulator.h" #include "fm_modulator.h"
void init_fm_modulator(FMModulator *fm, float frequency, float deviation, float sample_rate) { void init_fm_modulator(FMModulator *fm, float frequency, float deviation, float sample_rate) {
fm->frequency = frequency; fm->frequency = frequency;
fm->deviation = deviation; fm->deviation = deviation;
fm->sample_rate = sample_rate; fm->sample_rate = sample_rate;
fm->osc_phase = 0.0f; fm->osc_phase = 0.0f;
} }
float modulate_fm(FMModulator *fm, float sample) { float modulate_fm(FMModulator *fm, float sample) {
float inst_freq = fm->frequency+(sample*fm->deviation); float inst_freq = fm->frequency+(sample*fm->deviation);
if (inst_freq < 0.0f) inst_freq = 0.0f; if (inst_freq < 0.0f) inst_freq = 0.0f;
float out = sinf(fm->osc_phase); float out = sinf(fm->osc_phase);
fm->osc_phase = fmodf(fm->osc_phase + ((M_2PI * inst_freq) / fm->sample_rate), M_2PI); fm->osc_phase = fmodf(fm->osc_phase + ((M_2PI * inst_freq) / fm->sample_rate), M_2PI);
return out; return out;
} }

View File

@@ -4,10 +4,10 @@
typedef struct typedef struct
{ {
float frequency; float frequency;
float deviation; float deviation;
float osc_phase; float osc_phase;
float sample_rate; float sample_rate;
} FMModulator; } FMModulator;
void init_fm_modulator(FMModulator *fm, float frequency, float deviation, float sample_rate); void init_fm_modulator(FMModulator *fm, float frequency, float deviation, float sample_rate);

View File

@@ -1,37 +1,37 @@
#include "oscillator.h" #include "oscillator.h"
void init_oscillator(Oscillator *osc, float frequency, float sample_rate) { void init_oscillator(Oscillator *osc, float frequency, float sample_rate) {
osc->phase = 0.0f; osc->phase = 0.0f;
osc->phase_increment = (M_2PI * frequency) / sample_rate; osc->phase_increment = (M_2PI * frequency) / sample_rate;
osc->sample_rate = sample_rate; osc->sample_rate = sample_rate;
} }
void change_oscillator_frequency(Oscillator *osc, float frequency) { void change_oscillator_frequency(Oscillator *osc, float frequency) {
osc->phase_increment = (M_2PI * frequency) / osc->sample_rate; osc->phase_increment = (M_2PI * frequency) / osc->sample_rate;
} }
float get_oscillator_sin_sample(Oscillator *osc) { float get_oscillator_sin_sample(Oscillator *osc) {
float sample = sinf(osc->phase); float sample = sinf(osc->phase);
advance_oscillator(osc); advance_oscillator(osc);
return sample; return sample;
} }
float get_oscillator_cos_sample(Oscillator *osc) { float get_oscillator_cos_sample(Oscillator *osc) {
float sample = cosf(osc->phase); float sample = cosf(osc->phase);
advance_oscillator(osc); advance_oscillator(osc);
return sample; return sample;
} }
float get_oscillator_sin_multiplier_ni(Oscillator *osc, float multiplier) { // ni = No Increment float get_oscillator_sin_multiplier_ni(Oscillator *osc, float multiplier) { // ni = No Increment
return sinf(fmodf(osc->phase * multiplier, M_2PI)); return sinf(fmodf(osc->phase * multiplier, M_2PI));
} }
float get_oscillator_cos_multiplier_ni(Oscillator *osc, float multiplier) { float get_oscillator_cos_multiplier_ni(Oscillator *osc, float multiplier) {
return cosf(fmodf(osc->phase * multiplier, M_2PI)); return cosf(fmodf(osc->phase * multiplier, M_2PI));
} }
void advance_oscillator(Oscillator *osc) { void advance_oscillator(Oscillator *osc) {
osc->phase += osc->phase_increment; osc->phase += osc->phase_increment;
if (osc->phase >= M_2PI) { if (osc->phase >= M_2PI) {
osc->phase -= M_2PI; osc->phase -= M_2PI;
} }
} }

View File

@@ -4,9 +4,9 @@
#include "math.h" #include "math.h"
typedef struct { typedef struct {
float phase; float phase;
float phase_increment; float phase_increment;
float sample_rate; float sample_rate;
} Oscillator; } Oscillator;
void init_oscillator(Oscillator *osc, float frequency, float sample_rate); void init_oscillator(Oscillator *osc, float frequency, float sample_rate);

View File

@@ -41,266 +41,266 @@ volatile int sequence_type = SEQ_NONE;
volatile time_t last_sequence_time = 0; volatile time_t last_sequence_time = 0;
static void stop(int signum) { static void stop(int signum) {
(void)signum; (void)signum;
printf("\nReceived stop signal.\n"); printf("\nReceived stop signal.\n");
to_run = 0; to_run = 0;
} }
void show_version() { void show_version() {
printf("chimer95 (GTS time signal encoder by radio95) version 1.1\n"); printf("chimer95 (GTS time signal encoder by radio95) version 1.1\n");
} }
void show_help(char *name) { void show_help(char *name) {
printf( printf(
"Usage: %s\n" "Usage: %s\n"
" -o,--output Override output device [default: %s]\n" " -o,--output Override output device [default: %s]\n"
" -F,--frequency GTS Frequency [default: %.1f Hz]\n" " -F,--frequency GTS Frequency [default: %.1f Hz]\n"
" -s,--samplerate Output Samplerate [default: %d]\n" " -s,--samplerate Output Samplerate [default: %d]\n"
" -v,--volume Output volume [default: %.2f]\n" " -v,--volume Output volume [default: %.2f]\n"
" -t,--offset GTS Offset [default: %d s]\n" " -t,--offset GTS Offset [default: %d s]\n"
" -T,--test Enable test mode (plays full hour signal at end of every minute)\n" " -T,--test Enable test mode (plays full hour signal at end of every minute)\n"
,name ,name
,OUTPUT_DEVICE ,OUTPUT_DEVICE
,DEFAULT_FREQ ,DEFAULT_FREQ
,DEFAULT_SAMPLE_RATE ,DEFAULT_SAMPLE_RATE
,DEFAULT_MASTER_VOLUME ,DEFAULT_MASTER_VOLUME
,DEFAULT_OFFSET ,DEFAULT_OFFSET
); );
} }
void generate_signal(float *output, int buffer_size, Oscillator *osc, float volume, void generate_signal(float *output, int buffer_size, Oscillator *osc, float volume,
int *elapsed_samples, int total_samples, int pip_samples, int *elapsed_samples, int total_samples, int pip_samples,
int pause_samples, int beep_samples, int num_pips) { int pause_samples, int beep_samples, int num_pips) {
for (int i = 0; i < buffer_size; i++) { for (int i = 0; i < buffer_size; i++) {
if (*elapsed_samples >= total_samples) { if (*elapsed_samples >= total_samples) {
output[i] = 0; output[i] = 0;
playing_sequence = 0; playing_sequence = 0;
} else { } else {
int cycle_position = *elapsed_samples; int cycle_position = *elapsed_samples;
int pip_cycle = pip_samples + pause_samples; int pip_cycle = pip_samples + pause_samples;
if (cycle_position < num_pips * pip_cycle) { if (cycle_position < num_pips * pip_cycle) {
int within_cycle = cycle_position % pip_cycle; int within_cycle = cycle_position % pip_cycle;
if (within_cycle < pip_samples) { if (within_cycle < pip_samples) {
output[i] = get_oscillator_sin_sample(osc) * volume; output[i] = get_oscillator_sin_sample(osc) * volume;
} else { } else {
output[i] = 0; output[i] = 0;
} }
} else if (cycle_position < num_pips * pip_cycle + beep_samples) { } else if (cycle_position < num_pips * pip_cycle + beep_samples) {
output[i] = get_oscillator_sin_sample(osc) * volume; output[i] = get_oscillator_sin_sample(osc) * volume;
} else { } else {
output[i] = 0; output[i] = 0;
} }
(*elapsed_samples)++; (*elapsed_samples)++;
} }
} }
} }
int check_time_for_sequence(int test_mode, int offset) { int check_time_for_sequence(int test_mode, int offset) {
static time_t last_check = 0; static time_t last_check = 0;
static int last_minute = -1; static int last_minute = -1;
time_t now = time(NULL); time_t now = time(NULL);
if (now == last_check) { if (now == last_check) {
return SEQ_NONE; return SEQ_NONE;
} }
last_check = now; last_check = now;
struct tm *utc_time = gmtime(&now); struct tm *utc_time = gmtime(&now);
int minute = utc_time->tm_min; int minute = utc_time->tm_min;
int second = utc_time->tm_sec; int second = utc_time->tm_sec;
if (difftime(now, last_sequence_time) < 1.0) { if (difftime(now, last_sequence_time) < 1.0) {
return SEQ_NONE; return SEQ_NONE;
} }
if (minute == 29 && second == (56 + offset)) { if (minute == 29 && second == (56 + offset)) {
last_sequence_time = now; last_sequence_time = now;
return SEQ_29_56; return SEQ_29_56;
} }
if (minute == 59 && second == (55 + offset)) { if (minute == 59 && second == (55 + offset)) {
last_sequence_time = now; last_sequence_time = now;
return SEQ_59_55; return SEQ_59_55;
} }
if (test_mode && second == (55 + offset) && minute != last_minute) { if (test_mode && second == (55 + offset) && minute != last_minute) {
last_minute = minute; last_minute = minute;
last_sequence_time = now; last_sequence_time = now;
return SEQ_TEST_HOUR; return SEQ_TEST_HOUR;
} }
return SEQ_NONE; return SEQ_NONE;
} }
int main(int argc, char **argv) { int main(int argc, char **argv) {
show_version(); show_version();
pa_simple *output_device; pa_simple *output_device;
char audio_output_device[64] = OUTPUT_DEVICE; char audio_output_device[64] = OUTPUT_DEVICE;
float master_volume = DEFAULT_MASTER_VOLUME; float master_volume = DEFAULT_MASTER_VOLUME;
float freq = DEFAULT_FREQ; float freq = DEFAULT_FREQ;
int sample_rate = DEFAULT_SAMPLE_RATE; int sample_rate = DEFAULT_SAMPLE_RATE;
int offset = DEFAULT_OFFSET; int offset = DEFAULT_OFFSET;
int test_mode = 0; int test_mode = 0;
// Parse command line arguments // Parse command line arguments
int opt; int opt;
const char *short_opt = "o:F:s:v:t:Th"; const char *short_opt = "o:F:s:v:t:Th";
struct option long_opt[] = { struct option long_opt[] = {
{"output", required_argument, NULL, 'o'}, {"output", required_argument, NULL, 'o'},
{"frequency", required_argument, NULL, 'F'}, {"frequency", required_argument, NULL, 'F'},
{"samplerate", required_argument, NULL, 's'}, {"samplerate", required_argument, NULL, 's'},
{"volume", required_argument, NULL, 'v'}, {"volume", required_argument, NULL, 'v'},
{"offset", required_argument, NULL, 't'}, {"offset", required_argument, NULL, 't'},
{"test", no_argument, NULL, 'T'}, {"test", no_argument, NULL, 'T'},
{"help", no_argument, NULL, 'h'}, {"help", no_argument, NULL, 'h'},
{0, 0, 0, 0} {0, 0, 0, 0}
}; };
while((opt = getopt_long(argc, argv, short_opt, long_opt, NULL)) != -1) { while((opt = getopt_long(argc, argv, short_opt, long_opt, NULL)) != -1) {
switch(opt) { switch(opt) {
case 'o': case 'o':
strncpy(audio_output_device, optarg, sizeof(audio_output_device) - 1); strncpy(audio_output_device, optarg, sizeof(audio_output_device) - 1);
audio_output_device[sizeof(audio_output_device) - 1] = '\0'; audio_output_device[sizeof(audio_output_device) - 1] = '\0';
break; break;
case 'F': case 'F':
freq = strtof(optarg, NULL); freq = strtof(optarg, NULL);
break; break;
case 's': case 's':
sample_rate = strtol(optarg, NULL, 10); sample_rate = strtol(optarg, NULL, 10);
break; break;
case 'v': case 'v':
master_volume = strtof(optarg, NULL); master_volume = strtof(optarg, NULL);
break; break;
case 't': case 't':
offset = strtol(optarg, NULL, 10); offset = strtol(optarg, NULL, 10);
break; break;
case 'T': case 'T':
test_mode = 1; test_mode = 1;
break; break;
case 'h': case 'h':
show_help(argv[0]); show_help(argv[0]);
return 0; return 0;
} }
} }
printf("Configuration:\n"); printf("Configuration:\n");
printf(" Output device: %s\n", audio_output_device); printf(" Output device: %s\n", audio_output_device);
printf(" Frequency: %.1f Hz\n", freq); printf(" Frequency: %.1f Hz\n", freq);
printf(" Sample rate: %d Hz\n", sample_rate); printf(" Sample rate: %d Hz\n", sample_rate);
printf(" Volume: %.2f\n", master_volume); printf(" Volume: %.2f\n", master_volume);
printf(" Time offset: %d seconds\n", offset); printf(" Time offset: %d seconds\n", offset);
printf(" Test mode: %s\n", test_mode ? "Enabled" : "Disabled"); printf(" Test mode: %s\n", test_mode ? "Enabled" : "Disabled");
// Setup PulseAudio // Setup PulseAudio
pa_sample_spec mono_format = { pa_sample_spec mono_format = {
.format = PA_SAMPLE_FLOAT32NE, .format = PA_SAMPLE_FLOAT32NE,
.channels = 1, .channels = 1,
.rate = sample_rate .rate = sample_rate
}; };
pa_buffer_attr output_buffer_atr = { pa_buffer_attr output_buffer_atr = {
.maxlength = buffer_maxlength, .maxlength = buffer_maxlength,
.tlength = buffer_tlength_fragsize, .tlength = buffer_tlength_fragsize,
.prebuf = buffer_prebuf .prebuf = buffer_prebuf
}; };
int pulse_error; int pulse_error;
printf("Connecting to output device... (%s)\n", audio_output_device); printf("Connecting to output device... (%s)\n", audio_output_device);
output_device = pa_simple_new( output_device = pa_simple_new(
NULL, NULL,
"chimer95", "chimer95",
PA_STREAM_PLAYBACK, PA_STREAM_PLAYBACK,
audio_output_device, audio_output_device,
"GTS Output", "GTS Output",
&mono_format, &mono_format,
NULL, NULL,
&output_buffer_atr, &output_buffer_atr,
&pulse_error &pulse_error
); );
if (!output_device) { if (!output_device) {
fprintf(stderr, "Error: cannot open output device: %s\n", pa_strerror(pulse_error)); fprintf(stderr, "Error: cannot open output device: %s\n", pa_strerror(pulse_error));
return 1; return 1;
} }
Oscillator osc; Oscillator osc;
init_oscillator(&osc, freq, sample_rate); init_oscillator(&osc, freq, sample_rate);
signal(SIGINT, stop); signal(SIGINT, stop);
signal(SIGTERM, stop); signal(SIGTERM, stop);
float output[BUFFER_SIZE]; float output[BUFFER_SIZE];
int pip_samples = (int)((PIP_DURATION / 1000.0) * sample_rate); int pip_samples = (int)((PIP_DURATION / 1000.0) * sample_rate);
int pause_samples = (int)((PIP_PAUSE / 1000.0) * sample_rate); int pause_samples = (int)((PIP_PAUSE / 1000.0) * sample_rate);
int beep_samples = (int)((BEEP_DURATION / 1000.0) * sample_rate); int beep_samples = (int)((BEEP_DURATION / 1000.0) * sample_rate);
int samples_29_56 = 4 * (pip_samples + pause_samples) + beep_samples; int samples_29_56 = 4 * (pip_samples + pause_samples) + beep_samples;
int samples_59_55 = 5 * (pip_samples + pause_samples) + beep_samples; int samples_59_55 = 5 * (pip_samples + pause_samples) + beep_samples;
printf("Ready to play time signals.\n"); printf("Ready to play time signals.\n");
printf("Will trigger at XX:29:%02d and XX:59:%02d\n", 56+offset, 55+offset); printf("Will trigger at XX:29:%02d and XX:59:%02d\n", 56+offset, 55+offset);
if (test_mode) { if (test_mode) {
printf("TEST MODE: Will also play full hour signal at the end of every minute\n"); printf("TEST MODE: Will also play full hour signal at the end of every minute\n");
} }
int elapsed_samples = 0; int elapsed_samples = 0;
int total_sequence_samples = 0; int total_sequence_samples = 0;
int sequence_completed = 0; int sequence_completed = 0;
while (to_run) { while (to_run) {
if (!playing_sequence) { if (!playing_sequence) {
int new_sequence = check_time_for_sequence(test_mode, offset); int new_sequence = check_time_for_sequence(test_mode, offset);
if (new_sequence != SEQ_NONE) { if (new_sequence != SEQ_NONE) {
playing_sequence = 1; playing_sequence = 1;
sequence_type = new_sequence; sequence_type = new_sequence;
elapsed_samples = 0; elapsed_samples = 0;
sequence_completed = 0; sequence_completed = 0;
if (new_sequence == SEQ_29_56) { if (new_sequence == SEQ_29_56) {
total_sequence_samples = samples_29_56; total_sequence_samples = samples_29_56;
} else { } else {
total_sequence_samples = samples_59_55; total_sequence_samples = samples_59_55;
} }
memset(output, 0, sizeof(output)); memset(output, 0, sizeof(output));
} else { } else {
static int idle_counter = 0; static int idle_counter = 0;
if (idle_counter++ % 10 == 0) { if (idle_counter++ % 10 == 0) {
memset(output, 0, sizeof(output)); memset(output, 0, sizeof(output));
pa_simple_write(output_device, output, sizeof(output), &pulse_error); pa_simple_write(output_device, output, sizeof(output), &pulse_error);
} }
struct timespec ts = {0, 5000000}; // 5ms sleep struct timespec ts = {0, 5000000}; // 5ms sleep
nanosleep(&ts, NULL); nanosleep(&ts, NULL);
continue; continue;
} }
} }
int num_pips = (sequence_type == SEQ_29_56) ? 4 : 5; int num_pips = (sequence_type == SEQ_29_56) ? 4 : 5;
generate_signal(output, BUFFER_SIZE, &osc, master_volume, generate_signal(output, BUFFER_SIZE, &osc, master_volume,
&elapsed_samples, total_sequence_samples, &elapsed_samples, total_sequence_samples,
pip_samples, pause_samples, beep_samples, num_pips); pip_samples, pause_samples, beep_samples, num_pips);
if (!playing_sequence && !sequence_completed) { if (!playing_sequence && !sequence_completed) {
sequence_completed = 1; sequence_completed = 1;
} }
if (pa_simple_write(output_device, output, sizeof(output), &pulse_error) < 0) { if (pa_simple_write(output_device, output, sizeof(output), &pulse_error) < 0) {
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;
} }
} }
printf("Cleaning up...\n"); printf("Cleaning up...\n");
pa_simple_free(output_device); pa_simple_free(output_device);
return 0; return 0;
} }

View File

@@ -49,437 +49,437 @@ volatile int dcf77_bits[60];
unsigned int lfsr = 0; unsigned int lfsr = 0;
static void stop(int signum) { static void stop(int signum) {
(void)signum; (void)signum;
printf("\nReceived stop signal.\n"); printf("\nReceived stop signal.\n");
to_run = 0; to_run = 0;
} }
unsigned int generate_chip() { unsigned int generate_chip() {
unsigned int chip = lfsr & 1; unsigned int chip = lfsr & 1;
lfsr >>= 1; lfsr >>= 1;
if (chip || !lfsr) if (chip || !lfsr)
lfsr ^= 0x110; lfsr ^= 0x110;
return chip; return chip;
} }
void reset_lfsr() { void reset_lfsr() {
lfsr = 0; lfsr = 0;
} }
int is_cet_dst(struct tm *tm_time) { int is_cet_dst(struct tm *tm_time) {
int month = tm_time->tm_mon + 1; int month = tm_time->tm_mon + 1;
int day = tm_time->tm_mday; int day = tm_time->tm_mday;
int hour = tm_time->tm_hour; int hour = tm_time->tm_hour;
if (month == 3) { if (month == 3) {
int last_sunday = 31 - ((5 + 31) % 7); int last_sunday = 31 - ((5 + 31) % 7);
if ((day > last_sunday) || (day == last_sunday && hour >= 2)) { if ((day > last_sunday) || (day == last_sunday && hour >= 2)) {
return 1; return 1;
} }
} else if (month > 3 && month < 10) { } else if (month > 3 && month < 10) {
return 1; return 1;
} else if (month == 10) { } else if (month == 10) {
int last_sunday = 31 - ((5 + 31) % 7); int last_sunday = 31 - ((5 + 31) % 7);
if ((day < last_sunday) || (day == last_sunday && hour < 3)) { if ((day < last_sunday) || (day == last_sunday && hour < 3)) {
return 1; return 1;
} }
} }
return 0; return 0;
} }
int is_timezone_change_soon() { int is_timezone_change_soon() {
time_t now, in_an_hour; time_t now, in_an_hour;
struct tm cet_now, cet_later; struct tm cet_now, cet_later;
time(&now); time(&now);
in_an_hour = now + 3600; in_an_hour = now + 3600;
memset(&cet_now, 0, sizeof(struct tm)); memset(&cet_now, 0, sizeof(struct tm));
memset(&cet_later, 0, sizeof(struct tm)); memset(&cet_later, 0, sizeof(struct tm));
struct tm *gm_now = gmtime(&now); struct tm *gm_now = gmtime(&now);
struct tm *gm_later = gmtime(&in_an_hour); struct tm *gm_later = gmtime(&in_an_hour);
cet_now = *gm_now; cet_now = *gm_now;
cet_later = *gm_later; cet_later = *gm_later;
cet_now.tm_hour += 1; cet_now.tm_hour += 1;
cet_later.tm_hour += 1; cet_later.tm_hour += 1;
int is_dst_now = is_cet_dst(&cet_now); int is_dst_now = is_cet_dst(&cet_now);
int is_dst_later = is_cet_dst(&cet_later); int is_dst_later = is_cet_dst(&cet_later);
if (is_dst_now) cet_now.tm_hour += 1; if (is_dst_now) cet_now.tm_hour += 1;
if (is_dst_later) cet_later.tm_hour += 1; if (is_dst_later) cet_later.tm_hour += 1;
mktime(&cet_now); mktime(&cet_now);
mktime(&cet_later); mktime(&cet_later);
return is_dst_now != is_dst_later; return is_dst_now != is_dst_later;
} }
void calculate_dcf77_bits(time_t now, int *bits) { void calculate_dcf77_bits(time_t now, int *bits) {
struct tm *t = gmtime(&now); struct tm *t = gmtime(&now);
int cest = is_cet_dst(t); int cest = is_cet_dst(t);
memset(bits, 0, 60 * sizeof(int)); memset(bits, 0, 60 * sizeof(int));
bits[16] = is_timezone_change_soon(); bits[16] = is_timezone_change_soon();
if(cest) { if(cest) {
bits[17] = 1; bits[17] = 1;
} else { } else {
bits[18] = 1; bits[18] = 1;
} }
bits[20] = 1; bits[20] = 1;
int minutes = t->tm_min; int minutes = t->tm_min;
bits[21] = (minutes % 10) & 0x01; bits[21] = (minutes % 10) & 0x01;
bits[22] = ((minutes % 10) >> 1) & 0x01; bits[22] = ((minutes % 10) >> 1) & 0x01;
bits[23] = ((minutes % 10) >> 2) & 0x01; bits[23] = ((minutes % 10) >> 2) & 0x01;
bits[24] = ((minutes % 10) >> 3) & 0x01; bits[24] = ((minutes % 10) >> 3) & 0x01;
bits[25] = ((minutes / 10) & 0x01); bits[25] = ((minutes / 10) & 0x01);
bits[26] = ((minutes / 10) >> 1) & 0x01; bits[26] = ((minutes / 10) >> 1) & 0x01;
bits[27] = ((minutes / 10) >> 2) & 0x01; bits[27] = ((minutes / 10) >> 2) & 0x01;
int parity = 0; int parity = 0;
for (int i = 21; i <= 27; i++) { for (int i = 21; i <= 27; i++) {
parity ^= bits[i]; parity ^= bits[i];
} }
bits[28] = parity; bits[28] = parity;
int hours = t->tm_hour-1; int hours = t->tm_hour-1;
hours += 1; hours += 1;
if(cest) hours += 1; if(cest) hours += 1;
bits[29] = (hours % 10) & 0x01; bits[29] = (hours % 10) & 0x01;
bits[30] = ((hours % 10) >> 1) & 0x01; bits[30] = ((hours % 10) >> 1) & 0x01;
bits[31] = ((hours % 10) >> 2) & 0x01; bits[31] = ((hours % 10) >> 2) & 0x01;
bits[32] = ((hours % 10) >> 3) & 0x01; bits[32] = ((hours % 10) >> 3) & 0x01;
bits[33] = ((hours / 10) & 0x01); bits[33] = ((hours / 10) & 0x01);
bits[34] = ((hours / 10) >> 1) & 0x01; bits[34] = ((hours / 10) >> 1) & 0x01;
parity = 0; parity = 0;
for (int i = 29; i <= 34; i++) { for (int i = 29; i <= 34; i++) {
parity ^= bits[i]; parity ^= bits[i];
} }
bits[35] = parity; bits[35] = parity;
int day = t->tm_mday; int day = t->tm_mday;
bits[36] = (day % 10) & 0x01; bits[36] = (day % 10) & 0x01;
bits[37] = ((day % 10) >> 1) & 0x01; bits[37] = ((day % 10) >> 1) & 0x01;
bits[38] = ((day % 10) >> 2) & 0x01; bits[38] = ((day % 10) >> 2) & 0x01;
bits[39] = ((day % 10) >> 3) & 0x01; bits[39] = ((day % 10) >> 3) & 0x01;
bits[40] = ((day / 10) & 0x01); bits[40] = ((day / 10) & 0x01);
bits[41] = ((day / 10) >> 1) & 0x01; bits[41] = ((day / 10) >> 1) & 0x01;
int dow = t->tm_wday == 0 ? 7 : t->tm_wday; int dow = t->tm_wday == 0 ? 7 : t->tm_wday;
bits[42] = dow & 0x01; bits[42] = dow & 0x01;
bits[43] = (dow >> 1) & 0x01; bits[43] = (dow >> 1) & 0x01;
bits[44] = (dow >> 2) & 0x01; bits[44] = (dow >> 2) & 0x01;
int month = t->tm_mon + 1; int month = t->tm_mon + 1;
bits[45] = (month % 10) & 0x01; bits[45] = (month % 10) & 0x01;
bits[46] = ((month % 10) >> 1) & 0x01; bits[46] = ((month % 10) >> 1) & 0x01;
bits[47] = ((month % 10) >> 2) & 0x01; bits[47] = ((month % 10) >> 2) & 0x01;
bits[48] = ((month % 10) >> 3) & 0x01; bits[48] = ((month % 10) >> 3) & 0x01;
bits[49] = (month / 10) & 0x01; bits[49] = (month / 10) & 0x01;
int year = t->tm_year % 100; int year = t->tm_year % 100;
bits[50] = (year % 10) & 0x01; bits[50] = (year % 10) & 0x01;
bits[51] = ((year % 10) >> 1) & 0x01; bits[51] = ((year % 10) >> 1) & 0x01;
bits[52] = ((year % 10) >> 2) & 0x01; bits[52] = ((year % 10) >> 2) & 0x01;
bits[53] = ((year % 10) >> 3) & 0x01; bits[53] = ((year % 10) >> 3) & 0x01;
bits[54] = ((year / 10) & 0x01); bits[54] = ((year / 10) & 0x01);
bits[55] = ((year / 10) >> 1) & 0x01; bits[55] = ((year / 10) >> 1) & 0x01;
bits[56] = ((year / 10) >> 2) & 0x01; bits[56] = ((year / 10) >> 2) & 0x01;
bits[57] = ((year / 10) >> 3) & 0x01; bits[57] = ((year / 10) >> 3) & 0x01;
parity = 0; parity = 0;
for (int i = 36; i <= 57; i++) { for (int i = 36; i <= 57; i++) {
parity ^= bits[i]; parity ^= bits[i];
} }
bits[58] = parity; bits[58] = parity;
bits[59] = 2; bits[59] = 2;
} }
void print_dcf77_bits(const int *bits) { void print_dcf77_bits(const int *bits) {
printf("DCF77 Bit Pattern: "); printf("DCF77 Bit Pattern: ");
for (int i = 0; i < 60; i++) { for (int i = 0; i < 60; i++) {
printf("%d", bits[i]); printf("%d", bits[i]);
if ((i+1) % 10 == 0) printf(" "); if ((i+1) % 10 == 0) printf(" ");
} }
printf("\n"); printf("\n");
} }
void show_version() { void show_version() {
printf("dcf95 (DCF77 time signal encoder by radio95) version 1.1\n"); printf("dcf95 (DCF77 time signal encoder by radio95) version 1.1\n");
} }
void show_help(char *name) { void show_help(char *name) {
printf( printf(
"Usage: %s\n" "Usage: %s\n"
" -o,--output Override output device [default: %s]\n" " -o,--output Override output device [default: %s]\n"
" -F,--frequency DCF77 Frequency [default: %.1f Hz]\n" " -F,--frequency DCF77 Frequency [default: %.1f Hz]\n"
" -s,--samplerate Output Samplerate [default: %d]\n" " -s,--samplerate Output Samplerate [default: %d]\n"
" -v,--volume Output volume [default: %.2f]\n" " -v,--volume Output volume [default: %.2f]\n"
" -t,--offset Time Offset [default: %ds]\n" " -t,--offset Time Offset [default: %ds]\n"
" -T,--test Enable test mode\n" " -T,--test Enable test mode\n"
" -n,--no-phase Disable phase modulation\n" " -n,--no-phase Disable phase modulation\n"
,name ,name
,OUTPUT_DEVICE ,OUTPUT_DEVICE
,DEFAULT_FREQ ,DEFAULT_FREQ
,DEFAULT_SAMPLE_RATE ,DEFAULT_SAMPLE_RATE
,DEFAULT_MASTER_VOLUME ,DEFAULT_MASTER_VOLUME
,DEFAULT_OFFSET ,DEFAULT_OFFSET
); );
} }
int main(int argc, char **argv) { int main(int argc, char **argv) {
show_version(); show_version();
pa_simple *output_device; pa_simple *output_device;
char audio_output_device[64] = OUTPUT_DEVICE; char audio_output_device[64] = OUTPUT_DEVICE;
float master_volume = DEFAULT_MASTER_VOLUME; float master_volume = DEFAULT_MASTER_VOLUME;
float freq = DEFAULT_FREQ; float freq = DEFAULT_FREQ;
int sample_rate = DEFAULT_SAMPLE_RATE; int sample_rate = DEFAULT_SAMPLE_RATE;
int offset = DEFAULT_OFFSET; int offset = DEFAULT_OFFSET;
int test_mode = 0; int test_mode = 0;
int no_phase = 0; int no_phase = 0;
// #region Parse Arguments // #region Parse Arguments
int opt; int opt;
const char *short_opt = "o:F:s:v:t:Tnh"; const char *short_opt = "o:F:s:v:t:Tnh";
struct option long_opt[] = struct option long_opt[] =
{ {
{"output", required_argument, NULL, 'o'}, {"output", required_argument, NULL, 'o'},
{"frequency", required_argument, NULL, 'F'}, {"frequency", required_argument, NULL, 'F'},
{"samplerate", required_argument, NULL, 's'}, {"samplerate", required_argument, NULL, 's'},
{"volume", required_argument, NULL, 'v'}, {"volume", required_argument, NULL, 'v'},
{"offset", required_argument, NULL, 't'}, {"offset", required_argument, NULL, 't'},
{"test", no_argument, NULL, 'T'}, {"test", no_argument, NULL, 'T'},
{"no-phase", no_argument, NULL, 'n'}, {"no-phase", no_argument, NULL, 'n'},
{"help", no_argument, NULL, 'h'}, {"help", no_argument, NULL, 'h'},
{0, 0, 0, 0} {0, 0, 0, 0}
}; };
while((opt = getopt_long(argc, argv, short_opt, long_opt, NULL)) != -1) { while((opt = getopt_long(argc, argv, short_opt, long_opt, NULL)) != -1) {
switch(opt) { switch(opt) {
case 'o': // Output Device case 'o': // Output Device
memcpy(audio_output_device, optarg, 63); memcpy(audio_output_device, optarg, 63);
audio_output_device[63] = '\0'; // Ensure null-termination audio_output_device[63] = '\0'; // Ensure null-termination
break; break;
case 'F': // Frequency case 'F': // Frequency
freq = strtof(optarg, NULL); freq = strtof(optarg, NULL);
break; break;
case 's': // Sample rate case 's': // Sample rate
sample_rate = strtol(optarg, NULL, 10); sample_rate = strtol(optarg, NULL, 10);
break; break;
case 'v': // Volume case 'v': // Volume
master_volume = strtof(optarg, NULL); master_volume = strtof(optarg, NULL);
break; break;
case 't': // Offset case 't': // Offset
offset = strtol(optarg, NULL, 10); offset = strtol(optarg, NULL, 10);
break; break;
case 'T': // Test mode case 'T': // Test mode
test_mode = 1; test_mode = 1;
break; break;
case 'n': // Disable phase modulation case 'n': // Disable phase modulation
no_phase = 1; no_phase = 1;
break; break;
case 'h': case 'h':
show_help(argv[0]); show_help(argv[0]);
return 0; return 0;
} }
} }
// #endregion // #endregion
if(test_mode) { if(test_mode) {
time_t now = time(NULL) + offset + 60; time_t now = time(NULL) + offset + 60;
calculate_dcf77_bits(now, (int *)dcf77_bits); calculate_dcf77_bits(now, (int *)dcf77_bits);
print_dcf77_bits((int *)dcf77_bits); print_dcf77_bits((int *)dcf77_bits);
return 0; return 0;
} }
printf("Configuration:\n"); printf("Configuration:\n");
printf(" Output device: %s\n", audio_output_device); printf(" Output device: %s\n", audio_output_device);
printf(" Frequency: %.1f Hz\n", freq); printf(" Frequency: %.1f Hz\n", freq);
printf(" Sample rate: %d Hz\n", sample_rate); printf(" Sample rate: %d Hz\n", sample_rate);
printf(" Volume: %.2f\n", master_volume); printf(" Volume: %.2f\n", master_volume);
printf(" Time offset: %d seconds\n", offset); printf(" Time offset: %d seconds\n", offset);
if (no_phase) { if (no_phase) {
printf(" Phase modulation: Disabled\n"); printf(" Phase modulation: Disabled\n");
} else { } else {
printf(" Phase modulation: +/- %.1f degrees\n", PHASE_SHIFT); printf(" Phase modulation: +/- %.1f degrees\n", PHASE_SHIFT);
} }
// #region Setup devices // #region Setup devices
pa_sample_spec mono_format = { pa_sample_spec mono_format = {
.format = PA_SAMPLE_FLOAT32NE, .format = PA_SAMPLE_FLOAT32NE,
.channels = 1, .channels = 1,
.rate = sample_rate .rate = sample_rate
}; };
pa_buffer_attr output_buffer_atr = { pa_buffer_attr output_buffer_atr = {
.maxlength = buffer_maxlength, .maxlength = buffer_maxlength,
.tlength = buffer_tlength_fragsize, .tlength = buffer_tlength_fragsize,
.prebuf = buffer_prebuf .prebuf = buffer_prebuf
}; };
int opentime_pulse_error; int opentime_pulse_error;
printf("Connecting to output device... (%s)\n", audio_output_device); printf("Connecting to output device... (%s)\n", audio_output_device);
output_device = pa_simple_new( output_device = pa_simple_new(
NULL, NULL,
"dcf95", "dcf95",
PA_STREAM_PLAYBACK, PA_STREAM_PLAYBACK,
audio_output_device, audio_output_device,
"DCF77 Output", "DCF77 Output",
&mono_format, &mono_format,
NULL, NULL,
&output_buffer_atr, &output_buffer_atr,
&opentime_pulse_error &opentime_pulse_error
); );
if (!output_device) { if (!output_device) {
fprintf(stderr, "Error: cannot open output device: %s\n", pa_strerror(opentime_pulse_error)); fprintf(stderr, "Error: cannot open output device: %s\n", pa_strerror(opentime_pulse_error));
return 1; return 1;
} }
// #endregion // #endregion
Oscillator osc; Oscillator osc;
init_oscillator(&osc, freq, sample_rate); init_oscillator(&osc, freq, sample_rate);
signal(SIGINT, stop); signal(SIGINT, stop);
signal(SIGTERM, stop); signal(SIGTERM, stop);
int pulse_error; int pulse_error;
float output[BUFFER_SIZE]; float output[BUFFER_SIZE];
int current_second = -1; int current_second = -1;
int ms_within_second = 0; int ms_within_second = 0;
int dsss_start_samples = (int)((DSSS_START_MS / 1000.0) * sample_rate); int dsss_start_samples = (int)((DSSS_START_MS / 1000.0) * sample_rate);
int dsss_duration_samples = (int)((DSSS_DURATION_MS / 1000.0) * sample_rate); int dsss_duration_samples = (int)((DSSS_DURATION_MS / 1000.0) * sample_rate);
int dsss_end_samples = dsss_start_samples + dsss_duration_samples; int dsss_end_samples = dsss_start_samples + dsss_duration_samples;
float phase_shift_rad = (PHASE_SHIFT * M_PI) / 180.0; float phase_shift_rad = (PHASE_SHIFT * M_PI) / 180.0;
int current_chip_count = 0; int current_chip_count = 0;
int current_cycle_count = 0; int current_cycle_count = 0;
int in_dsss_period = 0; int in_dsss_period = 0;
int elapsed_samples = 0; int elapsed_samples = 0;
printf("DCF77 encoder ready.\n"); printf("DCF77 encoder ready.\n");
while (to_run) { while (to_run) {
memset(output, 0, sizeof(output)); memset(output, 0, sizeof(output));
time_t now = time(NULL) + offset + 60; time_t now = time(NULL) + offset + 60;
struct tm *t = gmtime(&now); struct tm *t = gmtime(&now);
int second = t->tm_sec; int second = t->tm_sec;
if (second == 0 && current_second != 0) { if (second == 0 && current_second != 0) {
calculate_dcf77_bits(now, (int *)dcf77_bits); calculate_dcf77_bits(now, (int *)dcf77_bits);
#ifdef DEBUG #ifdef DEBUG
print_dcf77_bits((int *)dcf77_bits); print_dcf77_bits((int *)dcf77_bits);
#endif #endif
bit_position = 0; bit_position = 0;
elapsed_samples = 0; elapsed_samples = 0;
transmitting = 1; transmitting = 1;
#ifdef DEBUG #ifdef DEBUG
printf("Starting new DCF77 transmission for %02d:%02d:%02d UTC\n", printf("Starting new DCF77 transmission for %02d:%02d:%02d UTC\n",
t->tm_hour, t->tm_min, t->tm_sec); t->tm_hour, t->tm_min, t->tm_sec);
#endif #endif
} }
if (second != current_second) { if (second != current_second) {
current_second = second; current_second = second;
reset_lfsr(); reset_lfsr();
current_chip_count = 0; current_chip_count = 0;
current_cycle_count = 0; current_cycle_count = 0;
if (transmitting) { if (transmitting) {
if (bit_position < 59) { if (bit_position < 59) {
#ifdef DEBUG #ifdef DEBUG
printf("Bit %2d: %d\n", bit_position, dcf77_bits[bit_position]); printf("Bit %2d: %d\n", bit_position, dcf77_bits[bit_position]);
#endif #endif
bit_position++; bit_position++;
} else { } else {
bit_position = 0; bit_position = 0;
#ifdef DEBUG #ifdef DEBUG
printf("End of minute, restarting bit sequence.\n"); printf("End of minute, restarting bit sequence.\n");
#endif #endif
} }
} }
elapsed_samples = 0; elapsed_samples = 0;
} }
for (int i = 0; i < BUFFER_SIZE; i++) { for (int i = 0; i < BUFFER_SIZE; i++) {
ms_within_second = (int)((elapsed_samples * 1000.0) / sample_rate); ms_within_second = (int)((elapsed_samples * 1000.0) / sample_rate);
int current_bit = bit_position > 0 ? bit_position - 1 : 59; int current_bit = bit_position > 0 ? bit_position - 1 : 59;
in_dsss_period = (elapsed_samples >= dsss_start_samples && in_dsss_period = (elapsed_samples >= dsss_start_samples &&
elapsed_samples < dsss_end_samples); elapsed_samples < dsss_end_samples);
float phase_offset = 0.0; float phase_offset = 0.0;
if (in_dsss_period && transmitting && !no_phase) { if (in_dsss_period && transmitting && !no_phase) {
if (current_cycle_count == 0) { if (current_cycle_count == 0) {
if (current_chip_count < CHIPS_PER_BIT) { if (current_chip_count < CHIPS_PER_BIT) {
unsigned int chip = generate_chip(); unsigned int chip = generate_chip();
unsigned int modulated_chip = chip ^ dcf77_bits[current_bit]; unsigned int modulated_chip = chip ^ dcf77_bits[current_bit];
if (modulated_chip == 0) { if (modulated_chip == 0) {
phase_offset = phase_shift_rad; phase_offset = phase_shift_rad;
} else { } else {
phase_offset = -phase_shift_rad; phase_offset = -phase_shift_rad;
} }
current_chip_count++; current_chip_count++;
} }
} }
current_cycle_count = (current_cycle_count + 1) % CHIP_CYCLES; current_cycle_count = (current_cycle_count + 1) % CHIP_CYCLES;
} }
float t = osc.phase + phase_offset; float t = osc.phase + phase_offset;
float carrier = sinf(t); float carrier = sinf(t);
advance_oscillator(&osc); advance_oscillator(&osc);
if (transmitting) { if (transmitting) {
if ((dcf77_bits[current_bit] == 0 && ms_within_second < PULSE_0_DURATION) || if ((dcf77_bits[current_bit] == 0 && ms_within_second < PULSE_0_DURATION) ||
(dcf77_bits[current_bit] == 1 && ms_within_second < PULSE_1_DURATION)) { (dcf77_bits[current_bit] == 1 && ms_within_second < PULSE_1_DURATION)) {
output[i] = carrier * master_volume * REDUCED_AMPLITUDE; output[i] = carrier * master_volume * REDUCED_AMPLITUDE;
} else { } else {
output[i] = carrier * master_volume; output[i] = carrier * master_volume;
} }
} else { } else {
output[i] = carrier * master_volume; output[i] = carrier * master_volume;
} }
elapsed_samples++; elapsed_samples++;
} }
if (pa_simple_write(output_device, output, sizeof(output), &pulse_error) < 0) { if (pa_simple_write(output_device, output, sizeof(output), &pulse_error) < 0) {
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;
} }
} }
printf("Cleaning up...\n"); printf("Cleaning up...\n");
pa_simple_free(output_device); pa_simple_free(output_device);
return 0; return 0;
} }

View File

@@ -35,490 +35,487 @@
#define DEFAULT_MASTER_VOLUME 1.0f // Volume of everything combined, for calibration #define DEFAULT_MASTER_VOLUME 1.0f // Volume of everything combined, for calibration
#define DEFAULT_AUDIO_VOLUME 1.0f // Audio volume, before clipper #define DEFAULT_AUDIO_VOLUME 1.0f // Audio volume, before clipper
#define MONO_VOLUME 0.45f // L+R Signal #define MONO_VOLUME 0.45f
#define PILOT_VOLUME 0.09f // 19 KHz Pilot #define PILOT_VOLUME 0.09f
#define STEREO_VOLUME 0.45f // L-R signal, should be same as MONO #define STEREO_VOLUME 0.45f
#define RDS_VOLUME 0.075f // RDS Volume, after dsb-sc #define RDS_VOLUME 0.075f
#define RDS2_VOLUME 0.075f // RDS2 Volume #define RDS2_VOLUME 0.0675f
#define SCA_VOLUME 0.1f // FM SCA signal, 10% #define SCA_VOLUME 0.1f
#define MPX_VOLUME 1.0f // Passtrough #define MPX_VOLUME 1.0f
#define MPX_CLIPPER_THRESHOLD 1.0f #define MPX_CLIPPER_THRESHOLD 1.0f
static volatile sig_atomic_t to_run = 1; static volatile sig_atomic_t to_run = 1;
void uninterleave(const float *input, float *left, float *right, size_t num_samples) { void uninterleave(const float *input, float *left, float *right, size_t num_samples) {
for (size_t i = 0; i < num_samples/2; i++) { for (size_t i = 0; i < num_samples/2; i++) {
left[i] = input[i * 2]; left[i] = input[i * 2];
right[i] = input[i * 2 + 1]; right[i] = input[i * 2 + 1];
} }
} }
static void stop(int signum) { static void stop(int signum) {
(void)signum; (void)signum;
printf("\nReceived stop signal.\n"); printf("\nReceived stop signal.\n");
to_run = 0; to_run = 0;
} }
void show_version() { void show_version() {
printf("fm95 (an FM Processor by radio95) version 1.5\n"); printf("fm95 (an FM Processor by radio95) version 1.5\n");
} }
void show_help(char *name) { void show_help(char *name) {
printf( printf(
"Usage: %s\n" "Usage: %s\n"
" -s,--stereo Force Stereo [default: %d]\n" " -s,--stereo Force Stereo [default: %d]\n"
" -i,--input Override input device [default: %s]\n" " -i,--input Override input device [default: %s]\n"
" -o,--output Override output device [default: %s]\n" " -o,--output Override output device [default: %s]\n"
" -M,--mpx Override MPX input device [default: %s]\n" " -M,--mpx Override MPX input device [default: %s]\n"
" -r,--rds Override RDS95 input device [default: %s]\n" " -r,--rds Override RDS95 input device [default: %s]\n"
" -C,--sca Override the SCA input device [default: %s]\n" " -C,--sca Override the SCA input device [default: %s]\n"
" -f,--sca_freq Override the SCA frequency [default: %.1f]\n" " -f,--sca_freq Override the SCA frequency [default: %.1f]\n"
" -F,--sca_dev Override the SCA deviation [default: %.2f]\n" " -F,--sca_dev Override the SCA deviation [default: %.2f]\n"
" -L,--sca_clip Override the SCA clipper threshold [default: %.2f]\n" " -L,--sca_clip Override the SCA clipper threshold [default: %.2f]\n"
" -c,--clipper Override the clipper threshold [default: %.2f]\n" " -c,--clipper Override the clipper threshold [default: %.2f]\n"
" -P,--polar Force Polar Stereo (does not take effect with -m%s)\n" " -P,--polar Force Polar Stereo (does not take effect with -m%s)\n"
" -R,--preemp Override preemphasis [default: %.2f µs]\n" " -R,--preemp Override preemphasis [default: %.2f µs]\n"
" -V,--calibrate Enable Calibration mode [default: off]\n" " -V,--calibrate Enable Calibration mode [default: off]\n"
" -A,--master_vol Set master volume [default: %.3f]\n" " -A,--master_vol Set master volume [default: %.3f]\n"
" -v,--audio_vol Set audio volume [default: %.3f]\n" " -v,--audio_vol Set audio volume [default: %.3f]\n"
,name ,name
,DEFAULT_STEREO ,DEFAULT_STEREO
,INPUT_DEVICE ,INPUT_DEVICE
,OUTPUT_DEVICE ,OUTPUT_DEVICE
#ifdef MPX_DEVICE #ifdef MPX_DEVICE
,MPX_DEVICE ,MPX_DEVICE
#else #else
,"not set" ,"not set"
#endif #endif
#ifdef RDS_DEVICE #ifdef RDS_DEVICE
,RDS_DEVICE ,RDS_DEVICE
#else #else
,"not set" ,"not set"
#endif #endif
#ifdef SCA_DEVICE #ifdef SCA_DEVICE
,SCA_DEVICE ,SCA_DEVICE
#else #else
,"not set" ,"not set"
#endif #endif
,DEFAULT_SCA_FREQUENCY ,DEFAULT_SCA_FREQUENCY
,DEFAULT_SCA_DEVIATION ,DEFAULT_SCA_DEVIATION
,DEFAULT_SCA_CLIPPER_THRESHOLD ,DEFAULT_SCA_CLIPPER_THRESHOLD
,DEFAULT_CLIPPER_THRESHOLD ,DEFAULT_CLIPPER_THRESHOLD
,(DEFAULT_STEREO_POLAR == 1) ? ", default" : "" ,(DEFAULT_STEREO_POLAR == 1) ? ", default" : ""
,DEFAULT_PREEMPHASIS_TAU/0.000001 ,DEFAULT_PREEMPHASIS_TAU/0.000001
,DEFAULT_MASTER_VOLUME ,DEFAULT_MASTER_VOLUME
,DEFAULT_AUDIO_VOLUME ,DEFAULT_AUDIO_VOLUME
); );
} }
int main(int argc, char **argv) { int main(int argc, char **argv) {
show_version(); show_version();
pa_simple *mpx_device = NULL; pa_simple *mpx_device = NULL;
pa_simple *rds_device = NULL; pa_simple *rds_device = NULL;
pa_simple *sca_device = NULL; pa_simple *sca_device = NULL;
pa_simple *input_device; pa_simple *input_device;
pa_simple *output_device; pa_simple *output_device;
float clipper_threshold = DEFAULT_CLIPPER_THRESHOLD; float clipper_threshold = DEFAULT_CLIPPER_THRESHOLD;
int stereo = DEFAULT_STEREO; int stereo = DEFAULT_STEREO;
int polar_stereo = DEFAULT_STEREO_POLAR; int polar_stereo = DEFAULT_STEREO_POLAR;
float sca_frequency = DEFAULT_SCA_FREQUENCY; float sca_frequency = DEFAULT_SCA_FREQUENCY;
float sca_deviation = DEFAULT_SCA_DEVIATION; float sca_deviation = DEFAULT_SCA_DEVIATION;
float sca_clipper_threshold = DEFAULT_SCA_CLIPPER_THRESHOLD; float sca_clipper_threshold = DEFAULT_SCA_CLIPPER_THRESHOLD;
char audio_input_device[64] = INPUT_DEVICE; char audio_input_device[64] = INPUT_DEVICE;
char audio_output_device[64] = OUTPUT_DEVICE; char audio_output_device[64] = OUTPUT_DEVICE;
#ifndef MPX_DEVICE #ifndef MPX_DEVICE
char audio_mpx_device[64] = "\0"; char audio_mpx_device[64] = "\0";
#else #else
char audio_mpx_device[64] = MPX_DEVICE; char audio_mpx_device[64] = MPX_DEVICE;
#endif #endif
#ifndef RDS_DEVICE #ifndef RDS_DEVICE
char audio_rds_device[64] = "\0"; char audio_rds_device[64] = "\0";
#else #else
char audio_rds_device[64] = RDS_DEVICE; char audio_rds_device[64] = RDS_DEVICE;
#endif #endif
#ifndef SCA_DEVICE #ifndef SCA_DEVICE
char audio_sca_device[64] = "\0"; char audio_sca_device[64] = "\0";
#else #else
char audio_sca_device[64] = SCA_DEVICE; char audio_sca_device[64] = SCA_DEVICE;
#endif #endif
float preemphasis_tau = DEFAULT_PREEMPHASIS_TAU; float preemphasis_tau = DEFAULT_PREEMPHASIS_TAU;
int calibration_mode = 0; int calibration_mode = 0;
float master_volume = DEFAULT_MASTER_VOLUME; float master_volume = DEFAULT_MASTER_VOLUME;
float audio_volume = DEFAULT_AUDIO_VOLUME; float audio_volume = DEFAULT_AUDIO_VOLUME;
int sample_rate = DEFAULT_SAMPLE_RATE; int sample_rate = DEFAULT_SAMPLE_RATE;
// #region Parse Arguments // #region Parse Arguments
int opt; int opt;
const char *short_opt = "s::i:o:M:r:C:f:F:L:c:P::R:VA:v:h"; const char *short_opt = "s::i:o:M:r:C:f:F:L:c:P::R:VA:v:h";
struct option long_opt[] = struct option long_opt[] =
{ {
{"stereo", optional_argument, NULL, 's'}, {"stereo", optional_argument, NULL, 's'},
{"input", required_argument, NULL, 'i'}, {"input", required_argument, NULL, 'i'},
{"output", required_argument, NULL, 'o'}, {"output", required_argument, NULL, 'o'},
{"mpx", required_argument, NULL, 'M'}, {"mpx", required_argument, NULL, 'M'},
{"rds", required_argument, NULL, 'r'}, {"rds", required_argument, NULL, 'r'},
{"sca", required_argument, NULL, 'C'}, {"sca", required_argument, NULL, 'C'},
{"sca_freq", required_argument, NULL, 'f'}, {"sca_freq", required_argument, NULL, 'f'},
{"sca_dev", required_argument, NULL, 'F'}, {"sca_dev", required_argument, NULL, 'F'},
{"sca_clip", required_argument, NULL, 'L'}, {"sca_clip", required_argument, NULL, 'L'},
{"clipper", required_argument, NULL, 'c'}, {"clipper", required_argument, NULL, 'c'},
{"polar", optional_argument, NULL, 'P'}, {"polar", optional_argument, NULL, 'P'},
{"preemp", required_argument, NULL, 'R'}, {"preemp", required_argument, NULL, 'R'},
{"calibrate", no_argument, NULL, 'V'}, {"calibrate", no_argument, NULL, 'V'},
{"master_vol", required_argument, NULL, 'A'}, {"master_vol", required_argument, NULL, 'A'},
{"audio_vol", required_argument, NULL, 'v'}, {"audio_vol", required_argument, NULL, 'v'},
{"help", no_argument, NULL, 'h'}, {"help", no_argument, NULL, 'h'},
{0, 0, 0, 0} {0, 0, 0, 0}
}; };
while((opt = getopt_long(argc, argv, short_opt, long_opt, NULL)) != -1) { while((opt = getopt_long(argc, argv, short_opt, long_opt, NULL)) != -1) {
switch(opt) { switch(opt) {
case 's': // Stereo case 's': // Stereo
if(optarg) { if(optarg) {
stereo = atoi(optarg); stereo = atoi(optarg);
} else { } else {
stereo = 1; stereo = 1;
} }
break; break;
case 'i': // Input Device case 'i': // Input Device
memcpy(audio_input_device, optarg, 63); memcpy(audio_input_device, optarg, 63);
break; break;
case 'o': // Output Device case 'o': // Output Device
memcpy(audio_output_device, optarg, 63); memcpy(audio_output_device, optarg, 63);
break;; break;;
case 'M': //MPX in case 'M': //MPX in
memcpy(audio_mpx_device, optarg, 63); memcpy(audio_mpx_device, optarg, 63);
break; break;
case 'r': // RDS in case 'r': // RDS in
memcpy(audio_rds_device, optarg, 63); memcpy(audio_rds_device, optarg, 63);
break; break;
case 'C': //SCA in case 'C': //SCA in
memcpy(audio_sca_device, optarg, 63); memcpy(audio_sca_device, optarg, 63);
break; break;
case 'f': //SCA freq case 'f': //SCA freq
sca_frequency = strtof(optarg, NULL); sca_frequency = strtof(optarg, NULL);
break; break;
case 'F': //SCA deviation case 'F': //SCA deviation
sca_deviation = strtof(optarg, NULL); sca_deviation = strtof(optarg, NULL);
break; break;
case 'L': //SCA clip case 'L': //SCA clip
sca_clipper_threshold = strtof(optarg, NULL); sca_clipper_threshold = strtof(optarg, NULL);
break; break;
case 'c': //Clipper case 'c': //Clipper
clipper_threshold = strtof(optarg, NULL); clipper_threshold = strtof(optarg, NULL);
break; break;
case 'P': //Polar case 'P': //Polar
if(optarg) { if(optarg) {
polar_stereo = atoi(optarg); polar_stereo = atoi(optarg);
} else { } else {
polar_stereo = 1; polar_stereo = 1;
} }
break; break;
case 'R': // Preemp case 'R': // Preemp
preemphasis_tau = strtof(optarg, NULL)*0.000001; preemphasis_tau = strtof(optarg, NULL)*0.000001;
break; break;
case 'V': // Calibration case 'V': // Calibration
calibration_mode = 1; calibration_mode = 1;
break; break;
case 'A': // Master vol case 'A': // Master vol
master_volume = strtof(optarg, NULL); master_volume = strtof(optarg, NULL);
break; break;
case 'v': // Audio Volume case 'v': // Audio Volume
audio_volume = strtof(optarg, NULL); audio_volume = strtof(optarg, NULL);
break; break;
case 'h': case 'h':
show_help(argv[0]); show_help(argv[0]);
return 1; return 1;
} }
} }
// #endregion // #endregion
int mpx_on = (strlen(audio_mpx_device) != 0); int mpx_on = (strlen(audio_mpx_device) != 0);
int rds_on = (strlen(audio_rds_device) != 0); int rds_on = (strlen(audio_rds_device) != 0);
int sca_on = (strlen(audio_sca_device) != 0); int sca_on = (strlen(audio_sca_device) != 0);
// #region Setup devices // #region Setup devices
// Define formats and buffer atributes // Define formats and buffer atributes
pa_sample_spec stereo_format = { pa_sample_spec stereo_format = {
.format = PA_SAMPLE_FLOAT32NE, .format = PA_SAMPLE_FLOAT32NE,
.channels = 2, .channels = 2,
.rate = sample_rate .rate = sample_rate
}; };
pa_sample_spec mono_format = { pa_sample_spec mono_format = {
.format = PA_SAMPLE_FLOAT32NE, .format = PA_SAMPLE_FLOAT32NE,
.channels = 1, .channels = 1,
.rate = sample_rate .rate = sample_rate
}; };
pa_buffer_attr input_buffer_atr = { pa_buffer_attr input_buffer_atr = {
.maxlength = buffer_maxlength, .maxlength = buffer_maxlength,
.fragsize = buffer_tlength_fragsize .fragsize = buffer_tlength_fragsize
}; };
pa_buffer_attr output_buffer_atr = { pa_buffer_attr output_buffer_atr = {
.maxlength = buffer_maxlength, .maxlength = buffer_maxlength,
.tlength = buffer_tlength_fragsize, .tlength = buffer_tlength_fragsize,
.prebuf = buffer_prebuf .prebuf = buffer_prebuf
}; };
int opentime_pulse_error; int opentime_pulse_error;
printf("Connecting to input device... (%s)\n", audio_input_device); printf("Connecting to input device... (%s)\n", audio_input_device);
input_device = pa_simple_new( input_device = pa_simple_new(
NULL, NULL,
"fm95", "fm95",
PA_STREAM_RECORD, PA_STREAM_RECORD,
audio_input_device, audio_input_device,
"Main Audio Input", "Main Audio Input",
&stereo_format, &stereo_format,
NULL, NULL,
&input_buffer_atr, &input_buffer_atr,
&opentime_pulse_error &opentime_pulse_error
); );
if (!input_device) { if (!input_device) {
fprintf(stderr, "Error: cannot open input device: %s\n", pa_strerror(opentime_pulse_error)); fprintf(stderr, "Error: cannot open input device: %s\n", pa_strerror(opentime_pulse_error));
return 1; return 1;
} }
if(mpx_on) { if(mpx_on) {
printf("Connecting to MPX device... (%s)\n", audio_mpx_device); printf("Connecting to MPX device... (%s)\n", audio_mpx_device);
mpx_device = pa_simple_new( mpx_device = pa_simple_new(
NULL, NULL,
"fm95", "fm95",
PA_STREAM_RECORD, PA_STREAM_RECORD,
audio_mpx_device, audio_mpx_device,
"MPX Input", "MPX Input",
&mono_format, &mono_format,
NULL, NULL,
&input_buffer_atr, &input_buffer_atr,
&opentime_pulse_error &opentime_pulse_error
); );
if (!mpx_device) { if (!mpx_device) {
fprintf(stderr, "Error: cannot open MPX device: %s\n", pa_strerror(opentime_pulse_error)); fprintf(stderr, "Error: cannot open MPX device: %s\n", pa_strerror(opentime_pulse_error));
pa_simple_free(input_device); pa_simple_free(input_device);
return 1; return 1;
} }
} }
if(rds_on) { if(rds_on) {
printf("Connecting to RDS95 device... (%s)\n", audio_rds_device); printf("Connecting to RDS95 device... (%s)\n", audio_rds_device);
rds_device = pa_simple_new( rds_device = pa_simple_new(
NULL, NULL,
"fm95", "fm95",
PA_STREAM_RECORD, PA_STREAM_RECORD,
audio_rds_device, audio_rds_device,
"RDS Input", "RDS Input",
&stereo_format, &stereo_format,
NULL, NULL,
&input_buffer_atr, &input_buffer_atr,
&opentime_pulse_error &opentime_pulse_error
); );
if (!rds_device) { if (!rds_device) {
fprintf(stderr, "Error: cannot open RDS device: %s\n", pa_strerror(opentime_pulse_error)); fprintf(stderr, "Error: cannot open RDS device: %s\n", pa_strerror(opentime_pulse_error));
pa_simple_free(input_device); pa_simple_free(input_device);
if(mpx_on) pa_simple_free(mpx_device); if(mpx_on) pa_simple_free(mpx_device);
return 1; return 1;
} }
} }
if(sca_on) { if(sca_on) {
printf("Connecting to SCA device... (%s)\n", audio_sca_device); printf("Connecting to SCA device... (%s)\n", audio_sca_device);
sca_device = pa_simple_new( sca_device = pa_simple_new(
NULL, NULL,
"fm95", "fm95",
PA_STREAM_RECORD, PA_STREAM_RECORD,
audio_sca_device, audio_sca_device,
"SCA Input", "SCA Input",
&mono_format, &mono_format,
NULL, NULL,
&input_buffer_atr, &input_buffer_atr,
&opentime_pulse_error &opentime_pulse_error
); );
if (!sca_device) { if (!sca_device) {
fprintf(stderr, "Error: cannot open SCA device: %s\n", pa_strerror(opentime_pulse_error)); fprintf(stderr, "Error: cannot open SCA device: %s\n", pa_strerror(opentime_pulse_error));
pa_simple_free(input_device); pa_simple_free(input_device);
if(mpx_on) pa_simple_free(mpx_device); if(mpx_on) pa_simple_free(mpx_device);
if(rds_on) pa_simple_free(rds_device); if(rds_on) pa_simple_free(rds_device);
return 1; return 1;
} }
} }
printf("Connecting to output device... (%s)\n", audio_output_device); printf("Connecting to output device... (%s)\n", audio_output_device);
output_device = pa_simple_new( output_device = pa_simple_new(
NULL, NULL,
"StereoEncoder", "StereoEncoder",
PA_STREAM_PLAYBACK, PA_STREAM_PLAYBACK,
audio_output_device, audio_output_device,
"MPX Output", "MPX Output",
&mono_format, &mono_format,
NULL, NULL,
&output_buffer_atr, &output_buffer_atr,
&opentime_pulse_error &opentime_pulse_error
); );
if (!output_device) { if (!output_device) {
fprintf(stderr, "Error: cannot open output device: %s\n", pa_strerror(opentime_pulse_error)); fprintf(stderr, "Error: cannot open output device: %s\n", pa_strerror(opentime_pulse_error));
pa_simple_free(input_device); pa_simple_free(input_device);
if(mpx_on) pa_simple_free(mpx_device); if(mpx_on) pa_simple_free(mpx_device);
if(rds_on) pa_simple_free(rds_device); if(rds_on) pa_simple_free(rds_device);
if(sca_on) pa_simple_free(sca_device); if(sca_on) pa_simple_free(sca_device);
return 1; return 1;
} }
// #endregion // #endregion
if(calibration_mode) { if(calibration_mode) {
Oscillator osc; Oscillator osc;
init_oscillator(&osc, 400, sample_rate); init_oscillator(&osc, 400, sample_rate);
signal(SIGINT, stop); signal(SIGINT, stop);
signal(SIGTERM, stop); signal(SIGTERM, stop);
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++) {
output[i] = get_oscillator_sin_sample(&osc)*master_volume; output[i] = get_oscillator_sin_sample(&osc)*master_volume;
} }
if (pa_simple_write(output_device, output, sizeof(output), &pulse_error) < 0) { if (pa_simple_write(output_device, output, sizeof(output), &pulse_error) < 0) {
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;
} }
} }
printf("Cleaning up...\n"); printf("Cleaning up...\n");
pa_simple_free(input_device); pa_simple_free(input_device);
if(mpx_on) pa_simple_free(mpx_device); if(mpx_on) pa_simple_free(mpx_device);
if(rds_on) pa_simple_free(rds_device); if(rds_on) pa_simple_free(rds_device);
if(sca_on) pa_simple_free(sca_device); if(sca_on) pa_simple_free(sca_device);
pa_simple_free(output_device); pa_simple_free(output_device);
return 0; return 0;
} }
// #region Setup Filters/Modulaltors/Oscillators // #region Setup Filters/Modulaltors/Oscillators
Oscillator osc; Oscillator osc;
init_oscillator(&osc, polar_stereo ? 31250.0 : 19000, sample_rate); init_oscillator(&osc, polar_stereo ? 31250.0 : 19000, sample_rate);
Oscillator rds2_osc; FMModulator sca_mod;
init_oscillator(&rds2_osc, 66500, sample_rate); init_fm_modulator(&sca_mod, sca_frequency, sca_deviation, sample_rate);
FMModulator sca_mod; ResistorCapacitor preemp_l, preemp_r;
init_fm_modulator(&sca_mod, sca_frequency, sca_deviation, sample_rate); init_preemphasis(&preemp_l, preemphasis_tau, sample_rate);
init_preemphasis(&preemp_r, preemphasis_tau, sample_rate);
ResistorCapacitor preemp_l, preemp_r; PLL rds2_pll;
init_preemphasis(&preemp_l, preemphasis_tau, sample_rate); init_pll(&rds2_pll, 7, 2, 19000, 100, 1, sample_rate);
init_preemphasis(&preemp_r, preemphasis_tau, sample_rate); // #endregion
PLL rds2_pll; signal(SIGINT, stop);
init_pll(&rds2_pll, 66500, 19000, 100, 1, sample_rate); signal(SIGTERM, stop);
// #endregion
signal(SIGINT, stop); int pulse_error;
signal(SIGTERM, stop);
int pulse_error; float audio_stereo_input[BUFFER_SIZE*2];
float audio_stereo_input[BUFFER_SIZE*2]; float rds1_rds2_in[BUFFER_SIZE*2] = {0};
float rds1_in[BUFFER_SIZE] = {0};
float rds2_in[BUFFER_SIZE] = {0};
float rds1_rds2_in[BUFFER_SIZE*2] = {0}; float mpx_in[BUFFER_SIZE] = {0};
float rds1_in[BUFFER_SIZE] = {0}; float sca_in[BUFFER_SIZE] = {0};
float rds2_in[BUFFER_SIZE] = {0}; float left[BUFFER_SIZE], right[BUFFER_SIZE];
float output[BUFFER_SIZE];
while (to_run) {
if (pa_simple_read(input_device, audio_stereo_input, sizeof(audio_stereo_input), &pulse_error) < 0) {
fprintf(stderr, "Error reading from input device: %s\n", pa_strerror(pulse_error));
to_run = 0;
break;
}
uninterleave(audio_stereo_input, left, right, BUFFER_SIZE*2);
if(mpx_on) {
if (pa_simple_read(mpx_device, mpx_in, sizeof(mpx_in), &pulse_error) < 0) {
fprintf(stderr, "Error reading from MPX device: %s\n", pa_strerror(pulse_error));
to_run = 0;
break;
}
}
if(rds_on) {
if (pa_simple_read(rds_device, rds1_rds2_in, sizeof(rds1_rds2_in), &pulse_error) < 0) {
fprintf(stderr, "Error reading from RDS95 device: %s\n", pa_strerror(pulse_error));
to_run = 0;
break;
}
uninterleave(rds1_rds2_in, rds1_in, rds2_in, BUFFER_SIZE*2);
}
if(sca_on) {
if (pa_simple_read(sca_device, sca_in, sizeof(sca_in), &pulse_error) < 0) {
fprintf(stderr, "Error reading from SCA device: %s\n", pa_strerror(pulse_error));
to_run = 0;
break;
}
}
float mpx_in[BUFFER_SIZE] = {0}; for (int i = 0; i < BUFFER_SIZE; i++) {
float sca_in[BUFFER_SIZE] = {0}; float l_in = left[i];
float left[BUFFER_SIZE], right[BUFFER_SIZE]; float r_in = right[i];
float output[BUFFER_SIZE]; float current_mpx_in = mpx_in[i];
while (to_run) { float current_rds_in = rds1_in[i];
if (pa_simple_read(input_device, audio_stereo_input, sizeof(audio_stereo_input), &pulse_error) < 0) { float current_rds2_in = rds2_in[i];
fprintf(stderr, "Error reading from input device: %s\n", pa_strerror(pulse_error)); float current_sca_in = sca_in[i];
to_run = 0;
break;
}
uninterleave(audio_stereo_input, left, right, BUFFER_SIZE*2);
if(mpx_on) {
if (pa_simple_read(mpx_device, mpx_in, sizeof(mpx_in), &pulse_error) < 0) {
fprintf(stderr, "Error reading from MPX device: %s\n", pa_strerror(pulse_error));
to_run = 0;
break;
}
}
if(rds_on) {
if (pa_simple_read(rds_device, rds1_rds2_in, sizeof(rds1_rds2_in), &pulse_error) < 0) {
fprintf(stderr, "Error reading from RDS95 device: %s\n", pa_strerror(pulse_error));
to_run = 0;
break;
}
uninterleave(rds1_rds2_in, rds1_in, rds2_in, BUFFER_SIZE*2);
}
if(sca_on) {
if (pa_simple_read(sca_device, sca_in, sizeof(sca_in), &pulse_error) < 0) {
fprintf(stderr, "Error reading from SCA device: %s\n", pa_strerror(pulse_error));
to_run = 0;
break;
}
}
for (int i = 0; i < BUFFER_SIZE; i++) { float ready_l = apply_preemphasis(&preemp_l, l_in)*2;
float l_in = left[i]; float ready_r = apply_preemphasis(&preemp_r, r_in)*2;
float r_in = right[i]; ready_l = hard_clip(ready_l*audio_volume, clipper_threshold);
float current_mpx_in = mpx_in[i]; ready_r = hard_clip(ready_r*audio_volume, clipper_threshold);
float current_rds_in = rds1_in[i];
float current_rds2_in = rds2_in[i];
float current_sca_in = sca_in[i];
float ready_l = apply_preemphasis(&preemp_l, l_in)*2; float mono = (ready_l + ready_r) / 2.0f;
float ready_r = apply_preemphasis(&preemp_r, r_in)*2; output[i] = mono*MONO_VOLUME;
ready_l = hard_clip(ready_l*audio_volume, clipper_threshold); if(stereo) {
ready_r = hard_clip(ready_r*audio_volume, clipper_threshold); float stereo = (ready_l - ready_r) / 2.0f;
float stereo_carrier = get_oscillator_sin_multiplier_ni(&osc, polar_stereo ? 1 : 2);
float mono = (ready_l + ready_r) / 2.0f; if(polar_stereo) {
output[i] = mono*MONO_VOLUME; output[i] += ((stereo+0.2)*stereo_carrier)*STEREO_VOLUME;
if(stereo) { } else {
float stereo = (ready_l - ready_r) / 2.0f; float pilot = get_oscillator_sin_multiplier_ni(&osc, 1);
float stereo_carrier = get_oscillator_sin_multiplier_ni(&osc, polar_stereo ? 1 : 2); output[i] += pilot*PILOT_VOLUME +
(stereo*stereo_carrier)*STEREO_VOLUME;
}
}
if(rds_on && polar_stereo == 0) {
float rds_carrier = get_oscillator_sin_multiplier_ni(&osc, 3);
output[i] += (current_rds_in*rds_carrier)*RDS_VOLUME;
if(!sca_on) {
float rds2_carrier_66 = apply_pll(&rds2_pll, get_oscillator_sin_multiplier_ni(&osc, 1));
output[i] += (current_rds2_in*rds2_carrier_66)*RDS2_VOLUME;
}
}
if(rds_on || stereo) advance_oscillator(&osc);
if(mpx_on) output[i] += hard_clip(current_mpx_in, MPX_CLIPPER_THRESHOLD)*MPX_VOLUME;
if(sca_on) output[i] += modulate_fm(&sca_mod, hard_clip(current_sca_in, sca_clipper_threshold))*SCA_VOLUME;
output[i] *= master_volume;
}
if(polar_stereo) { if (pa_simple_write(output_device, output, sizeof(output), &pulse_error) < 0) {
output[i] += ((stereo+0.2)*stereo_carrier)*STEREO_VOLUME; fprintf(stderr, "Error writing to output device: %s\n", pa_strerror(pulse_error));
} else { to_run = 0;
float pilot = get_oscillator_sin_multiplier_ni(&osc, 1); break;
output[i] += pilot*PILOT_VOLUME + }
(stereo*stereo_carrier)*STEREO_VOLUME; }
} printf("Cleaning up...\n");
} pa_simple_free(input_device);
if(rds_on && polar_stereo == 0) { if(mpx_on) pa_simple_free(mpx_device);
float rds_carrier = get_oscillator_sin_multiplier_ni(&osc, 3); if(rds_on) pa_simple_free(rds_device);
output[i] += (current_rds_in*rds_carrier)*RDS_VOLUME; if(sca_on) pa_simple_free(sca_device);
if(!sca_on) { pa_simple_free(output_device);
float rds2_carrier_66 = apply_pll(&rds2_pll, get_oscillator_sin_multiplier_ni(&osc, 1), get_oscillator_cos_sample(&rds2_osc)); return 0;
output[i] += (current_rds2_in*rds2_carrier_66)*RDS2_VOLUME;
}
}
if(rds_on || stereo) advance_oscillator(&osc);
if(mpx_on) output[i] += hard_clip(current_mpx_in, MPX_CLIPPER_THRESHOLD)*MPX_VOLUME;
if(sca_on) output[i] += modulate_fm(&sca_mod, hard_clip(current_sca_in, sca_clipper_threshold))*SCA_VOLUME;
output[i] *= master_volume;
}
if (pa_simple_write(output_device, output, sizeof(output), &pulse_error) < 0) {
fprintf(stderr, "Error writing to output device: %s\n", pa_strerror(pulse_error));
to_run = 0;
break;
}
}
printf("Cleaning up...\n");
pa_simple_free(input_device);
if(mpx_on) pa_simple_free(mpx_device);
if(rds_on) pa_simple_free(rds_device);
if(sca_on) pa_simple_free(sca_device);
pa_simple_free(output_device);
return 0;
} }