0
1
mirror of https://github.com/radio95-rnt/fm95.git synced 2026-02-26 11:22:00 +01:00
This commit is contained in:
2024-12-31 16:10:21 +01:00
parent c96fe78618
commit 1868cd98ca
16 changed files with 199 additions and 279 deletions

3
.gitignore vendored
View File

@@ -1,5 +1,4 @@
stereo_coder
sca_mod
build/
# Prerequisites
*.d

View File

@@ -1,5 +1,5 @@
{
"port": 13452,
"time": 1735641535890,
"time": 1735655261170,
"version": "0.0.3"
}

8
.vscode/settings.json vendored Normal file
View File

@@ -0,0 +1,8 @@
{
"files.associations": {
"filters.h": "c",
"features.h": "c",
"constants.h": "c",
"oscillator.h": "c"
}
}

33
CMakeLists.txt Normal file
View File

@@ -0,0 +1,33 @@
# Set the minimum required CMake version
cmake_minimum_required(VERSION 3.10)
# Define the project name and language
project(FMTools LANGUAGES C)
# Set the C standard (you can adjust this based on your project needs)
set(CMAKE_C_STANDARD 99)
set(CMAKE_C_STANDARD_REQUIRED YES)
# Find all C source files in the src/ directory
file(GLOB SRC_FILES "src/*.c")
# Find all C source files in the lib/ directory
file(GLOB LIB_FILES "lib/*.c")
# Create a library to hold all object files from lib/
add_library(libfm OBJECT ${LIB_FILES})
# Linker flags for libraries
set(LINK_LIBS "-lpulse -lpulse-simple -lm")
# Loop through each file in src and create an executable
foreach(SRC_FILE ${SRC_FILES})
# Get the filename without the directory and extension
get_filename_component(EXEC_NAME ${SRC_FILE} NAME_WE)
# Create the executable from each source file
add_executable(${EXEC_NAME} ${SRC_FILE})
# Link the necessary libraries and object files from lib/
target_link_libraries(${EXEC_NAME} PRIVATE libfm ${LINK_LIBS})
endforeach()

View File

