rf-tools/src/broadcast_fm/broadcast_fm.c

526 lines
18 KiB
C

///////////////////////////////////////////////////////////////////////////////////
//-------------------------------------------------------------------------------//
//-------------------------------------------------------------------------------//
//-----------H----H--X----X-----CCCCC----22222----0000-----0000------11----------//
//----------H----H----X-X-----C--------------2---0----0---0----0--1--1-----------//
//---------HHHHHH-----X------C----------22222---0----0---0----0-----1------------//
//--------H----H----X--X----C----------2-------0----0---0----0-----1-------------//
//-------H----H---X-----X---CCCCC-----222222----0000-----0000----1111------------//
//-------------------------------------------------------------------------------//
//----------------------------------------------------- http://hxc2001.free.fr --//
///////////////////////////////////////////////////////////////////////////////////
// File : broadcast_fm.c
// Contains: a broadcast FM Stereo modulator
//
// This file is part of rf-tools.
//
// Written by: Jean-François DEL NERO
//
// Copyright (C) 2022 Jean-François DEL NERO
//
// You are free to do what you want with this code.
// A credit is always appreciated if you use it into your product :)
//
// Change History (most recent first):
///////////////////////////////////////////////////////////////////////////////////
//
// Disclaimer / Legal warning : Radio spectrum and the law
//
// In most countries the use of any radio transmitting device is required to be
// either licensed or specifically exempted from licensing under the local regulator.
// Other than as used in accordance with a licence (or exemption),
// the use of radio equipment is illegal.
//
// So take care to limit the emitting range and power when testing this software !
//
// --------------------------------------------------------------------------------
// Example from the FCC (United states) :
//
// Part 15 Devices
//
// Unlicensed operation on the AM and FM radio broadcast bands is permitted for
// some extremely low powered devices covered under Part 15 of the FCC's rules.
// On FM frequencies, these devices are limited to an effective service range
// of approximately 200 feet (61 meters).
// See 47 CFR (Code of Federal Regulations) Section 15.239, and the July 24,
// 1991 Public Notice (still in effect).
//
// On the AM broadcast band, these devices are limited to an effective service
// range of approximately 200 feet (61 meters). See 47 CFR Sections 15.207,
// 15.209, 15.219, and 15.221. These devices must accept any interference
// caused by any other operation, which may further limit the effective service
// range.
//
// For more information on Part 15 devices, please see OET Bulletin No. 63
// ("Understanding the FCC Regulations for Low-Power, Non-Licensed Transmitters").
// Questions not answered by this Bulletin can be directed to the FCC's Office
// of Engineering and Technology, Customer Service Branch, at the Columbia,
// Maryland office, phone (301) 362 - 3000.
//
// [...]
//
// Penalties for Operation Without A Permit or License
//
// The Commission considers unauthorized broadcast operation to be a serious matter.
// Presently, the maximum penalty for operating an unlicensed or "pirate" broadcast
// station (one which is not permitted under Part 15 or is not a Carrier Current
// Station or Campus Radio Station) is set at $10,000 for a single violation or a
// single day of operation, up to a total maximum amount of $75,000.
//
// Adjustments may be made upwards or downwards depending on the circumstances
// involved. Equipment used for an unauthorized operation may also be confiscated.
// There are also criminal penalties (fine and/or imprisonment) for
// "willfully and knowingly" operating a radio station without a license.
// DON'T DO IT!
//
// More at : https://www.fcc.gov/media/radio/low-power-radio-general-information
//
// --------------------------------------------------------------------------------
//
// Broadcast FM subcarriers
//
// ________ Stereo L - R modulation
// / \ 38KHz DSB-SC
// / \ _______ _______
// / MONO \ Pilot / \ / \ RDS
// / L + R \ | / \ / \ _ _
// / \__|__/ \/ \__/ \/ \___
//30Hz 15Khz | | | | |
// | | | | 57KHz 5%
// 19KHz 23KHz 38KHz 53KHz
// 10%
//
// ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
// Current software design :
//
// Mod player / Left Audio -> Preemphasis (FIR) -> \ / -> Left + Right -> 15KHz low pass (FIR) -------------------------------|
// Sound \/ |
// generator /\ |
// \ Right Audio -> Preemphasis (FIR) -> / \ -> Left - Right -> 15KHz low pass (FIR) |
// | |
// Sample | Sample Rate at 200KHz V |
// Rate at | |MUL| (38KHz DSB-SC modulated)-> + ----> I+Q Modulator --> RF hardware transceiver
// 50KHz | ^ | (2MHz sample rate)
// | | |
// | 38KHz Osc |
// | (|) (These oscillators |
// | (|) must be kept in phase) |
// 19KHz Pilot Osc ------------------|
//
// ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <stdarg.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <time.h>
#include <stdint.h>
#include "wave.h"
#include "modulator.h"
#include "utils.h"
#include "fir_filters/FIR_Audio_Filter_Filter.h"
#include "fir_filters/AudioPreemphasis_Filter.h"
#include "hxcmod/hxcmod.h"
#define IQ_SAMPLE_RATE 2000000
#define SUBCARRIERS_SAMPLE_RATE 200000
#define IF_FREQ 0
#define BUFFER_SAMPLES_SIZE (1024*8)
#define printf(fmt...) do { \
if(!stdoutmode) \
fprintf(stdout, fmt); \
} while (0)
int stdoutmode;
int isOption(int argc, char* argv[],char * paramtosearch,char * argtoparam)
{
int param=1;
int i,j;
char option[512];
memset(option,0,512);
while(param<=argc)
{
if(argv[param])
{
if(argv[param][0]=='-')
{
memset(option,0,512);
j=0;
i=1;
while( argv[param][i] && argv[param][i]!=':')
{
option[j]=argv[param][i];
i++;
j++;
}
if( !strcmp(option,paramtosearch) )
{
if(argtoparam)
{
if(argv[param][i]==':')
{
i++;
j=0;
while( argv[param][i] )
{
argtoparam[j]=argv[param][i];
i++;
j++;
}
argtoparam[j]=0;
return 1;
}
else
{
return -1;
}
}
else
{
return 1;
}
}
}
}
param++;
}
return 0;
}
void printhelp(char* argv[])
{
printf("Options:\n");
printf(" -stdout \t\t\t: IQ stream send to stdout\n");
printf(" -mono \t\t\t: Mono FM mode\n");
printf(" -mod_file:[MODFILE.MOD]\t: MOD music file to play\n");
printf(" -generate \t\t\t: Generate the IQ stream\n");
printf(" -help \t\t\t: This help\n\n");
printf("Example : 100.9MHz broadcasting with an hackrf\n./broadcast_fm -generate -stdout -mod_file:meo-sleeping_waste.mod | hackrf_transfer -f 100900000 -t - -x 47 -a 1 -s 2000000\n");
printf("\n");
}
int main(int argc, char* argv[])
{
unsigned int i,j,k;
FILE *f;
char filename[512];
int mod_data_size = 0;
unsigned char * mod_data;
wave_io * wave1,*wave2;
double audio_sample_l;
double audio_sample_r;
double audio_sample_final;
double audio_sample_r_car;
double pilot_sample;
double balance_sample;
double fm_mod;
double old_freq,interpolation_step;
double leftplusright_audio,leftminusright_audio;
modcontext modctx;
FIR_Audio_Filter_Filter leftplusright_audio_filter,leftminusright_audio_filter;
AudioPreemphasis_Filter preamphasis_left_filter,preamphasis_right_filter;
//FM_Baseband_Filter fmband_filter;
iq_wave_gen iqgen;
wave_gen audio_l_gen;
wave_gen audio_r_gen;
wave_gen balance_ctrl;
wave_gen audiow_stereo38KHz_gen;
wave_gen stereo_pilot_gen;
uint16_t iq_wave_buf[ BUFFER_SAMPLES_SIZE * (IQ_SAMPLE_RATE/SUBCARRIERS_SAMPLE_RATE)];
int16_t mod_wave_buf[(BUFFER_SAMPLES_SIZE*2)/4];
int16_t subcarriers_dbg_wave_buf[BUFFER_SAMPLES_SIZE];
double subcarriers_float_wave_buf[BUFFER_SAMPLES_SIZE];
int monomode;
stdoutmode = 0;
monomode = 0;
if(isOption(argc,argv,"stdout",NULL)>0)
{
stdoutmode = 1;
}
if(!stdoutmode)
{
printf("broadcast_fm v0.0.1.1\n");
printf("Copyright (C) 2022 Jean-Francois DEL NERO\n");
printf("This program comes with ABSOLUTELY NO WARRANTY\n");
printf("This is free software, and you are welcome to redistribute it\n");
printf("under certain conditions;\n\n");
}
// help option...
if(isOption(argc,argv,"help",0)>0)
{
printhelp(argv);
}
memset(filename,0,sizeof(filename));
// Input file name option
if(isOption(argc,argv,"mod_file",(char*)&filename)>0)
{
printf("Input file : %s\n",filename);
}
if(isOption(argc,argv,"mono",NULL)>0)
{
monomode = 1;
}
if(isOption(argc,argv,"generate",0)>0)
{
// Init the .mod player and load the mod file.
hxcmod_init(&modctx);
hxcmod_setcfg(&modctx, SUBCARRIERS_SAMPLE_RATE/4, 1, 0); //(50000 samples/s, 25000 Hz)
mod_data_size = 0;
mod_data = NULL;
f = fopen(filename,"r");
if(f)
{
fseek(f,0,SEEK_END);
mod_data_size = ftell(f);
fseek(f,0,SEEK_SET);
mod_data = malloc(mod_data_size);
if(fread(mod_data,mod_data_size,1,f) == 1)
{
hxcmod_load(&modctx, mod_data, mod_data_size );
}
else
{
free(mod_data);
mod_data = NULL;
mod_data_size = 0;
}
fclose(f);
}
// Init all filters
AudioPreemphasis_Filter_init(&preamphasis_left_filter);
AudioPreemphasis_Filter_init(&preamphasis_right_filter);
FIR_Audio_Filter_Filter_init(&leftplusright_audio_filter);
FIR_Audio_Filter_Filter_init(&leftminusright_audio_filter);
//FM_Baseband_Filter_init(&fmband_filter);
// Init oscillators
// Left and Right audio freq (used if no .mod music file)
audio_l_gen.phase = 0;
audio_l_gen.Frequency = 700;
audio_l_gen.Amplitude = 22.5;
audio_l_gen.sample_rate = SUBCARRIERS_SAMPLE_RATE;
audio_r_gen.phase = 0;
audio_r_gen.Frequency = 1000;
audio_r_gen.Amplitude = 22.5;
audio_r_gen.sample_rate = SUBCARRIERS_SAMPLE_RATE;
// Left <> Right balance LFO (used if no .mod music file)
balance_ctrl.phase = 0;
balance_ctrl.Frequency = 1.2;
balance_ctrl.Amplitude = 12.5;
balance_ctrl.sample_rate = SUBCARRIERS_SAMPLE_RATE;
// Stereo Pilot
stereo_pilot_gen.phase = 0;
stereo_pilot_gen.Frequency = 19000;
stereo_pilot_gen.Amplitude = 10.0; // 10%
stereo_pilot_gen.sample_rate = SUBCARRIERS_SAMPLE_RATE;
// 38KHz +/- 15KHz stereo modulator
audiow_stereo38KHz_gen.phase = 0;
audiow_stereo38KHz_gen.Frequency = 38000;
audiow_stereo38KHz_gen.Amplitude = 1;
audiow_stereo38KHz_gen.sample_rate = SUBCARRIERS_SAMPLE_RATE;
// IQ Modulator
iqgen.phase = 0;
iqgen.Frequency = IF_FREQ;
iqgen.Amplitude = 127;
iqgen.sample_rate = IQ_SAMPLE_RATE;
audio_sample_r = 0;
audio_sample_l = 0;
if(stdoutmode)
{
// stdout / stream mode : IQ are outputed to the stdout -> use a pipe to hackrf_transfer
wave1 = create_wave(NULL,iqgen.sample_rate,WAVE_FILE_FORMAT_RAW_8BITS_IQ);
wave2 = NULL;
}
else
{
// file mode : create iq + wav files
wave1 = create_wave("broadcast_fm.iq",iqgen.sample_rate,WAVE_FILE_FORMAT_RAW_8BITS_IQ);
wave2 = create_wave("broadcast_fm.wav",SUBCARRIERS_SAMPLE_RATE,WAVE_FILE_FORMAT_WAV_16BITS_MONO);
}
if(wave1)
{
old_freq = IF_FREQ;
// Main loop...
for(i=0;(i<1024) || stdoutmode ;i++)
{
printf("%d / %d...\n",i,1024);
hxcmod_fillbuffer( &modctx, (msample*)&mod_wave_buf, BUFFER_SAMPLES_SIZE/4, NULL );
for(j=0;j<BUFFER_SAMPLES_SIZE;j++)
{
if(!mod_data_size)
{
// No music module loaded -> Play left/right tones.
// Dynamically set volumes for left and right oscillators.
balance_sample = f_get_next_sample(&balance_ctrl);
audio_l_gen.Amplitude = balance_sample;
audio_r_gen.Amplitude = -balance_sample;
if(audio_r_gen.Amplitude < 0) audio_r_gen.Amplitude = 0;
if(audio_l_gen.Amplitude < 0) audio_l_gen.Amplitude = 0;
// Get the left and right samples.
audio_sample_l = f_get_next_sample(&audio_l_gen);
audio_sample_r = f_get_next_sample(&audio_r_gen);
}
else
{
if(!(j&3))
{
audio_sample_l = ((double)((mod_wave_buf[((j/4)*2)]) / (double)32768)) * (double)22.5;
audio_sample_r = ((double)((mod_wave_buf[((j/4)*2)+1]) / (double)32768)) * (double)22.5;
// Left & Right Preamphasis filter.
AudioPreemphasis_Filter_put(&preamphasis_left_filter, audio_sample_l );
audio_sample_l = AudioPreemphasis_Filter_get(&preamphasis_left_filter);
AudioPreemphasis_Filter_put(&preamphasis_right_filter, audio_sample_r );
audio_sample_r = AudioPreemphasis_Filter_get(&preamphasis_right_filter);
}
}
// Main / Mono channel : Left + Right
leftplusright_audio = audio_sample_l + audio_sample_r;
// 0KHz<->15KHz pass band/low pass filter
FIR_Audio_Filter_Filter_put(&leftplusright_audio_filter, leftplusright_audio);
leftplusright_audio = FIR_Audio_Filter_Filter_get(&leftplusright_audio_filter);
if(!monomode)
{
// Stereo Channel : Left - Right
leftminusright_audio = audio_sample_l - audio_sample_r;
// 0KHz<->15KHz pass band/low pass filter
FIR_Audio_Filter_Filter_put(&leftminusright_audio_filter, leftminusright_audio);
leftminusright_audio = FIR_Audio_Filter_Filter_get(&leftminusright_audio_filter);
// Keep the 18KHz pilot and the 38KHz clock in phase :
audiow_stereo38KHz_gen.phase = (stereo_pilot_gen.phase * 2) + PI/2;
// 38KHz DSB-SC (Double-sideband suppressed-carrier) modulation
audio_sample_r_car = f_get_next_sample(&audiow_stereo38KHz_gen); // Get the 38KHz carrier
audio_sample_r_car = (audio_sample_r_car * leftminusright_audio ); // And multiply it with the left - right sample.
// 18KHz pilot
pilot_sample = f_get_next_sample(&stereo_pilot_gen);
}
else
{
// Mono : No pilot nor 38KHz modulation...
audio_sample_r_car = 0;
pilot_sample = 0;
}
// Mix all signals sources :
// 45% 45% 10%
audio_sample_final = ((leftplusright_audio) + audio_sample_r_car + pilot_sample);
// Main carrier frequency modulation : +/- 75KHz
fm_mod = ((audio_sample_final / (double)(100.0)) * (double)(75000));
// Low pass filter <100KHz
// Note : Not needed here since we use a 200KHz sample rate.
//FM_Baseband_Filter_put(&fmband_filter, fm_mod );
//fm_mod = FM_Baseband_Filter_get(&fmband_filter);
subcarriers_dbg_wave_buf[j] = fm_mod;
subcarriers_float_wave_buf[j] = fm_mod;
}
// Sub carriers sample rate to carrier IQ rate modulation + resampling
for(j=0;j<BUFFER_SAMPLES_SIZE;j++)
{
// linear interpolation. (TODO ?: Cubic interpolation)
interpolation_step = (subcarriers_float_wave_buf[j] - old_freq) / (double)(IQ_SAMPLE_RATE/SUBCARRIERS_SAMPLE_RATE);
for(k=0;k<(IQ_SAMPLE_RATE/SUBCARRIERS_SAMPLE_RATE);k++)
{
old_freq += interpolation_step;
iqgen.Frequency = ((double)IF_FREQ + old_freq);
iq_wave_buf[(j*(IQ_SAMPLE_RATE/SUBCARRIERS_SAMPLE_RATE))+k] = get_next_iq(&iqgen);
}
old_freq = subcarriers_float_wave_buf[j];
}
write_wave(wave1, &iq_wave_buf,BUFFER_SAMPLES_SIZE*(IQ_SAMPLE_RATE/SUBCARRIERS_SAMPLE_RATE));
write_wave(wave2, &subcarriers_dbg_wave_buf,BUFFER_SAMPLES_SIZE);
}
close_wave(wave1);
close_wave(wave2);
}
}
if( (isOption(argc,argv,"help",0)<=0) &&
(isOption(argc,argv,"generate",0)<=0)
)
{
printhelp(argv);
}
return 0;
}