From 94f026bfaf79a0a0ea3d6ff9544c07d4c698fdde Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Fran=C3=A7ois=20DEL=20NERO?= Date: Sat, 19 Nov 2022 15:21:59 +0100 Subject: [PATCH] Initial commit : A broadcast FM Stereo modulator. --- build/Makefile | 68 + readme | 11 + src/broadcast_fm/broadcast_fm.c | 525 +++++ .../fir_filters/AudioPreemphasis_Filter.c | 30 + .../fir_filters/AudioPreemphasis_Filter.h | 35 + .../fir_filters/FIR_Audio_Filter_Filter.c | 152 ++ .../fir_filters/FIR_Audio_Filter_Filter.h | 34 + .../fir_filters/FM_Baseband_Filter.c | 683 ++++++ .../fir_filters/FM_Baseband_Filter.h | 35 + src/common/hxcmod/hxcmod.c | 2037 +++++++++++++++++ src/common/hxcmod/hxcmod.h | 309 +++ src/common/modulator.c | 86 + src/common/modulator.h | 65 + src/common/rand_gen.c | 97 + src/common/rand_gen.h | 41 + src/common/utils.c | 126 + src/common/utils.h | 30 + src/common/wave.c | 160 ++ src/common/wave.h | 68 + 19 files changed, 4592 insertions(+) create mode 100644 build/Makefile create mode 100644 readme create mode 100644 src/broadcast_fm/broadcast_fm.c create mode 100644 src/broadcast_fm/fir_filters/AudioPreemphasis_Filter.c create mode 100644 src/broadcast_fm/fir_filters/AudioPreemphasis_Filter.h create mode 100644 src/broadcast_fm/fir_filters/FIR_Audio_Filter_Filter.c create mode 100644 src/broadcast_fm/fir_filters/FIR_Audio_Filter_Filter.h create mode 100644 src/broadcast_fm/fir_filters/FM_Baseband_Filter.c create mode 100644 src/broadcast_fm/fir_filters/FM_Baseband_Filter.h create mode 100644 src/common/hxcmod/hxcmod.c create mode 100644 src/common/hxcmod/hxcmod.h create mode 100644 src/common/modulator.c create mode 100644 src/common/modulator.h create mode 100644 src/common/rand_gen.c create mode 100644 src/common/rand_gen.h create mode 100644 src/common/utils.c create mode 100644 src/common/utils.h create mode 100644 src/common/wave.c create mode 100644 src/common/wave.h diff --git a/build/Makefile b/build/Makefile new file mode 100644 index 0000000..d865ff4 --- /dev/null +++ b/build/Makefile @@ -0,0 +1,68 @@ +CC=gcc + +UNAME := $(shell uname) + +DEBUG ?= 0 + +ifeq ($(DEBUG), 1) + CFLAGS=-O0 $(INCLUDES) -Wall -g -DDEBUG -I ../src/common/ + LDFLAGS= -lc -lm -ldl -lpthread -lasan +else + CFLAGS=-O3 $(INCLUDES) -Wall -I ../src/common/ + LDFLAGS= -lc -lm -ldl -s +endif + +ifeq ($(UNAME), Darwin) +CFLAGS += -arch x86_64 -mmacosx-version-min=10.9 +LDFLAGS += -arch x86_64 -mmacosx-version-min=10.9 +endif + +ifeq ($(UNAME), Linux) +LDFLAGS += -Wl,-rpath=. +endif + +ifeq ($(findstring CYGWIN,$(shell uname)),CYGWIN) +endif + +EXEC=broadcast_fm + +all: $(EXEC) + +broadcast_fm: broadcast_fm.o wave.o modulator.o FIR_Audio_Filter_Filter.o FM_Baseband_Filter.o AudioPreemphasis_Filter.o hxcmod.o rand_gen.o + $(CC) -o $@ $^ $(LDFLAGS) + +broadcast_fm.o: ../src/broadcast_fm/broadcast_fm.c + $(CC) -o $@ -c $< $(CFLAGS) + +utils.o: ../src/common/utils.c + $(CC) -o $@ -c $< $(CFLAGS) + +wave.o: ../src/common/wave.c + $(CC) -o $@ -c $< $(CFLAGS) + +modulator.o: ../src/common/modulator.c + $(CC) -o $@ -c $< $(CFLAGS) + +FIR_Audio_Filter_Filter.o: ../src/broadcast_fm/fir_filters/FIR_Audio_Filter_Filter.c + $(CC) -o $@ -c $< $(CFLAGS) + +FM_Baseband_Filter.o: ../src/broadcast_fm/fir_filters/FM_Baseband_Filter.c + $(CC) -o $@ -c $< $(CFLAGS) + +AudioPreemphasis_Filter.o: ../src/broadcast_fm/fir_filters/AudioPreemphasis_Filter.c + $(CC) -o $@ -c $< $(CFLAGS) + +hxcmod.o: ../src/common/hxcmod/hxcmod.c + $(CC) -o $@ -c $< $(CFLAGS) + +rand_gen.o: ../src/common/rand_gen.c + $(CC) -o $@ -c $< $(CFLAGS) + +clean: + rm -rf *.o + rm -rf *.so + +mrproper: clean + rm -rf $(EXEC) + +.PHONY: clean mrproper diff --git a/readme b/readme new file mode 100644 index 0000000..b5ff69e --- /dev/null +++ b/readme @@ -0,0 +1,11 @@ +rf-tools : Radio frequency tools and modulators + +Disclaimer / Legal warning : Radio spectrum and the law + +In most / all 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 these softwares ! + diff --git a/src/broadcast_fm/broadcast_fm.c b/src/broadcast_fm/broadcast_fm.c new file mode 100644 index 0000000..e7520be --- /dev/null +++ b/src/broadcast_fm/broadcast_fm.c @@ -0,0 +1,525 @@ +/////////////////////////////////////////////////////////////////////////////////// +//-------------------------------------------------------------------------------// +//-------------------------------------------------------------------------------// +//-----------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 +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#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 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;jhistory[i] = 0; + f->last_index = 0; +} + +void AudioPreemphasis_Filter_put(AudioPreemphasis_Filter* f, double input) { + f->history[f->last_index++] = input; + if(f->last_index == AUDIOPREEMPHASIS_FILTER_TAP_NUM) + f->last_index = 0; +} + +double AudioPreemphasis_Filter_get(AudioPreemphasis_Filter* f) { + double acc = 0; + int index = f->last_index, i; + for(i = 0; i < AUDIOPREEMPHASIS_FILTER_TAP_NUM; ++i) { + index = index != 0 ? index-1 : AUDIOPREEMPHASIS_FILTER_TAP_NUM-1; + acc += f->history[index] * filter_taps[i]; + }; + return acc; +} diff --git a/src/broadcast_fm/fir_filters/AudioPreemphasis_Filter.h b/src/broadcast_fm/fir_filters/AudioPreemphasis_Filter.h new file mode 100644 index 0000000..1c01cfc --- /dev/null +++ b/src/broadcast_fm/fir_filters/AudioPreemphasis_Filter.h @@ -0,0 +1,35 @@ +#ifndef AUDIOPREEMPHASIS_FILTER_H_ +#define AUDIOPREEMPHASIS_FILTER_H_ + +/* + +FIR filter designed with + http://t-filter.appspot.com + +sampling frequency: 50000 Hz + +* 0 Hz - 2100 Hz + gain = 1 + desired ripple = 5 dB + actual ripple = 1.2624293131559894 dB + +* 15500 Hz - 25000 Hz + gain = 6.309573444801933 + desired ripple = 5 dB + actual ripple = 2.740064802010604 dB + +*/ + +#define AUDIOPREEMPHASIS_FILTER_TAP_NUM 3 + +typedef struct { + double history[AUDIOPREEMPHASIS_FILTER_TAP_NUM]; + unsigned int last_index; +} AudioPreemphasis_Filter; + +void AudioPreemphasis_Filter_init(AudioPreemphasis_Filter* f); +void AudioPreemphasis_Filter_put(AudioPreemphasis_Filter* f, double input); +double AudioPreemphasis_Filter_get(AudioPreemphasis_Filter* f); + +#endif + diff --git a/src/broadcast_fm/fir_filters/FIR_Audio_Filter_Filter.c b/src/broadcast_fm/fir_filters/FIR_Audio_Filter_Filter.c new file mode 100644 index 0000000..d524a93 --- /dev/null +++ b/src/broadcast_fm/fir_filters/FIR_Audio_Filter_Filter.c @@ -0,0 +1,152 @@ +#include "FIR_Audio_Filter_Filter.h" + +static double filter_taps[FIR_AUDIO_FILTER_FILTER_TAP_NUM] = { + 0.005517301037304242, + 0.010958468006496662, + 0.010240455922518009, + 0.01626919575760614, + 0.017121566138134488, + 0.019559785776548867, + 0.018573985808748823, + 0.016981674306998703, + 0.013027824505458897, + 0.008348299295171996, + 0.002827934347197235, + -0.0022439756999176066, + -0.006402028253938463, + -0.008814800382330594, + -0.009267566652943171, + -0.007703207526723216, + -0.004562291988505165, + -0.0005232995432083925, + 0.003518575437801644, + 0.006694025550492667, + 0.008294079489543669, + 0.007965160640712026, + 0.005755920521670115, + 0.0021526778614508585, + -0.0020452123559512488, + -0.005875787341622662, + -0.008425796705843449, + -0.009052315787298352, + -0.00752225339131066, + -0.0040990782652213, + 0.00049750332784811, + 0.005234144776188673, + 0.008990696285244222, + 0.010798308794869566, + 0.010105847532439045, + 0.006897132667607324, + 0.0017653188321013905, + -0.00420373081909947, + -0.00964286796105942, + -0.013192556860158036, + -0.013826393925481531, + -0.011121337171052629, + -0.005396493245103629, + 0.0022726920185823602, + 0.010240805979570002, + 0.016615084025158867, + 0.019654280140049052, + 0.018177759921499358, + 0.011915646666813001, + 0.0016581423845507999, + -0.010757223751418863, + -0.02270805003461664, + -0.03121367797048097, + -0.033501256492323245, + -0.027529898309997605, + -0.012463160702206726, + 0.011056274270822274, + 0.04090736333522399, + 0.07374003152391906, + 0.10546517736325783, + 0.131881590198464, + 0.1493758864385702, + 0.1554926121172843, + 0.1493758864385702, + 0.131881590198464, + 0.10546517736325783, + 0.07374003152391906, + 0.04090736333522399, + 0.011056274270822274, + -0.012463160702206726, + -0.027529898309997605, + -0.033501256492323245, + -0.03121367797048097, + -0.02270805003461664, + -0.010757223751418863, + 0.0016581423845507999, + 0.011915646666813001, + 0.018177759921499358, + 0.019654280140049052, + 0.016615084025158867, + 0.010240805979570002, + 0.0022726920185823602, + -0.005396493245103629, + -0.011121337171052629, + -0.013826393925481531, + -0.013192556860158036, + -0.00964286796105942, + -0.00420373081909947, + 0.0017653188321013905, + 0.006897132667607324, + 0.010105847532439045, + 0.010798308794869566, + 0.008990696285244222, + 0.005234144776188673, + 0.00049750332784811, + -0.0040990782652213, + -0.00752225339131066, + -0.009052315787298352, + -0.008425796705843449, + -0.005875787341622662, + -0.0020452123559512488, + 0.0021526778614508585, + 0.005755920521670115, + 0.007965160640712026, + 0.008294079489543669, + 0.006694025550492667, + 0.003518575437801644, + -0.0005232995432083925, + -0.004562291988505165, + -0.007703207526723216, + -0.009267566652943171, + -0.008814800382330594, + -0.006402028253938463, + -0.0022439756999176066, + 0.002827934347197235, + 0.008348299295171996, + 0.013027824505458897, + 0.016981674306998703, + 0.018573985808748823, + 0.019559785776548867, + 0.017121566138134488, + 0.01626919575760614, + 0.010240455922518009, + 0.010958468006496662, + 0.005517301037304242 +}; + +void FIR_Audio_Filter_Filter_init(FIR_Audio_Filter_Filter* f) { + int i; + for(i = 0; i < FIR_AUDIO_FILTER_FILTER_TAP_NUM; ++i) + f->history[i] = 0; + f->last_index = 0; +} + +void FIR_Audio_Filter_Filter_put(FIR_Audio_Filter_Filter* f, double input) { + f->history[f->last_index++] = input; + if(f->last_index == FIR_AUDIO_FILTER_FILTER_TAP_NUM) + f->last_index = 0; +} + +double FIR_Audio_Filter_Filter_get(FIR_Audio_Filter_Filter* f) { + double acc = 0; + int index = f->last_index, i; + for(i = 0; i < FIR_AUDIO_FILTER_FILTER_TAP_NUM; ++i) { + index = index != 0 ? index-1 : FIR_AUDIO_FILTER_FILTER_TAP_NUM-1; + acc += f->history[index] * filter_taps[i]; + }; + return acc; +} diff --git a/src/broadcast_fm/fir_filters/FIR_Audio_Filter_Filter.h b/src/broadcast_fm/fir_filters/FIR_Audio_Filter_Filter.h new file mode 100644 index 0000000..7b02a3d --- /dev/null +++ b/src/broadcast_fm/fir_filters/FIR_Audio_Filter_Filter.h @@ -0,0 +1,34 @@ +#ifndef FIR_AUDIO_FILTER_FILTER_H_ +#define FIR_AUDIO_FILTER_FILTER_H_ + +/* + +FIR filter designed with + http://t-filter.appspot.com + +sampling frequency: 200000 Hz + +* 0 Hz - 15000 Hz + gain = 1 + desired ripple = 5 dB + actual ripple = 4.131020865324735 dB + +* 16600 Hz - 100000 Hz + gain = 0 + desired attenuation = -40 dB + actual attenuation = -40.090485410010906 dB + +*/ + +#define FIR_AUDIO_FILTER_FILTER_TAP_NUM 125 + +typedef struct { + double history[FIR_AUDIO_FILTER_FILTER_TAP_NUM]; + unsigned int last_index; +} FIR_Audio_Filter_Filter; + +void FIR_Audio_Filter_Filter_init(FIR_Audio_Filter_Filter* f); +void FIR_Audio_Filter_Filter_put(FIR_Audio_Filter_Filter* f, double input); +double FIR_Audio_Filter_Filter_get(FIR_Audio_Filter_Filter* f); + +#endif diff --git a/src/broadcast_fm/fir_filters/FM_Baseband_Filter.c b/src/broadcast_fm/fir_filters/FM_Baseband_Filter.c new file mode 100644 index 0000000..25b46bc --- /dev/null +++ b/src/broadcast_fm/fir_filters/FM_Baseband_Filter.c @@ -0,0 +1,683 @@ +#include "FM_Baseband_Filter.h" + +static double filter_taps[FM_BASEBAND_FILTER_TAP_NUM] = { + -0.006276773743368246, + -0.002906124689895695, + -0.0034810491402275856, + -0.004053348584904465, + -0.004602917018038953, + -0.00510864933708306, + -0.005549178890478986, + -0.005903803434753803, + -0.006153352756740714, + -0.0062810419368997195, + -0.006273284055978816, + -0.0061205500978807606, + -0.005817832832522139, + -0.005365157561460779, + -0.00476798650929006, + -0.00403722847791321, + -0.003188871268023831, + -0.0022438395226593234, + -0.0012271560122908633, + -0.00016698144545659763, + 0.0009061942378719186, + 0.0019610873421838713, + 0.0029665219630286475, + 0.003892678282644405, + 0.0047125166256176704, + 0.005402338389433701, + 0.005943231522926785, + 0.006321383314346843, + 0.00652916985473517, + 0.006565317350605899, + 0.006435200226996191, + 0.006150572165634373, + 0.005729115424630075, + 0.0051935953163101374, + 0.004570706217926675, + 0.0038898729887556515, + 0.003181825807588361, + 0.002477679601003533, + 0.001807325109970609, + 0.001198808101593799, + 0.000676466760403685, + 0.00026038278850552165, + -0.00003519485538394603, + -0.0002024986474888905, + -0.00024019085124103728, + -0.00015331390959515178, + 0.00004791480114281375, + 0.00034866017633577485, + 0.0007296960675978035, + 0.001167933929422274, + 0.001636917920572586, + 0.0021085978153374337, + 0.0025562644079397566, + 0.0029568799307201055, + 0.003291074349553435, + 0.0035414284335566213, + 0.0036920509207547503, + 0.00373312207850841, + 0.003666696471321502, + 0.003502213813807159, + 0.00324212650920447, + 0.0028914904576434483, + 0.0024934237802220847, + 0.002043603816320018, + 0.0015808468393586463, + 0.001122598250955268, + 0.0006936436970225945, + 0.00031496346957593816, + 0.000004837984574109052, + -0.00022232703032059695, + -0.0003566665079835044, + -0.00039339019608776595, + -0.000333110808989004, + -0.00018159497394687693, + 0.00005048775593523694, + 0.0003481061116814, + 0.0006927997977343629, + 0.0010638765819667507, + 0.0014393167512730105, + 0.0017971738401020903, + 0.0021166520909361806, + 0.0023791019266531147, + 0.0025693090410960793, + 0.002675931198702369, + 0.0026923543124503487, + 0.0026171805325111786, + 0.002454031118912115, + 0.0022116895646837804, + 0.0019030267726684862, + 0.0015446566595713781, + 0.0011557133218574656, + 0.0007570502661148399, + 0.00037004878160910566, + 0.00001556164895794005, + -0.0002873259421258282, + -0.0005225020827002275, + -0.0006778152785331262, + -0.0007458291450652108, + -0.0007237724027604178, + -0.0006139467826508875, + -0.0004230530550807403, + -0.0001628354231637733, + 0.00015129060306465846, + 0.0005003479818101034, + 0.0008636162701018116, + 0.0012200029156715312, + 0.001548740624466772, + 0.0018306821451188385, + 0.0020487233738411786, + 0.0021891204599193454, + 0.002243244512808436, + 0.0022077928016267247, + 0.002084143087394179, + 0.0018778523547873093, + 0.0015991122050812935, + 0.0012633901100129934, + 0.0008900089027853051, + 0.0004988789779988034, + 0.00011015896725885363, + -0.0002535400937416474, + -0.0005702276065943115, + -0.0008248106197065263, + -0.0010037105425627479, + -0.0010922057096824714, + -0.0010918347055704724, + -0.0009972609130431105, + -0.0008171434769424912, + -0.0005602703726361708, + -0.00024184631248718984, + 0.00011989306001557591, + 0.0005039966417534989, + 0.0008882139336450031, + 0.001250150108424033, + 0.0015684774729692547, + 0.001824332418711941, + 0.0020022457010850096, + 0.0020909788043975276, + 0.002084241239549793, + 0.0019812511669580956, + 0.001786670875323066, + 0.0015105823732014887, + 0.0011676956517593336, + 0.0007765672753843151, + 0.00035878897793891384, + -0.00006253548108123283, + -0.0004637552851828441, + -0.0008220930819068703, + -0.0011170665364279842, + -0.001331475322813246, + -0.001452868444758477, + -0.0014738073952633322, + -0.0013925554944245599, + -0.001212981144562626, + -0.000944644997827479, + -0.0006023329394979184, + -0.0002053053709292688, + 0.00022392080409527296, + 0.0006609144606176251, + 0.0010805902221096093, + 0.0014587022318349155, + 0.0017727389893637477, + 0.0020038657435381257, + 0.0021373573333903907, + 0.0021647196370404324, + 0.0020828998261889996, + 0.0018952302758044716, + 0.0016108477670202955, + 0.001244407904014974, + 0.0008161029752255834, + 0.00034959201045762735, + -0.00012940307877066482, + -0.0005940260022155878, + -0.0010176577556310668, + -0.0013754156241168216, + -0.0016462249002119438, + -0.0018142393477275655, + -0.0018687427312488538, + -0.0018046936739308204, + -0.0016246736311703472, + -0.001338448595903364, + -0.0009605147153735846, + -0.0005108126444730755, + -0.00001565838520828236, + 0.0004975083602194651, + 0.0010006023914098225, + 0.001462016316333705, + 0.001857288190223437, + 0.0021606030300284607, + 0.002354435255283183, + 0.0024253663999272922, + 0.0023675334058277852, + 0.002182089414459102, + 0.0018776951564996795, + 0.0014698815939004595, + 0.0009802099743280804, + 0.00043530470972525385, + -0.0001346731858706825, + -0.0006976628796186629, + -0.0012215159916327684, + -0.0016757415624847984, + -0.002033429226170653, + -0.0022726649592763708, + -0.002378134377120082, + -0.002341854284500264, + -0.0021636093678711237, + -0.00185139126645343, + -0.0014208790055546586, + -0.000894944574293136, + -0.0003023080541089114, + 0.00032421742432794986, + 0.0009492104739322405, + 0.001536916169711772, + 0.0020528749242516003, + 0.0024661761828563313, + 0.002751173465403013, + 0.0028891772783515117, + 0.0028695579254982137, + 0.002690486553108668, + 0.002359106318284556, + 0.0018913307558827979, + 0.0013113627449627718, + 0.0006501370380893174, + -0.00005591634751310549, + -0.0007677972770164452, + -0.001444647221437417, + -0.00204746257956741, + -0.0025398901810068483, + -0.002891736526827703, + -0.0030803898442519225, + -0.003091468726777203, + -0.0029207038921516223, + -0.0025741254950366896, + -0.002067973931355495, + -0.0014280157557714668, + -0.0006877721381443566, + 0.0001126863109365815, + 0.0009284005715601569, + 0.0017128970482479869, + 0.0024209880361363485, + 0.003010137740662827, + 0.0034433436111773724, + 0.0036925735900344213, + 0.0037392776355320164, + 0.0035750549963301497, + 0.003204686888263029, + 0.002644455073483638, + 0.0019206891969597553, + 0.0010720437886563461, + 0.00014236365066494383, + -0.0008162193794978551, + -0.001750205816244383, + -0.00260524888853366, + -0.003330448634355011, + -0.0038806885762527085, + -0.0042197018719619, + -0.004322310610781536, + -0.004175978670258643, + -0.003781919171806373, + -0.0031554249088873213, + -0.0023255577897850145, + -0.0013338142329436104, + -0.0002321551782802385, + 0.000919847567321573, + 0.002057758110308816, + 0.0031159521593161162, + 0.004031228465119642, + 0.004746373230231019, + 0.005213833074755009, + 0.005398368179397916, + 0.005279622770016463, + 0.0048537340045996654, + 0.004133945043852994, + 0.003150792733934946, + 0.0019506897625897466, + 0.0005941555193940558, + -0.0008472848222071792, + -0.002294579340030699, + -0.0036652622277313592, + -0.004877798689965437, + -0.00585630529385358, + -0.006535021981369312, + -0.006862209393606202, + -0.006803716425690461, + -0.0063451726267535384, + -0.005494638978117847, + -0.004281728798042104, + -0.0027587324158575986, + -0.0009964288997051905, + 0.0009165389378955883, + 0.002878840922760619, + 0.00478156331351961, + 0.0065128972068082235, + 0.00796484714665623, + 0.009038598083889997, + 0.009650143724551814, + 0.009736399158760185, + 0.009259146172550439, + 0.008207929636538491, + 0.006603125277341979, + 0.004496736872907695, + 0.0019706087511477417, + -0.0008653143095612088, + -0.0038758915334291878, + -0.006906924032827862, + -0.00979143836062656, + -0.012355548332571993, + -0.014427563844913744, + -0.015845425534902112, + -0.016463427343763833, + -0.016162193538925198, + -0.014851592429837827, + -0.012479356514209913, + -0.009032813295982706, + -0.00454241087129261, + 0.0009187305291106804, + 0.007235761344299075, + 0.014255324035897835, + 0.021790491494704824, + 0.029627294762135707, + 0.03753261929369517, + 0.04526278875865766, + 0.05257290583779067, + 0.05922632186497995, + 0.06500420017090862, + 0.06971390965779961, + 0.07319701890747447, + 0.07533548531500543, + 0.07605647784784841, + 0.07533548531500543, + 0.07319701890747447, + 0.06971390965779961, + 0.06500420017090862, + 0.05922632186497995, + 0.05257290583779067, + 0.04526278875865766, + 0.03753261929369517, + 0.029627294762135707, + 0.021790491494704824, + 0.014255324035897835, + 0.007235761344299075, + 0.0009187305291106804, + -0.00454241087129261, + -0.009032813295982706, + -0.012479356514209913, + -0.014851592429837827, + -0.016162193538925198, + -0.016463427343763833, + -0.015845425534902112, + -0.014427563844913744, + -0.012355548332571993, + -0.00979143836062656, + -0.006906924032827862, + -0.0038758915334291878, + -0.0008653143095612088, + 0.0019706087511477417, + 0.004496736872907695, + 0.006603125277341979, + 0.008207929636538491, + 0.009259146172550439, + 0.009736399158760185, + 0.009650143724551814, + 0.009038598083889997, + 0.00796484714665623, + 0.0065128972068082235, + 0.00478156331351961, + 0.002878840922760619, + 0.0009165389378955883, + -0.0009964288997051905, + -0.0027587324158575986, + -0.004281728798042104, + -0.005494638978117847, + -0.0063451726267535384, + -0.006803716425690461, + -0.006862209393606202, + -0.006535021981369312, + -0.00585630529385358, + -0.004877798689965437, + -0.0036652622277313592, + -0.002294579340030699, + -0.0008472848222071792, + 0.0005941555193940558, + 0.0019506897625897466, + 0.003150792733934946, + 0.004133945043852994, + 0.0048537340045996654, + 0.005279622770016463, + 0.005398368179397916, + 0.005213833074755009, + 0.004746373230231019, + 0.004031228465119642, + 0.0031159521593161162, + 0.002057758110308816, + 0.000919847567321573, + -0.0002321551782802385, + -0.0013338142329436104, + -0.0023255577897850145, + -0.0031554249088873213, + -0.003781919171806373, + -0.004175978670258643, + -0.004322310610781536, + -0.0042197018719619, + -0.0038806885762527085, + -0.003330448634355011, + -0.00260524888853366, + -0.001750205816244383, + -0.0008162193794978551, + 0.00014236365066494383, + 0.0010720437886563461, + 0.0019206891969597553, + 0.002644455073483638, + 0.003204686888263029, + 0.0035750549963301497, + 0.0037392776355320164, + 0.0036925735900344213, + 0.0034433436111773724, + 0.003010137740662827, + 0.0024209880361363485, + 0.0017128970482479869, + 0.0009284005715601569, + 0.0001126863109365815, + -0.0006877721381443566, + -0.0014280157557714668, + -0.002067973931355495, + -0.0025741254950366896, + -0.0029207038921516223, + -0.003091468726777203, + -0.0030803898442519225, + -0.002891736526827703, + -0.0025398901810068483, + -0.00204746257956741, + -0.001444647221437417, + -0.0007677972770164452, + -0.00005591634751310549, + 0.0006501370380893174, + 0.0013113627449627718, + 0.0018913307558827979, + 0.002359106318284556, + 0.002690486553108668, + 0.0028695579254982137, + 0.0028891772783515117, + 0.002751173465403013, + 0.0024661761828563313, + 0.0020528749242516003, + 0.001536916169711772, + 0.0009492104739322405, + 0.00032421742432794986, + -0.0003023080541089114, + -0.000894944574293136, + -0.0014208790055546586, + -0.00185139126645343, + -0.0021636093678711237, + -0.002341854284500264, + -0.002378134377120082, + -0.0022726649592763708, + -0.002033429226170653, + -0.0016757415624847984, + -0.0012215159916327684, + -0.0006976628796186629, + -0.0001346731858706825, + 0.00043530470972525385, + 0.0009802099743280804, + 0.0014698815939004595, + 0.0018776951564996795, + 0.002182089414459102, + 0.0023675334058277852, + 0.0024253663999272922, + 0.002354435255283183, + 0.0021606030300284607, + 0.001857288190223437, + 0.001462016316333705, + 0.0010006023914098225, + 0.0004975083602194651, + -0.00001565838520828236, + -0.0005108126444730755, + -0.0009605147153735846, + -0.001338448595903364, + -0.0016246736311703472, + -0.0018046936739308204, + -0.0018687427312488538, + -0.0018142393477275655, + -0.0016462249002119438, + -0.0013754156241168216, + -0.0010176577556310668, + -0.0005940260022155878, + -0.00012940307877066482, + 0.00034959201045762735, + 0.0008161029752255834, + 0.001244407904014974, + 0.0016108477670202955, + 0.0018952302758044716, + 0.0020828998261889996, + 0.0021647196370404324, + 0.0021373573333903907, + 0.0020038657435381257, + 0.0017727389893637477, + 0.0014587022318349155, + 0.0010805902221096093, + 0.0006609144606176251, + 0.00022392080409527296, + -0.0002053053709292688, + -0.0006023329394979184, + -0.000944644997827479, + -0.001212981144562626, + -0.0013925554944245599, + -0.0014738073952633322, + -0.001452868444758477, + -0.001331475322813246, + -0.0011170665364279842, + -0.0008220930819068703, + -0.0004637552851828441, + -0.00006253548108123283, + 0.00035878897793891384, + 0.0007765672753843151, + 0.0011676956517593336, + 0.0015105823732014887, + 0.001786670875323066, + 0.0019812511669580956, + 0.002084241239549793, + 0.0020909788043975276, + 0.0020022457010850096, + 0.001824332418711941, + 0.0015684774729692547, + 0.001250150108424033, + 0.0008882139336450031, + 0.0005039966417534989, + 0.00011989306001557591, + -0.00024184631248718984, + -0.0005602703726361708, + -0.0008171434769424912, + -0.0009972609130431105, + -0.0010918347055704724, + -0.0010922057096824714, + -0.0010037105425627479, + -0.0008248106197065263, + -0.0005702276065943115, + -0.0002535400937416474, + 0.00011015896725885363, + 0.0004988789779988034, + 0.0008900089027853051, + 0.0012633901100129934, + 0.0015991122050812935, + 0.0018778523547873093, + 0.002084143087394179, + 0.0022077928016267247, + 0.002243244512808436, + 0.0021891204599193454, + 0.0020487233738411786, + 0.0018306821451188385, + 0.001548740624466772, + 0.0012200029156715312, + 0.0008636162701018116, + 0.0005003479818101034, + 0.00015129060306465846, + -0.0001628354231637733, + -0.0004230530550807403, + -0.0006139467826508875, + -0.0007237724027604178, + -0.0007458291450652108, + -0.0006778152785331262, + -0.0005225020827002275, + -0.0002873259421258282, + 0.00001556164895794005, + 0.00037004878160910566, + 0.0007570502661148399, + 0.0011557133218574656, + 0.0015446566595713781, + 0.0019030267726684862, + 0.0022116895646837804, + 0.002454031118912115, + 0.0026171805325111786, + 0.0026923543124503487, + 0.002675931198702369, + 0.0025693090410960793, + 0.0023791019266531147, + 0.0021166520909361806, + 0.0017971738401020903, + 0.0014393167512730105, + 0.0010638765819667507, + 0.0006927997977343629, + 0.0003481061116814, + 0.00005048775593523694, + -0.00018159497394687693, + -0.000333110808989004, + -0.00039339019608776595, + -0.0003566665079835044, + -0.00022232703032059695, + 0.000004837984574109052, + 0.00031496346957593816, + 0.0006936436970225945, + 0.001122598250955268, + 0.0015808468393586463, + 0.002043603816320018, + 0.0024934237802220847, + 0.0028914904576434483, + 0.00324212650920447, + 0.003502213813807159, + 0.003666696471321502, + 0.00373312207850841, + 0.0036920509207547503, + 0.0035414284335566213, + 0.003291074349553435, + 0.0029568799307201055, + 0.0025562644079397566, + 0.0021085978153374337, + 0.001636917920572586, + 0.001167933929422274, + 0.0007296960675978035, + 0.00034866017633577485, + 0.00004791480114281375, + -0.00015331390959515178, + -0.00024019085124103728, + -0.0002024986474888905, + -0.00003519485538394603, + 0.00026038278850552165, + 0.000676466760403685, + 0.001198808101593799, + 0.001807325109970609, + 0.002477679601003533, + 0.003181825807588361, + 0.0038898729887556515, + 0.004570706217926675, + 0.0051935953163101374, + 0.005729115424630075, + 0.006150572165634373, + 0.006435200226996191, + 0.006565317350605899, + 0.00652916985473517, + 0.006321383314346843, + 0.005943231522926785, + 0.005402338389433701, + 0.0047125166256176704, + 0.003892678282644405, + 0.0029665219630286475, + 0.0019610873421838713, + 0.0009061942378719186, + -0.00016698144545659763, + -0.0012271560122908633, + -0.0022438395226593234, + -0.003188871268023831, + -0.00403722847791321, + -0.00476798650929006, + -0.005365157561460779, + -0.005817832832522139, + -0.0061205500978807606, + -0.006273284055978816, + -0.0062810419368997195, + -0.006153352756740714, + -0.005903803434753803, + -0.005549178890478986, + -0.00510864933708306, + -0.004602917018038953, + -0.004053348584904465, + -0.0034810491402275856, + -0.002906124689895695, + -0.006276773743368246 +}; + +void FM_Baseband_Filter_init(FM_Baseband_Filter* f) { + int i; + for(i = 0; i < FM_BASEBAND_FILTER_TAP_NUM; ++i) + f->history[i] = 0; + f->last_index = 0; +} + +void FM_Baseband_Filter_put(FM_Baseband_Filter* f, double input) { + f->history[f->last_index++] = input; + if(f->last_index == FM_BASEBAND_FILTER_TAP_NUM) + f->last_index = 0; +} + +double FM_Baseband_Filter_get(FM_Baseband_Filter* f) { + double acc = 0; + int index = f->last_index, i; + for(i = 0; i < FM_BASEBAND_FILTER_TAP_NUM; ++i) { + index = index != 0 ? index-1 : FM_BASEBAND_FILTER_TAP_NUM-1; + acc += f->history[index] * filter_taps[i]; + }; + return acc; +} + diff --git a/src/broadcast_fm/fir_filters/FM_Baseband_Filter.h b/src/broadcast_fm/fir_filters/FM_Baseband_Filter.h new file mode 100644 index 0000000..12e4ba6 --- /dev/null +++ b/src/broadcast_fm/fir_filters/FM_Baseband_Filter.h @@ -0,0 +1,35 @@ +#ifndef FM_BASEBAND_FILTER_H_ +#define FM_BASEBAND_FILTER_H_ + +/* + +FIR filter designed with + http://t-filter.appspot.com + +sampling frequency: 2000000 Hz + +* 0 Hz - 75000 Hz + gain = 1 + desired ripple = 5 dB + actual ripple = 4.140342542562957 dB + +* 78000 Hz - 1000000 Hz + gain = 0 + desired attenuation = -40 dB + actual attenuation = -40.07162834583716 dB + +*/ + +#define FM_BASEBAND_FILTER_TAP_NUM 655 + +typedef struct { + double history[FM_BASEBAND_FILTER_TAP_NUM]; + unsigned int last_index; +} FM_Baseband_Filter; + +void FM_Baseband_Filter_init(FM_Baseband_Filter* f); +void FM_Baseband_Filter_put(FM_Baseband_Filter* f, double input); +double FM_Baseband_Filter_get(FM_Baseband_Filter* f); + +#endif + diff --git a/src/common/hxcmod/hxcmod.c b/src/common/hxcmod/hxcmod.c new file mode 100644 index 0000000..9189d90 --- /dev/null +++ b/src/common/hxcmod/hxcmod.c @@ -0,0 +1,2037 @@ +/////////////////////////////////////////////////////////////////////////////////// +//-------------------------------------------------------------------------------// +//-------------------------------------------------------------------------------// +//-----------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 : hxcmod.c +// Contains: a tiny mod player +// +// Written by: Jean François DEL NERO +// +// You are free to do what you want with this code. +// A credit is always appreciated if you include it into your prod :) +// +// This file include some parts of the Noisetracker/Soundtracker/Protracker +// Module Format documentation written by Andrew Scott (Adrenalin Software) +// (modformat.txt) +// +// The core (hxcmod.c/hxcmod.h) is designed to have the least external dependency. +// So it should be usable on almost all OS and systems. +// Please also note that no dynamic allocation is done into the HxCMOD core. +// +// Change History (most recent first): +/////////////////////////////////////////////////////////////////////////////////// +// HxCMOD Core API: +// ------------------------------------------- +// int hxcmod_init(modcontext * modctx) +// +// - Initialize the modcontext buffer. Must be called before doing anything else. +// Return 1 if success. 0 in case of error. +// ------------------------------------------- +// int hxcmod_load( modcontext * modctx, void * mod_data, int mod_data_size ) +// +// - "Load" a MOD from memory (from "mod_data" with size "mod_data_size"). +// Return 1 if success. 0 in case of error. +// ------------------------------------------- +// void hxcmod_fillbuffer( modcontext * modctx, unsigned short * outbuffer, mssize nbsample, tracker_buffer_state * trkbuf ) +// +// - Generate and return the next samples chunk to outbuffer. +// nbsample specify the number of stereo 16bits samples you want. +// The output format is signed 44100Hz 16-bit Stereo PCM samples. +// The output buffer size in byte must be equal to ( nbsample * 2 * 2 ). +// The optional trkbuf parameter can be used to get detailed status of the player. Put NULL/0 is unused. +// ------------------------------------------- +// void hxcmod_unload( modcontext * modctx ) +// - "Unload" / clear the player status. +// ------------------------------------------- +/////////////////////////////////////////////////////////////////////////////////// + +#include "hxcmod.h" + +/////////////////////////////////////////////////////////////////////////////////// + +// Effects list +#define EFFECT_ARPEGGIO 0x0 // Supported +#define EFFECT_PORTAMENTO_UP 0x1 // Supported +#define EFFECT_PORTAMENTO_DOWN 0x2 // Supported +#define EFFECT_TONE_PORTAMENTO 0x3 // Supported +#define EFFECT_VIBRATO 0x4 // Supported +#define EFFECT_VOLSLIDE_TONEPORTA 0x5 // Supported +#define EFFECT_VOLSLIDE_VIBRATO 0x6 // Supported +#define EFFECT_VOLSLIDE_TREMOLO 0x7 // - TO BE DONE - +#define EFFECT_SET_PANNING 0x8 // - TO BE DONE - +#define EFFECT_SET_OFFSET 0x9 // Supported +#define EFFECT_VOLUME_SLIDE 0xA // Supported +#define EFFECT_JUMP_POSITION 0xB // Supported +#define EFFECT_SET_VOLUME 0xC // Supported +#define EFFECT_PATTERN_BREAK 0xD // Supported + +#define EFFECT_EXTENDED 0xE +#define EFFECT_E_FINE_PORTA_UP 0x1 // Supported +#define EFFECT_E_FINE_PORTA_DOWN 0x2 // Supported +#define EFFECT_E_GLISSANDO_CTRL 0x3 // - TO BE DONE - +#define EFFECT_E_VIBRATO_WAVEFORM 0x4 // - TO BE DONE - +#define EFFECT_E_SET_FINETUNE 0x5 // Supported +#define EFFECT_E_PATTERN_LOOP 0x6 // Supported +#define EFFECT_E_TREMOLO_WAVEFORM 0x7 // - TO BE DONE - +#define EFFECT_E_SET_PANNING_2 0x8 // - TO BE DONE - +#define EFFECT_E_RETRIGGER_NOTE 0x9 // Supported +#define EFFECT_E_FINE_VOLSLIDE_UP 0xA // Supported +#define EFFECT_E_FINE_VOLSLIDE_DOWN 0xB // Supported +#define EFFECT_E_NOTE_CUT 0xC // Supported +#define EFFECT_E_NOTE_DELAY 0xD // Supported +#define EFFECT_E_PATTERN_DELAY 0xE // Supported +#define EFFECT_E_INVERT_LOOP 0xF // Supported (W.I.P) +#define EFFECT_SET_SPEED 0xF0 // Supported +#define EFFECT_SET_TEMPO 0xF2 // Supported + +#define PERIOD_TABLE_LENGTH MAXNOTES + + +// +// Finetuning periods -> Amiga period * 2^(-finetune/12/8) +// + +static const short periodtable[]= +{ + // Finetune 0 (* 1.000000), Offset 0x0000 + 27392, 25856, 24384, 23040, 21696, 20480, 19328, 18240, 17216, 16256, 15360, 14496, + 13696, 12928, 12192, 11520, 10848, 10240, 9664, 9120, 8606, 8128, 7680, 7248, + 6848, 6464, 6096, 5760, 5424, 5120, 4832, 4560, 4304, 4064, 3840, 3624, + 3424, 3232, 3048, 2880, 2712, 2560, 2416, 2280, 2152, 2032, 1920, 1812, + 1712, 1616, 1524, 1440, 1356, 1280, 1208, 1140, 1076, 1016, 960, 906, + 856, 808, 762, 720, 678, 640, 604, 570, 538, 508, 480, 453, + 428, 404, 381, 360, 339, 320, 302, 285, 269, 254, 240, 226, + 214, 202, 190, 180, 170, 160, 151, 143, 135, 127, 120, 113, + 107, 101, 95, 90, 85, 80, 75, 71, 67, 63, 60, 56, + 53, 50, 47, 45, 42, 40, 37, 35, 33, 31, 30, 28, + 27, 25, 24, 22, 21, 20, 19, 18, 17, 16, 15, 14, + 13, 13, 12, 11, 11, 10, 9, 9, 8, 8, 7, 7, + + // Finetune 1 (* 0.992806), Offset 0x0120 + 27195, 25670, 24209, 22874, 21540, 20333, 19189, 18109, 17092, 16139, 15249, 14392, + 13597, 12835, 12104, 11437, 10770, 10166, 9594, 9054, 8544, 8070, 7625, 7196, + 6799, 6417, 6052, 5719, 5385, 5083, 4797, 4527, 4273, 4035, 3812, 3598, + 3399, 3209, 3026, 2859, 2692, 2542, 2399, 2264, 2137, 2017, 1906, 1799, + 1700, 1604, 1513, 1430, 1346, 1271, 1199, 1132, 1068, 1009, 953, 899, + 850, 802, 757, 715, 673, 635, 600, 566, 534, 504, 477, 450, + 425, 401, 378, 357, 337, 318, 300, 283, 267, 252, 238, 224, + 212, 201, 189, 179, 169, 159, 150, 142, 134, 126, 119, 112, + 106, 100, 94, 89, 84, 79, 74, 70, 67, 63, 60, 56, + 53, 50, 47, 45, 42, 40, 37, 35, 33, 31, 30, 28, + 27, 25, 24, 22, 21, 20, 19, 18, 17, 16, 15, 14, + 13, 13, 12, 11, 11, 10, 9, 9, 8, 8, 7, 7, + + // Finetune 2 (* 0.985663), Offset 0x0240 + 26999, 25485, 24034, 22710, 21385, 20186, 19051, 17978, 16969, 16023, 15140, 14288, + 13500, 12743, 12017, 11355, 10692, 10093, 9525, 8989, 8483, 8011, 7570, 7144, + 6750, 6371, 6009, 5677, 5346, 5047, 4763, 4495, 4242, 4006, 3785, 3572, + 3375, 3186, 3004, 2839, 2673, 2523, 2381, 2247, 2121, 2003, 1892, 1786, + 1687, 1593, 1502, 1419, 1337, 1262, 1191, 1124, 1061, 1001, 946, 893, + 844, 796, 751, 710, 668, 631, 595, 562, 530, 501, 473, 447, + 422, 398, 376, 355, 334, 315, 298, 281, 265, 250, 237, 223, + 211, 199, 187, 177, 168, 158, 149, 141, 133, 125, 118, 111, + 105, 100, 94, 89, 84, 79, 74, 70, 66, 62, 59, 55, + 52, 49, 46, 44, 41, 39, 36, 34, 33, 31, 30, 28, + 27, 25, 24, 22, 21, 20, 19, 18, 17, 16, 15, 14, + 13, 13, 12, 11, 11, 10, 9, 9, 8, 8, 7, 7, + + // Finetune 3 (* 0.978572), Offset 0x0360 + 26805, 25302, 23862, 22546, 21231, 20041, 18914, 17849, 16847, 15908, 15031, 14185, + 13403, 12651, 11931, 11273, 10616, 10021, 9457, 8925, 8422, 7954, 7515, 7093, + 6701, 6325, 5965, 5637, 5308, 5010, 4728, 4462, 4212, 3977, 3758, 3546, + 3351, 3163, 2983, 2818, 2654, 2505, 2364, 2231, 2106, 1988, 1879, 1773, + 1675, 1581, 1491, 1409, 1327, 1253, 1182, 1116, 1053, 994, 939, 887, + 838, 791, 746, 705, 663, 626, 591, 558, 526, 497, 470, 443, + 419, 395, 373, 352, 332, 313, 296, 279, 263, 249, 235, 221, + 209, 198, 186, 176, 166, 157, 148, 140, 132, 124, 117, 111, + 105, 99, 93, 88, 83, 78, 73, 69, 66, 62, 59, 55, + 52, 49, 46, 44, 41, 39, 36, 34, 32, 30, 29, 27, + 26, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, + 13, 13, 12, 11, 11, 10, 9, 9, 8, 8, 7, 7, + + // Finetune 4 (* 0.971532), Offset 0x0480 + 26612, 25120, 23690, 22384, 21078, 19897, 18778, 17721, 16726, 15793, 14923, 14083, + 13306, 12560, 11845, 11192, 10539, 9948, 9389, 8860, 8361, 7897, 7461, 7042, + 6653, 6280, 5922, 5596, 5270, 4974, 4694, 4430, 4181, 3948, 3731, 3521, + 3327, 3140, 2961, 2798, 2635, 2487, 2347, 2215, 2091, 1974, 1865, 1760, + 1663, 1570, 1481, 1399, 1317, 1244, 1174, 1108, 1045, 987, 933, 880, + 832, 785, 740, 700, 659, 622, 587, 554, 523, 494, 466, 440, + 416, 392, 370, 350, 329, 311, 293, 277, 261, 247, 233, 220, + 208, 196, 185, 175, 165, 155, 147, 139, 131, 123, 117, 110, + 104, 98, 92, 87, 83, 78, 73, 69, 65, 61, 58, 54, + 51, 49, 46, 44, 41, 39, 36, 34, 32, 30, 29, 27, + 26, 24, 23, 21, 20, 19, 18, 17, 17, 16, 15, 14, + 13, 13, 12, 11, 11, 10, 9, 9, 8, 8, 7, 7, + + // Finetune 5 (* 0.964542), Offset 0x05a0 + 26421, 24939, 23519, 22223, 20927, 19754, 18643, 17593, 16606, 15680, 14815, 13982, + 13210, 12470, 11760, 11112, 10463, 9877, 9321, 8797, 8301, 7840, 7408, 6991, + 6605, 6235, 5880, 5556, 5232, 4938, 4661, 4398, 4151, 3920, 3704, 3496, + 3303, 3117, 2940, 2778, 2616, 2469, 2330, 2199, 2076, 1960, 1852, 1748, + 1651, 1559, 1470, 1389, 1308, 1235, 1165, 1100, 1038, 980, 926, 874, + 826, 779, 735, 694, 654, 617, 583, 550, 519, 490, 463, 437, + 413, 390, 367, 347, 327, 309, 291, 275, 259, 245, 231, 218, + 206, 195, 183, 174, 164, 154, 146, 138, 130, 122, 116, 109, + 103, 97, 92, 87, 82, 77, 72, 68, 65, 61, 58, 54, + 51, 48, 45, 43, 41, 39, 36, 34, 32, 30, 29, 27, + 26, 24, 23, 21, 20, 19, 18, 17, 16, 15, 14, 14, + 13, 13, 12, 11, 11, 10, 9, 9, 8, 8, 7, 7, + + // Finetune 6 (* 0.957603), Offset 0x06c0 + 26231, 24760, 23350, 22063, 20776, 19612, 18509, 17467, 16486, 15567, 14709, 13881, + 13115, 12380, 11675, 11032, 10388, 9806, 9254, 8733, 8241, 7783, 7354, 6941, + 6558, 6190, 5838, 5516, 5194, 4903, 4627, 4367, 4122, 3892, 3677, 3470, + 3279, 3095, 2919, 2758, 2597, 2451, 2314, 2183, 2061, 1946, 1839, 1735, + 1639, 1547, 1459, 1379, 1299, 1226, 1157, 1092, 1030, 973, 919, 868, + 820, 774, 730, 689, 649, 613, 578, 546, 515, 486, 460, 434, + 410, 387, 365, 345, 325, 306, 289, 273, 258, 243, 230, 216, + 205, 193, 182, 172, 163, 153, 145, 137, 129, 122, 115, 108, + 102, 97, 91, 86, 81, 77, 72, 68, 64, 60, 57, 54, + 51, 48, 45, 43, 40, 38, 35, 34, 32, 30, 29, 27, + 26, 24, 23, 21, 20, 19, 18, 17, 16, 15, 14, 13, + 12, 12, 11, 11, 11, 10, 9, 9, 8, 8, 7, 7, + + // Finetune 7 (* 0.950714), Offset 0x07e0 + 26042, 24582, 23182, 21904, 20627, 19471, 18375, 17341, 16367, 15455, 14603, 13782, + 13021, 12291, 11591, 10952, 10313, 9735, 9188, 8671, 8182, 7727, 7301, 6891, + 6510, 6145, 5796, 5476, 5157, 4868, 4594, 4335, 4092, 3864, 3651, 3445, + 3255, 3073, 2898, 2738, 2578, 2434, 2297, 2168, 2046, 1932, 1825, 1723, + 1628, 1536, 1449, 1369, 1289, 1217, 1148, 1084, 1023, 966, 913, 861, + 814, 768, 724, 685, 645, 608, 574, 542, 511, 483, 456, 431, + 407, 384, 362, 342, 322, 304, 287, 271, 256, 241, 228, 215, + 203, 192, 181, 171, 162, 152, 144, 136, 128, 121, 114, 107, + 102, 96, 90, 86, 81, 76, 71, 68, 64, 60, 57, 53, + 50, 48, 45, 43, 40, 38, 35, 33, 31, 29, 29, 27, + 26, 24, 23, 21, 20, 19, 18, 17, 16, 15, 14, 13, + 12, 12, 11, 10, 10, 10, 9, 9, 8, 8, 7, 7, + + // Finetune -8 (* 1.059463), Offset 0x0900 + 29021, 27393, 25834, 24410, 22986, 21698, 20477, 19325, 18240, 17223, 16273, 15358, + 14510, 13697, 12917, 12205, 11493, 10849, 10239, 9662, 9118, 8611, 8137, 7679, + 7255, 6848, 6458, 6103, 5747, 5424, 5119, 4831, 4560, 4306, 4068, 3839, + 3628, 3424, 3229, 3051, 2873, 2712, 2560, 2416, 2280, 2153, 2034, 1920, + 1814, 1712, 1615, 1526, 1437, 1356, 1280, 1208, 1140, 1076, 1017, 960, + 907, 856, 807, 763, 718, 678, 640, 604, 570, 538, 509, 480, + 453, 428, 404, 381, 359, 339, 320, 302, 285, 269, 254, 239, + 227, 214, 201, 191, 180, 170, 160, 152, 143, 135, 127, 120, + 113, 107, 101, 95, 90, 85, 79, 75, 71, 67, 64, 59, + 56, 53, 50, 48, 44, 42, 39, 37, 35, 33, 32, 30, + 29, 26, 25, 23, 22, 21, 20, 19, 18, 17, 16, 15, + 14, 14, 13, 12, 12, 11, 10, 10, 8, 8, 7, 7, + + // Finetune -7 (* 1.051841), Offset 0x0a20 + 28812, 27196, 25648, 24234, 22821, 21542, 20330, 19186, 18108, 17099, 16156, 15247, + 14406, 13598, 12824, 12117, 11410, 10771, 10165, 9593, 9052, 8549, 8078, 7624, + 7203, 6799, 6412, 6059, 5705, 5385, 5082, 4796, 4527, 4275, 4039, 3812, + 3602, 3400, 3206, 3029, 2853, 2693, 2541, 2398, 2264, 2137, 2020, 1906, + 1801, 1700, 1603, 1515, 1426, 1346, 1271, 1199, 1132, 1069, 1010, 953, + 900, 850, 802, 757, 713, 673, 635, 600, 566, 534, 505, 476, + 450, 425, 401, 379, 357, 337, 318, 300, 283, 267, 252, 238, + 225, 212, 200, 189, 179, 168, 159, 150, 142, 134, 126, 119, + 113, 106, 100, 95, 89, 84, 79, 75, 70, 66, 63, 59, + 56, 53, 49, 47, 44, 42, 39, 37, 35, 33, 32, 29, + 28, 26, 25, 23, 22, 21, 20, 19, 18, 17, 16, 15, + 14, 14, 13, 12, 12, 11, 9, 9, 8, 8, 7, 7, + + // Finetune -6 (* 1.044274), Offset 0x0b40 + 28605, 27001, 25464, 24060, 22657, 21387, 20184, 19048, 17978, 16976, 16040, 15138, + 14302, 13500, 12732, 12030, 11328, 10693, 10092, 9524, 8987, 8488, 8020, 7569, + 7151, 6750, 6366, 6015, 5664, 5347, 5046, 4762, 4495, 4244, 4010, 3784, + 3576, 3375, 3183, 3008, 2832, 2673, 2523, 2381, 2247, 2122, 2005, 1892, + 1788, 1688, 1591, 1504, 1416, 1337, 1261, 1190, 1124, 1061, 1003, 946, + 894, 844, 796, 752, 708, 668, 631, 595, 562, 530, 501, 473, + 447, 422, 398, 376, 354, 334, 315, 298, 281, 265, 251, 236, + 223, 211, 198, 188, 178, 167, 158, 149, 141, 133, 125, 118, + 112, 105, 99, 94, 89, 84, 78, 74, 70, 66, 63, 58, + 55, 52, 49, 47, 44, 42, 39, 37, 34, 32, 31, 29, + 28, 26, 25, 23, 22, 21, 20, 19, 18, 17, 16, 15, + 14, 14, 13, 11, 11, 10, 9, 9, 8, 8, 7, 7, + + // Finetune -5 (* 1.036761), Offset 0x0c60 + 28399, 26806, 25280, 23887, 22494, 21233, 20039, 18911, 17849, 16854, 15925, 15029, + 14199, 13403, 12640, 11943, 11247, 10616, 10019, 9455, 8922, 8427, 7962, 7514, + 7100, 6702, 6320, 5972, 5623, 5308, 5010, 4728, 4462, 4213, 3981, 3757, + 3550, 3351, 3160, 2986, 2812, 2654, 2505, 2364, 2231, 2107, 1991, 1879, + 1775, 1675, 1580, 1493, 1406, 1327, 1252, 1182, 1116, 1053, 995, 939, + 887, 838, 790, 746, 703, 664, 626, 591, 558, 527, 498, 470, + 444, 419, 395, 373, 351, 332, 313, 295, 279, 263, 249, 234, + 222, 209, 197, 187, 176, 166, 157, 148, 140, 132, 124, 117, + 111, 105, 98, 93, 88, 83, 78, 74, 69, 65, 62, 58, + 55, 52, 49, 47, 44, 41, 38, 36, 34, 32, 31, 29, + 28, 26, 25, 23, 22, 21, 20, 19, 18, 17, 16, 15, + 13, 13, 12, 11, 11, 10, 9, 9, 8, 8, 7, 7, + + // Finetune -4 (* 1.029302), Offset 0x0d80 + 28195, 26614, 25099, 23715, 22332, 21080, 19894, 18774, 17720, 16732, 15810, 14921, + 14097, 13307, 12549, 11858, 11166, 10540, 9947, 9387, 8858, 8366, 7905, 7460, + 7049, 6653, 6275, 5929, 5583, 5270, 4974, 4694, 4430, 4183, 3953, 3730, + 3524, 3327, 3137, 2964, 2791, 2635, 2487, 2347, 2215, 2092, 1976, 1865, + 1762, 1663, 1569, 1482, 1396, 1318, 1243, 1173, 1108, 1046, 988, 933, + 881, 832, 784, 741, 698, 659, 622, 587, 554, 523, 494, 466, + 441, 416, 392, 371, 349, 329, 311, 293, 277, 261, 247, 233, + 220, 208, 196, 185, 175, 165, 155, 147, 139, 131, 124, 116, + 110, 104, 98, 93, 87, 82, 77, 73, 69, 65, 62, 58, + 55, 51, 48, 46, 43, 41, 38, 36, 34, 32, 31, 29, + 28, 26, 25, 23, 22, 21, 20, 19, 17, 16, 15, 14, + 13, 13, 12, 11, 11, 10, 9, 9, 8, 8, 7, 7, + + // Finetune -3 (* 1.021897), Offset 0x0ea0 + 27992, 26422, 24918, 23545, 22171, 20928, 19751, 18639, 17593, 16612, 15696, 14813, + 13996, 13211, 12459, 11772, 11086, 10464, 9876, 9320, 8794, 8306, 7848, 7407, + 6998, 6606, 6229, 5886, 5543, 5232, 4938, 4660, 4398, 4153, 3924, 3703, + 3499, 3303, 3115, 2943, 2771, 2616, 2469, 2330, 2199, 2076, 1962, 1852, + 1749, 1651, 1557, 1472, 1386, 1308, 1234, 1165, 1100, 1038, 981, 926, + 875, 826, 779, 736, 693, 654, 617, 582, 550, 519, 491, 463, + 437, 413, 389, 368, 346, 327, 309, 291, 275, 260, 245, 231, + 219, 206, 194, 184, 174, 164, 154, 146, 138, 130, 123, 115, + 109, 103, 97, 92, 87, 82, 77, 73, 68, 64, 61, 57, + 54, 51, 48, 46, 43, 41, 38, 36, 34, 32, 31, 29, + 28, 26, 25, 22, 21, 20, 19, 18, 17, 16, 15, 14, + 13, 13, 12, 11, 11, 10, 9, 9, 8, 8, 7, 7, + + // Finetune -2 (* 1.014545), Offset 0x0fc0 + 27790, 26232, 24739, 23375, 22012, 20778, 19609, 18505, 17466, 16492, 15583, 14707, + 13895, 13116, 12369, 11688, 11006, 10389, 9805, 9253, 8731, 8246, 7792, 7353, + 6948, 6558, 6185, 5844, 5503, 5194, 4902, 4626, 4367, 4123, 3896, 3677, + 3474, 3279, 3092, 2922, 2751, 2597, 2451, 2313, 2183, 2062, 1948, 1838, + 1737, 1640, 1546, 1461, 1376, 1299, 1226, 1157, 1092, 1031, 974, 919, + 868, 820, 773, 730, 688, 649, 613, 578, 546, 515, 487, 460, + 434, 410, 387, 365, 344, 325, 306, 289, 273, 258, 243, 229, + 217, 205, 193, 183, 172, 162, 153, 145, 137, 129, 122, 115, + 109, 102, 96, 91, 86, 81, 76, 72, 68, 64, 61, 57, + 54, 51, 48, 46, 43, 41, 38, 36, 33, 31, 30, 28, + 27, 25, 24, 22, 21, 20, 19, 18, 17, 16, 15, 14, + 13, 13, 12, 11, 11, 10, 9, 9, 8, 8, 7, 7, + + // Finetune -1 (* 1.007246), Offset 0x10e0 + 27590, 26043, 24561, 23207, 21853, 20628, 19468, 18372, 17341, 16374, 15471, 14601, + 13795, 13022, 12280, 11603, 10927, 10314, 9734, 9186, 8668, 8187, 7736, 7301, + 6898, 6511, 6140, 5802, 5463, 5157, 4867, 4593, 4335, 4093, 3868, 3650, + 3449, 3255, 3070, 2901, 2732, 2579, 2434, 2297, 2168, 2047, 1934, 1825, + 1724, 1628, 1535, 1450, 1366, 1289, 1217, 1148, 1084, 1023, 967, 913, + 862, 814, 768, 725, 683, 645, 608, 574, 542, 512, 483, 456, + 431, 407, 384, 363, 341, 322, 304, 287, 271, 256, 242, 228, + 216, 203, 191, 181, 171, 161, 152, 144, 136, 128, 121, 114, + 108, 102, 96, 91, 86, 81, 76, 72, 67, 63, 60, 56, + 53, 50, 47, 45, 42, 40, 37, 35, 33, 31, 30, 28, + 27, 25, 24, 22, 21, 20, 19, 18, 17, 16, 15, 14, + 13, 13, 12, 11, 11, 10, 9, 9, 8, 8, 7, 7 +}; + +static const short * periodtable_finetune_ptr[]= +{ + &periodtable[0x0000], &periodtable[0x0090], &periodtable[0x0120], &periodtable[0x01B0], + &periodtable[0x0240], &periodtable[0x02D0], &periodtable[0x0360], &periodtable[0x03F0], + &periodtable[0x0480], &periodtable[0x0510], &periodtable[0x05A0], &periodtable[0x0630], + &periodtable[0x06C0], &periodtable[0x0750], &periodtable[0x07E0], &periodtable[0x0870] +}; + +static const short sintable[]={ + 0, 24, 49, 74, 97, 120, 141, 161, + 180, 197, 212, 224, 235, 244, 250, 253, + 255, 253, 250, 244, 235, 224, 212, 197, + 180, 161, 141, 120, 97, 74, 49, 24 +}; + +static const muchar InvertLoopTable[]={ + 0, 5, 6, 7, 8, 10, 11, 13, + 16, 19, 22, 26, 32, 43, 64, 128 +}; + +typedef struct modtype_ +{ + unsigned char signature[5]; + int numberofchannels; +}modtype; + +static modtype modlist[]= +{ + { "M!K!",4}, + { "M.K.",4}, + { "M&K!",4}, + { "PATT",4}, + { "NSMS",4}, + { "LARD",4}, + { "FEST",4}, + { "FIST",4}, + { "N.T.",4}, + { "OKTA",8}, + { "OCTA",8}, + { "$CHN",-1}, + { "$$CH",-1}, + { "$$CN",-1}, + { "$$$C",-1}, + { "FLT$",-1}, + { "EXO$",-1}, + { "CD$1",-1}, + { "TDZ$",-1}, + { "FA0$",-1}, + { "",0} +}; + +#ifdef HXCMOD_BIGENDIAN_MACHINE + +#define GET_BGI_W( big_endian_word ) ( big_endian_word ) + +#else + +#define GET_BGI_W( big_endian_word ) ( (big_endian_word >> 8) | ((big_endian_word&0xFF) << 8) ) + +#endif + + +/////////////////////////////////////////////////////////////////////////////////// + +static void memcopy( void * dest, void *source, unsigned long size ) +{ + unsigned long i; + unsigned char * d,*s; + + d=(unsigned char*)dest; + s=(unsigned char*)source; + for(i=0;i= ptr[i]) + { + return i; + } + } + + return MAXNOTES; +} + +static void doFunk(channel * cptr) +{ + if(cptr->funkspeed) + { + cptr->funkoffset += InvertLoopTable[cptr->funkspeed]; + if( cptr->funkoffset > 128 ) + { + cptr->funkoffset = 0; + if( cptr->sampdata && cptr->length && (cptr->replen > 1) ) + { + if( ( (cptr->samppos) >> 11 ) >= (unsigned long)(cptr->replen+cptr->reppnt) ) + { + cptr->samppos = ((unsigned long)(cptr->reppnt)<<11) + (cptr->samppos % ((unsigned long)(cptr->replen+cptr->reppnt)<<11)); + } + +#ifndef HXCMOD_MOD_FILE_IN_ROM + // Note : Directly modify the sample in the mod buffer... + // The current Invert Loop effect implementation can't be played from ROM. + cptr->sampdata[cptr->samppos >> 10] = -1 - cptr->sampdata[cptr->samppos >> 10]; +#endif + } + } + } +} + +static void worknote( note * nptr, channel * cptr,char t,modcontext * mod ) +{ + muint sample, period, effect, operiod; + muint curnote, arpnote; + muchar effect_op; + muchar effect_param,effect_param_l,effect_param_h; + muint enable_nxt_smp; + const short * period_table_ptr; + + sample = (nptr->sampperiod & 0xF0) | (nptr->sampeffect >> 4); + period = ((nptr->sampperiod & 0xF) << 8) | nptr->period; + effect = ((nptr->sampeffect & 0xF) << 8) | nptr->effect; + effect_op = nptr->sampeffect & 0xF; + effect_param = nptr->effect; + effect_param_l = effect_param & 0x0F; + effect_param_h = effect_param >> 4; + + enable_nxt_smp = 0; + + operiod = cptr->period; + + if ( period || sample ) + { + if( sample && ( sample < 32 ) ) + { + cptr->sampnum = sample - 1; + } + + if( period || sample ) + { + if( period ) + { + if( ( effect_op != EFFECT_TONE_PORTAMENTO ) || ( ( effect_op == EFFECT_TONE_PORTAMENTO ) && !cptr->sampdata ) ) + { + // Not a Tone Partamento effect or no sound currently played : + if ( ( effect_op != EFFECT_EXTENDED || effect_param_h != EFFECT_E_NOTE_DELAY ) || ( ( effect_op == EFFECT_EXTENDED && effect_param_h == EFFECT_E_NOTE_DELAY ) && !effect_param_l ) ) + { + // Immediately (re)trigger the new note + cptr->sampdata = mod->sampledata[cptr->sampnum]; + cptr->length = GET_BGI_W( mod->song.samples[cptr->sampnum].length ); + cptr->reppnt = GET_BGI_W( mod->song.samples[cptr->sampnum].reppnt ); + cptr->replen = GET_BGI_W( mod->song.samples[cptr->sampnum].replen ); + + cptr->lst_sampdata = cptr->sampdata; + cptr->lst_length = cptr->length; + cptr->lst_reppnt = cptr->reppnt; + cptr->lst_replen = cptr->replen; + } + else + { + cptr->dly_sampdata = mod->sampledata[cptr->sampnum]; + cptr->dly_length = GET_BGI_W( mod->song.samples[cptr->sampnum].length ); + cptr->dly_reppnt = GET_BGI_W( mod->song.samples[cptr->sampnum].reppnt ); + cptr->dly_replen = GET_BGI_W( mod->song.samples[cptr->sampnum].replen ); + cptr->note_delay = effect_param_l; + } + // Cancel any delayed note... + cptr->update_nxt_repeat = 0; + } + else + { + // Partamento effect - Play the new note after the current sample. + if( effect_op == EFFECT_TONE_PORTAMENTO ) + enable_nxt_smp = 1; + } + } + else // Note without period : Trigger it after the current sample. + enable_nxt_smp = 1; + + if ( enable_nxt_smp ) + { + // Prepare the next sample retrigger after the current one + cptr->nxt_sampdata = mod->sampledata[cptr->sampnum]; + cptr->nxt_length = GET_BGI_W( mod->song.samples[cptr->sampnum].length ); + cptr->nxt_reppnt = GET_BGI_W( mod->song.samples[cptr->sampnum].reppnt ); + cptr->nxt_replen = GET_BGI_W( mod->song.samples[cptr->sampnum].replen ); + + if(cptr->nxt_replen < 2) // Protracker : don't play the sample if not looped... + cptr->nxt_sampdata = 0; + + cptr->update_nxt_repeat = 1; + } + + cptr->finetune = (mod->song.samples[cptr->sampnum].finetune) & 0xF; + + if( effect_op != EFFECT_VIBRATO && effect_op != EFFECT_VOLSLIDE_VIBRATO ) + { + cptr->vibraperiod = 0; + cptr->vibrapointeur = 0; + } + } + + if( (sample != 0) && ( effect_op != EFFECT_VOLSLIDE_TONEPORTA ) ) + { + cptr->volume = mod->song.samples[cptr->sampnum].volume; + cptr->volumeslide = 0; +#ifdef HXCMOD_USE_PRECALC_VOLUME_TABLE + cptr->volume_table = mod->volume_selection_table[cptr->volume]; +#endif + } + + if( ( effect_op != EFFECT_TONE_PORTAMENTO ) && ( effect_op != EFFECT_VOLSLIDE_TONEPORTA ) ) + { + if ( period != 0 ) + cptr->samppos = 0; + } + + cptr->decalperiod = 0; + if( period ) + { + if( cptr->finetune ) + { + period_table_ptr = periodtable_finetune_ptr[cptr->finetune&0xF]; + period = period_table_ptr[getnote(mod,period)]; + } + + cptr->period = period; + } + } + + cptr->effect = 0; + cptr->parameffect = 0; + cptr->effect_code = effect; + +#ifdef EFFECTS_USAGE_STATE + if(effect_op || ((effect_op==EFFECT_ARPEGGIO) && effect_param)) + { + mod->effects_event_counts[ effect_op ]++; + } + + if(effect_op == 0xE) + mod->effects_event_counts[ 0x10 + effect_param_h ]++; +#endif + + switch ( effect_op ) + { + case EFFECT_ARPEGGIO: + /* + [0]: Arpeggio + Where [0][x][y] means "play note, note+x semitones, note+y + semitones, then return to original note". The fluctuations are + carried out evenly spaced in one pattern division. They are usually + used to simulate chords, but this doesn't work too well. They are + also used to produce heavy vibrato. A major chord is when x=4, y=7. + A minor chord is when x=3, y=7. + */ + + if( effect_param ) + { + cptr->effect = EFFECT_ARPEGGIO; + cptr->parameffect = effect_param; + + cptr->ArpIndex = 0; + + curnote = getnote(mod,cptr->period); + + cptr->Arpperiods[0] = cptr->period; + + period_table_ptr = periodtable_finetune_ptr[cptr->finetune&0xF]; + + arpnote = curnote + (((cptr->parameffect>>4)&0xF)); + if( arpnote >= MAXNOTES ) + arpnote = (MAXNOTES) - 1; + + cptr->Arpperiods[1] = period_table_ptr[arpnote]; + + arpnote = curnote + (((cptr->parameffect)&0xF)); + if( arpnote >= MAXNOTES ) + arpnote = (MAXNOTES) - 1; + + cptr->Arpperiods[2] = period_table_ptr[arpnote]; + } + break; + + case EFFECT_PORTAMENTO_UP: + /* + [1]: Slide up + Where [1][x][y] means "smoothly decrease the period of current + sample by x*16+y after each tick in the division". The + ticks/division are set with the 'set speed' effect (see below). If + the period of the note being played is z, then the final period + will be z - (x*16 + y)*(ticks - 1). As the slide rate depends on + the speed, changing the speed will change the slide. You cannot + slide beyond the note B3 (period 113). + */ + + cptr->effect = EFFECT_PORTAMENTO_UP; + cptr->parameffect = effect_param; + break; + + case EFFECT_PORTAMENTO_DOWN: + /* + [2]: Slide down + Where [2][x][y] means "smoothly increase the period of current + sample by x*16+y after each tick in the division". Similar to [1], + but lowers the pitch. You cannot slide beyond the note C1 (period + 856). + */ + + cptr->effect = EFFECT_PORTAMENTO_DOWN; + cptr->parameffect = effect_param; + break; + + case EFFECT_TONE_PORTAMENTO: + /* + [3]: Slide to note + Where [3][x][y] means "smoothly change the period of current sample + by x*16+y after each tick in the division, never sliding beyond + current period". The period-length in this channel's division is a + parameter to this effect, and hence is not played. Sliding to a + note is similar to effects [1] and [2], but the slide will not go + beyond the given period, and the direction is implied by that + period. If x and y are both 0, then the old slide will continue. + */ + + cptr->effect = EFFECT_TONE_PORTAMENTO; + if( effect_param != 0 ) + { + cptr->portaspeed = (short)( effect_param ); + } + + if(period!=0) + { + cptr->portaperiod = period; + cptr->period = operiod; + } + break; + + case EFFECT_VIBRATO: + /* + [4]: Vibrato + Where [4][x][y] means "oscillate the sample pitch using a + particular waveform with amplitude y/16 semitones, such that (x * + ticks)/64 cycles occur in the division". The waveform is set using + effect [14][4]. By placing vibrato effects on consecutive + divisions, the vibrato effect can be maintained. If either x or y + are 0, then the old vibrato values will be used. + */ + + cptr->effect = EFFECT_VIBRATO; + if( effect_param_l != 0 ) // Depth continue or change ? + cptr->vibraparam = ( cptr->vibraparam & 0xF0 ) | effect_param_l; + if( effect_param_h != 0 ) // Speed continue or change ? + cptr->vibraparam = ( cptr->vibraparam & 0x0F ) | ( effect_param_h << 4 ); + + break; + + case EFFECT_VOLSLIDE_TONEPORTA: + /* + [5]: Continue 'Slide to note', but also do Volume slide + Where [5][x][y] means "either slide the volume up x*(ticks - 1) or + slide the volume down y*(ticks - 1), at the same time as continuing + the last 'Slide to note'". It is illegal for both x and y to be + non-zero. You cannot slide outside the volume range 0..64. The + period-length in this channel's division is a parameter to this + effect, and hence is not played. + */ + + if( period != 0 ) + { + cptr->portaperiod = period; + cptr->period = operiod; + } + + cptr->effect = EFFECT_VOLSLIDE_TONEPORTA; + if( effect_param != 0 ) + cptr->volumeslide = effect_param; + + break; + + case EFFECT_VOLSLIDE_VIBRATO: + /* + [6]: Continue 'Vibrato', but also do Volume slide + Where [6][x][y] means "either slide the volume up x*(ticks - 1) or + slide the volume down y*(ticks - 1), at the same time as continuing + the last 'Vibrato'". It is illegal for both x and y to be non-zero. + You cannot slide outside the volume range 0..64. + */ + + cptr->effect = EFFECT_VOLSLIDE_VIBRATO; + if( effect_param != 0 ) + cptr->volumeslide = effect_param; + break; + + case EFFECT_SET_OFFSET: + /* + [9]: Set sample offset + Where [9][x][y] means "play the sample from offset x*4096 + y*256". + The offset is measured in words. If no sample is given, yet one is + still playing on this channel, it should be retriggered to the new + offset using the current volume. + If xy is 00, the previous value is used. + */ + + cptr->samppos = ( ( ((muint)effect_param_h) << 12) + ( (((muint)effect_param_l) << 8) ) ) << 10; + + if(!cptr->samppos) + cptr->samppos = cptr->last_set_offset; + + cptr->last_set_offset = cptr->samppos; + break; + + case EFFECT_VOLUME_SLIDE: + /* + [10]: Volume slide + Where [10][x][y] means "either slide the volume up x*(ticks - 1) or + slide the volume down y*(ticks - 1)". If both x and y are non-zero, + then the y value is ignored (assumed to be 0). You cannot slide + outside the volume range 0..64. + */ + + cptr->effect = EFFECT_VOLUME_SLIDE; + cptr->volumeslide = effect_param; + break; + + case EFFECT_JUMP_POSITION: + /* + [11]: Position Jump + Where [11][x][y] means "stop the pattern after this division, and + continue the song at song-position x*16+y". This shifts the + 'pattern-cursor' in the pattern table (see above). Legal values for + x*16+y are from 0 to 127. + */ + + mod->tablepos = effect_param; + if(mod->tablepos >= mod->song.length) + mod->tablepos = 0; + mod->patternpos = 0; + mod->jump_loop_effect = 1; + + break; + + case EFFECT_SET_VOLUME: + /* + [12]: Set volume + Where [12][x][y] means "set current sample's volume to x*16+y". + Legal volumes are 0..64. + */ + + cptr->volume = effect_param; + + if(cptr->volume > 64) + cptr->volume = 64; + +#ifdef HXCMOD_USE_PRECALC_VOLUME_TABLE + cptr->volume_table = mod->volume_selection_table[cptr->volume]; +#endif + break; + + case EFFECT_PATTERN_BREAK: + /* + [13]: Pattern Break + Where [13][x][y] means "stop the pattern after this division, and + continue the song at the next pattern at division x*10+y" (the 10 + is not a typo). Legal divisions are from 0 to 63 (note Protracker + exception above). + */ + + mod->patternpos = ( ((muint)(effect_param_h) * 10) + effect_param_l ); + + if(mod->patternpos >= 64) + mod->patternpos = 63; + + mod->patternpos *= mod->number_of_channels; + + if(!mod->jump_loop_effect) + { + mod->tablepos++; + if(mod->tablepos >= mod->song.length) + mod->tablepos = 0; + } + + mod->jump_loop_effect = 1; + break; + + case EFFECT_EXTENDED: + switch( effect_param_h ) + { + case EFFECT_E_FINE_PORTA_UP: + /* + [14][1]: Fineslide up + Where [14][1][x] means "decrement the period of the current sample + by x". The incrementing takes place at the beginning of the + division, and hence there is no actual sliding. You cannot slide + beyond the note B3 (period 113). + */ + + cptr->period -= effect_param_l; + if( cptr->period < 113 ) + cptr->period = 113; + break; + + case EFFECT_E_FINE_PORTA_DOWN: + /* + [14][2]: Fineslide down + Where [14][2][x] means "increment the period of the current sample + by x". Similar to [14][1] but shifts the pitch down. You cannot + slide beyond the note C1 (period 856). + */ + + cptr->period += effect_param_l; + if( cptr->period > 856 ) + cptr->period = 856; + break; + + case EFFECT_E_GLISSANDO_CTRL: + /* + [14][3]: Set glissando on/off + Where [14][3][x] means "set glissando ON if x is 1, OFF if x is 0". + Used in conjunction with [3] ('Slide to note'). If glissando is on, + then 'Slide to note' will slide in semitones, otherwise will + perform the default smooth slide. + */ + + cptr->glissando = effect_param_l; + break; + + case EFFECT_E_FINE_VOLSLIDE_UP: + /* + [14][10]: Fine volume slide up + Where [14][10][x] means "increment the volume of the current sample + by x". The incrementing takes place at the beginning of the + division, and hence there is no sliding. You cannot slide beyond + volume 64. + */ + + cptr->volume += effect_param_l; + if( cptr->volume > 64 ) + cptr->volume = 64; +#ifdef HXCMOD_USE_PRECALC_VOLUME_TABLE + cptr->volume_table = mod->volume_selection_table[cptr->volume]; +#endif + break; + + case EFFECT_E_FINE_VOLSLIDE_DOWN: + /* + [14][11]: Fine volume slide down + Where [14][11][x] means "decrement the volume of the current sample + by x". Similar to [14][10] but lowers volume. You cannot slide + beyond volume 0. + */ + + cptr->volume -= effect_param_l; + if( cptr->volume > 200 ) + cptr->volume = 0; +#ifdef HXCMOD_USE_PRECALC_VOLUME_TABLE + cptr->volume_table = mod->volume_selection_table[cptr->volume]; +#endif + break; + + case EFFECT_E_SET_FINETUNE: + /* + [14][5]: Set finetune value + Where [14][5][x] means "sets the finetune value of the current + sample to the signed nibble x". x has legal values of 0..15, + corresponding to signed nibbles 0..7,-8..-1 (see start of text for + more info on finetune values). + */ + + cptr->finetune = effect_param_l; + + if( period ) + { + period_table_ptr = periodtable_finetune_ptr[cptr->finetune&0xF]; + period = period_table_ptr[getnote(mod,period)]; + cptr->period = period; + } + + break; + + case EFFECT_E_PATTERN_LOOP: + /* + [14][6]: Loop pattern + Where [14][6][x] means "set the start of a loop to this division if + x is 0, otherwise after this division, jump back to the start of a + loop and play it another x times before continuing". If the start + of the loop was not set, it will default to the start of the + current pattern. Hence 'loop pattern' cannot be performed across + multiple patterns. Note that loops do not support nesting, and you + may generate an infinite loop if you try to nest 'loop pattern's. + */ + + if( effect_param_l ) + { + if( cptr->patternloopcnt ) + { + cptr->patternloopcnt--; + if( cptr->patternloopcnt ) + { + mod->patternpos = cptr->patternloopstartpoint; + mod->jump_loop_effect = 1; + } + else + { + cptr->patternloopstartpoint = mod->patternpos ; + } + } + else + { + cptr->patternloopcnt = effect_param_l; + mod->patternpos = cptr->patternloopstartpoint; + mod->jump_loop_effect = 1; + } + } + else // Start point + { + cptr->patternloopstartpoint = mod->patternpos; + } + + break; + + case EFFECT_E_PATTERN_DELAY: + /* + [14][14]: Delay pattern + Where [14][14][x] means "after this division there will be a delay + equivalent to the time taken to play x divisions after which the + pattern will be resumed". The delay only relates to the + interpreting of new divisions, and all effects and previous notes + continue during delay. + */ + + mod->patterndelay = effect_param_l; + break; + + case EFFECT_E_RETRIGGER_NOTE: + /* + [14][9]: Retrigger sample + Where [14][9][x] means "trigger current sample every x ticks in + this division". If x is 0, then no retriggering is done (acts as if + no effect was chosen), otherwise the retriggering begins on the + first tick and then x ticks after that, etc. + */ + + if( effect_param_l ) + { + cptr->effect = EFFECT_EXTENDED; + cptr->parameffect = (EFFECT_E_RETRIGGER_NOTE<<4); + cptr->retrig_param = effect_param_l; + cptr->retrig_cnt = 0; + } + break; + + case EFFECT_E_NOTE_CUT: + /* + [14][12]: Cut sample + Where [14][12][x] means "after the current sample has been played + for x ticks in this division, its volume will be set to 0". This + implies that if x is 0, then you will not hear any of the sample. + If you wish to insert "silence" in a pattern, it is better to use a + "silence"-sample (see above) due to the lack of proper support for + this effect. + */ + + cptr->effect = EFFECT_E_NOTE_CUT; + cptr->cut_param = effect_param_l; + if( !cptr->cut_param ) + { + cptr->volume = 0; +#ifdef HXCMOD_USE_PRECALC_VOLUME_TABLE + cptr->volume_table = mod->volume_selection_table[cptr->volume]; +#endif + } + break; + + case EFFECT_E_NOTE_DELAY: + /* + Where [14][13][x] means "do not start this division's sample for + the first x ticks in this division, play the sample after this". + This implies that if x is 0, then you will hear no delay, but + actually there will be a VERY small delay. Note that this effect + only influences a sample if it was started in this division. + */ + + cptr->effect = EFFECT_EXTENDED; + cptr->parameffect = (EFFECT_E_NOTE_DELAY<<4); + break; + + case EFFECT_E_INVERT_LOOP: + /* + Where [14][15][x] means "if x is greater than 0, then play the + current sample's loop upside down at speed x". Each byte in the + sample's loop will have its sign changed (negated). It will only + work if the sample's loop (defined previously) is not too big. The + speed is based on an internal table. + */ + + cptr->funkspeed = effect_param_l; + + doFunk(cptr); + + break; + + default: + + break; + } + break; + + case 0xF: + /* + [15]: Set speed + Where [15][x][y] means "set speed to x*16+y". Though it is nowhere + near that simple. Let z = x*16+y. Depending on what values z takes, + different units of speed are set, there being two: ticks/division + and beats/minute (though this one is only a label and not strictly + true). If z=0, then what should technically happen is that the + module stops, but in practice it is treated as if z=1, because + there is already a method for stopping the module (running out of + patterns). If z<=32, then it means "set ticks/division to z" + otherwise it means "set beats/minute to z" (convention says that + this should read "If z<32.." but there are some composers out there + that defy conventions). Default values are 6 ticks/division, and + 125 beats/minute (4 divisions = 1 beat). The beats/minute tag is + only meaningful for 6 ticks/division. To get a more accurate view + of how things work, use the following formula: + 24 * beats/minute + divisions/minute = ----------------- + ticks/division + Hence divisions/minute range from 24.75 to 6120, eg. to get a value + of 2000 divisions/minute use 3 ticks/division and 250 beats/minute. + If multiple "set speed" effects are performed in a single division, + the ones on higher-numbered channels take precedence over the ones + on lower-numbered channels. This effect has a large number of + different implementations, but the one described here has the + widest usage. + */ + + + if( effect_param ) + { + + if( effect_param < 0x20 ) + { + mod->song.speed = effect_param; + } + else + { // effect_param >= 0x20 + /// HZ = 2 * BPM / 5 + mod->bpm = effect_param; + } + +#ifdef HXCMOD_16BITS_TARGET + // song.speed = 1 <> 31 + // playrate = 8000 <> 22050 + // bpm = 32 <> 255 + + mod->patternticksem = (muint)( ( (mulong)mod->playrate * 5 ) / ( (muint)mod->bpm * 2 ) ); +#else + // song.speed = 1 <> 31 + // playrate = 8000 <> 96000 + // bpm = 32 <> 255 + + mod->patternticksem = ( ( mod->playrate * 5 ) / ( (mulong)mod->bpm * 2 ) ); +#endif + mod->patternticksaim = mod->song.speed * mod->patternticksem; + } + + break; + + default: + // Unsupported effect + break; + + } + +} + +static void workeffect( modcontext * modctx, note * nptr, channel * cptr ) +{ + doFunk(cptr); + + switch(cptr->effect) + { + case EFFECT_ARPEGGIO: + + if( cptr->parameffect ) + { + cptr->decalperiod = cptr->period - cptr->Arpperiods[cptr->ArpIndex]; + + cptr->ArpIndex++; + if( cptr->ArpIndex>2 ) + cptr->ArpIndex = 0; + } + break; + + case EFFECT_PORTAMENTO_UP: + + if( cptr->period ) + { + cptr->period -= cptr->parameffect; + + if( cptr->period < 113 || cptr->period > 20000 ) + cptr->period = 113; + } + + break; + + case EFFECT_PORTAMENTO_DOWN: + + if( cptr->period ) + { + cptr->period += cptr->parameffect; + + if( cptr->period > 20000 ) + cptr->period = 20000; + } + + break; + + case EFFECT_VOLSLIDE_TONEPORTA: + case EFFECT_TONE_PORTAMENTO: + + if( cptr->period && ( cptr->period != cptr->portaperiod ) && cptr->portaperiod ) + { + if( cptr->period > cptr->portaperiod ) + { + if( cptr->period - cptr->portaperiod >= cptr->portaspeed ) + { + cptr->period -= cptr->portaspeed; + } + else + { + cptr->period = cptr->portaperiod; + } + } + else + { + if( cptr->portaperiod - cptr->period >= cptr->portaspeed ) + { + cptr->period += cptr->portaspeed; + } + else + { + cptr->period = cptr->portaperiod; + } + } + + if( cptr->period == cptr->portaperiod ) + { + // If the slide is over, don't let it to be retriggered. + cptr->portaperiod = 0; + } + } + + if( cptr->glissando ) + { + // TODO : Glissando effect. + } + + if( cptr->effect == EFFECT_VOLSLIDE_TONEPORTA ) + { + if( cptr->volumeslide & 0xF0 ) + { + cptr->volume += ( cptr->volumeslide >> 4 ); + + if( cptr->volume > 63 ) + cptr->volume = 63; + } + else + { + cptr->volume -= ( cptr->volumeslide & 0x0F ); + + if( cptr->volume > 63 ) + cptr->volume = 0; + } +#ifdef HXCMOD_USE_PRECALC_VOLUME_TABLE + cptr->volume_table = modctx->volume_selection_table[cptr->volume]; +#endif + } + break; + + case EFFECT_VOLSLIDE_VIBRATO: + case EFFECT_VIBRATO: + + cptr->vibraperiod = ( (cptr->vibraparam&0xF) * sintable[cptr->vibrapointeur&0x1F] )>>7; + + if( cptr->vibrapointeur > 31 ) + cptr->vibraperiod = -cptr->vibraperiod; + + cptr->vibrapointeur = ( cptr->vibrapointeur + ( ( cptr->vibraparam>>4 ) & 0x0F) ) & 0x3F; + + if( cptr->effect == EFFECT_VOLSLIDE_VIBRATO ) + { + if( cptr->volumeslide & 0xF0 ) + { + cptr->volume += ( cptr->volumeslide >> 4 ); + + if( cptr->volume > 64 ) + cptr->volume = 64; + } + else + { + cptr->volume -= cptr->volumeslide; + + if( cptr->volume > 64 ) + cptr->volume = 0; + } +#ifdef HXCMOD_USE_PRECALC_VOLUME_TABLE + cptr->volume_table = modctx->volume_selection_table[cptr->volume]; +#endif + } + + break; + + case EFFECT_VOLUME_SLIDE: + + if( cptr->volumeslide & 0xF0 ) + { + cptr->volume += ( cptr->volumeslide >> 4 ); + + if( cptr->volume > 64 ) + cptr->volume = 64; + } + else + { + cptr->volume -= cptr->volumeslide; + + if( cptr->volume > 64 ) + cptr->volume = 0; + } +#ifdef HXCMOD_USE_PRECALC_VOLUME_TABLE + cptr->volume_table = modctx->volume_selection_table[cptr->volume]; +#endif + break; + + case EFFECT_EXTENDED: + switch( cptr->parameffect >> 4 ) + { + + case EFFECT_E_NOTE_CUT: + if( cptr->cut_param ) + cptr->cut_param--; + + if( !cptr->cut_param ) + { + cptr->volume = 0; +#ifdef HXCMOD_USE_PRECALC_VOLUME_TABLE + cptr->volume_table = modctx->volume_selection_table[cptr->volume]; +#endif + } + break; + + case EFFECT_E_RETRIGGER_NOTE: + cptr->retrig_cnt++; + if( cptr->retrig_cnt >= cptr->retrig_param ) + { + cptr->retrig_cnt = 0; + + cptr->sampdata = cptr->lst_sampdata; + cptr->length = cptr->lst_length; + cptr->reppnt = cptr->lst_reppnt; + cptr->replen = cptr->lst_replen; + cptr->samppos = 0; + } + break; + + case EFFECT_E_NOTE_DELAY: + if( cptr->note_delay ) + { + if( (unsigned char)( cptr->note_delay - 1 ) == modctx->tick_cnt ) + { + cptr->sampdata = cptr->dly_sampdata; + cptr->length = cptr->dly_length; + cptr->reppnt = cptr->dly_reppnt; + cptr->replen = cptr->dly_replen; + + cptr->lst_sampdata = cptr->sampdata; + cptr->lst_length = cptr->length; + cptr->lst_reppnt = cptr->reppnt; + cptr->lst_replen = cptr->replen; + cptr->note_delay = 0; + } + } + break; + default: + break; + } + break; + + default: + break; + + } + +} + +/////////////////////////////////////////////////////////////////////////////////// +int hxcmod_init(modcontext * modctx) +{ +#ifdef HXCMOD_USE_PRECALC_VOLUME_TABLE + muint c; + mint i,j; +#endif + if( modctx ) + { + memclear(modctx,0,sizeof(modcontext)); + modctx->playrate = 44100; + modctx->stereo = 1; + modctx->stereo_separation = 1; + modctx->bits = 16; + modctx->filter = 1; + +#ifdef HXCMOD_USE_PRECALC_VOLUME_TABLE + c = 0; + for(i=0;i<65;i++) + { + for(j=-128;j<128;j++) + { + modctx->precalc_volume_array[c] = i * j; + c++; + } + + modctx->volume_selection_table[i] = &modctx->precalc_volume_array[(i*256) + 128]; + } +#endif + + return 1; + } + + return 0; +} + +int hxcmod_setcfg(modcontext * modctx, int samplerate, int stereo_separation, int filter) +{ + if( modctx ) + { + modctx->playrate = samplerate; + + if(stereo_separation < 4) + { + modctx->stereo_separation = stereo_separation; + } + + if( filter ) + modctx->filter = 1; + else + modctx->filter = 0; + + return 1; + } + + return 0; +} + +int hxcmod_load( modcontext * modctx, void * mod_data, int mod_data_size ) +{ + muint i, j, max, digitfactor; + sample *sptr; + unsigned char * modmemory,* endmodmemory; + + modmemory = (unsigned char *)mod_data; + endmodmemory = modmemory + mod_data_size; + + if( modmemory ) + { + if( modctx ) + { +#ifdef FULL_STATE + memclear(&(modctx->effects_event_counts),0,sizeof(modctx->effects_event_counts)); +#endif + memcopy(&(modctx->song.title),modmemory,1084); + + i = 0; + modctx->number_of_channels = 0; + while(modlist[i].numberofchannels && !modctx->number_of_channels) + { + digitfactor = 0; + + j = 0; + while( j < 4 ) + { + if( modlist[i].signature[j] == '$' ) + { + if(digitfactor) + digitfactor *= 10; + else + digitfactor = 1; + } + j++; + } + + modctx->number_of_channels = 0; + + j = 0; + while( j < 4 ) + { + if( (modlist[i].signature[j] == modctx->song.signature[j]) || modlist[i].signature[j] == '$' ) + { + if( modlist[i].signature[j] == '$' ) + { + if(modctx->song.signature[j] >= '0' && modctx->song.signature[j] <= '9') + { + modctx->number_of_channels += (modctx->song.signature[j] - '0') * digitfactor; + digitfactor /= 10; + } + else + { + modctx->number_of_channels = 0; + break; + } + } + j++; + } + else + { + modctx->number_of_channels = 0; + break; + } + } + + if( j == 4 ) + { + if(!modctx->number_of_channels) + modctx->number_of_channels = modlist[i].numberofchannels; + } + + i++; + } + + if( !modctx->number_of_channels ) + { + // 15 Samples modules support + // Shift the whole datas to make it look likes a standard 4 channels mod. + memcopy(&(modctx->song.signature), "M.K.", 4); + memcopy(&(modctx->song.length), &(modctx->song.samples[15]), 130); + memclear(&(modctx->song.samples[15]), 0, 480); + modmemory += 600; + modctx->number_of_channels = 4; + } + else + { + modmemory += 1084; + } + + if( modctx->number_of_channels > NUMMAXCHANNELS ) + return 0; // Too much channels ! - Increase/define HXCMOD_MAXCHANNELS ! + + if( modmemory >= endmodmemory ) + return 0; // End passed ? - Probably a bad file ! + + // Patterns loading + for (i = max = 0; i < 128; i++) + { + while (max <= modctx->song.patterntable[i]) + { + modctx->patterndata[max] = (note*)modmemory; + modmemory += (256*modctx->number_of_channels); + max++; + + if( modmemory >= endmodmemory ) + return 0; // End passed ? - Probably a bad file ! + } + } + + for (i = 0; i < 31; i++) + modctx->sampledata[i]=0; + + // Samples loading + for (i = 0, sptr = modctx->song.samples; i <31; i++, sptr++) + { + if (sptr->length == 0) continue; + + modctx->sampledata[i] = (mchar*)modmemory; + modmemory += (GET_BGI_W(sptr->length)*2); + + if (GET_BGI_W(sptr->replen) + GET_BGI_W(sptr->reppnt) > GET_BGI_W(sptr->length)) + sptr->replen = GET_BGI_W((GET_BGI_W(sptr->length) - GET_BGI_W(sptr->reppnt))); + + if( modmemory > endmodmemory ) + return 0; // End passed ? - Probably a bad file ! + } + + // States init + + modctx->tablepos = 0; + modctx->patternpos = 0; + modctx->song.speed = 6; + modctx->bpm = 125; + +#ifdef HXCMOD_16BITS_TARGET + // song.speed = 1 <> 31 + // playrate = 8000 <> 22050 + // bpm = 32 <> 255 + + modctx->patternticksem = (muint)( ( (mulong)modctx->playrate * 5 ) / ( (muint)modctx->bpm * 2 ) ); +#else + // song.speed = 1 <> 31 + // playrate = 8000 <> 96000 + // bpm = 32 <> 255 + + modctx->patternticksem = ( ( modctx->playrate * 5 ) / ( (mulong)modctx->bpm * 2 ) ); +#endif + modctx->patternticksaim = modctx->song.speed * modctx->patternticksem; + + modctx->patternticks = modctx->patternticksaim + 1; + + modctx->sampleticksconst = ((3546894UL * 16) / modctx->playrate) << 6; //8448*428/playrate; + + for(i=0; i < modctx->number_of_channels; i++) + { + modctx->channels[i].volume = 0; + modctx->channels[i].period = 0; +#ifdef HXCMOD_USE_PRECALC_VOLUME_TABLE + modctx->channels[i].volume_table = modctx->volume_selection_table[0]; +#endif + } + + modctx->mod_loaded = 1; + + return 1; + } + } + + return 0; +} + +void hxcmod_fillbuffer(modcontext * modctx, msample * outbuffer, mssize nbsample, tracker_buffer_state * trkbuf) +{ + mssize i; + muint j; + muint c; + + unsigned long k; + unsigned int state_remaining_steps; + +#ifdef HXCMOD_OUTPUT_FILTER +#ifndef HXCMOD_MONO_OUTPUT + int ll,tl; +#endif + int lr,tr; +#endif + +#ifndef HXCMOD_MONO_OUTPUT + int l; +#endif + int r; + + short finalperiod; + note *nptr; + channel *cptr; + + if( modctx && outbuffer ) + { + if(modctx->mod_loaded) + { + state_remaining_steps = 0; + +#ifdef HXCMOD_STATE_REPORT_SUPPORT + if( trkbuf ) + { + trkbuf->cur_rd_index = 0; + + memcopy(trkbuf->name,modctx->song.title,sizeof(modctx->song.title)); + + for(i=0;i<31;i++) + { + memcopy(trkbuf->instruments[i].name,modctx->song.samples[i].name,sizeof(trkbuf->instruments[i].name)); + } + } +#endif + +#ifdef HXCMOD_OUTPUT_FILTER + #ifndef HXCMOD_MONO_OUTPUT + ll = modctx->last_l_sample; + #endif + lr = modctx->last_r_sample; +#endif + + for (i = 0; i < nbsample; i++) + { + //--------------------------------------- + if( modctx->patternticks++ > modctx->patternticksaim ) + { + if( !modctx->patterndelay ) + { + nptr = modctx->patterndata[modctx->song.patterntable[modctx->tablepos]]; + nptr = nptr + modctx->patternpos; + cptr = modctx->channels; + + modctx->tick_cnt = 0; + + modctx->patternticks = 0; + modctx->patterntickse = 0; + + for(c=0;cnumber_of_channels;c++) + { + worknote((note*)(nptr), (channel*)(cptr),(char)(c+1),modctx); + + if (cptr->period != 0) + { + finalperiod = cptr->period - cptr->decalperiod - cptr->vibraperiod; + if (finalperiod) + { + cptr->sampinc = ((modctx->sampleticksconst) / finalperiod); + } + else + { + cptr->sampinc = 0; + } + } + else + cptr->sampinc = 0; + + nptr++; + cptr++; + } + + if( !modctx->jump_loop_effect ) + modctx->patternpos += modctx->number_of_channels; + else + modctx->jump_loop_effect = 0; + + if( modctx->patternpos == 64*modctx->number_of_channels ) + { + modctx->tablepos++; + modctx->patternpos = 0; + if(modctx->tablepos >= modctx->song.length) + modctx->tablepos = 0; + } + } + else + { + modctx->patterndelay--; + modctx->patternticks = 0; + modctx->patterntickse = 0; + modctx->tick_cnt = 0; + } + + } + + if (modctx->patterntickse++ > modctx->patternticksem) + { + nptr = modctx->patterndata[modctx->song.patterntable[modctx->tablepos]]; + nptr = nptr + modctx->patternpos; + cptr = modctx->channels; + + for(c=0;cnumber_of_channels;c++) + { + workeffect( modctx, nptr, cptr ); + + if (cptr->period != 0) + { + finalperiod = cptr->period - cptr->decalperiod - cptr->vibraperiod; + if (finalperiod) + { + cptr->sampinc = ((modctx->sampleticksconst) / finalperiod); + } + else + { + cptr->sampinc = 0; + } + } + else + cptr->sampinc = 0; + + nptr++; + cptr++; + } + + modctx->tick_cnt++; + modctx->patterntickse = 0; + } + + //--------------------------------------- + +#ifdef HXCMOD_STATE_REPORT_SUPPORT + if( trkbuf && !state_remaining_steps ) + { + if( trkbuf->nb_of_state < trkbuf->nb_max_of_state ) + { + memclear(&trkbuf->track_state_buf[trkbuf->nb_of_state],0,sizeof(tracker_state)); + } + } +#endif + +#ifndef HXCMOD_MONO_OUTPUT + l=0; +#endif + r=0; + + for( j = 0, cptr = modctx->channels; j < modctx->number_of_channels ; j++, cptr++) + { + if( cptr->period != 0 ) + { + cptr->samppos += cptr->sampinc; + + if( cptr->replen < 2 ) + { + if( ( cptr->samppos >> 11) >= cptr->length ) + { + cptr->length = 0; + cptr->reppnt = 0; + + if(cptr->update_nxt_repeat) + { + cptr->replen = cptr->nxt_replen; + cptr->reppnt = cptr->nxt_reppnt; + cptr->sampdata = cptr->nxt_sampdata; + cptr->length = cptr->nxt_length; + + cptr->lst_sampdata = cptr->sampdata; + cptr->lst_length = cptr->length; + cptr->lst_reppnt = cptr->reppnt; + cptr->lst_replen = cptr->replen; + + cptr->update_nxt_repeat = 0; + } + + if( cptr->length ) + cptr->samppos = cptr->samppos % (((unsigned long)cptr->length)<<11); + else + cptr->samppos = 0; + } + } + else + { + if( ( cptr->samppos >> 11 ) >= (unsigned long)(cptr->replen+cptr->reppnt) ) + { + if( cptr->update_nxt_repeat ) + { + cptr->replen = cptr->nxt_replen; + cptr->reppnt = cptr->nxt_reppnt; + cptr->sampdata = cptr->nxt_sampdata; + cptr->length = cptr->nxt_length; + + cptr->lst_sampdata = cptr->sampdata; + cptr->lst_length = cptr->length; + cptr->lst_reppnt = cptr->reppnt; + cptr->lst_replen = cptr->replen; + + cptr->update_nxt_repeat = 0; + } + + if( cptr->sampdata ) + { + cptr->samppos = ((unsigned long)(cptr->reppnt)<<11) + (cptr->samppos % ((unsigned long)(cptr->replen+cptr->reppnt)<<11)); + } + } + } + + k = cptr->samppos >> 10; + +#ifdef HXCMOD_MONO_OUTPUT + if( cptr->sampdata!=0 ) + { +#ifdef HXCMOD_USE_PRECALC_VOLUME_TABLE + r += cptr->volume_table[cptr->sampdata[k]]; +#else + r += ( cptr->sampdata[k] * cptr->volume ); +#endif + } +#else + if (cptr->sampdata != 0) + { + if ( !(j & 3) || ((j & 3) == 3) ) + { +#ifdef HXCMOD_USE_PRECALC_VOLUME_TABLE + l += cptr->volume_table[cptr->sampdata[k]]; +#else + l += (cptr->sampdata[k] * cptr->volume); +#endif + } + else + { +#ifdef HXCMOD_USE_PRECALC_VOLUME_TABLE + r += cptr->volume_table[cptr->sampdata[k]]; +#else + r += (cptr->sampdata[k] * cptr->volume); +#endif + } + } +#endif + +#ifdef HXCMOD_STATE_REPORT_SUPPORT + if( trkbuf && !state_remaining_steps ) + { + if( trkbuf->nb_of_state < trkbuf->nb_max_of_state ) + { + trkbuf->track_state_buf[trkbuf->nb_of_state].number_of_tracks = modctx->number_of_channels; + trkbuf->track_state_buf[trkbuf->nb_of_state].buf_index = i; + trkbuf->track_state_buf[trkbuf->nb_of_state].cur_pattern = modctx->song.patterntable[modctx->tablepos]; + trkbuf->track_state_buf[trkbuf->nb_of_state].cur_pattern_pos = modctx->patternpos / modctx->number_of_channels; + trkbuf->track_state_buf[trkbuf->nb_of_state].cur_pattern_table_pos = modctx->tablepos; + trkbuf->track_state_buf[trkbuf->nb_of_state].bpm = modctx->bpm; + trkbuf->track_state_buf[trkbuf->nb_of_state].speed = modctx->song.speed; + trkbuf->track_state_buf[trkbuf->nb_of_state].tracks[j].cur_effect = cptr->effect_code; + trkbuf->track_state_buf[trkbuf->nb_of_state].tracks[j].cur_parameffect = cptr->parameffect; + if(cptr->sampinc) + trkbuf->track_state_buf[trkbuf->nb_of_state].tracks[j].cur_period = (muint)(modctx->sampleticksconst / cptr->sampinc); + else + trkbuf->track_state_buf[trkbuf->nb_of_state].tracks[j].cur_period = 0; + trkbuf->track_state_buf[trkbuf->nb_of_state].tracks[j].cur_volume = cptr->volume; + trkbuf->track_state_buf[trkbuf->nb_of_state].tracks[j].instrument_number = (unsigned char)cptr->sampnum; + } + } +#endif + } + } + +#ifdef HXCMOD_STATE_REPORT_SUPPORT + if( trkbuf && !state_remaining_steps ) + { + state_remaining_steps = trkbuf->sample_step; + + if(trkbuf->nb_of_state < trkbuf->nb_max_of_state) + trkbuf->nb_of_state++; + } + else +#endif + { + state_remaining_steps--; + } + +#ifdef HXCMOD_MONO_OUTPUT + + #ifdef HXCMOD_OUTPUT_FILTER + tr = (short)r; + + if ( modctx->filter ) + { + // Filter + r = (r+lr)>>1; + } + #endif + + #ifdef HXCMOD_CLIPPING_CHECK + // Level limitation + if( r > 32767 ) r = 32767; + if( r < -32768 ) r = -32768; + #endif + // Store the final sample. + #ifdef HXCMOD_8BITS_OUTPUT + + #ifdef HXCMOD_UNSIGNED_OUTPUT + *outbuffer++ = (r >> 8) + 127; + #else + *outbuffer++ = r >> 8; + #endif + + #else + + #ifdef HXCMOD_UNSIGNED_OUTPUT + *outbuffer++ = r + 32767; + #else + *outbuffer++ = r; + #endif + + #endif + + #ifdef HXCMOD_OUTPUT_FILTER + lr = tr; + #endif + +#else + + #ifdef HXCMOD_OUTPUT_FILTER + tl = (short)l; + tr = (short)r; + + if ( modctx->filter ) + { + // Filter + l = (l+ll)>>1; + r = (r+lr)>>1; + } + #endif + + #ifdef HXCMOD_OUTPUT_STEREO_MIX + if ( modctx->stereo_separation == 1 ) + { + // Left & Right Stereo panning + l = (l+(r>>1)); + r = (r+(l>>1)); + } + #endif + + #ifdef HXCMOD_CLIPPING_CHECK + // Level limitation + if( l > 32767 ) l = 32767; + if( l < -32768 ) l = -32768; + if( r > 32767 ) r = 32767; + if( r < -32768 ) r = -32768; + #endif + // Store the final sample. + + + #ifdef HXCMOD_8BITS_OUTPUT + + #ifdef HXCMOD_UNSIGNED_OUTPUT + *outbuffer++ = ( l >> 8 ) + 127; + *outbuffer++ = ( r >> 8 ) + 127; + #else + *outbuffer++ = l >> 8; + *outbuffer++ = r >> 8; + #endif + + #else + + #ifdef HXCMOD_UNSIGNED_OUTPUT + *outbuffer++ = l + 32767; + *outbuffer++ = r + 32767; + #else + *outbuffer++ = l; + *outbuffer++ = r; + #endif + + #endif + + #ifdef HXCMOD_OUTPUT_FILTER + ll = tl; + lr = tr; + #endif + +#endif // HXCMOD_MONO_OUTPUT + + } + +#ifdef HXCMOD_OUTPUT_FILTER + #ifndef HXCMOD_MONO_OUTPUT + modctx->last_l_sample = ll; + #endif + modctx->last_r_sample = lr; +#endif + } + else + { + for (i = 0; i < nbsample; i++) + { + // Mod not loaded. Return blank buffer. +#ifdef HXCMOD_MONO_OUTPUT + outbuffer[i] = 0; +#else + *outbuffer++ = 0; + *outbuffer++ = 0; +#endif + } + +#ifdef HXCMOD_STATE_REPORT_SUPPORT + if(trkbuf) + { + trkbuf->nb_of_state = 0; + trkbuf->cur_rd_index = 0; + trkbuf->name[0] = 0; + memclear(trkbuf->track_state_buf,0,sizeof(tracker_state) * trkbuf->nb_max_of_state); + memclear(trkbuf->instruments,0,sizeof(trkbuf->instruments)); + } +#endif + } + } +} + +void hxcmod_unload( modcontext * modctx ) +{ + if(modctx) + { + memclear(&modctx->song,0,sizeof(modctx->song)); + memclear(&modctx->sampledata,0,sizeof(modctx->sampledata)); + memclear(&modctx->patterndata,0,sizeof(modctx->patterndata)); + modctx->tablepos = 0; + modctx->patternpos = 0; + modctx->patterndelay = 0; + modctx->jump_loop_effect = 0; + modctx->bpm = 0; + modctx->patternticks = 0; + modctx->patterntickse = 0; + modctx->patternticksaim = 0; + modctx->sampleticksconst = 0; + + memclear(modctx->channels,0,sizeof(modctx->channels)); + + modctx->number_of_channels = 0; + + modctx->mod_loaded = 0; + + modctx->last_r_sample = 0; + modctx->last_l_sample = 0; + } +} diff --git a/src/common/hxcmod/hxcmod.h b/src/common/hxcmod/hxcmod.h new file mode 100644 index 0000000..602ba31 --- /dev/null +++ b/src/common/hxcmod/hxcmod.h @@ -0,0 +1,309 @@ +/////////////////////////////////////////////////////////////////////////////////// +//-------------------------------------------------------------------------------// +//-------------------------------------------------------------------------------// +//-----------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 : hxcmod.h +// Contains: a tiny mod player +// +// Written by: Jean François DEL NERO +// +// Change History (most recent first): +/////////////////////////////////////////////////////////////////////////////////// +#ifndef MODPLAY_DEF +#define MODPLAY_DEF + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef HXCMOD_SLOW_TARGET + #define HXCMOD_STATE_REPORT_SUPPORT 1 + #define HXCMOD_OUTPUT_FILTER 1 + #define HXCMOD_OUTPUT_STEREO_MIX 1 + #define HXCMOD_CLIPPING_CHECK 1 +#endif + +// Warning : The following option +// required 32KB of additional RAM : +// #define HXCMOD_USE_PRECALC_VOLUME_TABLE 1 + +// Basic type +typedef unsigned char muchar; +typedef signed char mchar; +typedef unsigned short muint; +typedef short mint; +typedef unsigned long mulong; + +#ifdef HXCMOD_16BITS_TARGET + typedef unsigned short mssize; +#else + typedef unsigned long mssize; +#endif + +#ifdef HXCMOD_8BITS_OUTPUT + #ifdef HXCMOD_UNSIGNED_OUTPUT + typedef unsigned char msample; + #else + typedef signed char msample; + #endif +#else + #ifdef HXCMOD_UNSIGNED_OUTPUT + typedef unsigned short msample; + #else + typedef signed short msample; + #endif +#endif + +#ifdef HXCMOD_MAXCHANNELS + #define NUMMAXCHANNELS HXCMOD_MAXCHANNELS +#else + #define NUMMAXCHANNELS 32 +#endif + +#define MAXNOTES 12*12 + +// +// MOD file structures +// + +#pragma pack(1) + +typedef struct { + muchar name[22]; + muint length; + muchar finetune; + muchar volume; + muint reppnt; + muint replen; +} sample; + +typedef struct { + muchar sampperiod; + muchar period; + muchar sampeffect; + muchar effect; +} note; + +typedef struct { + muchar title[20]; + sample samples[31]; + muchar length; + muchar protracker; + muchar patterntable[128]; + muchar signature[4]; + muchar speed; +} module; + +#pragma pack() + +// +// HxCMod Internal structures +// +typedef struct { + mchar * sampdata; + muint length; + muint reppnt; + muint replen; + muchar sampnum; + + mchar * nxt_sampdata; + muint nxt_length; + muint nxt_reppnt; + muint nxt_replen; + muchar update_nxt_repeat; + + mchar * dly_sampdata; + muint dly_length; + muint dly_reppnt; + muint dly_replen; + muchar note_delay; + + mchar * lst_sampdata; + muint lst_length; + muint lst_reppnt; + muint lst_replen; + muchar retrig_cnt; + muchar retrig_param; + + muint funkoffset; + mint funkspeed; + + mint glissando; + + mulong samppos; + mulong sampinc; + muint period; + muchar volume; +#ifdef HXCMOD_USE_PRECALC_VOLUME_TABLE + mint * volume_table; +#endif + muchar effect; + muchar parameffect; + muint effect_code; + + mulong last_set_offset; + + mint decalperiod; + mint portaspeed; + mint portaperiod; + mint vibraperiod; + mint Arpperiods[3]; + muchar ArpIndex; + + muchar volumeslide; + + muchar vibraparam; + muchar vibrapointeur; + + muchar finetune; + + muchar cut_param; + + muint patternloopcnt; + muint patternloopstartpoint; +} channel; + +typedef struct { + module song; + mchar * sampledata[31]; + note * patterndata[128]; + +#ifdef HXCMOD_16BITS_TARGET + muint playrate; +#else + mulong playrate; +#endif + + muint tablepos; + muint patternpos; + muint patterndelay; + muchar jump_loop_effect; + muchar bpm; + +#ifdef HXCMOD_16BITS_TARGET + muint patternticks; + muint patterntickse; + muint patternticksaim; + muint patternticksem; + muint tick_cnt; +#else + mulong patternticks; + mulong patterntickse; + mulong patternticksaim; + mulong patternticksem; + mulong tick_cnt; +#endif + + mulong sampleticksconst; + + channel channels[NUMMAXCHANNELS]; + + muint number_of_channels; + + muint mod_loaded; + + mint last_r_sample; + mint last_l_sample; + + mint stereo; + mint stereo_separation; + mint bits; + mint filter; + +#ifdef EFFECTS_USAGE_STATE + int effects_event_counts[32]; +#endif + +#ifdef HXCMOD_USE_PRECALC_VOLUME_TABLE + mint precalc_volume_array[65*256]; + mint * volume_selection_table[65]; +#endif + +} modcontext; + +// +// Player states structures +// +typedef struct track_state_ +{ + unsigned char instrument_number; + unsigned short cur_period; + unsigned char cur_volume; + unsigned short cur_effect; + unsigned short cur_parameffect; +}track_state; + +typedef struct tracker_state_ +{ + int number_of_tracks; + int bpm; + int speed; + int cur_pattern; + int cur_pattern_pos; + int cur_pattern_table_pos; + unsigned int buf_index; + track_state tracks[NUMMAXCHANNELS]; +}tracker_state; + +typedef struct tracker_state_instrument_ +{ + char name[22]; + int active; +}tracker_state_instrument; + +typedef struct tracker_buffer_state_ +{ + int nb_max_of_state; + int nb_of_state; + int cur_rd_index; + int sample_step; + char name[64]; + tracker_state_instrument instruments[31]; + tracker_state * track_state_buf; +}tracker_buffer_state; + +/////////////////////////////////////////////////////////////////////////////////// +// HxCMOD Core API: +// ------------------------------------------- +// int hxcmod_init(modcontext * modctx) +// +// - Initialize the modcontext buffer. Must be called before doing anything else. +// Return 1 if success. 0 in case of error. +// ------------------------------------------- +// int hxcmod_load( modcontext * modctx, void * mod_data, int mod_data_size ) +// +// - "Load" a MOD from memory (from "mod_data" with size "mod_data_size"). +// Return 1 if success. 0 in case of error. +// ------------------------------------------- +// void hxcmod_fillbuffer( modcontext * modctx, unsigned short * outbuffer, mssize nbsample, tracker_buffer_state * trkbuf ) +// +// - Generate and return the next samples chunk to outbuffer. +// nbsample specify the number of stereo 16bits samples you want. +// The output format is signed 44100Hz 16-bit Stereo PCM samples. +// The output buffer size in byte must be equal to ( nbsample * 2 * 2 ). +// The optional trkbuf parameter can be used to get detailed status of the player. Put NULL/0 is unused. +// ------------------------------------------- +// void hxcmod_unload( modcontext * modctx ) +// +// - "Unload" / clear the player status. +// ------------------------------------------- +/////////////////////////////////////////////////////////////////////////////////// + +int hxcmod_init( modcontext * modctx ); +int hxcmod_setcfg( modcontext * modctx, int samplerate, int stereo_separation, int filter ); +int hxcmod_load( modcontext * modctx, void * mod_data, int mod_data_size ); +void hxcmod_fillbuffer( modcontext * modctx, msample * outbuffer, mssize nbsample, tracker_buffer_state * trkbuf ); +void hxcmod_unload( modcontext * modctx ); + +#ifdef __cplusplus +} +#endif + +#endif /* MODPLAY_DEF */ diff --git a/src/common/modulator.c b/src/common/modulator.c new file mode 100644 index 0000000..ae5654c --- /dev/null +++ b/src/common/modulator.c @@ -0,0 +1,86 @@ +/////////////////////////////////////////////////////////////////////////////////// +//-------------------------------------------------------------------------------// +//-------------------------------------------------------------------------------// +//-----------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 : modulator.c +// Contains: oscillator and IQ 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): +/////////////////////////////////////////////////////////////////////////////////// + +#include +#include + +#include "modulator.h" + +// +// Note about the IQ +// +// If NCO enabled and If I="cos", Q="sin" -> Output Freq = NCO Freq + IQ Freq +// If NCO enabled and If I="sin", Q="cos" -> Output Freq = NCO Freq - IQ Freq +// If NCO enabled and If I="cos", Q="cos" -> Output Freq = NCO Freq - IQ Freq AND NCO Freq + IQ Freq +// If NCO enabled and If I="const", Q="sin" -> Output Freq = NCO Freq - IQ Freq AND NCO Freq AND NCO Freq + IQ Freq +// If NCO enabled and If I="const", Q="const" -> Output Freq = NCO Freq +// + +uint16_t get_next_iq(iq_wave_gen * wg) +{ + int i_sample,q_sample; + + char i_ub_sample,q_ub_sample; + + i_sample = (int)(cos(wg->phase)*wg->Amplitude); + q_sample = (int)(sin(wg->phase)*wg->Amplitude); + + wg->phase += ( (2.0 * PI * wg->Frequency) / (double)wg->sample_rate ); + +/* if(wg->phase > (2.0 * PI) ) + wg->phase -= (2.0 * PI); + + if(wg->phase < (-2.0 * PI) ) + wg->phase += (2.0 * PI); + */ + + i_ub_sample = (i_sample); + q_ub_sample = (q_sample); + + return (unsigned short)((i_ub_sample)&0xFF) | ((((unsigned short)q_ub_sample)<<8)&0xFF00); +} + +// +// 2*PI -> One cycle. +// Phase step = ( (2 * PI) / (SAMPLE_RATE / Frequency) ) -> ( (2 * PI) * Frequency ) / SAMPLE_RATE +// + +double f_get_next_sample(wave_gen * wg) +{ + double sample; + + sample = ( cos( wg->phase ) * wg->Amplitude ); + + wg->phase += ( (2.0 * PI * wg->Frequency) / (double)wg->sample_rate ); + + if(wg->phase > (2.0 * PI) ) + wg->phase -= (2.0 * PI); + + if(wg->phase < (-2.0 * PI) ) + wg->phase += (2.0 * PI); + + return sample; +} diff --git a/src/common/modulator.h b/src/common/modulator.h new file mode 100644 index 0000000..32fb3f4 --- /dev/null +++ b/src/common/modulator.h @@ -0,0 +1,65 @@ +/////////////////////////////////////////////////////////////////////////////////// +//-------------------------------------------------------------------------------// +//-------------------------------------------------------------------------------// +//-----------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 : modulator.h +// Contains: oscillator and IQ 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): +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef __MODULATOR_H__ +#define __MODULATOR_H__ + +#if defined(M_PI) +#define PI M_PI +#else +#define PI 3.1415926535897932384626433832795 +#endif + +typedef struct iq_wave_gen_ +{ + double phase; + + double Frequency,OldFrequency; + + int sample_rate; + double Amplitude; +}iq_wave_gen; + +typedef struct wave_gen_ +{ + double phase; + + double Frequency,OldFrequency; + + int sample_rate; + double Amplitude; +}wave_gen; + +int find_zero_phase_index(short * buf, int size); +double find_phase(iq_wave_gen * wg, double sinoffset,short samplevalue,short * buf, int size); +int Fill_IQ_Wave_Buffer(iq_wave_gen * wg,unsigned short * wave_buf, int Size); +uint16_t get_next_iq(iq_wave_gen * wg); + +int get_next_sample(wave_gen * wg); + +double f_get_next_sample(wave_gen * wg); + +#endif diff --git a/src/common/rand_gen.c b/src/common/rand_gen.c new file mode 100644 index 0000000..1fc6809 --- /dev/null +++ b/src/common/rand_gen.c @@ -0,0 +1,97 @@ +/////////////////////////////////////////////////////////////////////////////////// +//-------------------------------------------------------------------------------// +//-------------------------------------------------------------------------------// +//-----------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 : rand_gen.c +// Contains: random generators +// +// 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): +/////////////////////////////////////////////////////////////////////////////////// + +#include + +#include "rand_gen.h" + +uint8_t rand_gen_get_next_byte(rand_gen_state *state) +{ + uint8_t retbyte; + uint32_t seed; + uint32_t byte_number; + + seed = state->current_seed; + byte_number = state->byte_number; + + if( byte_number > 3 ) + { + byte_number = 0x00; + + /* Algorithm "xor" from p. 4 of Marsaglia, "Xorshift RNGs" */ + seed ^= seed << 13; + seed ^= seed >> 17; + seed ^= seed << 5; + + state->current_seed = seed; + } + + retbyte = ( seed >> ( (3 - byte_number) << 3) ) & 0xFF; + + byte_number++; + + state->byte_number = byte_number; + + return retbyte; +} + +uint32_t rand_gen_get_next_word(rand_gen_state *state) +{ + uint32_t retword; + uint32_t seed; + + seed = state->current_seed; + + retword = seed; + + /* Algorithm "xor" from p. 4 of Marsaglia, "Xorshift RNGs" */ + seed ^= seed << 13; + seed ^= seed >> 17; + seed ^= seed << 5; + + state->byte_number = 0x00; + state->current_seed = seed; + + return retword; +} + +void rand_gen_init(rand_gen_state *state, uint32_t seed ) +{ + if(!state) + return; + + if( seed ) + { + state->current_seed = seed; + state->byte_number = 0; + } + else + { + state->current_seed = 0x12E6C816; + state->byte_number = 0; + } +} + diff --git a/src/common/rand_gen.h b/src/common/rand_gen.h new file mode 100644 index 0000000..3feb8d1 --- /dev/null +++ b/src/common/rand_gen.h @@ -0,0 +1,41 @@ +/////////////////////////////////////////////////////////////////////////////////// +//-------------------------------------------------------------------------------// +//-------------------------------------------------------------------------------// +//-----------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 : rand_gen.h +// Contains: random generators +// +// 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): +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef __RAND_GEN_H__ +#define __RAND_GEN_H__ + +typedef struct _rand_gen_state +{ + uint32_t current_seed; + int byte_number; +} rand_gen_state; + +void rand_gen_init(rand_gen_state *state, uint32_t seed ); +uint8_t rand_gen_get_next_byte(rand_gen_state *state); +uint32_t rand_gen_get_next_word(rand_gen_state *state); + +#endif /* __RAND_GEN_H__ */ + diff --git a/src/common/utils.c b/src/common/utils.c new file mode 100644 index 0000000..579f36a --- /dev/null +++ b/src/common/utils.c @@ -0,0 +1,126 @@ +/////////////////////////////////////////////////////////////////////////////////// +//-------------------------------------------------------------------------------// +//-------------------------------------------------------------------------------// +//-----------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 : utils.h +// Contains: misc / utils functions +// +// 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): +/////////////////////////////////////////////////////////////////////////////////// + +#include +#include +#include + +void get_filename(char * path,char * filename) +{ + int i,done; + + i=strlen(path); + done=0; + while(i && !done) + { + i--; + + if(path[i]=='/') + { + done=1; + i++; + } + } + + sprintf(filename,"%s",&path[i]); + + i=0; + while(filename[i]) + { + if(filename[i]=='.') + { + filename[i]='_'; + } + + i++; + } + + return; +} + +int is_printable_char(unsigned char c) +{ + int i; + unsigned char specialchar[]={"&#{}()|_@=$!?;+*-"}; + + if( (c >= 'A' && c <= 'Z') || + (c >= 'a' && c <= 'z') || + (c >= '0' && c <= '9') ) + { + return 1; + } + + i = 0; + while(specialchar[i]) + { + if(specialchar[i] == c) + { + return 1; + } + + i++; + } + + return 0; +} + +void printbuf(void * buf,int size) +{ + #define PRINTBUF_HEXPERLINE 16 + #define PRINTBUF_MAXLINE_SIZE ((3*PRINTBUF_HEXPERLINE)+1+PRINTBUF_HEXPERLINE+2) + + int i,j; + unsigned char *ptr = buf; + char tmp[8]; + char str[PRINTBUF_MAXLINE_SIZE]; + + memset(str, ' ', PRINTBUF_MAXLINE_SIZE); + str[PRINTBUF_MAXLINE_SIZE-1] = 0; + + j = 0; + for(i=0;i +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "wave.h" + +#include "utils.h" + +wave_io * create_wave(char * path,int samplerate, int type) +{ + wav_hdr wavhdr; + wave_io * w_io; + int sample_byte_size; + + w_io = malloc(sizeof(wave_io)); + if(w_io) + { + memset(w_io,0,sizeof(wave_io)); + + if(!path) + w_io->file = stdout;//fopen(stdout,"w+b"); + else + w_io->file = fopen(path,"w+b"); + + if(w_io->file) + { + w_io->total_nb_samples = 0; + w_io->type = type; + switch(w_io->type) + { + case WAVE_FILE_FORMAT_RAW_8BITS_IQ: // Raw / IQ + w_io->sample_byte_size = 2; // I + Q + break; + + case WAVE_FILE_FORMAT_WAV_8BITS_STEREO: // Wave 8 bits stereo + case WAVE_FILE_FORMAT_WAV_16BITS_STEREO: // Wave 16 bits stereo + case WAVE_FILE_FORMAT_WAV_16BITS_MONO: // Wave 16 bits mono + memset(&wavhdr,0,sizeof(wavhdr)); + + memcpy((char*)&wavhdr.RIFF,"RIFF",4); + memcpy((char*)&wavhdr.WAVE,"WAVE",4); + memcpy((char*)&wavhdr.fmt,"fmt ",4); + wavhdr.Subchunk1Size = 16; + wavhdr.AudioFormat = 1; + + wavhdr.NumOfChan = 2; // I & Q + if(w_io->type == WAVE_FILE_FORMAT_WAV_16BITS_MONO) + wavhdr.NumOfChan = 1; + + wavhdr.SamplesPerSec = samplerate; + if(w_io->type == WAVE_FILE_FORMAT_WAV_8BITS_STEREO) + wavhdr.bitsPerSample = 8; + else + wavhdr.bitsPerSample = 16; + + sample_byte_size = ((wavhdr.bitsPerSample*wavhdr.NumOfChan)/8); + + w_io->sample_byte_size = sample_byte_size; + + wavhdr.bytesPerSec = ((samplerate*wavhdr.bitsPerSample)/8) * wavhdr.NumOfChan; + wavhdr.blockAlign = sample_byte_size; + memcpy((char*)&wavhdr.Subchunk2ID,"data",4); + + wavhdr.ChunkSize = (w_io->total_nb_samples * sample_byte_size) + sizeof(wav_hdr) - 8; + wavhdr.Subchunk2Size = (w_io->total_nb_samples * sample_byte_size); + + fwrite((void*)&wavhdr,sizeof(wav_hdr),1,w_io->file); + + // Note about the following raw data format : + // 8-bit samples are stored as unsigned bytes, ranging from 0 to 255. + // 16-bit samples are stored as 2's-complement signed integers, ranging from -32768 to 32767. + + break; + } + } + } + + return w_io; +} + +void write_wave(wave_io * w_io, void * buffer, int nbsamples) +{ + if(w_io) + { + if(!w_io->file) + return; + + fwrite(buffer,nbsamples * w_io->sample_byte_size,1, w_io->file); + w_io->total_nb_samples += nbsamples; + } +} + +void close_wave(wave_io * w_io) +{ + wav_hdr wavhdr; + + if(w_io) + { + if(!w_io->file) + return; + + switch(w_io->type) + { + case WAVE_FILE_FORMAT_RAW_8BITS_IQ: // Raw / IQ + fclose(w_io->file); + break; + + case WAVE_FILE_FORMAT_WAV_8BITS_STEREO: // Wave 8 bits stereo + case WAVE_FILE_FORMAT_WAV_16BITS_STEREO: // Wave 16 bits stereo + case WAVE_FILE_FORMAT_WAV_16BITS_MONO: // Wave 16 bits mono + fseek(w_io->file,0,SEEK_SET); + if(fread(&wavhdr,sizeof(wav_hdr),1,w_io->file) == 1) + { + wavhdr.ChunkSize += ( w_io->total_nb_samples * w_io->sample_byte_size ); + wavhdr.Subchunk2Size += ( w_io->total_nb_samples * w_io->sample_byte_size ); + + fseek(w_io->file,0,SEEK_SET); + fwrite(&wavhdr,sizeof(wav_hdr),1,w_io->file); + } + fclose(w_io->file); + break; + } + + free(w_io); + } +} diff --git a/src/common/wave.h b/src/common/wave.h new file mode 100644 index 0000000..1ffc706 --- /dev/null +++ b/src/common/wave.h @@ -0,0 +1,68 @@ +/////////////////////////////////////////////////////////////////////////////////// +//-------------------------------------------------------------------------------// +//-------------------------------------------------------------------------------// +//-----------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 : wave.h +// Contains: wave file helpers +// +// 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): +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef __WAVE_H__ +#define __WAVE_H__ + +#pragma pack(1) + +typedef struct wav_hdr_ // +{ + char RIFF[4]; // RIFF Header + int ChunkSize; // RIFF Chunk Size + char WAVE[4]; // WAVE Header + char fmt[4]; // FMT header + int Subchunk1Size; // Size of the fmt chunk + short int AudioFormat; // Audio format 1=PCM,6=mulaw,7=alaw, 257=IBM Mu-Law, 258=IBM A-Law, 259=ADPCM + short int NumOfChan; // Number of channels 1=Mono 2=Stereo + int SamplesPerSec; // Sampling Frequency in Hz + int bytesPerSec; // bytes per second */ + short int blockAlign; // 2=16-bit mono, 4=16-bit stereo + short int bitsPerSample; // Number of bits per sample + char Subchunk2ID[4]; // "data" string + int Subchunk2Size; // Sampled data length +}wav_hdr; + +#pragma pack() + +typedef struct wave_io_ +{ + FILE * file; + int type; + int total_nb_samples; + int sample_byte_size; +}wave_io; + +#define WAVE_FILE_FORMAT_RAW_8BITS_IQ 0 +#define WAVE_FILE_FORMAT_WAV_8BITS_STEREO 1 +#define WAVE_FILE_FORMAT_WAV_16BITS_STEREO 2 +#define WAVE_FILE_FORMAT_WAV_16BITS_MONO 3 + +wave_io * create_wave(char * path,int samplerate,int type); +void write_wave(wave_io * w_io, void * buffer, int nbsamples); +void close_wave(wave_io * w_io); + +#endif