/* utilities.c This file is part of a program that implements a Software-Defined Radio. Copyright (C) 2013, 2019, 2024 Warren Pratt, NR0V This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. The author can be reached by email at warren@wpratt.com */ #define _CRT_SECURE_NO_WARNINGS #include "comm.h" /******************************************************************************************************** * * * Required Utilities * * * ********************************************************************************************************/ PORT void *malloc0 (int size) { int alignment = 16; void* p = _aligned_malloc (size, alignment); if (p != 0) memset (p, 0, size); return p; } #if !defined(linux) && !defined(__APPLE__) // Exported calls PORT void *NewCriticalSection() { // used by VAC LPCRITICAL_SECTION cs_ptr; cs_ptr = (LPCRITICAL_SECTION)calloc (1,sizeof (CRITICAL_SECTION)); return (void *)cs_ptr; } PORT void DestroyCriticalSection (LPCRITICAL_SECTION cs_ptr) { // used by VAC free ((char *)cs_ptr); } #endif /******************************************************************************************************** * * * Test & Debug Utilities * * * ********************************************************************************************************/ void print_impulse (const char* filename, int N, double* impulse, int rtype, int pr_mode) { int i; FILE* file; if (pr_mode == 0) file = fopen (filename, "w"); else file = fopen (filename, "a"); if (file) { if (rtype == 0) for (i = 0; i < N; i++) fprintf(file, "%.17e\n", impulse[i]); else for (i = 0; i < N; i++) fprintf(file, "%.17e\t%.17e\n", impulse[2 * i + 0], impulse[2 * i + 1]); fprintf(file, "\n\n\n\n"); fflush(file); fclose(file); } } PORT void analyze_bandpass_filter (int N, double f_low, double f_high, double samplerate, int wintype, int rtype, double scale) { double* linphase_imp; double* minphase_imp = (double *) malloc0 (N * sizeof (complex)); linphase_imp = fir_bandpass (N, f_low, f_high, samplerate, wintype, rtype, scale); mp_imp (N, linphase_imp, minphase_imp, 16, 0); print_impulse ("linear_phase_impulse.txt", N, linphase_imp, 1, 0); print_impulse ("minimum_phase_impulse.txt", N, minphase_imp, 1, 0); _aligned_free (minphase_imp); _aligned_free (linphase_imp); } void print_peak_val (const char* filename, int N, double* buff, double thresh) { int i; static unsigned int seqnum; double peak = 0.0; FILE* file; for (i = 0; i < N; i++) if (buff[i] > peak) peak = buff[i]; if (peak >= thresh) { if (file = fopen(filename, "a")) { fprintf(file, "%d\t\t%.17e\n", seqnum, peak); fflush(file); fclose(file); } } seqnum++; } void print_peak_env (const char* filename, int N, double* buff, double thresh) { int i; static unsigned int seqnum; double peak = 0.0; double new_peak; FILE* file; for (i = 0; i < N; i++) { new_peak = sqrt (buff[2 * i + 0] * buff[2 * i + 0] + buff[2 * i + 1] * buff[2 * i + 1]); if (new_peak > peak) peak = new_peak; } if (peak >= thresh) { if (file = fopen(filename, "a")) { fprintf(file, "%d\t\t%.17e\n", seqnum, peak); fflush(file); fclose(file); } } seqnum++; } void print_peak_env_f2 (const char* filename, int N, float* Ibuff, float* Qbuff) { int i; double peak = 0.0; double new_peak; FILE* file; if (file = fopen(filename, "a")) { for (i = 0; i < N; i++) { new_peak = sqrt (Ibuff[i] * Ibuff[i] + Qbuff[i] * Qbuff[i]); if (new_peak > peak) peak = new_peak; } fprintf(file, "%.17e\n", peak); fflush(file); fclose(file); } } void print_iqc_values (const char* filename, int state, double env_in, double I, double Q, double ym, double yc, double ys, double thresh) { static unsigned int seqnum; double env_out; FILE* file; env_out = sqrt (I * I + Q * Q); if (env_out > thresh) { if (file = fopen(filename, "a")) { if (seqnum == 0) fprintf(file, "seqnum\tstate\tenv_in\t\tenv_out\t\tym\t\tyc\t\tys\n"); fprintf(file, "%d\t%d\t%f\t%f\t%f\t%f\t%f\n", seqnum, state, env_in, env_out, ym, yc, ys); fflush(file); fclose(file); seqnum++; } } } PORT void print_buffer_parameters (const char* filename, int channel) { IOB a = ch[channel].iob.pc; FILE* file; if (file = fopen(filename, "a")) { fprintf(file, "channel = %d\n", channel); fprintf(file, "in_size = %d\n", a->in_size); fprintf(file, "r1_outsize = %d\n", a->r1_outsize); fprintf(file, "r1_size = %d\n", a->r1_size); fprintf(file, "r2_size = %d\n", a->r2_size); fprintf(file, "out_size = %d\n", a->out_size); fprintf(file, "r2_insize = %d\n", a->r2_insize); fprintf(file, "r1_active_buffsize = %d\n", a->r1_active_buffsize); fprintf(file, "f2_active_buffsize = %d\n", a->r2_active_buffsize); fprintf(file, "r1_inidx = %d\n", a->r1_inidx); fprintf(file, "r1_outidx = %d\n", a->r1_outidx); fprintf(file, "r1_unqueuedsamps = %d\n", a->r1_unqueuedsamps); fprintf(file, "r2_inidx = %d\n", a->r2_inidx); fprintf(file, "r2_outidx = %d\n", a->r2_outidx); fprintf(file, "r2_havesamps = %d\n", a->r2_havesamps); fprintf(file, "in_rate = %d\n", ch[channel].in_rate); fprintf(file, "dsp_rate = %d\n", ch[channel].dsp_rate); fprintf(file, "out_rate = %d\n", ch[channel].out_rate); fprintf(file, "\n"); fflush(file); fclose(file); } } void print_meter (const char* filename, double* meter, int enum_av, int enum_pk, int enum_gain) { FILE* file; if (file = fopen(filename, "a")) { if (enum_gain >= 0) fprintf(file, "%.4e\t%.4e\t%.4e\n", meter[enum_av], meter[enum_pk], meter[enum_gain]); else fprintf(file, "%.4e\t%.4e\n", meter[enum_av], meter[enum_pk]); fflush(file); fclose(file); } } void print_message (const char* filename, const char* message, int p0, int p1, int p2) { FILE* file; if (file = fopen(filename, "a")) { const char* msg = message; fprintf(file, "%s %d %d %d\n", msg, p0, p1, p2); fflush(file); fclose(file); } } void print_window_gain (const char* filename, int wintype, double inv_coherent_gain, double inherent_power_gain) { FILE* file; if (file = fopen(filename, "a")) { double enb = inherent_power_gain * inv_coherent_gain * inv_coherent_gain; switch (wintype) { case 0: fprintf(file, "Rectangular %.4f\t%.4f\t%.4f\n", inv_coherent_gain, inherent_power_gain, enb); break; case 1: fprintf(file, "Blackman-Harris 4-term %.4f\t%.4f\t%.4f\n", inv_coherent_gain, inherent_power_gain, enb); break; case 2: fprintf(file, "Hann %.4f\t%.4f\t%.4f\n", inv_coherent_gain, inherent_power_gain, enb); break; case 3: fprintf(file, "Flat Top %.4f\t%.4f\t%.4f\n", inv_coherent_gain, inherent_power_gain, enb); break; case 4: fprintf(file, "Hamming %.4f\t%.4f\t%.4f\n", inv_coherent_gain, inherent_power_gain, enb); break; case 5: fprintf(file, "Kaiser %.4f\t%.4f\t%.4f\n", inv_coherent_gain, inherent_power_gain, enb); break; case 6: fprintf(file, "Blackman-Harris 7-term %.4f\t%.4f\t%.4f\n", inv_coherent_gain, inherent_power_gain, enb); break; default: fprintf(file, "Specified Window Type is Invalid\n"); break; } fflush(file); fclose(file); } } void print_deviation (const char* filename, double dpmax, double rate) { FILE* file; if (file = fopen(filename, "a")) { double peak = dpmax * rate / TWOPI; fprintf(file, "Peak Dev = %.4f\n", peak); fflush(file); fclose(file); } } void __cdecl CalccPrintSamples (void *pargs) { int i; double env_tx, env_rx; int channel = (int)(uintptr_t)pargs; CALCC a = txa[channel].calcc.p; FILE* file; if (file = fopen("samples.txt", "w")) { fprintf(file, "\n"); for (i = 0; i < a->nsamps; i++) { env_tx = sqrt(a->txs[2 * i + 0] * a->txs[2 * i + 0] + a->txs[2 * i + 1] * a->txs[2 * i + 1]); env_rx = sqrt(a->rxs[2 * i + 0] * a->rxs[2 * i + 0] + a->rxs[2 * i + 1] * a->rxs[2 * i + 1]); fprintf(file, "%.12f %.12f %.12f %.12f %.12f %.12f\n", a->txs[2 * i + 0], a->txs[2 * i + 1], env_tx, a->rxs[2 * i + 0], a->rxs[2 * i + 1], env_rx); } fflush(file); fclose(file); } _endthread(); } void doCalccPrintSamples(int channel) { // no sample buffering - use in single cal mode _beginthread(CalccPrintSamples, 0, (void *)(uintptr_t)channel); } void print_anb_parms (const char* filename, ANB a) { FILE* file; if (file = fopen(filename, "a")) { fprintf(file, "Run = %d\n", a->run); fprintf(file, "Buffer Size = %d\n", a->buffsize); fprintf(file, "Sample Rate = %d\n", (int)a->samplerate); fprintf(file, "Threshold = %.6f\n", a->threshold); fprintf(file, "BackTau = %.6f\n", a->backtau); fprintf(file, "BackMult = %.6f\n", a->backmult); fprintf(file, "Tau = %.6f\n", a->tau); fflush(file); fclose(file); } } #if !defined(linux) && !defined(__APPLE__) // Audacity: Import Raw Data, Signed 32-bit PCM, Little-endian, Mono/Stereo per mode selection, 48K rate int audiocount = 0; int* data = 0; int ready = 0; int done = 0; void WriteAudioFile(void* arg) { byte* dat = (byte *)arg; FILE* file; // reverse bits of each byte (possibly needed on some platforms) // int i; // byte b; // for (i = 0; i < 4 * audiocount; i++) // { // b = dat[i]; // b = ((b >> 1) & 0x55) | ((b << 1) & 0xaa); // b = ((b >> 2) & 0x33) | ((b << 2) & 0xcc); // b = ((b >> 4) & 0x0f) | ((b << 4) & 0xf0); // dat[i] = b; // } if (file = fopen("AudioFile", "wb")) { fwrite((int*)dat, sizeof(int), audiocount, file); fflush(file); fclose(file); } _aligned_free(data); data = 0; _endthread(); } void WriteAudioWDSP (double seconds, int rate, int size, double* indata, int mode, double gain) { // seconds - number of seconds of audio to record // rate - sample rate // size - number of complex samples // indata - pointer to data static int n; int i; const double conv = 2147483647.0 * gain; if (!ready) { if (mode != 3) n = (int)(seconds * rate); else n = 2 * (int)(seconds * rate); ready = 1; } if (!data) data = (int*)malloc0(n * sizeof(int)); for (i = 0; i < size; i++) { if (audiocount < n) { switch (mode) { case 0: // I only (mono) data[audiocount++] = (int)(conv * indata[2 * i + 0]); break; case 1: // Q only (mono) data[audiocount++] = (int)(conv * indata[2 * i + 1]); break; case 2: // envelope (mono) data[audiocount++] = (int)(conv * sqrt(indata[2 * i + 0] * indata[2 * i + 0] + indata[2 * i + 1] * indata[2 * i + 1])); break; case 3: // complex samples (stereo) data[audiocount++] = (int)(conv * indata[2 * i + 0]); data[audiocount++] = (int)(conv * indata[2 * i + 1]); break; case 4: // double samples (mono) data[audiocount++] = (int)(conv * indata[i]); break; default: // invalid mode/datatype data[audiocount++] = 0; break; } } } if ((audiocount == n) && !done) { done = 1; _beginthread(WriteAudioFile, 0, (void *)data); } } void WriteScaledAudioFile (void* arg) { typedef struct { int n; double* ddata; } *dstr; dstr dstruct = (dstr)arg; FILE* file; int i; double max = 0.0; double abs_val; const double conv = 2147483647.0; int *idata = (int *) malloc0 (dstruct->n * sizeof (int)); for (i = 0; i < dstruct->n; i++) { abs_val = fabs (dstruct->ddata[i]); if (abs_val > max) max = abs_val; } for (i = 0; i < dstruct->n; i++) idata[i] = dstruct->ddata[i] >= 0.0 ? (int)floor(conv * dstruct->ddata[i] / max + 0.5) : (int)ceil(conv * dstruct->ddata[i] / max - 0.5); if (file = fopen("AudioFile", "wb")) { fwrite(idata, sizeof(int), dstruct->n, file); fflush(file); fclose(file); } _aligned_free (dstruct->ddata); _aligned_free (dstruct); _aligned_free (idata); _endthread(); } void WriteScaledAudio ( double seconds, // number of seconds of audio to record int rate, // sample rate int size, // incoming buffer size double* indata ) // pointer to incoming data buffer { static int ready; typedef struct { int n; double* ddata; } dstr, *DSTR; static DSTR dstruct; static int count, complete; int i; if (!ready) { dstruct = (DSTR) malloc0 (sizeof (dstr)); dstruct->n = 2 * (int)(seconds * rate); dstruct->ddata = (double *) malloc0 (dstruct->n * sizeof (double)); ready = 1; } for (i = 0; i < size; i++) { if (count < dstruct->n) { dstruct->ddata[count++] = indata[2 * i + 0]; dstruct->ddata[count++] = indata[2 * i + 1]; } } if ((count >= dstruct->n) && !complete) { complete = 1; _beginthread (WriteScaledAudioFile, 0, (void *)dstruct); } } #endif /******************************************************************************************************** * * * Bandpass Filter Characterization Utility * * * ********************************************************************************************************/ double* model_bandpass(int nc, double f_low, double f_high, double rate, int wtype, int points) { double* h = fir_bandpass(nc, f_low, f_high, rate, wtype, 1, 1.0 / (double)nc); double* in = (double*)malloc0(points * sizeof(complex)); double* out = (double*)malloc0(points * sizeof(complex)); memcpy(in, h, nc * sizeof(complex)); fftw_plan p = fftw_plan_dft_1d(points, (fftw_complex*)in, (fftw_complex*)out, FFTW_FORWARD, FFTW_PATIENT); fftw_execute(p); fftw_destroy_plan(p); double* mag = (double*)malloc0(points * sizeof(double)); double mult = 1.0/sqrt(out[0] * out[0] + out[1] * out[1]); for (int i = 0; i < points; i++) { mag[i] = mult * sqrt(out[2 * i + 0] * out[2 * i + 0] + out[2 * i + 1] * out[2 * i + 1]); if (mag[i] > 1.0e-300) mag[i] = 20.0 * log10(mag[i]); else mag[i] = -200.0; } // reverse normal-order double* magrev = (double*)malloc0(points * sizeof(double)); memcpy(magrev, &mag[points / 2], points / 2 * sizeof(double)); memcpy(&magrev[points / 2], mag, points / 2 * sizeof(double)); _aligned_free(mag); _aligned_free(out); _aligned_free(in); _aligned_free(h); return magrev; } void print_bandpass_response (const char* filename, int points, double* response) { int i; FILE* file; if (file = fopen(filename, "w")) { for (i = 0; i < points; i++) fprintf(file, "%.17e\n", response[i]); fflush(file); fclose(file); } } BFCU pbfcu[4]; PORT int create_bfcu(int id, int min_size, int max_size, double rate, double corner, int points) { // id - from 0 through 3 // min_size = minimum impulse response size for filters (power of two) // max_size = maximum impulse response size for filters (power of two) // rate = sample-rate at which filters run // corner = -6dB corner frequency; bandpass filter is symmetrical about zero // two corners, one at +corner and the other at -corner // points = number of points to generate for each filter response (power of two; >= max_size) if (max_size > points) return -1; BFCU a = (BFCU)malloc0(sizeof(bfcu)); int nc, i_wtype, i_dataset; int i_corner_offset; a->id = id; a->min_size = min_size; a->max_size = max_size; a->rate = rate; a->corner = corner; a->points = points; nc = a->min_size; while (nc <= a->max_size) { for (i_wtype = 0; i_wtype < 2; i_wtype++) { i_dataset = 2 * (int)log2(nc / a->min_size) + i_wtype; a->dataset[i_dataset] = model_bandpass(nc, -a->corner, +a->corner, a->rate, i_wtype, a->points); } nc *= 2; } i_corner_offset = (int)round(a->corner / a->rate * a->points); a->i_lower_corner = (a->points / 2) - i_corner_offset; a->i_upper_corner = (a->points / 2) + i_corner_offset; pbfcu[a->id] = a; return 0; } PORT void destroy_bfcu(int id) { BFCU a = pbfcu[id]; int nc = a->min_size; while (nc <= a->max_size) { int i_dataset = 2 * (int)log2(nc / a->min_size) + 0; _aligned_free(a->dataset[i_dataset]); _aligned_free(a->dataset[i_dataset + 1]); nc *= 2; } _aligned_free(a); } PORT void getFilterCorners(int id, int* lower_index, int* upper_index) { // stores the index of the lower corner and the index of the upper corner at the pointers given. BFCU a = pbfcu[id]; *lower_index = a->i_lower_corner; *upper_index = a->i_upper_corner; } PORT void getFilterCurve(int id, int size, int w_type, int index_low, int index_high, double* segment) { // size = filter_size // w_type = window_type (0 -> bh4; 1->bh7) // index_low = lower index of the segment you want (range is 0 through points-1) // index_high = upper index of the segment you want (range is 0 through points-1) // segment = pointer to location where you want the result stored BFCU a = pbfcu[id]; int i_dataset = 2 * (int)log2(size / a->min_size) + w_type; memcpy(segment, &a->dataset[i_dataset][index_low], (index_high - index_low + 1) * sizeof(double)); } void test_bfcu() { create_bfcu(0, 1024, 16384, 48000.0, 1000.0, 16384); int lower_corner, upper_corner; getFilterCorners(0, &lower_corner, &upper_corner); double* segment = (double*)malloc0(1025 * sizeof(double)); getFilterCurve(0, 4096, 1, upper_corner - 512, upper_corner + 512, segment); print_bandpass_response("response", 1025, segment); _aligned_free(segment); destroy_bfcu(0); }