@@ -1,3 +1,6 @@
# FMTools
FMTools is a repository of apps you can use to make your FM broadcast better, pirate or not this will help you if you don't have something, maybe you want a better stereo encoder? SCA? or even went crazy and decided to do quadrophonic? We have what you need, for RDS just use MiniRDS
# STCode
STCode is a simple stereo encoder for FM, it uses pasimple and math to:
- Calculate mono signal ((L+R)/2)
@@ -9,4 +12,7 @@ All that in about 3.5% cpu usage on a RPI-5 (lpf makes it 10, but stereo tool ha
Also nearly no latency, not like Stereo Tool (or mpxgen which doesn't even work)
# SCAMod
SCAMod is a simple FM modulator which can be used to modulate a secondary audio stream, has similiar cpu usage and latency as STCode
SCAMod is a simple FM modulator which can be used to modulate a secondary audio stream, has similiar cpu usage and latency as STCode
# QDCode
QD code is a FM quadrophonic encoder, following the Dorren standard

View File

@@ -1 +0,0 @@
gcc sca_mod.c -lpulse -lpulse-simple -lm -o sca_mod

View File

@@ -1 +0,0 @@
gcc stereo_coder.c -lpulse -lpulse-simple -lm -o stereo_coder

6
lib/constants.h Normal file
View File

@@ -0,0 +1,6 @@
#ifndef PI
#define PI 3.14159265358979323846
#endif
#ifndef M_2PI
#define M_2PI (3.14159265358979323846 * 2.0)
#endif

41
lib/filters.c Normal file
View File

@@ -0,0 +1,41 @@
#include "filters.h"
void init_emphasis(Emphasis *pe, float tau, float sample_rate) {
pe->prev_sample = 0.0f;
pe->alpha = exp(-1 / (tau * sample_rate));
}
float apply_pre_emphasis(Emphasis *pe, float sample) {
float audio = sample-pe->alpha*pe->prev_sample;
pe->prev_sample = audio;
return audio*2;
}
void init_low_pass_filter(LowPassFilter *lp, float cutoff_frequency, float sample_rate) {
for (int i = 0; i < FIR_TAPS; i++) {
for (int j = 0; j < FIR_PHASES; j++) {
int mi = i * FIR_PHASES + j + 1;
float sincpos = mi - (((FIR_TAPS * FIR_PHASES) + 1.0f) / 2.0f);
float firlowpass = (sincpos == 0.0f) ? 1.0f : sinf(M_2PI * cutoff_frequency * sincpos / sample_rate) / (PI * sincpos);
float window = 0.54f - 0.46f * cosf(M_2PI * mi / (FIR_TAPS * FIR_PHASES)); // Hamming window
lp->low_pass_fir[j][i] = firlowpass * window;
}
}
memset(lp->sample_buffer, 0, sizeof(lp->sample_buffer));
lp->buffer_index = 0;
}
float apply_low_pass_filter(LowPassFilter *lp, float sample) {
// Update the sample buffer
lp->sample_buffer[lp->buffer_index] = sample;
lp->buffer_index = (lp->buffer_index + 1) % FIR_TAPS;
// Apply the filter
float result = 0.0f;
int index = lp->buffer_index;
for (int i = 0; i < FIR_TAPS; i++) {
result += lp->low_pass_fir[0][i] * lp->sample_buffer[index];
index = (index + 1) % FIR_TAPS;
}
return result*6;
}

23
lib/filters.h Normal file
View File

@@ -0,0 +1,23 @@
#include "math.h"
#include <string.h>
#include "constants.h"
#define FIR_PHASES 32
#define FIR_TAPS 32
typedef struct {
float alpha;
float prev_sample;
} Emphasis;
void init_emphasis(Emphasis *pe, float tau, float sample_rate);
float apply_pre_emphasis(Emphasis *pe, float sample);
typedef struct {
float low_pass_fir[FIR_PHASES][FIR_TAPS];
float sample_buffer[FIR_TAPS];
int buffer_index;
} LowPassFilter;
void init_low_pass_filter(LowPassFilter *lp, float cutoff_frequency, float sample_rate);
float apply_low_pass_filter(LowPassFilter *lp, float sample);

29
lib/oscillator.c Normal file
View File

@@ -0,0 +1,29 @@
#include "oscillator.h"
void init_oscillator(Oscillator *osc, float frequency, float sample_rate) {
osc->phase = 0.0f;
osc->phase_increment = (M_2PI * frequency) / sample_rate;
osc->sample_rate = sample_rate;
}
void change_oscillator_frequency(Oscillator *osc, float frequency) {
osc->phase_increment = (M_2PI * frequency) / osc->sample_rate;
}
float get_oscillator_sin_sample(Oscillator *osc) {
float sample = sinf(osc->phase);
osc->phase += osc->phase_increment;
if (osc->phase >= M_2PI) {
osc->phase -= M_2PI;
}
return sample;
}
float get_oscillator_cos_sample(Oscillator *osc) {
float sample = cosf(osc->phase);
osc->phase += osc->phase_increment;
if (osc->phase >= M_2PI) {
osc->phase -= M_2PI;
}
return sample;
}

13
lib/oscillator.h Normal file
View File

@@ -0,0 +1,13 @@
#include "constants.h"
#include "math.h"
typedef struct {
float phase;
float phase_increment;
float sample_rate;
} Oscillator;
void init_oscillator(Oscillator *osc, float frequency, float sample_rate);
void change_oscillator_frequency(Oscillator *osc, float frequency);
float get_oscillator_sin_sample(Oscillator *osc);
float get_oscillator_cos_sample(Oscillator *osc);

2
src/features.h Normal file
View File

@@ -0,0 +1,2 @@
// #define PREEMPHASIS
#define LPF

View File

@@ -8,9 +8,12 @@
#include <signal.h>
#include <string.h>
#include "../lib/constants.h"
#include "../lib/oscillator.h"
#include "../lib/filters.h"
// Features
#define PREEMPHASIS
#define LPF
#include "features.h"
#define SAMPLE_RATE 192000 // Don't go lower than 182 KHz (91*2)
@@ -20,7 +23,7 @@
#define CLIPPER_THRESHOLD 0.425 // Adjust this as needed
#define MONO_VOLUME 0.45f // L+R Signal
#define PILOT_VOLUME 0.0225f // 19 KHz Pilot
#define PILOT_VOLUME 0.0175f // 19 KHz Pilot
#define SIN38_VOLUME 0.35f
#define COS38_VOLUME 0.35f
#define SIN76_VOLUME 0.35f
@@ -54,88 +57,6 @@ void uninterleave(const float *input, float *front_left, float *front_right, flo
}
}
#define FIR_PHASES 32
#define FIR_TAPS 32
#define PI 3.14159265358979323846
#define M_2PI (3.14159265358979323846 * 2.0)
// Track phase continuously to maintain frequency accuracy
typedef struct {
float phase;
float phase_increment;
} Oscillator;
void init_oscillator(Oscillator *osc, float frequency, float sample_rate) {
osc->phase = 0.0f;
osc->phase_increment = (M_2PI * frequency) / sample_rate;
}
float get_next_sample(Oscillator *osc) {
float sample = sinf(osc->phase);
osc->phase += osc->phase_increment;
if (osc->phase >= M_2PI) {
osc->phase -= M_2PI;
}
return sample;
}
#ifdef PREEMPHASIS
typedef struct {
float alpha;
float prev_sample;
} Emphasis;
void init_emphasis(Emphasis *pe, float sample_rate) {
pe->prev_sample = 0.0f;
pe->alpha = exp(-1 / (PREEMPHASIS_TAU * sample_rate));
}
float apply_pre_emphasis(Emphasis *pe, float sample) {
float audio = sample-pe->alpha*pe->prev_sample;
pe->prev_sample = audio;
return audio*2;
}
#endif
#ifdef LPF
typedef struct {
float low_pass_fir[FIR_PHASES][FIR_TAPS];
float sample_buffer[FIR_TAPS];
int buffer_index;
} LowPassFilter;
void init_low_pass_filter(LowPassFilter *lp, float sample_rate) {
for (int i = 0; i < FIR_TAPS; i++) {
for (int j = 0; j < FIR_PHASES; j++) {
int mi = i * FIR_PHASES + j + 1;
float sincpos = mi - (((FIR_TAPS * FIR_PHASES) + 1.0f) / 2.0f);
float firlowpass = (sincpos == 0.0f) ? 1.0f : sinf(M_2PI * LPF_CUTOFF * sincpos / sample_rate) / (PI * sincpos);
float window = 0.54f - 0.46f * cosf(M_2PI * mi / (FIR_TAPS * FIR_PHASES)); // Hamming window
lp->low_pass_fir[j][i] = firlowpass * window;
}
}
memset(lp->sample_buffer, 0, sizeof(lp->sample_buffer));
lp->buffer_index = 0;
}
float apply_low_pass_filter(LowPassFilter *lp, float sample) {
// Update the sample buffer
lp->sample_buffer[lp->buffer_index] = sample;
lp->buffer_index = (lp->buffer_index + 1) % FIR_TAPS;
// Apply the filter
float result = 0.0f;
int index = lp->buffer_index;
for (int i = 0; i < FIR_TAPS; i++) {
result += lp->low_pass_fir[0][i] * lp->sample_buffer[index];
index = (index + 1) % FIR_TAPS;
}
return result*6;
}
#endif
static void stop(int signum) {
(void)signum;
printf("\nReceived stop signal. Cleaning up...\n");
@@ -207,17 +128,17 @@ int main() {
init_oscillator(&pilot_osc, 19000.0, SAMPLE_RATE); // Pilot, it's there to indicate stereo and as a refrence signal with the stereo carrier
#ifdef PREEMPHASIS
Emphasis preemp_lf, preemp_lr, preemp_rf, preemp_rr;
init_emphasis(&preemp_lf, SAMPLE_RATE);
init_emphasis(&preemp_lr, SAMPLE_RATE);
init_emphasis(&preemp_rf, SAMPLE_RATE);
init_emphasis(&preemp_rr, SAMPLE_RATE);
init_emphasis(&preemp_lf, PREEMPHASIS_TAU, SAMPLE_RATE);
init_emphasis(&preemp_lr, PREEMPHASIS_TAU, SAMPLE_RATE);
init_emphasis(&preemp_rf, PREEMPHASIS_TAU, SAMPLE_RATE);
init_emphasis(&preemp_rr, PREEMPHASIS_TAU, SAMPLE_RATE);
#endif
#ifdef LPF
LowPassFilter lpf_lf, lpf_lr, lpf_rf, lpf_rr;
init_low_pass_filter(&lpf_lf, SAMPLE_RATE);
init_low_pass_filter(&lpf_lr, SAMPLE_RATE);
init_low_pass_filter(&lpf_rf, SAMPLE_RATE);
init_low_pass_filter(&lpf_rr, SAMPLE_RATE);
init_low_pass_filter(&lpf_lf, LPF_CUTOFF, SAMPLE_RATE);
init_low_pass_filter(&lpf_lr, LPF_CUTOFF, SAMPLE_RATE);
init_low_pass_filter(&lpf_rf, LPF_CUTOFF, SAMPLE_RATE);
init_low_pass_filter(&lpf_rr, LPF_CUTOFF, SAMPLE_RATE);
#endif
signal(SIGINT, stop);
@@ -238,7 +159,7 @@ int main() {
float sin38 = sinf((pilot_osc.phase+(0.5*PI))*2);
float cos38 = cosf((pilot_osc.phase+(0.5*PI))*2);
float sin76 = sinf((pilot_osc.phase+(0.5*PI))*4);
float pilot = get_next_sample(&pilot_osc);
float pilot = get_oscillator_sin_sample(&pilot_osc);
float lf_in = left_front[i];
float lr_in = left_rear[i];
float rf_in = right_front[i];

View File

@@ -6,9 +6,12 @@
#include <signal.h>
#include <string.h>
#include "../lib/constants.h"
#include "../lib/oscillator.h"
#include "../lib/filters.h"
// Features
// #define PREEMPHASIS
#define LPF
#include "features.h"
#define SAMPLE_RATE 192000
@@ -41,90 +44,6 @@ float clip(float sample) {
}
}
#define FIR_PHASES 32
#define FIR_TAPS 32
#define PI 3.14159265358979323846
#define M_2PI (3.14159265358979323846 * 2.0)
// Track phase continuously to maintain frequency accuracy
typedef struct {
float phase;
float frequency;
float sample_rate;
} Oscillator;
void init_oscillator(Oscillator *osc, float frequency, float sample_rate) {
osc->phase = 0.0f;
osc->frequency = frequency;
osc->sample_rate = sample_rate;
}
float get_next_sample(Oscillator *osc) {
float phase_increment = (M_2PI * osc->frequency) / osc->sample_rate; // If you want to have dynamic frequency changing you have to compute this every sample
float sample = sinf(osc->phase);
osc->phase += phase_increment;
if (osc->phase >= M_2PI) {
osc->phase -= M_2PI;
}
return sample;
}
#ifdef PREEMPHASIS
typedef struct {
float alpha;
float prev_sample;
} Emphasis;
void init_emphasis(Emphasis *pe, float sample_rate) {
pe->prev_sample = 0.0f;
pe->alpha = exp(-1 / (PREEMPHASIS_TAU * sample_rate));
}
float apply_pre_emphasis(Emphasis *pe, float sample) {
float audio = sample-pe->alpha*pe->prev_sample;
pe->prev_sample = audio;
return audio*2;
}
#endif
#ifdef LPF
typedef struct {
float low_pass_fir[FIR_PHASES][FIR_TAPS];
float sample_buffer[FIR_TAPS];
int buffer_index;
} LowPassFilter;
void init_low_pass_filter(LowPassFilter *lp, float sample_rate) {
for (int i = 0; i < FIR_TAPS; i++) {
for (int j = 0; j < FIR_PHASES; j++) {
int mi = i * FIR_PHASES + j + 1;
float sincpos = mi - (((FIR_TAPS * FIR_PHASES) + 1.0f) / 2.0f);
float firlowpass = (sincpos == 0.0f) ? 1.0f : sinf(M_2PI * LPF_CUTOFF * sincpos / sample_rate) / (PI * sincpos);
float window = 0.54f - 0.46f * cosf(M_2PI * mi / (FIR_TAPS * FIR_PHASES)); // Hamming window
lp->low_pass_fir[j][i] = firlowpass * window;
}
}
memset(lp->sample_buffer, 0, sizeof(lp->sample_buffer));
lp->buffer_index = 0;
}
float apply_low_pass_filter(LowPassFilter *lp, float sample) {
// Update the sample buffer
lp->sample_buffer[lp->buffer_index] = sample;
lp->buffer_index = (lp->buffer_index + 1) % FIR_TAPS;
// Apply the filter
float result = 0.0f;
int index = lp->buffer_index;
for (int i = 0; i < FIR_TAPS; i++) {
result += lp->low_pass_fir[0][i] * lp->sample_buffer[index];
index = (index + 1) % FIR_TAPS;
}
return result*6;
}
#endif
static void stop(int signum) {
(void)signum;
printf("\nReceived stop signal. Cleaning up...\n");
@@ -192,11 +111,11 @@ int main() {
init_oscillator(&osc, FREQUENCY, SAMPLE_RATE);
#ifdef PREEMPHASIS
Emphasis preemp;
init_emphasis(&preemp, SAMPLE_RATE);
init_emphasis(&preemp, PREEMPHASIS_TAU, SAMPLE_RATE);
#endif
#ifdef LPF
LowPassFilter lpf;
init_low_pass_filter(&lpf, SAMPLE_RATE);
init_low_pass_filter(&lpf, LPF_CUTOFF, SAMPLE_RATE);
#endif
signal(SIGINT, stop);
@@ -231,8 +150,8 @@ int main() {
#endif
#endif
osc.frequency = (FREQUENCY+(current_input*DEVIATION));
signal[i] = get_next_sample(&osc)*VOLUME;
change_oscillator_frequency(&osc, (FREQUENCY+(current_input*DEVIATION)));
signal[i] = get_oscillator_sin_sample(&osc)*VOLUME;
}
if (pa_simple_write(output_device, signal, sizeof(signal), NULL) < 0) {

View File

@@ -6,9 +6,12 @@
#include <signal.h>
#include <string.h>
#include "../lib/constants.h"
#include "../lib/oscillator.h"
#include "../lib/filters.h"
// Features
// #define PREEMPHASIS
#define LPF
#include "features.h"
#define SAMPLE_RATE 192000 // Don't go lower than 108 KHz, becuase it (53000*2) and (38000+15000)
@@ -18,7 +21,7 @@
#define CLIPPER_THRESHOLD 0.425 // Adjust this as needed
#define MONO_VOLUME 0.45f // L+R Signal
#define PILOT_VOLUME 0.0225f // 19 KHz Pilot
#define PILOT_VOLUME 0.0175f // 19 KHz Pilot
#define STEREO_VOLUME 0.35f // L-R signal
#ifdef PREEMPHASIS
@@ -49,87 +52,6 @@ void uninterleave(const float *input, float *left, float *right, size_t num_samp
}
}
#define FIR_PHASES 32
#define FIR_TAPS 32
#define PI 3.14159265358979323846
#define M_2PI (3.14159265358979323846 * 2.0)
// Track phase continuously to maintain frequency accuracy
typedef struct {
float phase;
float phase_increment;
} Oscillator;
void init_oscillator(Oscillator *osc, float frequency, float sample_rate) {
osc->phase = 0.0f;
osc->phase_increment = (M_2PI * frequency) / sample_rate;
}
float get_next_sample(Oscillator *osc) {
float sample = sinf(osc->phase);
osc->phase += osc->phase_increment;
if (osc->phase >= M_2PI) {
osc->phase -= M_2PI;
}
return sample;
}
#ifdef PREEMPHASIS
typedef struct {
float alpha;
float prev_sample;
} Emphasis;
void init_emphasis(Emphasis *pe, float sample_rate) {
pe->prev_sample = 0.0f;
pe->alpha = exp(-1 / (PREEMPHASIS_TAU * sample_rate));
}
float apply_pre_emphasis(Emphasis *pe, float sample) {
float audio = sample-pe->alpha*pe->prev_sample;
pe->prev_sample = audio;
return audio*2;
}
#endif
#ifdef LPF
typedef struct {
float low_pass_fir[FIR_PHASES][FIR_TAPS];
float sample_buffer[FIR_TAPS];
int buffer_index;
} LowPassFilter;
void init_low_pass_filter(LowPassFilter *lp, float sample_rate) {
for (int i = 0; i < FIR_TAPS; i++) {
for (int j = 0; j < FIR_PHASES; j++) {
int mi = i * FIR_PHASES + j + 1;
float sincpos = mi - (((FIR_TAPS * FIR_PHASES) + 1.0f) / 2.0f);
float firlowpass = (sincpos == 0.0f) ? 1.0f : sinf(M_2PI * LPF_CUTOFF * sincpos / sample_rate) / (PI * sincpos);
float window = 0.54f - 0.46f * cosf(M_2PI * mi / (FIR_TAPS * FIR_PHASES)); // Hamming window
lp->low_pass_fir[j][i] = firlowpass * window;
}
}
memset(lp->sample_buffer, 0, sizeof(lp->sample_buffer));
lp->buffer_index = 0;
}
float apply_low_pass_filter(LowPassFilter *lp, float sample) {
// Update the sample buffer
lp->sample_buffer[lp->buffer_index] = sample;
lp->buffer_index = (lp->buffer_index + 1) % FIR_TAPS;
// Apply the filter
float result = 0.0f;
int index = lp->buffer_index;
for (int i = 0; i < FIR_TAPS; i++) {
result += lp->low_pass_fir[0][i] * lp->sample_buffer[index];
index = (index + 1) % FIR_TAPS;
}
return result*6;
}
#endif
static void stop(int signum) {
(void)signum;
printf("\nReceived stop signal. Cleaning up...\n");
@@ -201,13 +123,13 @@ int main() {
init_oscillator(&pilot_osc, 19000.0, SAMPLE_RATE); // Pilot, it's there to indicate stereo and as a refrence signal with the stereo carrier
#ifdef PREEMPHASIS
Emphasis preemp_l, preemp_r;
init_emphasis(&preemp_l, SAMPLE_RATE);
init_emphasis(&preemp_r, SAMPLE_RATE);
init_emphasis(&preemp_l, PREEMPHASIS_TAU, SAMPLE_RATE);
init_emphasis(&preemp_r, PREEMPHASIS_TAU, SAMPLE_RATE);
#endif
#ifdef LPF
LowPassFilter lpf_l, lpf_r;
init_low_pass_filter(&lpf_l, SAMPLE_RATE);
init_low_pass_filter(&lpf_r, SAMPLE_RATE);
init_low_pass_filter(&lpf_l, LPF_CUTOFF, SAMPLE_RATE);
init_low_pass_filter(&lpf_r, LPF_CUTOFF, SAMPLE_RATE);
#endif
signal(SIGINT, stop);
@@ -225,7 +147,7 @@ int main() {
for (int i = 0; i < BUFFER_SIZE; i++) {
float stereo_carrier = sinf(pilot_osc.phase*2); // Stereo carrier should be a harmonic of the pilot which is in phase, best way to generate the harmonic is to multiply the pilot's phase by two, so it is mathematically impossible for them to not be in phase
float pilot = get_next_sample(&pilot_osc); // This is after because if it was before then the stereo would be out of phase by one increment, so [GET STEREO] ([GET PILOT] [INCREMENT PHASE])
float pilot = get_oscillator_sin_sample(&pilot_osc); // This is after because if it was before then the stereo would be out of phase by one increment, so [GET STEREO] ([GET PILOT] [INCREMENT PHASE])
float l_in = left[i];
float r_in = right[i];