387 lines
15 KiB
C
387 lines
15 KiB
C
/*
|
|
PiFmRds - FM/RDS transmitter for the Raspberry Pi
|
|
Copyright (C) 2014 Christophe Jacquet, F8FTK
|
|
|
|
See https://github.com/ChristopheJacquet/PiFmRds
|
|
|
|
rds_wav.c is a test program that writes a RDS baseband signal to a WAV
|
|
file. It requires libsndfile.
|
|
|
|
This program is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
(at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
fm_mpx.c: generates an FM multiplex signal containing RDS plus possibly
|
|
monaural or stereo audio.
|
|
*/
|
|
|
|
#include <sndfile.h>
|
|
#include <stdlib.h>
|
|
#include <strings.h>
|
|
#include <math.h>
|
|
|
|
#include "rds.h"
|
|
|
|
|
|
#define PI 3.14159265359
|
|
|
|
|
|
#define FIR_PHASES (32)
|
|
#define FIR_TAPS (32) // MUST be a power of 2 for the circular buffer
|
|
|
|
size_t length;
|
|
|
|
// coefficients of the low-pass FIR filter
|
|
float low_pass_fir[FIR_PHASES][FIR_TAPS];
|
|
|
|
|
|
float carrier_38[] = {0.0, 0.8660254037844386, 0.8660254037844388, 1.2246467991473532e-16, -0.8660254037844384, -0.8660254037844386};
|
|
|
|
float carrier_19[] = {0.0, 0.5, 0.8660254037844386, 1.0, 0.8660254037844388, 0.5, 1.2246467991473532e-16, -0.5, -0.8660254037844384, -1.0, -0.8660254037844386, -0.5}; //can someone generate a 31.25 khz carrier?
|
|
|
|
float carrier_3125[] = {0.0, 0.8660254037844386, 0.8660254037844388, 1.2246467991473532e-16, -0.8660254037844384, -0.8660254037844386};
|
|
|
|
int phase_38 = 0;
|
|
int phase_3125 = 0;
|
|
int phase_19 = 0;
|
|
|
|
|
|
float downsample_factor;
|
|
|
|
int raw_;
|
|
|
|
float *audio_buffer;
|
|
int audio_index = 0;
|
|
int audio_len = 0;
|
|
float audio_pos;
|
|
|
|
float fir_buffer_left[FIR_TAPS] = {0};
|
|
float fir_buffer_right[FIR_TAPS] = {0};
|
|
int fir_index = 0;
|
|
int channels;
|
|
float left_max=1, right_max=1; // start compressor with low gain
|
|
|
|
SNDFILE *inf;
|
|
|
|
|
|
|
|
float *alloc_empty_buffer(size_t length) {
|
|
float *p =(float *) malloc(length * sizeof(float));
|
|
if(p == NULL) return NULL;
|
|
|
|
bzero(p, length * sizeof(float));
|
|
|
|
return p;
|
|
}
|
|
|
|
|
|
int fm_mpx_open(char *filename, size_t len, int raw, double preemphasis, int rawSampleRate, int rawChannels, float cutoff_freq) {
|
|
length = len;
|
|
raw_ = raw;
|
|
|
|
if(filename != NULL) {
|
|
// Open the input file
|
|
SF_INFO sfinfo;
|
|
|
|
if(raw) {
|
|
sfinfo.format = SF_FORMAT_RAW | SF_FORMAT_PCM_16;
|
|
sfinfo.samplerate = rawSampleRate;
|
|
sfinfo.channels = rawChannels;
|
|
}
|
|
|
|
// stdin or file on the filesystem?
|
|
if(filename[0] == '-') {
|
|
if(! (inf = sf_open_fd(fileno(stdin), SFM_READ, &sfinfo, 0))) {
|
|
fprintf(stderr, "Error: could not open stdin for audio input.\n") ;
|
|
return -1;
|
|
} else {
|
|
printf("Using stdin for audio input.\n");
|
|
}
|
|
} else {
|
|
if(! (inf = sf_open(filename, SFM_READ, &sfinfo))) {
|
|
fprintf(stderr, "Error: could not open input file %s.\n", filename) ;
|
|
return -1;
|
|
} else {
|
|
printf("Using audio file: %s\n", filename);
|
|
}
|
|
}
|
|
|
|
int in_samplerate = sfinfo.samplerate;
|
|
downsample_factor = 228000. / in_samplerate;
|
|
|
|
printf("Input: %d Hz, upsampling factor: %.2f\n", in_samplerate, downsample_factor);
|
|
|
|
channels = sfinfo.channels;
|
|
if(channels > 1) {
|
|
printf("%d channels, generating stereo multiplex.\n", channels);
|
|
} else {
|
|
printf("1 channel, monophonic operation.\n");
|
|
}
|
|
|
|
// Choose a cutoff frequency for the low-pass FIR filter
|
|
if(in_samplerate/2 < cutoff_freq) cutoff_freq = in_samplerate/2 * .8;
|
|
|
|
|
|
// Create the low-pass FIR filter, with pre-emphasis
|
|
double window, firlowpass, firpreemph , sincpos;
|
|
double taup, deltap, bp, ap, a0, a1, b1;
|
|
if(preemphasis != 0) {
|
|
// IIR pre-emphasis filter
|
|
// Reference material: http://jontio.zapto.org/hda1/preempiir.pdf
|
|
double tau=preemphasis; //why? well im gonna listen to rule: "if it works dont touch it"
|
|
double delta=1/(2*PI*20000);//double delta=1.96e-6;
|
|
taup=1.0/(2.0*(in_samplerate*FIR_PHASES))/tan( 1.0/(2*tau*(in_samplerate*FIR_PHASES) ));
|
|
deltap=1.0/(2.0*(in_samplerate*FIR_PHASES))/tan( 1.0/(2*delta*(in_samplerate*FIR_PHASES) ));
|
|
bp=sqrt( -taup*taup + sqrt(taup*taup*taup*taup + 8.0*taup*taup*deltap*deltap) ) / 2.0 ;
|
|
ap=sqrt( 2*bp*bp + taup*taup );
|
|
a0=( 2.0*ap + 1.0/(in_samplerate*FIR_PHASES) )/(2.0*bp + 1.0/(in_samplerate*FIR_PHASES) );
|
|
a1=(-2.0*ap + 1.0/(in_samplerate*FIR_PHASES) )/(2.0*bp + 1.0/(in_samplerate*FIR_PHASES) );
|
|
b1=( 2.0*bp - 1.0/(in_samplerate*FIR_PHASES) )/(2.0*bp + 1.0/(in_samplerate*FIR_PHASES) );
|
|
}
|
|
double x=0,y=0;
|
|
|
|
for(int i=0; i<FIR_TAPS; i++) {
|
|
for(int j=0; j<FIR_PHASES; j++) {
|
|
int mi=i*FIR_PHASES + j+1;// match indexing of Matlab script
|
|
sincpos = (mi)-(((FIR_TAPS*FIR_PHASES)+1.0)/2.0); // offset by 0.5 so sincpos!=0 (causes NaN x/0 )
|
|
//printf("%d=%f \n",mi ,sincpos);
|
|
firlowpass = sin(2 * PI * cutoff_freq * sincpos / (in_samplerate*FIR_PHASES) ) / (PI * sincpos) ;
|
|
// Find the combined impulse response
|
|
if(preemphasis != 0) {
|
|
y=a0*firlowpass + a1*x + b1*y;
|
|
} else {
|
|
y=firlowpass;
|
|
}
|
|
x=firlowpass; // of FIR low-pass and IIR pre-emphasis
|
|
firpreemph=y; // y could be replaced by firpreemph but this
|
|
// matches the example in the reference material
|
|
|
|
|
|
window = (.54 - .46 * cos(2*PI * (mi) / (double) FIR_TAPS*FIR_PHASES )) ; // Hamming window
|
|
low_pass_fir[j][i] = firpreemph * window;
|
|
}
|
|
}
|
|
|
|
printf("Created low-pass FIR filter for audio channels, with cutoff at %.1f Hz\n", cutoff_freq);
|
|
|
|
if( 0 )
|
|
{
|
|
printf("f = [ ");
|
|
for(int i=0; i<FIR_TAPS; i++) {
|
|
for(int j=0; j<FIR_PHASES; j++) {
|
|
printf("%.5f ", low_pass_fir[j][i]);
|
|
}
|
|
}
|
|
printf("]; \n");
|
|
}
|
|
|
|
audio_pos = downsample_factor;
|
|
audio_buffer = alloc_empty_buffer(length * channels);
|
|
if(audio_buffer == NULL) return -1;
|
|
|
|
} // end if(filename != NULL)
|
|
else {
|
|
inf = NULL;
|
|
// inf == NULL indicates that there is no audio
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
// samples provided by this function are in 0..10: they need to be divided by
|
|
// 10 after.
|
|
int fm_mpx_get_samples(float *mpx_buffer, int drds, float compressor_decay, float compressor_attack, float compressor_max_gain_recip, int disablestereo, float gain, int enablecompressor, int rds_ct_enabled, float rds_volume, int paused, float pilot_volume, int generate_multiplex) {
|
|
audio_buffer = 0.0; //in order to avoild what i call "frame looping", so the exact same thing isnt played, if the audio stream is cut off
|
|
int stereo_capable = (channels > 1) && (!disablestereo); //chatgpt
|
|
if(!drds && generate_multiplex) get_rds_samples(mpx_buffer, length, stereo_capable, rds_ct_enabled, rds_volume);
|
|
|
|
if(inf == NULL) return 0; // if there is no audio, stop here
|
|
|
|
for(int i=0; i<length; i++) {
|
|
if(audio_pos >= downsample_factor) {
|
|
audio_pos -= downsample_factor;
|
|
|
|
if(audio_len <=channels ) {
|
|
for(int j=0; j<2; j++) { // one retry
|
|
audio_len = sf_read_float(inf, audio_buffer, length);
|
|
if (audio_len < 0) {
|
|
fprintf(stderr, "Error reading audio\n");
|
|
return -1;
|
|
}
|
|
if(audio_len == 0) {
|
|
if( sf_seek(inf, 0, SEEK_SET) < 0 ) {
|
|
if(!raw_) {
|
|
fprintf(stderr, "Could not rewind in audio file, terminating\n");
|
|
return -1;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
audio_index = 0;
|
|
} else {
|
|
audio_index += channels;
|
|
audio_len -= channels;
|
|
}
|
|
|
|
fir_index++; // fir_index will point to newest valid data soon
|
|
if(fir_index >= FIR_TAPS) fir_index = 0;
|
|
// Store the current sample(s) into the FIR filter's ring buffer
|
|
fir_buffer_left[fir_index] = audio_buffer[audio_index];
|
|
if(channels > 1) {
|
|
fir_buffer_right[fir_index] = audio_buffer[audio_index+1];
|
|
}
|
|
} // if need new sample
|
|
|
|
// Polyphase FIR filter
|
|
float out_left = 0;
|
|
float out_right = 0;
|
|
// Calculate which FIR phase to use
|
|
//int iphase = FIR_PHASES-1 - ((int) (audio_pos/downsample_factor*FIR_PHASES) );
|
|
int iphase = ((int) (audio_pos*FIR_PHASES/downsample_factor) );// I think this is correct
|
|
//int iphase=FIR_PHASES-1; // test override
|
|
//printf("%d %d \n",fir_index,iphase); // diagnostics
|
|
// Sanity checks
|
|
if ( iphase < 0 ) {iphase=0; printf("low\n"); }// Seems to run faster with these checks in place
|
|
if ( iphase >= FIR_PHASES ) {iphase=FIR_PHASES-2; printf("high\n"); }
|
|
|
|
if( channels > 1 )
|
|
{
|
|
for(int fi=0; fi<FIR_TAPS; fi++) // fi = Filter Index
|
|
{ // use bit masking to implement circular buffer
|
|
out_left+= low_pass_fir[iphase][fi]*fir_buffer_left[(fir_index-fi)&(FIR_TAPS-1)];
|
|
out_right+=low_pass_fir[iphase][fi]*fir_buffer_right[(fir_index-fi)&(FIR_TAPS-1)];
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for(int fi=0; fi<FIR_TAPS; fi++) // fi = Filter Index
|
|
{ // use bit masking to implement circular buffer
|
|
out_left+=low_pass_fir[iphase][fi] * fir_buffer_left[(fir_index-fi)&(FIR_TAPS-1)];
|
|
}
|
|
}
|
|
|
|
//Add the gain
|
|
out_left = out_left * gain;
|
|
if(channels > 1) out_right = out_right * gain;
|
|
|
|
// Simple broadcast compressor
|
|
//
|
|
// The goal is to get the loudest sounding audio while
|
|
// keeping the deviation within legal limits, and
|
|
// without degrading the audio quality significantly.
|
|
// Don't expect this simple code to match the
|
|
// performance of commercial broadcast equipment.
|
|
float left_abs, right_abs;
|
|
// Setting attack to anything other than 1.0 could cause overshoot.
|
|
left_abs=fabsf(out_left);
|
|
if( left_abs>left_max )
|
|
{
|
|
left_max+= (left_abs-left_max)*compressor_attack;
|
|
}
|
|
else
|
|
{
|
|
left_max*=compressor_decay;
|
|
}
|
|
|
|
if( channels > 1 )
|
|
{
|
|
right_abs=fabsf(out_right);
|
|
if( right_abs>right_max )
|
|
{
|
|
right_max+= (right_abs-right_max)*compressor_attack;
|
|
}
|
|
else
|
|
{
|
|
right_max*=compressor_decay;
|
|
}
|
|
if( 1 )// Experimental joint compressor mode
|
|
{
|
|
if( left_max > right_max )
|
|
right_max=left_max;
|
|
else if( left_max < right_max )
|
|
left_max=right_max;
|
|
}
|
|
if(enablecompressor) out_right=out_right/(right_max+compressor_max_gain_recip);
|
|
}
|
|
if(enablecompressor) out_left= out_left/(left_max+compressor_max_gain_recip); // Adjust volume with limited maximum gain
|
|
if(drds) mpx_buffer[i] = 0.0; //do not remove this, the bandwidht will go nuts
|
|
if(paused) {
|
|
out_left = 0;
|
|
if(channels > 1) out_right = 0;
|
|
}
|
|
|
|
// Generate the stereo mpx
|
|
if( channels > 1 ) {
|
|
if(!disablestereo) {
|
|
if(generate_multiplex) {
|
|
if(1) {
|
|
mpx_buffer[i] += 4.05*(out_left+out_right) + // Stereo sum signal
|
|
4.05 * carrier_38[phase_38] * (out_left-out_right) + // Stereo difference signal
|
|
pilot_volume*carrier_19[phase_19]; // Stereo pilot tone
|
|
|
|
phase_19++;
|
|
phase_38++;
|
|
if(phase_19 >= 12) phase_19 = 0;
|
|
if(phase_38 >= 6) phase_38 = 0;
|
|
} else { // polar stereo (https://forums.stereotool.com/viewtopic.php?t=6233, https://personal.utdallas.edu/~dlm/3350%20comm%20sys/ITU%20std%20on%20FM%20--%20R-REC-BS.450-3-200111-I!!PDF-E.pdf)
|
|
mpx_buffer[i] += 4.05*(out_left+out_right) + // Stereo sum signal (L+R)
|
|
4.05 * carrier_3125[phase_3125] * (out_left-out_right); // Stereo difference signal
|
|
//NO PIOT TONE!!!!!!!!!!!!!!!!!!!!!!!!!!!! (its missplelled correctly probably just like misspelled)
|
|
|
|
phase_3125++;
|
|
if(phase_3125 >= 6) phase_3125 = 0;
|
|
}
|
|
}
|
|
} else {
|
|
if(generate_multiplex) {
|
|
mpx_buffer[i] =
|
|
mpx_buffer[i] +
|
|
4.05*(out_left+out_right); // Unmodulated L+R signal
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if(generate_multiplex) {
|
|
mpx_buffer[i] =
|
|
mpx_buffer[i] +
|
|
4.05*out_left; // Unmodulated monophonic signal
|
|
}
|
|
}
|
|
if(!generate_multiplex) {
|
|
mpx_buffer[i] =
|
|
0.0; //nothing, rpitx works like this (i think): theres a array with data and rpitx goes thought it to transmit it, now here the functions with the mpx_buffer such as this one update the array, but what if the array is not updated? well, then it keeps transmitting the exact same thing, it doesnt update whats its transmitting, no really, take a sdr and remove this and turn off the mpx gen, if no music then look at rds
|
|
}
|
|
|
|
audio_pos++;
|
|
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
int fm_mpx_close() {
|
|
if(sf_close(inf) ) {
|
|
fprintf(stderr, "Error closing audio file");
|
|
}
|
|
|
|
if(audio_buffer != NULL) free(audio_buffer);
|
|
|
|
return 0;
|
|
}
|