commit eb0b1c39d4f14ac79b2a4edf85714c566fc50bc3 Author: Andreas Eversberg Date: Fri Mar 18 16:12:54 2022 +0100 Add libraries from osmocom-analog diff --git a/src/libdebug/Makefile.am b/src/libdebug/Makefile.am new file mode 100644 index 0000000..210a097 --- /dev/null +++ b/src/libdebug/Makefile.am @@ -0,0 +1,7 @@ +AM_CPPFLAGS = -Wall -Wextra -g $(all_includes) + +noinst_LIBRARIES = libdebug.a + +libdebug_a_SOURCES = \ + debug.c + diff --git a/src/libdebug/debug.c b/src/libdebug/debug.c new file mode 100644 index 0000000..1045f89 --- /dev/null +++ b/src/libdebug/debug.c @@ -0,0 +1,327 @@ +/* Simple debug functions for level and category filtering + * + * (C) 2016 by Andreas Eversberg + * All Rights Reserved + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "debug.h" + +const char *debug_level[] = { + "debug ", + "info ", + "notice ", + "error ", + NULL, +}; + +struct debug_cat { + const char *name; + const char *color; +} debug_cat[] = { + { "options", "\033[0;33m" }, + { "sender", "\033[1;33m" }, + { "sound", "\033[0;35m" }, + { "dsp", "\033[0;31m" }, + { "anetz", "\033[1;34m" }, + { "bnetz", "\033[1;34m" }, + { "cnetz", "\033[1;34m" }, + { "nmt", "\033[1;34m" }, + { "amps", "\033[1;34m" }, + { "r2000", "\033[1;34m" }, + { "imts", "\033[1;34m" }, + { "mpt1327", "\033[1;34m" }, + { "jollycom", "\033[1;34m" }, + { "eurosignal", "\033[1;34m" }, + { "pocsag", "\033[1;34m" }, + { "5-ton-folge", "\033[1;34m" }, + { "frame", "\033[0;36m" }, + { "call", "\033[0;37m" }, + { "cc", "\033[1;32m" }, + { "database", "\033[0;33m" }, + { "transaction", "\033[0;32m" }, + { "dms", "\033[0;33m" }, + { "sms", "\033[1;37m" }, + { "sdr", "\033[1;31m" }, + { "uhd", "\033[1;35m" }, + { "soapy", "\033[1;35m" }, + { "wave", "\033[1;33m" }, + { "radio", "\033[1;34m" }, + { "am791x", "\033[0;31m" }, + { "uart", "\033[0;32m" }, + { "device", "\033[0;33m" }, + { "datenklo", "\033[1;34m" }, + { "zeit", "\033[1;34m" }, + { "sim layer 1", "\033[0;31m" }, + { "sim layer 2", "\033[0;33m" }, + { "sim ICL layer", "\033[0;36m" }, + { "sim layer 7", "\033[0;37m" }, + { "mtp layer 2", "\033[1;33m" }, + { "mtp layer 3", "\033[1;36m" }, + { "MuP", "\033[1;37m" }, + { "router", "\033[1;35m" }, + { "stderr", "\033[1;37m" }, + { "ss5", "\033[1;34m" }, + { "isdn", "\033[1;35m" }, + { "misdn", "\033[0;34m" }, + { "dss1", "\033[1;34m" }, + { "sip", "\033[1;35m" }, + { "telephone", "\033[1;34m" }, + { "UK0", "\033[0;31m" }, + { "ph", "\033[0;33m" }, + { NULL, NULL } +}; + +int debuglevel = DEBUG_INFO; +int debug_date = 0; +uint64_t debug_mask = ~0; +extern int num_kanal; + +void (*clear_console_text)(void) = NULL; +void (*print_console_text)(void) = NULL; + +int debug_limit_scroll = 0; + +static int lock_initialized = 0; +static pthread_mutex_t debug_mutex; + +void lock_debug(void) +{ + int rc; + + if (!lock_initialized) { + rc = pthread_mutex_init(&debug_mutex, NULL); + if (rc == 0) + lock_initialized = 1; + } + if (lock_initialized) + pthread_mutex_lock(&debug_mutex); +} + +void unlock_debug(void) +{ + if (lock_initialized) + pthread_mutex_unlock(&debug_mutex); +} + +void get_win_size(int *w, int *h) +{ + struct winsize win; + int rc; + + rc = ioctl(0, TIOCGWINSZ, &win); + if (rc) { + *w = 80; + *h = 25; + return; + } + + *h = win.ws_row; + *w = win.ws_col; +} + +void _printdebug(const char *file, const char __attribute__((unused)) *function, int line, int cat, int level, const char *kanal, const char *fmt, ...) +{ + char buffer[4096], *b = buffer; + int s = sizeof(buffer) - 1; + const char *p; + va_list args; + int w, h; + + if (debuglevel > level) + return; + + if (!(debug_mask & ((uint64_t)1 << cat))) + return; + + lock_debug(); + + buffer[sizeof(buffer) - 1] = '\0'; + + /* if kanal is used, prefix the channel number */ + if (num_kanal > 1 && kanal) { + sprintf(buffer, "(chan %s) ", kanal); + b = strchr(buffer, '\0'); + s -= strlen(buffer); + } + + va_start(args, fmt); + vsnprintf(b, s, fmt, args); + va_end(args); + + while ((p = strchr(file, '/'))) + file = p + 1; + if (clear_console_text) + clear_console_text(); + if (debug_limit_scroll) { + get_win_size(&w, &h); + printf("\0337\033[%d;%dr\0338", debug_limit_scroll + 1, h); + } + if (debug_date) { + struct timeval tv; + struct tm *tm; + + gettimeofday(&tv, NULL); + tm = localtime(&tv.tv_sec); + + printf("%04d-%02d-%02d %02d:%02d:%02d.%03d ", tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec, (int)(tv.tv_usec / 10000.0)); + } + printf("%s%s:%4d %s: %s\033[0;39m", debug_cat[cat].color, file, line, debug_level[level], buffer); + if (debug_limit_scroll) + printf("\0337\033[%d;%dr\0338", 1, h); + if (print_console_text) + print_console_text(); + fflush(stdout); + + unlock_debug(); +} + +const char *debug_amplitude(double level) +{ + static char text[42]; + + strcpy(text, " : "); + if (level > 1.0) + level = 1.0; + if (level < -1.0) + level = -1.0; + text[20 + (int)(level * 20)] = '*'; + + return text; +} + +#define level2db(level) (20 * log10(level)) + +const char *debug_db(double level_db) +{ + static char text[128]; + int l; + + strcpy(text, ": . : . : . : . : . : . : . : . | . : . : . : . : . : . : . : . :"); + if (level_db <= 0.0) + return text; + l = (int)round(level2db(level_db)); + if (l > 48) + return text; + if (l < -48) + return text; + text[l + 48] = '*'; + + return text; +} + +void debug_print_help(void) +{ + printf(" -v --verbose | ,[,[,...]] | list\n"); + printf(" Use 'list' to get a list of all levels and categories\n"); + printf(" Verbose level: digit of debug level (default = '%d')\n", debuglevel); + printf(" Verbose level+category: level digit followed by one or more categories\n"); + printf(" -> If no category is specified, all categories are selected\n"); + printf(" -v --verbose date\n"); + printf(" Show date with debug output\n"); +} + +void debug_list_cat(void) +{ + int i; + + printf("Give number of debug level:\n"); + for (i = 0; debug_level[i]; i++) + printf(" %d = %s\n", i, debug_level[i]); + printf("\n"); + + printf("Give name(s) of debug category:\n"); + for (i = 0; debug_cat[i].name; i++) + printf(" %s%s\033[0;39m\n", debug_cat[i].color, debug_cat[i].name); + printf("\n"); +} + +int parse_debug_opt(const char *optarg) +{ + int i, max_level = 0; + char *dup, *dstring, *p; + + if (!strcasecmp(optarg, "date")) { + debug_date = 1; + return 0; + } + + for (i = 0; debug_level[i]; i++) + max_level = i; + + dup = dstring = strdup(optarg); + p = strsep(&dstring, ","); + for (i = 0; i < p[i]; i++) { + if (p[i] < '0' || p[i] > '9') { + fprintf(stderr, "Only digits are allowed for debug level!\n"); + free(dup); + return -EINVAL; + } + } + debuglevel = atoi(p); + if (debuglevel > max_level) { + fprintf(stderr, "Debug level too high, use 'list' to show available levels!\n"); + free(dup); + return -EINVAL; + } + if (dstring) + debug_mask = 0; + while((p = strsep(&dstring, ","))) { + for (i = 0; debug_cat[i].name; i++) { + if (!strcasecmp(p, debug_cat[i].name)) + break; + } + if (!debug_cat[i].name) { + fprintf(stderr, "Given debug category '%s' unknown, use 'list' to show available categories!\n", p); + free(dup); + return -EINVAL; + } + debug_mask |= ((uint64_t)1 << i); + } + + free(dup); + return 0; +} + +const char *debug_hex(const uint8_t *data, int len) +{ + static char *text = NULL; + char *p; + int i; + + if (text) + free(text); + p = text = calloc(1, len * 3 + 1); + for (i = 0; i < len; i++) { + sprintf(p, "%02x ", *data++); + p += 3; + } + if (text[0]) + p[-1] = '\0'; + + return text; +} + diff --git a/src/libdebug/debug.h b/src/libdebug/debug.h new file mode 100644 index 0000000..6ef3ec0 --- /dev/null +++ b/src/libdebug/debug.h @@ -0,0 +1,82 @@ + +#define DEBUG_DEBUG 0 /* debug info, not for normal use */ +#define DEBUG_INFO 1 /* all info about process */ +#define DEBUG_NOTICE 2 /* something unexpected happens */ +#define DEBUG_ERROR 3 /* there is an error with this software */ + +#define DOPTIONS 0 +#define DSENDER 1 +#define DSOUND 2 +#define DDSP 3 +#define DANETZ 4 +#define DBNETZ 5 +#define DCNETZ 6 +#define DNMT 7 +#define DAMPS 8 +#define DR2000 9 +#define DIMTS 10 +#define DMPT1327 11 +#define DJOLLY 12 +#define DEURO 13 +#define DPOCSAG 14 +#define DFUENF 15 +#define DFRAME 16 +#define DCALL 17 +#define DCC 18 +#define DDB 19 +#define DTRANS 20 +#define DDMS 21 +#define DSMS 22 +#define DSDR 23 +#define DUHD 24 +#define DSOAPY 25 +#define DWAVE 26 +#define DRADIO 27 +#define DAM791X 28 +#define DUART 29 +#define DDEVICE 30 +#define DDATENKLO 31 +#define DZEIT 32 +#define DSIM1 33 +#define DSIM2 34 +#define DSIMI 35 +#define DSIM7 36 +#define DMTP2 37 +#define DMTP3 38 +#define DMUP 39 +#define DROUTER 40 +#define DSTDERR 41 +#define DSS5 42 +#define DISDN 43 +#define DMISDN 44 +#define DDSS1 45 +#define DSIP 46 +#define DTEL 47 +#define DUK0 48 +#define DPH 49 + +void lock_debug(void); +void unlock_debug(void); + +void get_win_size(int *w, int *h); + +#define PDEBUG(cat, level, fmt, arg...) _printdebug(__FILE__, __FUNCTION__, __LINE__, cat, level, NULL, fmt, ## arg) +#define PDEBUG_CHAN(cat, level, fmt, arg...) _printdebug(__FILE__, __FUNCTION__, __LINE__, cat, level, CHAN, fmt, ## arg) +void _printdebug(const char *file, const char *function, int line, int cat, int level, const char *chan_str, const char *fmt, ...) __attribute__ ((__format__ (__printf__, 7, 8))); + +const char *debug_amplitude(double level); +const char *debug_db(double level_db); + +void debug_print_help(void); +void debug_list_cat(void); +int parse_debug_opt(const char *opt); + +extern int debuglevel; + +extern void (*clear_console_text)(void); +extern void (*print_console_text)(void); + +extern int debug_limit_scroll; + +const char *debug_hex(const uint8_t *data, int len); + diff --git a/src/libdisplay/Makefile.am b/src/libdisplay/Makefile.am new file mode 100644 index 0000000..8f6097d --- /dev/null +++ b/src/libdisplay/Makefile.am @@ -0,0 +1,19 @@ +AM_CPPFLAGS = -Wall -Wextra -g $(all_includes) + +noinst_LIBRARIES = libdisplay.a + +libdisplay_a_SOURCES = \ + display_status.c \ + display_wave.c \ + display_measurements.c + +if HAVE_SDR +libdisplay_a_SOURCES += \ + display_iq.c \ + display_spectrum.c +endif + +if HAVE_SDR +AM_CPPFLAGS += -DHAVE_SDR +endif + diff --git a/src/libdisplay/display.h b/src/libdisplay/display.h new file mode 100644 index 0000000..064e327 --- /dev/null +++ b/src/libdisplay/display.h @@ -0,0 +1,103 @@ +#define DISPLAY_MEAS_INTERVAL 0.1 /* time (in seconds) for each measurement values interval */ +#define DISPLAY_INTERVAL 0.04 /* time (in seconds) for each other interval */ +#define DISPLAY_PARAM_HISTORIES 10 /* number of intervals (should result in one seconds) */ + +#define MAX_DISPLAY_WIDTH 1024 + +typedef struct display_wave { + const char *kanal; + int interval_pos; + int interval_max; + int offset; + sample_t buffer[MAX_DISPLAY_WIDTH]; +} dispwav_t; + +enum display_measurements_type { + DISPLAY_MEAS_LAST, /* display last value */ + DISPLAY_MEAS_PEAK, /* display peak value */ + DISPLAY_MEAS_PEAK2PEAK, /* display peak value of min..max range */ + DISPLAY_MEAS_AVG, /* display average value */ +}; + +enum display_measurements_bar { + DISPLAY_MEAS_LEFT, /* bar graph from left */ + DISPLAY_MEAS_CENTER, /* bar graph from center */ +}; + +typedef struct display_measurements_param { + struct display_measurements_param *next; + char name[32]; /* parameter name (e.g. 'Deviation') */ + char format[32]; /* unit name (e.g. "%.2f KHz") */ + enum display_measurements_type type; + enum display_measurements_bar bar; + double min; /* minimum value */ + double max; /* maximum value */ + double mark; /* mark (target) value */ + double value; /* current value (peak, sum...) */ + double value2; /* max value for min..max range */ + double last; /* last valid value (used for DISPLAY_MEAS_LAST) */ + int value_count; /* count number of values of one interval */ + double value_history[DISPLAY_PARAM_HISTORIES]; /* history of values of last second */ + double value2_history[DISPLAY_PARAM_HISTORIES]; /* stores max for min..max range */ + int value_history_pos; /* next history value to write */ +} dispmeasparam_t; + +typedef struct display_measurements { + struct display_measurements *next; + const char *kanal; + dispmeasparam_t *param; +} dispmeas_t; + +#define MAX_DISPLAY_IQ 1024 + +typedef struct display_iq { + int interval_pos; + int interval_max; + float buffer[MAX_DISPLAY_IQ * 2]; +} dispiq_t; + +#define MAX_DISPLAY_SPECTRUM 1024 + +typedef struct display_spectrum_mark { + struct display_spectrum_mark *next; + const char *kanal; + double frequency; +} dispspectrum_mark_t; + +typedef struct display_spectrum { + int interval_pos; + int interval_max; + double buffer_I[MAX_DISPLAY_SPECTRUM]; + double buffer_Q[MAX_DISPLAY_SPECTRUM]; + dispspectrum_mark_t *mark; +} dispspectrum_t; + +#define MAX_HEIGHT_STATUS 32 + +void display_wave_init(dispwav_t *disp, int samplerate, const char *kanal); +void display_wave_on(int on); +void display_wave(dispwav_t *disp, sample_t *samples, int length, double range); + +void display_status_on(int on); +void display_status_start(void); +void display_status_channel(const char *kanal, const char *type, const char *state); +void display_status_subscriber(const char *number, const char *state); +void display_status_end(void); + +void display_measurements_init(dispmeas_t *disp, int samplerate, const char *kanal); +void display_measurements_exit(dispmeas_t *disp); +void display_measurements_on(int on); +dispmeasparam_t *display_measurements_add(dispmeas_t *disp, char *name, char *format, enum display_measurements_type type, enum display_measurements_bar bar, double min, double max, double mark); +void display_measurements_update(dispmeasparam_t *param, double value, double value2); +void display_measurements(double elapsed); + +void display_iq_init(int samplerate); +void display_iq_on(int on); +void display_iq(float *samples, int length); + +void display_spectrum_init(int samplerate, double center_frequency); +void display_spectrum_add_mark(const char *kanal, double frequency); +void display_spectrum_exit(void); +void display_spectrum_on(int on); +void display_spectrum(float *samples, int length); + diff --git a/src/libdisplay/display_iq.c b/src/libdisplay/display_iq.c new file mode 100644 index 0000000..83cec6e --- /dev/null +++ b/src/libdisplay/display_iq.c @@ -0,0 +1,282 @@ +/* display IQ data form functions + * + * (C) 2016 by Andreas Eversberg + * All Rights Reserved + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include +#include +#include +#include +#include +#include "../libsample/sample.h" +#include "../libdebug/debug.h" +#include "../libdisplay/display.h" + +/* must be odd value! */ +#define SIZE 23 + +static char screen[SIZE][MAX_DISPLAY_WIDTH]; +static uint8_t screen_color[SIZE][MAX_DISPLAY_WIDTH]; +static uint8_t screen_history[SIZE * 2][MAX_DISPLAY_WIDTH]; +static int iq_on = 0; +static double db = 80; + +static dispiq_t disp; + +void display_iq_init(int samplerate) +{ + memset(&disp, 0, sizeof(disp)); + memset(&screen_history, 0, sizeof(screen_history)); + disp.interval_max = (double)samplerate * DISPLAY_INTERVAL + 0.5; + /* should not happen due to low interval */ + if (disp.interval_max < MAX_DISPLAY_IQ - 1) + disp.interval_max = MAX_DISPLAY_IQ - 1; +} + +void display_iq_on(int on) +{ + int j; + int w, h; + + get_win_size(&w, &h); + if (w > MAX_DISPLAY_WIDTH - 1) + w = MAX_DISPLAY_WIDTH - 1; + + if (iq_on) { + memset(&screen, ' ', sizeof(screen)); + memset(&screen_history, 0, sizeof(screen_history)); + lock_debug(); + printf("\0337\033[H"); + for (j = 0; j < SIZE; j++) { + screen[j][w] = '\0'; + puts(screen[j]); + } + printf("\0338"); fflush(stdout); + unlock_debug(); + } + + if (on < 0) { + if (++iq_on == 3) + iq_on = 0; + } else + iq_on = on; + + if (iq_on) + debug_limit_scroll = SIZE; + else + debug_limit_scroll = 0; +} + +/* + * plot IQ data: + * + * theoretical example: SIZE = 3 allows 6 steps plotted as dots + * + * Line 0: : + * Line 1: : + * Line 2: : + * + * The level of -1.0 .. 1.0 is scaled to -3 and 3. + * + * The lowest of the upper 3 dots ranges from 0.0 .. <1.5. + * The upper most dot ranges from 2.5 .. <3.5. + * The highest of the lower 3 dots ranges from <0.0 .. >-1.5; + * The lower most dot ranges from -2.5 .. >-3.5. + * + * The center column ranges from -0.5 .. <0.5. + * The columns about the center from -1.5 .. <1.5. + */ +void display_iq(float *samples, int length) +{ + int pos, max; + float *buffer; + int i, j, k; + int color = 9; /* default color */ + int x_center, y_center; + double I, Q, L, l, s; + int x, y; + int v, r; + int width, h; + + if (!iq_on) + return; + + lock_debug(); + + get_win_size(&width, &h); + if (width > MAX_DISPLAY_WIDTH - 1) + width = MAX_DISPLAY_WIDTH - 1; + + /* at what line we draw our zero-line and what character we use */ + x_center = width >> 1; + y_center = (SIZE - 1) >> 1; + + pos = disp.interval_pos; + max = disp.interval_max; + buffer = disp.buffer; + + for (i = 0; i < length; i++) { + if (pos >= MAX_DISPLAY_IQ) { + if (++pos == max) + pos = 0; + continue; + } + buffer[pos * 2] = samples[i * 2]; + buffer[pos * 2 + 1] = samples[i * 2 + 1]; + pos++; + if (pos == MAX_DISPLAY_IQ) { + memset(&screen, ' ', sizeof(screen)); + memset(&screen_color, 7, sizeof(screen_color)); + /* render screen history to screen */ + for (y = 0; y < SIZE * 2; y++) { + for (x = 0; x < width; x++) { + v = screen_history[y][x]; + v -= 8; + if (v < 0) + v = 0; + screen_history[y][x] = v; + r = random() & 0x3f; + if (r >= v) + continue; + if (screen[y/2][x] == ':') + continue; + if (screen[y/2][x] == '.') { + if ((y & 1) == 0) + screen[y/2][x] = ':'; + continue; + } + if (screen[y/2][x] == '\'') { + if ((y & 1)) + screen[y/2][x] = ':'; + continue; + } + if ((y & 1) == 0) + screen[y/2][x] = '\''; + else + screen[y/2][x] = '.'; + screen_color[y/2][x] = 4; + } + } + /* plot current IQ date */ + for (j = 0; j < MAX_DISPLAY_IQ; j++) { + I = buffer[j * 2]; + Q = buffer[j * 2 + 1]; + L = I*I + Q*Q; + if (iq_on > 1) { + /* logarithmic scale */ + l = sqrt(L); + s = log10(l) * 20 + db; + if (s < 0) + s = 0; + I = (I / l) * (s / db); + Q = (Q / l) * (s / db); + } + x = x_center + (int)(I * (double)SIZE + (double)width + 0.5) - width; + if (x < 0) + continue; + if (x > width - 1) + continue; + if (Q >= 0) + y = SIZE - 1 - (int)(Q * (double)SIZE - 0.5); + else + y = SIZE - (int)(Q * (double)SIZE + 0.5); + if (y < 0) + continue; + if (y > SIZE * 2 - 1) + continue; + if (screen[y/2][x] == ':' && screen_color[y/2][x] >= 10) + goto cont; + if (screen[y/2][x] == '.' && screen_color[y/2][x] >= 10) { + if ((y & 1) == 0) + screen[y/2][x] = ':'; + goto cont; + } + if (screen[y/2][x] == '\'' && screen_color[y/2][x] >= 10) { + if ((y & 1)) + screen[y/2][x] = ':'; + goto cont; + } + if ((y & 1) == 0) + screen[y/2][x] = '\''; + else + screen[y/2][x] = '.'; +cont: + screen_history[y][x] = 255; + /* overdrive: + * red = close to -1..1 or above + * yellow = close to -0.5..0.5 or above + * Note: L is square of vector length, + * so we compare with square values. + */ + if (L > 0.9 * 0.9) + screen_color[y/2][x] = 11; + else if (L > 0.45 * 0.45 && screen_color[y/2][x] != 11) + screen_color[y/2][x] = 13; + else if (screen_color[y/2][x] < 10) + screen_color[y/2][x] = 12; + } + if (iq_on == 1) + sprintf(screen[0], "(IQ linear"); + else + sprintf(screen[0], "(IQ log %.0f dB", db); + *strchr(screen[0], '\0') = ')'; + printf("\0337\033[H"); + for (j = 0; j < SIZE; j++) { + for (k = 0; k < width; k++) { + if ((j == y_center || k == x_center) && screen[j][k] == ' ') { + /* cross */ + if (color != 4) { + color = 4; + printf("\033[0;34m"); + } + if (j == y_center) { + if (k == x_center) + putchar('o'); + else if (k == x_center - SIZE) + putchar('+'); + else if (k == x_center + SIZE) + putchar('+'); + else + putchar('-'); + } else { + if (j == 0 || j == SIZE - 1) + putchar('+'); + else + putchar('|'); + } + } else { + if (screen_color[j][k] != color) { + color = screen_color[j][k]; + printf("\033[%d;3%dm", color / 10, color % 10); + } + putchar(screen[j][k]); + } + } + printf("\n"); + } + /* reset color and position */ + printf("\033[0;39m\0338"); fflush(stdout); + } + } + + disp.interval_pos = pos; + + unlock_debug(); +} + + diff --git a/src/libdisplay/display_measurements.c b/src/libdisplay/display_measurements.c new file mode 100644 index 0000000..e2c2f0f --- /dev/null +++ b/src/libdisplay/display_measurements.c @@ -0,0 +1,360 @@ +/* display measurements functions + * + * (C) 2017 by Andreas Eversberg + * All Rights Reserved + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include +#include +#include +#include +#include +#include +#include "../libsample/sample.h" +#include "../libdebug/debug.h" +#include "../libdisplay/display.h" + +#define MAX_NAME_LEN 16 +#define MAX_UNIT_LEN 16 + +static int has_init = 0; +static int measurements_on = 0; +double time_elapsed = 0.0; +static int lines_total = 0; +static char line[MAX_DISPLAY_WIDTH]; +static char line_color[MAX_DISPLAY_WIDTH]; + +dispmeas_t *meas_head = NULL; + +void display_measurements_init(dispmeas_t *disp, int __attribute__((unused)) samplerate, const char *kanal) +{ + dispmeas_t **disp_p; + + memset(disp, 0, sizeof(*disp)); + disp->kanal = kanal; + has_init = 1; + lines_total = 0; + time_elapsed = 0.0; + + disp_p = &meas_head; + while (*disp_p) + disp_p = &((*disp_p)->next); + *disp_p = disp; +} + +void display_measurements_exit(dispmeas_t *disp) +{ + dispmeasparam_t *param = disp->param, *temp; + + while (param) { + temp = param; + param = param->next; + free(temp); + } + disp->param = NULL; + has_init = 0; +} + +static int color; + +static void display_line(int on, int w) +{ + int j; + + if (on) { + for (j = 0; j < w; j++) { + if (line_color[j] != color && line[j] != ' ') { + color = line_color[j]; + printf("\033[%d;3%dm", color / 10, color % 10); + } + putchar(line[j]); + } + } else { + for (j = 0; j < w; j++) + putchar(' '); + } + putchar('\n'); + lines_total++; +} + +static void print_measurements(int on) +{ + dispmeas_t *disp; + dispmeasparam_t *param; + int i, j; + int width, h; + char text[128]; + double value = 0.0, value2 = 0.0, hold, hold2; + int bar_width, bar_left, bar_right, bar_hold, bar_mark; + + get_win_size(&width, &h); + if (width > MAX_DISPLAY_WIDTH - 1) + width = MAX_DISPLAY_WIDTH - 1; + + /* no display, if bar graph is less than one character */ + bar_width = width - MAX_NAME_LEN - MAX_UNIT_LEN; + if (bar_width < 1) + return; + + lock_debug(); + + lines_total = 0; + color = -1; + printf("\0337\033[H"); + for (disp = meas_head; disp; disp = disp->next) { + memset(line, ' ', width); + memset(line_color, 7, width); + sprintf(line, "(chan %s", disp->kanal); + *strchr(line, '\0') = ')'; + display_line(on, width); + for (param = disp->param; param; param = param->next) { + memset(line, ' ', width); + memset(line_color, 7, width); + memset(line_color, 3, MAX_NAME_LEN); /* yellow */ + switch (param->type) { + case DISPLAY_MEAS_LAST: + value = param->value; + param->value = -NAN; + break; + case DISPLAY_MEAS_PEAK: + /* peak value */ + value = param->value; + param->value = -NAN; + param->value_count = 0; + break; + case DISPLAY_MEAS_PEAK2PEAK: + /* peak to peak value */ + value = param->value; + value2 = param->value2; + param->value = -NAN; + param->value2 = -NAN; + param->value_count = 0; + break; + case DISPLAY_MEAS_AVG: + /* average value */ + if (param->value_count) + value = param->value / (double)param->value_count; + else + value = -NAN; + param->value = 0.0; + param->value_count = 0; + break; + } + /* add current value to history */ + param->value_history[param->value_history_pos] = value; + param->value2_history[param->value_history_pos] = value2; + param->value_history_pos = param->value_history_pos % DISPLAY_PARAM_HISTORIES; + /* calculate hold values */ + hold = -NAN; + hold2 = -NAN; + switch (param->type) { + case DISPLAY_MEAS_LAST: + /* if we have valid value, we update 'last' */ + if (!isnan(value)) { + param->last = value; + hold = value; + } else + hold = param->last; + break; + case DISPLAY_MEAS_PEAK: + for (i = 0; i < DISPLAY_PARAM_HISTORIES; i++) { + if (isnan(param->value_history[i])) + continue; + if (isnan(hold) || param->value_history[i] > hold) + hold = param->value_history[i]; + } + break; + case DISPLAY_MEAS_PEAK2PEAK: + for (i = 0; i < DISPLAY_PARAM_HISTORIES; i++) { + if (isnan(param->value_history[i])) + continue; + if (isnan(hold) || param->value_history[i] < hold) + hold = param->value_history[i]; + if (isnan(hold2) || param->value2_history[i] > hold2) + hold2 = param->value2_history[i]; + } + if (!isnan(hold)) + hold = hold2 - hold; + if (!isnan(value)) + value = value2 - value; + break; + case DISPLAY_MEAS_AVG: + for (i = 0, j = 0; i < DISPLAY_PARAM_HISTORIES; i++) { + if (isnan(param->value_history[i])) + continue; + if (j == 0) + hold = 0.0; + hold += param->value_history[i]; + j++; + } + if (j) + hold /= j; + break; + } + /* "Deviation ::::::::::............ 4.5 KHz" */ + memcpy(line, param->name, (strlen(param->name) < MAX_NAME_LEN) ? strlen(param->name) : MAX_NAME_LEN); + if (isinf(value) || isnan(value)) { + bar_left = -1; + bar_right = -1; + } else if (param->bar == DISPLAY_MEAS_CENTER) { + if (value >= 0.0) { + bar_left = (-param->min) / (param->max - param->min) * ((double)bar_width - 1.0); + bar_right = (value - param->min) / (param->max - param->min) * ((double)bar_width - 1.0); + } else { + bar_left = (value - param->min) / (param->max - param->min) * ((double)bar_width - 1.0); + bar_right = (-param->min) / (param->max - param->min) * ((double)bar_width - 1.0); + } + } else { + bar_left = -1; + bar_right = (value - param->min) / (param->max - param->min) * ((double)bar_width - 1.0); + } + if (isinf(hold) || isnan(hold)) + bar_hold = -1; + else + bar_hold = (hold - param->min) / (param->max - param->min) * ((double)bar_width - 1.0); + if (isinf(param->mark)) + bar_mark = -1; + else + bar_mark = (param->mark - param->min) / (param->max - param->min) * ((double)bar_width - 1.0); + for (i = 0; i < bar_width; i++) { + line[i + MAX_NAME_LEN] = ':'; + if (i == bar_hold) + line_color[i + MAX_NAME_LEN] = 13; + else if (i == bar_mark) + line_color[i + MAX_NAME_LEN] = 14; + else if (i >= bar_left && i <= bar_right) + line_color[i + MAX_NAME_LEN] = 2; + else + line_color[i + MAX_NAME_LEN] = 4; + } + sprintf(text, param->format, hold); + if (isnan(hold)) + memset(line_color + width - MAX_UNIT_LEN, 4, MAX_UNIT_LEN); /* blue */ + else + memset(line_color + width - MAX_UNIT_LEN, 3, MAX_UNIT_LEN); /* yellow */ + strncpy(line + width - MAX_UNIT_LEN + 1, text, (strlen(text) < MAX_UNIT_LEN) ? strlen(text) : MAX_UNIT_LEN); + display_line(on, width); + } + } + /* reset color and position */ + printf("\033[0;39m\0338"); fflush(stdout); + + debug_limit_scroll = lines_total; + + unlock_debug(); +} + +void display_measurements_on(int on) +{ + if (measurements_on) + print_measurements(0); + + if (on < 0) + measurements_on = 1 - measurements_on; + else + measurements_on = on; + + debug_limit_scroll = 0; +} + +/* add new parameter on startup to the list of measurements */ +dispmeasparam_t *display_measurements_add(dispmeas_t *disp, char *name, char *format, enum display_measurements_type type, enum display_measurements_bar bar, double min, double max, double mark) +{ + dispmeasparam_t *param, **param_p = &disp->param; + int i; + + if (!has_init) { + fprintf(stderr, "Not initialized prior adding measurement, please fix!\n"); + abort(); + } + + while (*param_p) + param_p = &((*param_p)->next); + *param_p = calloc(sizeof(dispmeasparam_t), 1); + if (!*param_p) + return NULL; + param = *param_p; + strncpy(param->name, name, sizeof(param->name) - 1); + strncpy(param->format, format, sizeof(param->format) - 1); + param->type = type; + param->bar = bar; + param->min = min; + param->max = max; + param->mark = mark; + param->value = -NAN; + param->value2 = -NAN; + param->last = -NAN; + for (i = 0; i < DISPLAY_PARAM_HISTORIES; i++) + param->value_history[i] = -NAN; + param->value_count = 0; + + return param; +} + +void display_measurements_update(dispmeasparam_t *param, double value, double value2) +{ + /* special case where we do not have an instance of the parameter */ + if (!param) + return; + + if (!has_init) { + fprintf(stderr, "Not initialized prior updating measurement value, please fix!\n"); + abort(); + } + + switch (param->type) { + case DISPLAY_MEAS_LAST: + param->value = value; + break; + case DISPLAY_MEAS_PEAK: + if (isnan(param->value) || value > param->value) + param->value = value; + break; + case DISPLAY_MEAS_PEAK2PEAK: + if (param->value_count == 0 || value < param->value) + param->value = value; + if (param->value_count == 0 || value2 > param->value2) + param->value2 = value2; + param->value_count++; + break; + case DISPLAY_MEAS_AVG: + param->value += value; + param->value_count++; + break; + default: + fprintf(stderr, "Parameter '%s' has unknown type %d, please fix!\n", param->name, param->type); + abort(); + } +} + +void display_measurements(double elapsed) +{ + if (!measurements_on) + return; + + if (!has_init) + return; + + /* count and check if we need to display this time */ + time_elapsed += elapsed; + if (time_elapsed < DISPLAY_MEAS_INTERVAL) + return; + time_elapsed = fmod(time_elapsed, DISPLAY_MEAS_INTERVAL); + + print_measurements(1); +} + diff --git a/src/libdisplay/display_spectrum.c b/src/libdisplay/display_spectrum.c new file mode 100644 index 0000000..b0f30f4 --- /dev/null +++ b/src/libdisplay/display_spectrum.c @@ -0,0 +1,414 @@ +/* display spectrum of IQ data + * + * (C) 2016 by Andreas Eversberg + * All Rights Reserved + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include +#include +#include +#include +#include "../libsample/sample.h" +#include "../libfft/fft.h" +#include "../libdebug/debug.h" +#include "../libdisplay/display.h" + +#define HEIGHT 20 + +static int has_init = 0; +static double buffer_delay[MAX_DISPLAY_SPECTRUM]; +static double buffer_hold[MAX_DISPLAY_SPECTRUM]; +static char screen[HEIGHT][MAX_DISPLAY_WIDTH]; +static uint8_t screen_color[HEIGHT][MAX_DISPLAY_WIDTH]; +static int spectrum_on = 0; +static double db = 120; +static double center_frequency, frequency_range; + +static dispspectrum_t disp; + +void display_spectrum_init(int samplerate, double _center_frequency) +{ + memset(&disp, 0, sizeof(disp)); + disp.interval_max = (double)samplerate * DISPLAY_INTERVAL + 0.5; + /* should not happen due to low interval */ + if (disp.interval_max < MAX_DISPLAY_SPECTRUM - 1) + disp.interval_max = MAX_DISPLAY_SPECTRUM - 1; + memset(buffer_delay, 0, sizeof(buffer_delay)); + + center_frequency = _center_frequency; + frequency_range = (double)samplerate; + + has_init = 1; +} + +void display_spectrum_add_mark(const char *kanal, double frequency) +{ + dispspectrum_mark_t *mark, **mark_p; + + if (!has_init) + return; + + mark = calloc(1, sizeof(*mark)); + if (!mark) { + fprintf(stderr, "no mem!"); + abort(); + } + mark->kanal = kanal; + mark->frequency = frequency; + + mark_p = &disp.mark; + while (*mark_p) + mark_p = &((*mark_p)->next); + *mark_p = mark; +} + +void display_spectrum_exit(void) +{ + dispspectrum_mark_t *mark = disp.mark, *temp; + + while (mark) { + temp = mark; + mark = mark->next; + free(temp); + } + disp.mark = NULL; + has_init = 0; +} + +void display_spectrum_on(int on) +{ + int j; + int w, h; + + get_win_size(&w, &h); + if (w > MAX_DISPLAY_WIDTH - 1) + w = MAX_DISPLAY_WIDTH - 1; + + if (spectrum_on) { + memset(&screen, ' ', sizeof(screen)); + memset(&buffer_hold, 0, sizeof(buffer_hold)); + lock_debug(); + printf("\0337\033[H"); + for (j = 0; j < HEIGHT; j++) { + screen[j][w] = '\0'; + puts(screen[j]); + } + printf("\0338"); fflush(stdout); + unlock_debug(); + } + + if (on < 0) { + if (++spectrum_on == 3) + spectrum_on = 0; + } else + spectrum_on = on; + + if (spectrum_on) + debug_limit_scroll = HEIGHT; + else + debug_limit_scroll = 0; +} + +/* + * plot spectrum data: + * + */ +void display_spectrum(float *samples, int length) +{ + dispspectrum_mark_t *mark; + char print_channel[32], print_frequency[32]; + int width, h; + int pos, max; + double *buffer_I, *buffer_Q; + int color = 9; /* default color */ + int i, j, k, o; + double I, Q, v; + int s, e, l, n; + + if (!spectrum_on) + return; + + lock_debug(); + + get_win_size(&width, &h); + if (width > MAX_DISPLAY_WIDTH - 1) + width = MAX_DISPLAY_WIDTH - 1; + + /* calculate size of FFT */ + int m, fft_size = 0, fft_taps = 0; + for (m = 0; m < 16; m++) { + if ((1 << m) > MAX_DISPLAY_SPECTRUM) + break; + if ((1 << m) <= width) { + fft_taps = m; + fft_size = 1 << m; + } + } + if (m == 16) { + fprintf(stderr, "Size of spectrum is not a power of 2, please fix!\n"); + abort(); + } + + int hold[fft_size], delay[fft_size], current[fft_size]; + + pos = disp.interval_pos; + max = disp.interval_max; + buffer_I = disp.buffer_I; + buffer_Q = disp.buffer_Q; + + for (i = 0; i < length; i++) { + if (pos >= fft_size) { + if (++pos == max) + pos = 0; + continue; + } + buffer_I[pos] = samples[i * 2]; + buffer_Q[pos] = samples[i * 2 + 1]; + pos++; + if (pos == fft_size) { + fft_process(1, fft_taps, buffer_I, buffer_Q); + k = 0; + for (j = 0; j < fft_size; j++) { + /* scale result vertically */ + I = buffer_I[(j + fft_size / 2) % fft_size]; + Q = buffer_Q[(j + fft_size / 2) % fft_size]; + v = sqrt(I*I + Q*Q); + v = log10(v) * 20 + db; + if (v < 0) + v = 0; + v /= db; + /* delayed */ + buffer_delay[j] -= DISPLAY_INTERVAL / 10.0; + if (v > buffer_delay[j]) + buffer_delay[j] = v; + delay[j] = (double)(HEIGHT * 2 - 1) * (1.0 - buffer_delay[j]); + if (delay[j] < 0) + delay[j] = 0; + if (delay[j] >= (HEIGHT * 2)) + delay[j] = (HEIGHT * 2) - 1; + /* hold */ + if (spectrum_on == 2) { + if (v > buffer_hold[j]) + buffer_hold[j] = v; + hold[j] = (double)(HEIGHT * 2 - 1) * (1.0 - buffer_hold[j]); + if (hold[j] < 0) + hold[j] = 0; + if (hold[j] >= (HEIGHT * 2)) + hold[j] = (HEIGHT * 2) - 1; + } + /* current */ + current[j] = (double)(HEIGHT * 2 - 1) * (1.0 - v); + if (current[j] < 0) + current[j] = 0; + if (current[j] >= (HEIGHT * 2)) + current[j] = (HEIGHT * 2) - 1; + } + /* plot scaled buffer */ + memset(&screen, ' ', sizeof(screen)); + memset(&screen_color, 7, sizeof(screen_color)); /* all white */ + sprintf(screen[0], "(spectrum log %.0f dB%s", db, (spectrum_on == 2) ? " HOLD" : ""); + *strchr(screen[0], '\0') = ')'; + for (j = 2; j < HEIGHT; j += 2) { + memset(screen_color[j], 4, 7); /* blue */ + sprintf(screen[j], "%4.0f dB", -(double)(j+1) * db / (double)(HEIGHT - 1)); + screen[j][7] = ' '; + } + o = (width - fft_size) / 2; /* offset from left border */ + for (j = 0; j < fft_size; j++) { + /* show current spectrum in yellow */ + s = l = n = current[j]; + /* get last and next value */ + if (j > 0) + l = (current[j - 1] + s) / 2; + if (j < fft_size - 1) + n = (current[j + 1] + s) / 2; + if (s > l && s > n) { + /* current value is a minimum */ + e = s; + s = (l < n) ? (l + 1) : (n + 1); + } else if (s < l && s < n) { + /* current value is a maximum */ + e = (l > n) ? l : n; + } else if (l < n) { + /* last value is higher, next value is lower */ + s = l + 1; + e = n; + } else if (l > n) { + /* last value is lower, next value is higher */ + s = n + 1; + e = l; + } else { + /* current, last and next values are equal */ + e = s; + } + if (s == e) { + if ((s & 1) == 0) + screen[s >> 1][j + o] = '\''; + else + screen[s >> 1][j + o] = '.'; + screen_color[s >> 1][j + o] = 13; + } else { + if ((s & 1) == 0) + screen[s >> 1][j + o] = '|'; + else + screen[s >> 1][j + o] = '.'; + screen_color[s >> 1][j + o] = 13; + if ((e & 1) == 0) + screen[e >> 1][j + o] = '\''; + else + screen[e >> 1][j + o] = '|'; + screen_color[e >> 1][j + o] = 13; + for (k = (s >> 1) + 1; k < (e >> 1); k++) { + screen[k][j + o] = '|'; + screen_color[k][j + o] = 13; + } + } + /* show delayed spectrum in blue */ + e = s; + s = delay[j]; + if ((s >> 1) < (e >> 1)) { + if ((s & 1) == 0) + screen[s >> 1][j + o] = '|'; + else + screen[s >> 1][j + o] = '.'; + screen_color[s >> 1][j + o] = 4; + for (k = (s >> 1) + 1; k < (e >> 1); k++) { + screen[k][j + o] = '|'; + screen_color[k][j + o] = 4; + } + } + if (spectrum_on == 2) { + /* show hold spectrum in white */ + s = l = n = hold[j]; + /* get last and next value */ + if (j > 0) + l = (hold[j - 1] + s) / 2; + if (j < fft_size - 1) + n = (hold[j + 1] + s) / 2; + if (s > l && s > n) { + /* hold value is a minimum */ + e = s; + s = (l < n) ? (l + 1) : (n + 1); + } else if (s < l && s < n) { + /* hold value is a maximum */ + e = (l > n) ? l : n; + } else if (l < n) { + /* last value is higher, next value is lower */ + s = l + 1; + e = n; + } else if (l > n) { + /* last value is lower, next value is higher */ + s = n + 1; + e = l; + } else { + /* hold, last and next values are equal */ + e = s; + } + if (s == e) { + if ((s & 1) == 0) + screen[s >> 1][j + o] = '\''; + else + screen[s >> 1][j + o] = '.'; + screen_color[s >> 1][j + o] = 17; + } else { + if ((s & 1) == 0) + screen[s >> 1][j + o] = '|'; + else + screen[s >> 1][j + o] = '.'; + screen_color[s >> 1][j + o] = 17; + if ((e & 1) == 0) + screen[e >> 1][j + o] = '\''; + else + screen[e >> 1][j + o] = '|'; + screen_color[e >> 1][j + o] = 17; + for (k = (s >> 1) + 1; k < (e >> 1); k++) { + screen[k][j + o] = '|'; + screen_color[k][j + o] = 17; + } + } + } + } + /* add channel positions in spectrum */ + for (mark = disp.mark; mark; mark = mark->next) { + j = (int)((mark->frequency - center_frequency) / frequency_range * (double) fft_size + width / 2 + 0.5); + if (j < 0 || j >= width) /* check out-of-range, should not happen */ + continue; + for (k = 0; k < HEIGHT; k++) { + /* skip yellow/white graph */ + if (screen_color[k][j] == 13 || screen_color[k][j] == 17) + continue; + screen[k][j] = ':'; + screen_color[k][j] = 12; + } + sprintf(print_channel, "Ch%s", mark->kanal); + for (o = 0; o < (int)strlen(print_channel); o++) { + s = j - strlen(print_channel) + o; + if (s >= 0 && s < width) { + screen[HEIGHT - 1][s] = print_channel[o]; + screen_color[HEIGHT - 1][s] = 7; + } + } + if (fmod(mark->frequency, 1000.0)) + sprintf(print_frequency, "%.4f", mark->frequency / 1e6); + else + sprintf(print_frequency, "%.3f", mark->frequency / 1e6); + for (o = 0; o < (int)strlen(print_frequency); o++) { + s = j + o + 1; + if (s >= 0 && s < width) { + screen[HEIGHT - 1][s] = print_frequency[o]; + screen_color[HEIGHT - 1][s] = 7; + } + } + } + /* add center (DC line) to spectrum */ + j = width / 2 + 0.5; + if (j < 1 || j >= width-1) /* check out-of-range, should not happen */ + continue; + for (k = 0; k < HEIGHT; k++) { + /* skip green/yellow/white graph */ + if (screen_color[k][j] == 13 || screen_color[k][j] == 17 || screen_color[k][j] == 12) + continue; + screen[k][j] = '.'; + screen_color[k][j] = 7; + } + screen[0][j-1] = 'D'; + screen[0][j+1] = 'C'; + screen_color[0][j-1] = 7; + screen_color[0][j+1] = 7; + /* display buffer */ + printf("\0337\033[H"); + for (j = 0; j < HEIGHT; j++) { + for (k = 0; k < width; k++) { + if (screen_color[j][k] != color) { + color = screen_color[j][k]; + printf("\033[%d;3%dm", color / 10, color % 10); + } + putchar(screen[j][k]); + } + printf("\n"); + } + /* reset color and position */ + printf("\033[0;39m\0338"); fflush(stdout); + } + } + + disp.interval_pos = pos; + + unlock_debug(); +} + + diff --git a/src/libdisplay/display_status.c b/src/libdisplay/display_status.c new file mode 100644 index 0000000..de38c9e --- /dev/null +++ b/src/libdisplay/display_status.c @@ -0,0 +1,145 @@ +/* display status functions + * + * (C) 2017 by Andreas Eversberg + * All Rights Reserved + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include +#include +#include +#include +#include "../libsample/sample.h" +#include "../libdebug/debug.h" +#include "../libdisplay/display.h" + +static int status_on = 0; +static int line_count = 0; +static int lines_total = 0; +static char screen[MAX_HEIGHT_STATUS][MAX_DISPLAY_WIDTH]; + +static void print_status(int on) +{ + int i, j; + int w, h; + + get_win_size(&w, &h); + if (w > MAX_DISPLAY_WIDTH - 1) + w = MAX_DISPLAY_WIDTH - 1; + + if (w > MAX_DISPLAY_WIDTH) + w = MAX_DISPLAY_WIDTH; + h--; + if (h > lines_total) + h = lines_total; + + lock_debug(); + printf("\0337\033[H\033[1;37m"); + for (i = 0; i < h; i++) { + j = 0; + if (on) { + for (j = 0; j < w; j++) + putchar(screen[i][j]); + } else { + for (j = 0; j < w; j++) + putchar(' '); + } + putchar('\n'); + } + printf("\0338"); fflush(stdout); + unlock_debug(); +} + +void display_status_on(int on) +{ + if (status_on) + print_status(0); + + if (on < 0) + status_on = 1 - status_on; + else + status_on = on; + + if (status_on) + print_status(1); + + if (status_on) + debug_limit_scroll = lines_total; + else + debug_limit_scroll = 0; +} + +/* start status display */ +void display_status_start(void) +{ + memset(screen, ' ', sizeof(screen)); + memset(screen[0], '-', sizeof(screen[0])); + memcpy(screen[0] + 4, "Channel Status", 14); + line_count = 1; +} + +void display_status_channel(const char *kanal, const char *type, const char *state) +{ + char line[MAX_DISPLAY_WIDTH]; + + /* add empty line after previous channel+subscriber */ + if (line_count > 1 && line_count < MAX_HEIGHT_STATUS) + line_count++; + + if (line_count == MAX_HEIGHT_STATUS) + return; + + if (type) + snprintf(line, sizeof(line), "Channel: %s Type: %s State: %s", kanal, type, state); + else + snprintf(line, sizeof(line), "Channel: %s State: %s", kanal, state); + line[sizeof(line) - 1] = '\0'; + memcpy(screen[line_count++], line, strlen(line)); +} + +void display_status_subscriber(const char *number, const char *state) +{ + char line[MAX_DISPLAY_WIDTH]; + + if (line_count == MAX_HEIGHT_STATUS) + return; + + if (state) + snprintf(line, sizeof(line), " Subscriber: %s State: %s", number, state); + else + snprintf(line, sizeof(line), " Subscriber: %s", number); + line[sizeof(line) - 1] = '\0'; + memcpy(screen[line_count++], line, strlen(line)); +} + +void display_status_end(void) +{ + if (line_count < MAX_HEIGHT_STATUS) { + memset(screen[line_count], '-', sizeof(screen[line_count])); + line_count++; + } + /* if last total lines exceed current line count, keep it, so removed lines are overwritten with spaces */ + if (line_count > lines_total) + lines_total = line_count; + if (status_on) + print_status(1); + /* set new total lines */ + lines_total = line_count; + if (status_on) + debug_limit_scroll = lines_total; +} + + diff --git a/src/libdisplay/display_wave.c b/src/libdisplay/display_wave.c new file mode 100644 index 0000000..713873f --- /dev/null +++ b/src/libdisplay/display_wave.c @@ -0,0 +1,252 @@ +/* display wave form functions + * + * (C) 2016 by Andreas Eversberg + * All Rights Reserved + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include +#include +#include +#include +#include +#include "../libsample/sample.h" +#include "../libdebug/debug.h" +#include "../libdisplay/display.h" + +#define HEIGHT 11 + +static int num_sender = 0; +static char screen[HEIGHT][MAX_DISPLAY_WIDTH]; +static int wave_on = 0; + +void display_wave_init(dispwav_t *disp, int samplerate, const char *kanal) +{ + memset(disp, 0, sizeof(*disp)); + disp->offset = (num_sender++) * HEIGHT; + disp->interval_max = (double)samplerate * DISPLAY_INTERVAL + 0.5; + disp->kanal = kanal; +} + +void display_wave_on(int on) +{ + int i, j; + int w, h; + + get_win_size(&w, &h); + if (w > MAX_DISPLAY_WIDTH - 1) + w = MAX_DISPLAY_WIDTH - 1; + + if (wave_on) { + memset(&screen, ' ', sizeof(screen)); + lock_debug(); + printf("\0337\033[H"); + for (i = 0; i < num_sender; i++) { + for (j = 0; j < HEIGHT; j++) { + screen[j][w] = '\0'; + puts(screen[j]); + } + } + printf("\0338"); fflush(stdout); + unlock_debug(); + } + + if (on < 0) + wave_on = 1 - wave_on; + else + wave_on = on; + + if (wave_on) + debug_limit_scroll = HEIGHT * num_sender; + else + debug_limit_scroll = 0; +} + +/* + * draw wave form: + * + * theoretical example: HEIGHT = 3 allows 5 steps + * + * Line 0: '. + * Line 1: '. + * Line 2: ' + * + * HEIGHT is odd, so the center line's char is ''' (otherwise '.') + * (HEIGHT - 1) / 2 = 1, so the center line is drawn in line 1 + * + * y is in range of 0..4, so these are 5 steps, where 2 is the + * center line. this is calculated by (HEIGHT * 2 - 1) + */ +void display_wave(dispwav_t *disp, sample_t *samples, int length, double range) +{ + int pos, max; + sample_t *buffer; + int i, j, k, s, e; + double last, current, next; + int color = 9; /* default color */ + int center_line; + char center_char; + int width, h; + + if (!wave_on) + return; + + lock_debug(); + + get_win_size(&width, &h); + if (width > MAX_DISPLAY_WIDTH - 1) + width = MAX_DISPLAY_WIDTH - 1; + + /* at what line we draw our zero-line and what character we use */ + center_line = (HEIGHT - 1) >> 1; + center_char = (HEIGHT & 1) ? '\'' : '.'; + + pos = disp->interval_pos; + max = disp->interval_max; + buffer = disp->buffer; + + for (i = 0; i < length; i++) { + if (pos >= width + 2) { + if (++pos == max) + pos = 0; + continue; + } + buffer[pos++] = samples[i]; + if (pos == width + 2) { + memset(&screen, ' ', sizeof(screen)); + for (j = 0; j < width; j++) { + /* Input value is scaled to range -1 .. 1 and then subtracted from 1, + * so the result ranges from 0 .. 2. + * HEIGHT-1 is multiplied with the range, so a HEIGHT of 3 would allow + * 0..4 (5 steps) and a HEIGHT of 11 would allow 0..20 (21 steps). + * We always use odd number of steps, so there will be a center between + * values. + */ + last = (1.0 - buffer[j] / range) * (double)(HEIGHT - 1); + current = (1.0 - buffer[j + 1] / range) * (double)(HEIGHT - 1); + next = (1.0 - buffer[j + 2] / range) * (double)(HEIGHT - 1); + /* calculate start and end for vertical line + * if the current value is a peak (above or below last AND next point), + * round this peak point to become one end of the vertical line. + * the other end is rounded up or down, so the end of the line will + * not overlap with the ends of the surrounding lines. + */ + if (last > current) { + if (next > current) { + /* current point is a peak up */ + s = round(current); + /* use lowest neighbor point and end is half way */ + if (last > next) + e = floor((last + current) / 2.0); + else + e = floor((next + current) / 2.0); + /* end point must not be above start point */ + if (e < s) + e = s; + } else { + /* current point is a transition upwards */ + s = ceil((next + current) / 2.0); + e = floor((last + current) / 2.0); + /* end point must not be above start point */ + if (e < s) + s = e = round(current); + } + } else { + if (next <= current) { + /* current point is a peak down */ + e = round(current); + /* use heighes neighbor point and start is half way */ + if (last <= next) + s = ceil((last + current) / 2.0); + else + s = ceil((next + current) / 2.0); + /* start point must not be below end point */ + if (s > e) + s = e; + } else { + /* current point is a transition downwards */ + s = ceil((last + current) / 2.0); + e = floor((next + current) / 2.0); + /* start point must not be below end point */ + if (s > e) + s = e = round(current); + } + } + /* only draw line, if it is in range */ + if (e >= 0 && s < HEIGHT * 2 - 1) { + /* clip */ + if (s < 0) + s = 0; + if (e >= HEIGHT * 2 - 1) + e = HEIGHT * 2 - 1; + /* plot start and end point */ + if ((s & 1)) + screen[s >> 1][j] = '.'; + else if (e != s) + screen[s >> 1][j] = '|'; + if (!(e & 1)) + screen[e >> 1][j] = '\''; + else if (e != s) + screen[e >> 1][j] = '|'; + /* plot line between start and end point */ + for (k = (s >> 1) + 1; k < (e >> 1); k++) + screen[k][j] = '|'; + } + } + sprintf(screen[0], "(chan %s", disp->kanal); + *strchr(screen[0], '\0') = ')'; + printf("\0337\033[H"); + for (j = 0; j < disp->offset; j++) + puts(""); + for (j = 0; j < HEIGHT; j++) { + for (k = 0; k < width; k++) { + if (j == center_line && screen[j][k] == ' ') { + /* blue 0-line */ + if (color != 4) { + color = 4; + printf("\033[0;34m"); + } + putchar(center_char); + } else if (screen[j][k] == '\'' || screen[j][k] == '.' || screen[j][k] == '|') { + /* green scope curve */ + if (color != 2) { + color = 2; + printf("\033[1;32m"); + } + putchar(screen[j][k]); + } else if (screen[j][k] != ' ') { + /* white other characters */ + if (color != 7) { + color = 7; + printf("\033[1;37m"); + } + putchar(screen[j][k]); + } else + putchar(screen[j][k]); + } + printf("\n"); + } + /* reset color and position */ + printf("\033[0;39m\0338"); fflush(stdout); + } + } + + disp->interval_pos = pos; + + unlock_debug(); +} + + diff --git a/src/libfilter/Makefile.am b/src/libfilter/Makefile.am new file mode 100644 index 0000000..45d2ec2 --- /dev/null +++ b/src/libfilter/Makefile.am @@ -0,0 +1,8 @@ +AM_CPPFLAGS = -Wall -Wextra -g $(all_includes) + +noinst_LIBRARIES = libfilter.a + +libfilter_a_SOURCES = \ + iir_filter.c \ + fir_filter.c + diff --git a/src/libfilter/fir_filter.c b/src/libfilter/fir_filter.c new file mode 100644 index 0000000..c86ac5b --- /dev/null +++ b/src/libfilter/fir_filter.c @@ -0,0 +1,197 @@ +/* FIR filter + * + * (C) 2017 by Andreas Eversberg + * All Rights Reserved + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include +#include +#include +#include +#include "../libsample/sample.h" +#include "fir_filter.h" + +//#define DEBUG_TAPS + +static void kernel(double *taps, int M, double cutoff, int invert) +{ + int i; + double sum; + + for (i = 0; i <= M; i++) { + /* gen sinc */ + if (i == M / 2) + taps[i] = 2.0 * M_PI * cutoff; + else + taps[i] = sin(2.0 * M_PI * cutoff * (double)(i - M / 2)) + / (double)(i - M / 2); + /* blackman window */ + taps[i] *= 0.42 - 0.50 * cos(2 * M_PI * (double)(i / M)) + + 0.08 * cos(4 * M_PI * (double)(i / M)); + } + + /* normalize */ + sum = 0; + for (i = 0; i <= M; i++) + sum += taps[i]; + for (i = 0; i <= M; i++) + taps[i] /= sum; + + /* invert */ + if (invert) { + for (i = 0; i <= M; i++) + taps[i] = -taps[i]; + taps[M / 2] += 1.0; + } + +#ifdef DEBUG_TAPS + puts("start"); + for (i = 0; i <= M; i++) + puts(debug_amplitude(taps[i])); +#endif +} + +static fir_filter_t *fir_init(double samplerate, double transition_bandwidth) +{ + fir_filter_t *fir; + int M; + + /* alloc struct */ + fir = calloc(1, sizeof(*fir)); + if (!fir) { + fprintf(stderr, "No memory creating FIR filter!\n"); + return NULL; + } + + /* transition bandwidth */ + M = ceil(1.0 / (transition_bandwidth / samplerate)); + if ((M & 1)) + M++; + +// printf("cutoff=%.4f\n", cutoff / samplerate); +// printf("tb=%.4f\n", transition_bandwidth / samplerate); + fir->ntaps = M + 1; + fir->delay = M / 2; + + /* alloc taps */ + fir->taps = calloc(fir->ntaps, sizeof(*fir->taps)); + if (!fir->taps) { + fprintf(stderr, "No memory creating FIR filter!\n"); + fir_exit(fir); + return NULL; + } + + /* alloc ring buffer */ + fir->buffer = calloc(fir->ntaps, sizeof(*fir->buffer)); + if (!fir->buffer) { + fprintf(stderr, "No memory creating FIR filter!\n"); + fir_exit(fir); + return NULL; + } + + + return fir; +} + +fir_filter_t *fir_lowpass_init(double samplerate, double cutoff, double transition_bandwidth) +{ + /* calculate kernel */ + fir_filter_t *fir = fir_init(samplerate, transition_bandwidth); + if (!fir) + return NULL; + kernel(fir->taps, fir->ntaps - 1, cutoff / samplerate, 0); + return fir; +} + +fir_filter_t *fir_highpass_init(double samplerate, double cutoff, double transition_bandwidth) +{ + fir_filter_t *fir = fir_init(samplerate, transition_bandwidth); + if (!fir) + return NULL; + kernel(fir->taps, fir->ntaps - 1, cutoff / samplerate, 1); + return fir; +} + +fir_filter_t *fir_allpass_init(double samplerate, double transition_bandwidth) +{ + fir_filter_t *fir = fir_init(samplerate, transition_bandwidth); + if (!fir) + return NULL; + fir->taps[(fir->ntaps - 1) / 2] = 1.0; + return fir; +} + +fir_filter_t *fir_twopass_init(double samplerate, double cutoff_low, double cutoff_high, double transition_bandwidth) +{ + int i; + double sum; + fir_filter_t *fir = fir_init(samplerate, transition_bandwidth); + if (!fir) + return NULL; + double lp_taps[fir->ntaps], hp_taps[fir->ntaps]; + kernel(lp_taps, fir->ntaps - 1, cutoff_low / samplerate, 0); + kernel(hp_taps, fir->ntaps - 1, cutoff_high / samplerate, 1); + sum = 0; + printf("#warning does not work as expected\n"); + abort(); + for (i = 0; i < fir->ntaps; i++) { + fir->taps[i] = lp_taps[i] + hp_taps[i]; + sum += fir->taps[i]; + } + /* hp will die */ +// for (i = 0; i < fir->ntaps; i++) + // fir->taps[i] /= sum; + return fir; +} + +void fir_exit(fir_filter_t *fir) +{ + if (!fir) + return; + free(fir->taps); + free(fir->buffer); + free(fir); +} + +void fir_process(fir_filter_t *fir, sample_t *samples, int num) +{ + int i, j; + double y; + + for (i = 0; i < num; i++) { + /* put sample in ring buffer */ + fir->buffer[fir->buffer_pos] = samples[i]; + if (++fir->buffer_pos == fir->ntaps) + fir->buffer_pos = 0; + + /* convolve samples */ + y = 0; + for (j = 0; j < fir->ntaps; j++) { + /* convolve sample from ring buffer, starting with oldest */ + y += fir->buffer[fir->buffer_pos] * fir->taps[j]; + if (++fir->buffer_pos == fir->ntaps) + fir->buffer_pos = 0; + } + samples[i] = y; + } +} + +int fir_get_delay(fir_filter_t *fir) +{ + return fir->delay; +} + diff --git a/src/libfilter/fir_filter.h b/src/libfilter/fir_filter.h new file mode 100644 index 0000000..7d94091 --- /dev/null +++ b/src/libfilter/fir_filter.h @@ -0,0 +1,21 @@ +#ifndef _FIR_FILTER_H +#define _FIR_FILTER_H + +typedef struct fir_filter { + int ntaps; + int delay; + double *taps; + double *buffer; + int buffer_pos; +} fir_filter_t; + +fir_filter_t *fir_lowpass_init(double samplerate, double cutoff, double transition_bandwidth); +fir_filter_t *fir_highpass_init(double samplerate, double cutoff, double transition_bandwidth); +fir_filter_t *fir_allpass_init(double samplerate, double transition_bandwidth); +fir_filter_t *fir_twopass_init(double samplerate, double cutoff_low, double cutoff_high, double transition_bandwidth); +void fir_exit(fir_filter_t *fir); +void fir_process(fir_filter_t *fir, sample_t *samples, int num); +int fir_get_delay(fir_filter_t *fir); + +#endif /* _FIR_FILTER_H */ + diff --git a/src/libfilter/iir_filter.c b/src/libfilter/iir_filter.c new file mode 100644 index 0000000..5872d61 --- /dev/null +++ b/src/libfilter/iir_filter.c @@ -0,0 +1,204 @@ +/* cut-off filter (biquad) based on Nigel Redmon (www.earlevel.com) + * + * (C) 2016 by Andreas Eversberg + * All Rights Reserved + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include +#include +#include +#include +#include "../libsample/sample.h" +#include "iir_filter.h" + +//#define DEBUG_NAN + +#define PI M_PI + +void iir_lowpass_init(iir_filter_t *filter, double frequency, int samplerate, int iterations) +{ + double Fc, Q, K, norm; + + if (iterations > 64) { + fprintf(stderr, "%s failed: too many iterations, please fix!\n", __func__); + abort(); + } + + memset(filter, 0, sizeof(*filter)); + filter->iter = iterations; + Q = pow(sqrt(0.5), 1.0 / (double)iterations); /* 0.7071 @ 1 iteration */ + Fc = frequency / (double)samplerate; + K = tan(PI * Fc); + norm = 1 / (1 + K / Q + K * K); + filter->a0 = K * K * norm; + filter->a1 = 2 * filter->a0; + filter->a2 = filter->a0; + filter->b1 = 2 * (K * K - 1) * norm; + filter->b2 = (1 - K / Q + K * K) * norm; +#ifdef DEBUG_NAN + printf("%p\n", filter); +#endif +} + +void iir_highpass_init(iir_filter_t *filter, double frequency, int samplerate, int iterations) +{ + double Fc, Q, K, norm; + + memset(filter, 0, sizeof(*filter)); + filter->iter = iterations; + Q = pow(sqrt(0.5), 1.0 / (double)iterations); /* 0.7071 @ 1 iteration */ + Fc = frequency / (double)samplerate; + K = tan(PI * Fc); + norm = 1 / (1 + K / Q + K * K); + filter->a0 = 1 * norm; + filter->a1 = -2 * filter->a0; + filter->a2 = filter->a0; + filter->b1 = 2 * (K * K - 1) * norm; + filter->b2 = (1 - K / Q + K * K) * norm; +} + +void iir_bandpass_init(iir_filter_t *filter, double frequency, int samplerate, int iterations) +{ + double Fc, Q, K, norm; + + memset(filter, 0, sizeof(*filter)); + filter->iter = iterations; + Q = pow(sqrt(0.5), 1.0 / (double)iterations); /* 0.7071 @ 1 iteration */ + Fc = frequency / (double)samplerate; + K = tan(PI * Fc); + norm = 1 / (1 + K / Q + K * K); + filter->a0 = K / Q * norm; + filter->a1 = 0; + filter->a2 = -filter->a0; + filter->b1 = 2 * (K * K - 1) * norm; + filter->b2 = (1 - K / Q + K * K) * norm; +} + +void iir_notch_init(iir_filter_t *filter, double frequency, int samplerate, int iterations) +{ + double Fc, Q, K, norm; + + memset(filter, 0, sizeof(*filter)); + filter->iter = iterations; + Q = pow(sqrt(0.5), 1.0 / (double)iterations); /* 0.7071 @ 1 iteration */ + Fc = frequency / (double)samplerate; + K = tan(PI * Fc); + norm = 1 / (1 + K / Q + K * K); + filter->a0 = (1 + K * K) * norm; + filter->a1 = 2 * (K * K - 1) * norm; + filter->a2 = filter->a0; + filter->b1 = filter->a1; + filter->b2 = (1 - K / Q + K * K) * norm; +} + +void iir_process(iir_filter_t *filter, sample_t *samples, int length) +{ + double a0, a1, a2, b1, b2; + double *z1, *z2; + double in, out; + int iterations = filter->iter; + int i, j; + + /* get states */ + a0 = filter->a0; + a1 = filter->a1; + a2 = filter->a2; + b1 = filter->b1; + b2 = filter->b2; + + /* these are state pointers, so no need to write back */ + z1 = filter->z1; + z2 = filter->z2; + + /* process filter */ + for (i = 0; i < length; i++) { + /* add a small value, otherwise this loop will perform really bad on my 'nuedel' machine!!! */ + in = *samples + 0.000000001; + for (j = 0; j < iterations; j++) { + out = in * a0 + z1[j]; + z1[j] = in * a1 + z2[j] - b1 * out; + z2[j] = in * a2 - b2 * out; + in = out; + } + *samples++ = in; + } +} + +#ifdef DEBUG_NAN +#pragma GCC push_options +//#pragma GCC optimize ("O0") +#endif + +void iir_process_baseband(iir_filter_t *filter, float *baseband, int length) +{ + double a0, a1, a2, b1, b2; + double *z1, *z2; + double in, out; + int iterations = filter->iter; + int i, j; + + /* get states */ + a0 = filter->a0; + a1 = filter->a1; + a2 = filter->a2; + b1 = filter->b1; + b2 = filter->b2; + + /* these are state pointers, so no need to write back */ + z1 = filter->z1; + z2 = filter->z2; + + /* process filter */ + for (i = 0; i < length; i++) { + /* add a small value, otherwise this loop will perform really bad on my 'nuedel' machine!!! */ + in = *baseband + 0.000000001; + for (j = 0; j < iterations; j++) { + out = in * a0 + z1[j]; +#ifdef DEBUG_NAN + if (!(out > -100 && out < 100)) { + printf("%p\n", filter); + printf("1. i=%d j=%d z=%.5f in=%.5f a0=%.5f out=%.5f\n", i, j, z1[j], in, a0, out); + abort(); + } +#endif + z1[j] = in * a1 + z2[j] - b1 * out; +#ifdef DEBUG_NAN + if (!(z1[j] > -100 && z1[j] < 100)) { + printf("%p\n", filter); + printf("2. i=%d j=%d z1=%.5f z2=%.5f in=%.5f a1=%.5f out=%.5f b1=%.5f\n", i, j, z1[j], z2[j], in, a1, out, b1); + abort(); + } +#endif + z2[j] = in * a2 - b2 * out; +#ifdef DEBUG_NAN + if (!(z2[j] > -100 && z2[j] < 100)) { + printf("%p\n", filter); + printf("%.5f\n", (in * a2) - (b2 * out)); + printf("3. i=%d j=%d z2=%.5f in=%.5f a2=%.5f b2=%.5f out=%.5f\n", i, j, z2[j], in, a2, b2, out); + abort(); + } +#endif + in = out; + } + *baseband = in; + baseband += 2; + } +} + +#ifdef DEBUG_NAN +#pragma GCC pop_options +#endif diff --git a/src/libfilter/iir_filter.h b/src/libfilter/iir_filter.h new file mode 100644 index 0000000..a5956c8 --- /dev/null +++ b/src/libfilter/iir_filter.h @@ -0,0 +1,17 @@ +#ifndef _FILTER_H +#define _FILTER_H + +typedef struct iir_filter { + int iter; + double a0, a1, a2, b1, b2; + double z1[64], z2[64]; +} iir_filter_t; + +void iir_lowpass_init(iir_filter_t *filter, double frequency, int samplerate, int iterations); +void iir_highpass_init(iir_filter_t *filter, double frequency, int samplerate, int iterations); +void iir_bandpass_init(iir_filter_t *filter, double frequency, int samplerate, int iterations); +void iir_notch_init(iir_filter_t *filter, double frequency, int samplerate, int iterations); +void iir_process(iir_filter_t *filter, sample_t *samples, int length); +void iir_process_baseband(iir_filter_t *filter, float *baseband, int length); + +#endif /* _FILTER_H */ diff --git a/src/liboptions/Makefile.am b/src/liboptions/Makefile.am new file mode 100644 index 0000000..0d10fa7 --- /dev/null +++ b/src/liboptions/Makefile.am @@ -0,0 +1,7 @@ +AM_CPPFLAGS = -Wall -Wextra -g $(all_includes) + +noinst_LIBRARIES = liboptions.a + +liboptions_a_SOURCES = \ + options.c + diff --git a/src/liboptions/options.c b/src/liboptions/options.c new file mode 100644 index 0000000..d49e698 --- /dev/null +++ b/src/liboptions/options.c @@ -0,0 +1,339 @@ +/* command line options and config file parsing + * + * (C) 2018 by Andreas Eversberg + * All Rights Reserved + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include +#include +#include +#include +#include "options.h" +#include "../libdebug/debug.h" + +typedef struct option { + struct option *next; + int short_option; + const char *long_option; + int parameter_count; +} option_t; + +static option_t *option_head = NULL; +static option_t **option_tailp = &option_head; +static int first_option = 1; + +static struct options_strdup_entry { + struct options_strdup_entry *next; + char s[1]; +} *options_strdup_list = NULL; + +char *options_strdup(const char *s) +{ + struct options_strdup_entry *o; + + o = malloc(sizeof(struct options_strdup_entry) + strlen(s)); + if (!o) { + PDEBUG(DOPTIONS, DEBUG_ERROR, "No mem!\n"); + abort(); + } + o->next = options_strdup_list; + options_strdup_list = o; + strcpy(o->s, s); + + return o->s; +} + +void option_add(int short_option, const char *long_option, int parameter_count) +{ + option_t *option; + + /* check if option already exists or is not allowed */ + for (option = option_head; option; option = option->next) { + if (!strcmp(option->long_option, "config")) { + PDEBUG(DOPTIONS, DEBUG_ERROR, "Option '%s' is not allowed to add, please fix!\n", option->long_option); + abort(); + } + if (option->short_option == short_option + || !strcmp(option->long_option, long_option)) { + PDEBUG(DOPTIONS, DEBUG_ERROR, "Option '%s' added twice, please fix!\n", option->long_option); + abort(); + } + } + + option = calloc(1, sizeof(*option)); + if (!option) { + PDEBUG(DOPTIONS, DEBUG_ERROR, "No mem!\n"); + abort(); + } + + option->short_option = short_option; + option->long_option = long_option; + option->parameter_count = parameter_count; + *option_tailp = option; + option_tailp = &(option->next); +} + +int options_config_file(int argc, char *argv[], const char *config_file, int (*handle_options)(int short_option, int argi, char *argv[])) +{ + static const char *home; + char config[256]; + FILE *fp; + char buffer[256], opt[256], param[256], *p, *args[16]; + char params[1024]; + int line; + int rc = 1; + int i, j, quote; + option_t *option; + + /* select for alternative config file */ + if (argc > 2 && !strcmp(argv[1], "--config")) + config_file = argv[2]; + + /* add home directory */ + if (config_file[0] == '~' && config_file[1] == '/') { + home = getenv("HOME"); + if (home == NULL) + return 1; + sprintf(config, "%s/%s", home, config_file + 2); + } else + strcpy(config, config_file); + + /* open config file */ + fp = fopen(config, "r"); + if (!fp) { + PDEBUG(DOPTIONS, DEBUG_INFO, "Config file '%s' seems not to exist, using command line options only.\n", config); + return 1; + } + + /* parse config file */ + line = 0; + while((fgets(buffer, sizeof(buffer), fp))) { + line++; + /* prevent buffer overflow */ + buffer[sizeof(buffer) - 1] = '\0'; + /* cut away new-line and white spaces */ + while (buffer[0] && buffer[strlen(buffer) - 1] <= ' ') + buffer[strlen(buffer) - 1] = '\0'; + p = buffer; + /* remove white spaces in front of first keyword */ + while (*p > '\0' && *p <= ' ') + p++; + /* ignore '#' lines */ + if (*p == '#') + continue; + /* get option form line */ + i = 0; + while (*p > ' ') + opt[i++] = *p++; + opt[i] = '\0'; + if (opt[0] == '\0') + continue; + /* skip white spaces behind option */ + while (*p > '\0' && *p <= ' ') + p++; + /* get param from line */ + params[0] = '\0'; + i = 0; + while (*p) { + /* copy parameter */ + j = 0; + quote = 0; + while (*p) { + /* escape allows all following characters */ + if (*p == '\\') { + p++; + if (*p) + param[j++] = *p++; + continue; + } + /* no quote, check for them or break on white space */ + if (quote == 0) { + if (*p == '\'') { + quote = 1; + p++; + continue; + } + if (*p == '\"') { + quote = 2; + p++; + continue; + } + if (*p <= ' ') + break; + } + /* single quote, check for unquote */ + if (quote == 1 && *p == '\'') { + quote = 0; + p++; + continue; + } + /* double quote, check for unquote */ + if (quote == 2 && *p == '\"') { + quote = 0; + p++; + continue; + } + /* copy character */ + param[j++] = *p++; + } + param[j] = '\0'; + args[i] = options_strdup(param); + sprintf(strchr(params, '\0'), " '%s'", param); + /* skip white spaces behind option */ + while (*p > '\0' && *p <= ' ') + p++; + i++; + } + /* search option */ + for (option = option_head; option; option = option->next) { + if (opt[0] == option->short_option && opt[1] == '\0') { + PDEBUG(DOPTIONS, DEBUG_INFO, "Config file option '%s' ('%s'), parameter%s\n", opt, option->long_option, params); + break; + } + if (!strcmp(opt, option->long_option)) { + PDEBUG(DOPTIONS, DEBUG_INFO, "Config file option '%s', parameter%s\n", opt, params); + break; + } + } + if (!option) { + PDEBUG(DOPTIONS, DEBUG_ERROR, "Given option '%s' in config file '%s' at line %d is not a valid option, use '-h' for help!\n", opt, config_file, line); + rc = -EINVAL; + goto done; + } + if (option->parameter_count != i) { + PDEBUG(DOPTIONS, DEBUG_ERROR, "Given option '%s' in config file '%s' at line %d requires %d parameter(s), use '-h' for help!\n", opt, config_file, line, option->parameter_count); + return -EINVAL; + } + rc = handle_options(option->short_option, 0, args); + if (rc <= 0) + goto done; + first_option = 0; + } + +done: + /* close config file */ + fclose(fp); + + return rc; +} + +int options_command_line(int argc, char *argv[], int (*handle_options)(int short_option, int argi, char *argv[])) +{ + option_t *option; + char params[1024]; + int argi, i; + int rc; + + for (argi = 1; argi < argc; argi++) { + /* --config */ + if (!strcmp(argv[argi], "--config")) { + if (argi > 1) { + PDEBUG(DOPTIONS, DEBUG_ERROR, "Given command line option '%s' must be the first option specified, use '-h' for help!\n", argv[argi]); + return -EINVAL; + } + if (argc <= 2) { + PDEBUG(DOPTIONS, DEBUG_ERROR, "Given command line option '%s' requires 1 parameter, use '-h' for help!\n", argv[argi]); + return -EINVAL; + } + argi += 1; + continue; + } + if (argv[argi][0] == '-') { + if (argv[argi][1] != '-') { + if (strlen(argv[argi]) != 2) { + PDEBUG(DOPTIONS, DEBUG_ERROR, "Given command line option '%s' exceeds one character, use '-h' for help!\n", argv[argi]); + return -EINVAL; + } + /* -x */ + for (option = option_head; option; option = option->next) { + if (argv[argi][1] == option->short_option) { + if (option->parameter_count && argi + option->parameter_count < argc) { + params[0] = '\0'; + for (i = 0; i < option->parameter_count; i++) + sprintf(strchr(params, '\0'), " '%s'", argv[argi + 1 + i]); + PDEBUG(DOPTIONS, DEBUG_INFO, "Command line option '%s' ('--%s'), parameter%s\n", argv[argi], option->long_option, params); + } else + PDEBUG(DOPTIONS, DEBUG_INFO, "Command line option '%s' ('--%s')\n", argv[argi], option->long_option); + break; + } + } + } else { + /* --xxxxxx */ + for (option = option_head; option; option = option->next) { + if (!strcmp(argv[argi] + 2, option->long_option)) { + if (option->parameter_count && argi + option->parameter_count < argc) { + params[0] = '\0'; + for (i = 0; i < option->parameter_count; i++) + sprintf(strchr(params, '\0'), " '%s'", argv[argi + 1 + i]); + PDEBUG(DOPTIONS, DEBUG_INFO, "Command line option '%s', parameter%s\n", argv[argi], params); + } else + PDEBUG(DOPTIONS, DEBUG_INFO, "Command line option '%s'\n", argv[argi]); + break; + } + } + } + if (!option) { + PDEBUG(DOPTIONS, DEBUG_ERROR, "Given command line option '%s' is not a valid option, use '-h' for help!\n", argv[argi]); + return -EINVAL; + } + if (argi + option->parameter_count >= argc) { + PDEBUG(DOPTIONS, DEBUG_ERROR, "Given command line option '%s' requires %d parameter(s), use '-h' for help!\n", argv[argi], option->parameter_count); + return -EINVAL; + } + rc = handle_options(option->short_option, argi + 1, argv); + if (rc <= 0) + return rc; + first_option = 0; + argi += option->parameter_count; + } else + break; + } + + /* no more options, so we check if there is an option after a non-option parameter */ + for (i = argi; i < argc; i++) { + if (argv[i][0] == '-') { + PDEBUG(DOPTIONS, DEBUG_ERROR, "Given command line option '%s' behind command line parameter '%s' not allowed! Please put all command line options before command line parameter(s).\n", argv[i], argv[argi]); + return -EINVAL; + } + } + + return argi; +} + +int option_is_first(void) +{ + return first_option; +} + +void options_free(void) +{ + while (options_strdup_list) { + struct options_strdup_entry *o; + o = options_strdup_list; + options_strdup_list = o->next; + free(o); + } + + while (option_head) { + option_t *o; + o = option_head; + option_head = o->next; + free(o); + } + option_tailp = &option_head; +} + diff --git a/src/liboptions/options.h b/src/liboptions/options.h new file mode 100644 index 0000000..5f494e0 --- /dev/null +++ b/src/liboptions/options.h @@ -0,0 +1,8 @@ + +char *options_strdup(const char *s); +void option_add(int short_option, const char *long_option, int parameter_count); +int options_config_file(int argc, char *argv[], const char *config_file, int (*handle_options)(int short_option, int argi, char *argv[])); +int options_command_line(int argc, char *argv[], int (*handle_options)(int short_option, int argi, char *argv[])); +int option_is_first(void); +void options_free(void); + diff --git a/src/libsample/Makefile.am b/src/libsample/Makefile.am new file mode 100644 index 0000000..5ae865c --- /dev/null +++ b/src/libsample/Makefile.am @@ -0,0 +1,6 @@ +AM_CPPFLAGS = -Wall -Wextra -g $(all_includes) + +noinst_LIBRARIES = libsample.a + +libsample_a_SOURCES = \ + sample.c diff --git a/src/libsample/sample.c b/src/libsample/sample.c new file mode 100644 index 0000000..a084b66 --- /dev/null +++ b/src/libsample/sample.c @@ -0,0 +1,64 @@ +/* Sample definition + * + * (C) 2017 by Andreas Eversberg + * All Rights Reserved + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include "sample.h" + +/* + * A regular voice conversation takes place at this factor below the full range + * of 16 bits signed value: + */ +static double int_16_speech_level = SPEECH_LEVEL * 0.7079; /* 16 dBm below dBm0, which is about 3dBm below full 16 bit range */ + +/* A sample_t is a value that has virtually infinite precision but will also + * support high numbers. 'double' or 'float' types are sufficient. + * + * When using sample_t inside signal processing of each base station, the + * level of +- 1 is relative to the normal speech evenlope. + * + * When converting sample_t to int16_t, the level of +- 1 is reduced by factor. + * This way the speech may be louder before clipping happens. + * + * When using sample_t to modulate (SDR or sound card), the level is changed, + * so it represents the frequency deviation in Hz. The deviation of speech + * envelope is network dependent. + */ + +void samples_to_int16(int16_t *spl, sample_t *samples, int length) +{ + int32_t value; + + while (length--) { + value = *samples++ * int_16_speech_level * 32768.0; + if (value > 32767.0) + *spl++ = 32767; + else if (value < -32767.0) + *spl++ = -32767; + else + *spl++ = (uint16_t)value; + } +} + +void int16_to_samples(sample_t *samples, int16_t *spl, int length) +{ + while (length--) { + *samples++ = (double)(*spl++) / 32767.0 / int_16_speech_level; + } +} + diff --git a/src/libsample/sample.h b/src/libsample/sample.h new file mode 100644 index 0000000..01a17df --- /dev/null +++ b/src/libsample/sample.h @@ -0,0 +1,8 @@ + +typedef double sample_t; + +#define SPEECH_LEVEL 0.1585 + +void samples_to_int16(int16_t *spl, sample_t *samples, int length); +void int16_to_samples(sample_t *samples, int16_t *spl, int length); + diff --git a/src/libsound/Makefile.am b/src/libsound/Makefile.am new file mode 100644 index 0000000..afbbb0a --- /dev/null +++ b/src/libsound/Makefile.am @@ -0,0 +1,8 @@ +AM_CPPFLAGS = -Wall -Wextra -g $(all_includes) + +noinst_LIBRARIES = libsound.a + +libsound_a_SOURCES = \ + sound_alsa.c + +AM_CPPFLAGS += -DHAVE_ALSA diff --git a/src/libsound/sound.h b/src/libsound/sound.h new file mode 100644 index 0000000..173dfd3 --- /dev/null +++ b/src/libsound/sound.h @@ -0,0 +1,10 @@ + +enum paging_signal; + +void *sound_open(const char *audiodev, double *tx_frequency, double *rx_frequency, int *am, int channels, double paging_frequency, int samplerate, int buffer_size, double interval, double max_deviation, double max_modulation, double modulation_index); +int sound_start(void *inst); +void sound_close(void *inst); +int sound_write(void *inst, sample_t **samples, uint8_t **power, int num, enum paging_signal *paging_signal, int *on, int channels); +int sound_read(void *inst, sample_t **samples, int num, int channels, double *rf_level_db); +int sound_get_tosend(void *inst, int buffer_size); + diff --git a/src/libsound/sound_alsa.c b/src/libsound/sound_alsa.c new file mode 100644 index 0000000..20a4a31 --- /dev/null +++ b/src/libsound/sound_alsa.c @@ -0,0 +1,535 @@ +/* Sound device access + * + * (C) 2016 by Andreas Eversberg + * All Rights Reserved + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include +#include +#include +#include "../libsample/sample.h" +#include "../libdebug/debug.h" +#ifdef HAVE_MOBILE +#include "../libmobile/sender.h" +#else +#include "sound.h" +#endif + +typedef struct sound { + snd_pcm_t *phandle, *chandle; + int pchannels, cchannels; + int channels; /* required number of channels */ + int samplerate; /* required sample rate */ + char *audiodev; /* required device */ + double spl_deviation; /* how much deviation is one sample step */ +#ifdef HAVE_MOBILE + double paging_phaseshift; /* phase to shift every sample */ + double paging_phase; /* current phase */ + double rx_frequency[2]; /* rx frequency of radio connected to channel */ + dispmeasparam_t *dmp[2]; +#endif +} sound_t; + +static int set_hw_params(snd_pcm_t *handle, int samplerate, int *channels) +{ + snd_pcm_hw_params_t *hw_params = NULL; + int rc; + unsigned int rrate; + + rc = snd_pcm_hw_params_malloc(&hw_params); + if (rc < 0) { + PDEBUG(DSOUND, DEBUG_ERROR, "Failed to allocate hw_params! (%s)\n", snd_strerror(rc)); + goto error; + } + + rc = snd_pcm_hw_params_any(handle, hw_params); + if (rc < 0) { + PDEBUG(DSOUND, DEBUG_ERROR, "cannot initialize hardware parameter structure (%s)\n", snd_strerror(rc)); + goto error; + } + + rc = snd_pcm_hw_params_set_rate_resample(handle, hw_params, 0); + if (rc < 0) { + PDEBUG(DSOUND, DEBUG_ERROR, "cannot set real hardware rate (%s)\n", snd_strerror(rc)); + goto error; + } + + rc = snd_pcm_hw_params_set_access (handle, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED); + if (rc < 0) { + PDEBUG(DSOUND, DEBUG_ERROR, "cannot set access to interleaved (%s)\n", snd_strerror(rc)); + goto error; + } + + rc = snd_pcm_hw_params_set_format(handle, hw_params, SND_PCM_FORMAT_S16); + if (rc < 0) { + PDEBUG(DSOUND, DEBUG_ERROR, "cannot set sample format (%s)\n", snd_strerror(rc)); + goto error; + } + + rrate = samplerate; + rc = snd_pcm_hw_params_set_rate_near(handle, hw_params, &rrate, 0); + if (rc < 0) { + PDEBUG(DSOUND, DEBUG_ERROR, "cannot set sample rate (%s)\n", snd_strerror(rc)); + goto error; + } + if ((int)rrate != samplerate) { + PDEBUG(DSOUND, DEBUG_ERROR, "Rate doesn't match (requested %dHz, get %dHz)\n", samplerate, rrate); + rc = -EIO; + goto error; + } + + *channels = 1; + rc = snd_pcm_hw_params_set_channels(handle, hw_params, *channels); + if (rc < 0) { + *channels = 2; + rc = snd_pcm_hw_params_set_channels(handle, hw_params, *channels); + if (rc < 0) { + PDEBUG(DSOUND, DEBUG_ERROR, "cannot set channel count to 1 nor 2 (%s)\n", snd_strerror(rc)); + goto error; + } + } + + rc = snd_pcm_hw_params(handle, hw_params); + if (rc < 0) { + PDEBUG(DSOUND, DEBUG_ERROR, "cannot set parameters (%s)\n", snd_strerror(rc)); + goto error; + } + + snd_pcm_hw_params_free(hw_params); + + return 0; + +error: + if (hw_params) { + snd_pcm_hw_params_free(hw_params); + } + + return rc; +} + +static int dev_open(sound_t *sound) +{ + int rc, rc_rec, rc_play; + + rc_play = snd_pcm_open(&sound->phandle, sound->audiodev, SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK); + rc_rec = snd_pcm_open(&sound->chandle, sound->audiodev, SND_PCM_STREAM_CAPTURE, SND_PCM_NONBLOCK); + if (rc_play < 0 && rc_rec < 0) { + PDEBUG(DSOUND, DEBUG_ERROR, "Failed to open '%s'! (%s)\n", sound->audiodev, snd_strerror(rc_play)); + PDEBUG(DSOUND, DEBUG_ERROR, "Run 'aplay -l' to get a list of available cards and devices.\n"); + PDEBUG(DSOUND, DEBUG_ERROR, "Then use 'hw::' for audio device.\n"); + return rc_play; + } + if (rc_play < 0) { + PDEBUG(DSOUND, DEBUG_ERROR, "Failed to open '%s' for playback! (%s) Please select a device that supports both direction audio.\n", sound->audiodev, snd_strerror(rc_play)); + return rc_play; + } + if (rc_rec < 0) { + PDEBUG(DSOUND, DEBUG_ERROR, "Failed to open '%s' for capture! (%s) Please select a device that supports both direction audio.\n", sound->audiodev, snd_strerror(rc_rec)); + return rc_rec; + } + + rc = set_hw_params(sound->phandle, sound->samplerate, &sound->pchannels); + if (rc < 0) { + PDEBUG(DSOUND, DEBUG_ERROR, "Failed to set playback hw params\n"); + return rc; + } + if (sound->pchannels < sound->channels) { + PDEBUG(DSOUND, DEBUG_ERROR, "Sound card only supports %d channel for playback.\n", sound->pchannels); + return rc; + } + PDEBUG(DSOUND, DEBUG_DEBUG, "Playback with %d channels.\n", sound->pchannels); + + rc = set_hw_params(sound->chandle, sound->samplerate, &sound->cchannels); + if (rc < 0) { + PDEBUG(DSOUND, DEBUG_ERROR, "Failed to set capture hw params\n"); + return rc; + } + if (sound->cchannels < sound->channels) { + PDEBUG(DSOUND, DEBUG_ERROR, "Sound card only supports %d channel for capture.\n", sound->cchannels); + return -EIO; + } + PDEBUG(DSOUND, DEBUG_DEBUG, "Capture with %d channels.\n", sound->cchannels); + + rc = snd_pcm_prepare(sound->phandle); + if (rc < 0) { + PDEBUG(DSOUND, DEBUG_ERROR, "cannot prepare audio interface for use (%s)\n", snd_strerror(rc)); + return rc; + } + + rc = snd_pcm_prepare(sound->chandle); + if (rc < 0) { + PDEBUG(DSOUND, DEBUG_ERROR, "cannot prepare audio interface for use (%s)\n", snd_strerror(rc)); + return rc; + } + + return 0; +} + +static void dev_close(sound_t *sound) +{ + if (sound->phandle != NULL) + snd_pcm_close(sound->phandle); + if (sound->chandle != NULL) + snd_pcm_close(sound->chandle); +} + +void *sound_open(const char *audiodev, double __attribute__((unused)) *tx_frequency, double __attribute__((unused)) *rx_frequency, int __attribute__((unused)) *am, int channels, double __attribute__((unused)) paging_frequency, int samplerate, int __attribute((unused)) buffer_size, double __attribute__((unused)) interval, double max_deviation, double __attribute__((unused)) max_modulation, double __attribute__((unused)) modulation_index) +{ + sound_t *sound; + int rc; + + if (channels < 1 || channels > 2) { + PDEBUG(DSOUND, DEBUG_ERROR, "Cannot use more than two channels with the same sound card!\n"); + return NULL; + } + + sound = calloc(1, sizeof(sound_t)); + if (!sound) { + PDEBUG(DSOUND, DEBUG_ERROR, "Failed to alloc memory!\n"); + return NULL; + } + + sound->audiodev = strdup(audiodev); // is feed when closed + sound->channels = channels; + sound->samplerate = samplerate; + sound->spl_deviation = max_deviation / 32767.0; +#ifdef HAVE_MOBILE + sound->paging_phaseshift = 1.0 / ((double)samplerate / 1000.0); +#endif + + rc = dev_open(sound); + if (rc < 0) + goto error; + +#ifdef HAVE_MOBILE + if (rx_frequency) { + sender_t *sender; + int i; + for (i = 0; i < channels; i++) { + sound->rx_frequency[i] = rx_frequency[i]; + sender = get_sender_by_empfangsfrequenz(sound->rx_frequency[i]); + if (!sender) + continue; + sound->dmp[i] = display_measurements_add(&sender->dispmeas, "RX Level", "%.1f dB", DISPLAY_MEAS_PEAK, DISPLAY_MEAS_LEFT, -96.0, 0.0, -INFINITY); + } + } +#endif + + return sound; + +error: + sound_close(sound); + return NULL; +} + +/* start streaming */ +int sound_start(void *inst) +{ + sound_t *sound = (sound_t *)inst; + int16_t buff[2]; + + /* trigger capturing */ + snd_pcm_readi(sound->chandle, buff, 1); + + return 0; +} + +void sound_close(void *inst) +{ + sound_t *sound = (sound_t *)inst; + + dev_close(sound); + free(sound->audiodev); + free(sound); +} + +#ifdef HAVE_MOBILE +static void gen_paging_tone(sound_t *sound, int16_t *samples, int length, enum paging_signal paging_signal, int on) +{ + double phaseshift, phase; + int i; + + switch (paging_signal) { + case PAGING_SIGNAL_NOTONE: + /* no tone if paging signal is on */ + on = !on; + /* FALLTHRU */ + case PAGING_SIGNAL_TONE: + /* tone if paging signal is on */ + if (on) { + phaseshift = sound->paging_phaseshift; + phase = sound->paging_phase; + for (i = 0; i < length; i++) { + if (phase < 0.5) + *samples++ = 30000; + else + *samples++ = -30000; + phase += phaseshift; + if (phase >= 1.0) + phase -= 1.0; + } + sound->paging_phase = phase; + } else + memset(samples, 0, length << 1); + break; + case PAGING_SIGNAL_NEGATIVE: + /* negative signal if paging signal is on */ + on = !on; + /* FALLTHRU */ + case PAGING_SIGNAL_POSITIVE: + /* positive signal if paging signal is on */ + if (on) + memset(samples, 127, length << 1); + else + memset(samples, 128, length << 1); + break; + case PAGING_SIGNAL_NONE: + break; + } +} +#endif + +int sound_write(void *inst, sample_t **samples, uint8_t __attribute__((unused)) **power, int num, enum paging_signal __attribute__((unused)) *paging_signal, int __attribute__((unused)) *on, int channels) +{ + sound_t *sound = (sound_t *)inst; + double spl_deviation = sound->spl_deviation; + int32_t value; + int16_t buff[num << 1]; + int rc; + int i, ii; + + if (sound->pchannels == 2) { + /* two channels */ +#ifdef HAVE_MOBILE + if (paging_signal && on && paging_signal[0] != PAGING_SIGNAL_NONE) { + int16_t paging[num << 1]; + gen_paging_tone(sound, paging, num, paging_signal[0], on[0]); + for (i = 0, ii = 0; i < num; i++) { + value = samples[0][i] / spl_deviation; + if (value > 32767) + value = 32767; + else if (value < -32767) + value = -32767; + buff[ii++] = value; + buff[ii++] = paging[i]; + } + } else +#endif + if (channels == 2) { + for (i = 0, ii = 0; i < num; i++) { + value = samples[0][i] / spl_deviation; + if (value > 32767) + value = 32767; + else if (value < -32767) + value = -32767; + buff[ii++] = value; + value = samples[1][i] / spl_deviation; + if (value > 32767) + value = 32767; + else if (value < -32767) + value = -32767; + buff[ii++] = value; + } + } else { + for (i = 0, ii = 0; i < num; i++) { + value = samples[0][i] / spl_deviation; + if (value > 32767) + value = 32767; + else if (value < -32767) + value = -32767; + buff[ii++] = value; + buff[ii++] = value; + } + } + } else { + /* one channel */ + for (i = 0, ii = 0; i < num; i++) { + value = samples[0][i] / spl_deviation; + if (value > 32767) + value = 32767; + else if (value < -32767) + value = -32767; + buff[ii++] = value; + } + } + rc = snd_pcm_writei(sound->phandle, buff, num); + + if (rc < 0) { + PDEBUG(DSOUND, DEBUG_ERROR, "failed to write audio to interface (%s)\n", snd_strerror(rc)); + if (rc == -EPIPE) { + dev_close(sound); + rc = dev_open(sound); + if (rc < 0) + return rc; + sound_start(sound); + return -EPIPE; /* indicate what happened */ + } + return rc; + } + + if (rc != num) + PDEBUG(DSOUND, DEBUG_ERROR, "short write to audio interface, written %d bytes, got %d bytes\n", num, rc); + + return rc; +} + +#define KEEP_FRAMES 8 /* minimum frames not to read, due to bug in ALSA */ + +int sound_read(void *inst, sample_t **samples, int num, int channels, double __attribute__((unused)) *rf_level_db) +{ + sound_t *sound = (sound_t *)inst; + double spl_deviation = sound->spl_deviation; + int16_t buff[num << 1]; + int32_t spl; + int32_t max[2], a; + int in, rc; + int i, ii; + + /* make valgrind happy, because snd_pcm_readi() does not seem to initially fill buffer with values */ + memset(buff, 0, sizeof(buff)); + + /* get samples in rx buffer */ + in = snd_pcm_avail(sound->chandle); + /* if not more than KEEP_FRAMES frames available, try next time */ + if (in <= KEEP_FRAMES) + return 0; + /* read some frames less than in buffer, because snd_pcm_readi() seems + * to corrupt last frames */ + in -= KEEP_FRAMES; + if (in > num) + in = num; + + rc = snd_pcm_readi(sound->chandle, buff, in); + + if (rc < 0) { + if (errno == EAGAIN) + return 0; + PDEBUG(DSOUND, DEBUG_ERROR, "failed to read audio from interface (%s)\n", snd_strerror(rc)); + /* recover read */ + if (rc == -EPIPE) { + dev_close(sound); + rc = dev_open(sound); + if (rc < 0) + return rc; + sound_start(sound); + return -EPIPE; /* indicate what happened */ + } + return rc; + } + + if (rc == 0) + return rc; + + if (sound->cchannels == 2) { + if (channels < 2) { + for (i = 0, ii = 0; i < rc; i++) { + spl = buff[ii++]; + spl += buff[ii++]; + a = (spl >= 0) ? spl : -spl; + if (i == 0 || a > max[0]) + max[0] = a; + samples[0][i] = (double)spl * spl_deviation; + } + } else { + for (i = 0, ii = 0; i < rc; i++) { + spl = buff[ii++]; + a = (spl >= 0) ? spl : -spl; + if (i == 0 || a > max[0]) + max[0] = a; + samples[0][i] = (double)spl * spl_deviation; + spl = buff[ii++]; + a = (spl >= 0) ? spl : -spl; + if (i == 0 || a > max[1]) + max[1] = a; + samples[1][i] = (double)spl * spl_deviation; + } + } + } else { + for (i = 0, ii = 0; i < rc; i++) { + spl = buff[ii++]; + a = (spl >= 0) ? spl : -spl; + if (i == 0 || a > max[0]) + max[0] = a; + samples[0][i] = (double)spl * spl_deviation; + } + } + +#ifdef HAVE_MOBILE + sender_t *sender; + for (i = 0; i < channels; i++) { + sender = get_sender_by_empfangsfrequenz(sound->rx_frequency[i]); + if (!sender) + continue; + display_measurements_update(sound->dmp[i], log10((double)max[i] / 32768.0) * 20, 0.0); + if (rf_level_db) + rf_level_db[i] = 0.0; + } +#endif + + return rc; +} + +/* + * get playback buffer space + * + * return number of samples to be sent */ +int sound_get_tosend(void *inst, int buffer_size) +{ + sound_t *sound = (sound_t *)inst; + int rc; + snd_pcm_sframes_t delay; + int tosend; + + rc = snd_pcm_delay(sound->phandle, &delay); + if (rc < 0) { + if (rc == -32) + PDEBUG(DSOUND, DEBUG_ERROR, "Buffer underrun: Please use higher buffer and enable real time scheduling\n"); + else + PDEBUG(DSOUND, DEBUG_ERROR, "failed to get delay from interface (%s)\n", snd_strerror(rc)); + if (rc == -EPIPE) { + dev_close(sound); + rc = dev_open(sound); + if (rc < 0) + return rc; + sound_start(sound); + return -EPIPE; /* indicate what happened */ + } + return rc; + } + + tosend = buffer_size - delay; + return tosend; +} + +int sound_is_stereo_capture(void *inst) +{ + sound_t *sound = (sound_t *)inst; + + if (sound->cchannels == 2) + return 1; + return 0; +} + +int sound_is_stereo_playback(void *inst) +{ + sound_t *sound = (sound_t *)inst; + + if (sound->pchannels == 2) + return 1; + return 0; +} + diff --git a/src/libwave/Makefile.am b/src/libwave/Makefile.am new file mode 100644 index 0000000..c573515 --- /dev/null +++ b/src/libwave/Makefile.am @@ -0,0 +1,6 @@ +AM_CPPFLAGS = -Wall -Wextra -g $(all_includes) + +noinst_LIBRARIES = libwave.a + +libwave_a_SOURCES = \ + wave.c diff --git a/src/libwave/wave.c b/src/libwave/wave.c new file mode 100644 index 0000000..50c2c96 --- /dev/null +++ b/src/libwave/wave.c @@ -0,0 +1,519 @@ +/* wave recording and playback functions + * + * (C) 2016 by Andreas Eversberg + * All Rights Reserved + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include +#include +#include +#include +#include +#include +#include "../libsample/sample.h" +#include "../libdebug/debug.h" +#include "wave.h" + +/* NOTE: No locking required for writing and reading buffer pointers, since 'int' is atomic on >=32 bit machines */ + +static void *record_child(void *arg) +{ + wave_rec_t *rec = (wave_rec_t *)arg; + int to_write, to_end, len; + + while (!rec->finish || rec->buffer_writep != rec->buffer_readp) { + /* how much data is in buffer */ + to_write = (rec->buffer_size + rec->buffer_writep - rec->buffer_readp) % rec->buffer_size; + if (to_write == 0) { + usleep(10000); + continue; + } + /* only write up to the end of buffer */ + to_end = rec->buffer_size - rec->buffer_readp; + if (to_end < to_write) + to_write = to_end; + /* write */ + errno = 0; + len = fwrite(rec->buffer + rec->buffer_readp, 1, to_write, rec->fp); + /* quit on error */ + if (len < 0) { +error: + PDEBUG(DWAVE, DEBUG_ERROR, "Failed to write to recording WAVE file! (errno %d)\n", errno); + rec->finish = 1; + return NULL; + } + /* increment read pointer */ + rec->buffer_readp += len; + if (rec->buffer_readp == rec->buffer_size) + rec->buffer_readp = 0; + /* quit on end of file */ + if (len != to_write) + goto error; + } + + return NULL; +} + +static void *playback_child(void *arg) +{ + wave_play_t *play = (wave_play_t *)arg; + int to_read, to_end, len; + + while(!play->finish) { + /* how much space is in buffer */ + to_read = (play->buffer_size + play->buffer_readp - play->buffer_writep - 1) % play->buffer_size; + if (to_read == 0) { + usleep(10000); + continue; + } + /* only read up to the end of buffer */ + to_end = play->buffer_size - play->buffer_writep; + if (to_end < to_read) + to_read = to_end; + /* read */ + len = fread(play->buffer + play->buffer_writep, 1, to_read, play->fp); + /* quit on error */ + if (len < 0) { + PDEBUG(DWAVE, DEBUG_ERROR, "Failed to read from playback WAVE file! (errno %d)\n", errno); + play->finish = 1; + return NULL; + } + /* increment write pointer */ + play->buffer_writep += len; + if (play->buffer_writep == play->buffer_size) + play->buffer_writep = 0; + /* quit on end of file */ + if (len != to_read) { + play->finish = 1; + return NULL; + } + } + + return NULL; +} + +struct fmt { + uint16_t format; /* 1 = pcm, 2 = adpcm */ + uint16_t channels; /* number of channels */ + uint32_t sample_rate; /* sample rate */ + uint32_t data_rate; /* data rate */ + uint16_t bytes_sample; /* bytes per sample (all channels) */ + uint16_t bits_sample; /* bits per sample (one channel) */ +}; + +int wave_create_record(wave_rec_t *rec, const char *filename, int samplerate, int channels, double max_deviation) +{ + /* RIFFxxxxWAVEfmt xxxx(fmt size)dataxxxx... */ + char dummyheader[4 + 4 + 4 + 4 + 4 + sizeof(struct fmt) + 4 + 4]; + int __attribute__((__unused__)) len; + int rc; + + memset(rec, 0, sizeof(*rec)); + rec->samplerate = samplerate; + rec->channels = channels; + rec->max_deviation = max_deviation; + + rec->fp = fopen(filename, "w"); + if (!rec->fp) { + PDEBUG(DWAVE, DEBUG_ERROR, "Failed to open recording file '%s'! (errno %d)\n", filename, errno); + return -errno; + } + + memset(&dummyheader, 0, sizeof(dummyheader)); + len = fwrite(dummyheader, 1, sizeof(dummyheader), rec->fp); + + rec->buffer_size = samplerate * 2 * channels; + rec->buffer = calloc(rec->buffer_size, 1); + if (!rec->buffer) { + PDEBUG(DWAVE, DEBUG_NOTICE, "No mem!\n"); + rc = ENOMEM; + goto error; + } + + rc = pthread_create(&rec->tid, NULL, record_child, rec); + if (rc < 0) { + PDEBUG(DWAVE, DEBUG_ERROR, "Failed to create thread to record WAVE file! (errno %d)\n", errno); + goto error; + } + + PDEBUG(DWAVE, DEBUG_NOTICE, "*** Writing WAVE file to %s.\n", filename); + + return 0; + +error: + if (rec->buffer) { + free(rec->buffer); + rec->buffer = NULL; + } + if (rec->fp) { + fclose(rec->fp); + rec->fp = NULL; + } + return rc; +} + +int wave_create_playback(wave_play_t *play, const char *filename, int *samplerate_p, int *channels_p, double max_deviation) +{ + uint8_t buffer[256]; + struct fmt fmt; + int32_t size, chunk, len; + int gotfmt = 0, gotdata = 0; + int rc = -EINVAL; + + memset(&fmt, 0, sizeof(fmt)); + memset(play, 0, sizeof(*play)); + play->max_deviation = max_deviation; + + play->fp = fopen(filename, "r"); + if (!play->fp) { + PDEBUG(DWAVE, DEBUG_ERROR, "Failed to open playback file '%s'! (errno %d)\n", filename, errno); + return -errno; + } + + len = fread(buffer, 1, 12, play->fp); + if (len != 12) { + PDEBUG(DWAVE, DEBUG_ERROR, "Failed to read RIFF header!\n"); + rc = -EIO; + goto error; + } + if (!!strncmp((char *)buffer, "RIFF", 4)) { + PDEBUG(DWAVE, DEBUG_ERROR, "Missing RIFF header, seems that this is no WAVE file!\n"); + rc = -EINVAL; + goto error; + } + size = buffer[4] + (buffer[5] << 8) + (buffer[6] << 16) + (buffer[7] << 24); + if (!!strncmp((char *)buffer + 8, "WAVE", 4)) { + PDEBUG(DWAVE, DEBUG_ERROR, "Missing WAVE header, seems that this is no WAVE file!\n"); + rc = -EINVAL; + goto error; + } + size -= 4; + while (size) { + if (size < 8) { + PDEBUG(DWAVE, DEBUG_ERROR, "Short read of WAVE file!\n"); + rc = -EINVAL; + goto error; + } + len = fread(buffer, 1, 8, play->fp); + if (len != 8) { + PDEBUG(DWAVE, DEBUG_ERROR, "Failed to read chunk of WAVE file!\n"); + rc = -EIO; + goto error; + } + chunk = buffer[4] + (buffer[5] << 8) + (buffer[6] << 16) + (buffer[7] << 24); + size -= 8 + chunk; + if (size < 0) { + PDEBUG(DWAVE, DEBUG_ERROR, "WAVE error: Chunk '%c%c%c%c' overflows file size!\n", buffer[4], buffer[5], buffer[6], buffer[7]); + rc = -EIO; + goto error; + } + if (!strncmp((char *)buffer, "fmt ", 4)) { + if (chunk < 16 || chunk > (int)sizeof(buffer)) { + PDEBUG(DWAVE, DEBUG_ERROR, "WAVE error: Short or corrupt 'fmt' chunk!\n"); + rc = -EINVAL; + goto error; + } + len = fread(buffer, 1, chunk, play->fp); + fmt.format = buffer[0] + (buffer[1] << 8); + fmt.channels = buffer[2] + (buffer[3] << 8); + fmt.sample_rate = buffer[4] + (buffer[5] << 8) + (buffer[6] << 16) + (buffer[7] << 24); + fmt.data_rate = buffer[8] + (buffer[9] << 8) + (buffer[10] << 16) + (buffer[11] << 24); + fmt.bytes_sample = buffer[12] + (buffer[13] << 8); + fmt.bits_sample = buffer[14] + (buffer[15] << 8); + gotfmt = 1; + } else + if (!strncmp((char *)buffer, "data", 4)) { + if (!gotfmt) { + PDEBUG(DWAVE, DEBUG_ERROR, "WAVE error: 'data' without 'fmt' chunk!\n"); + rc = -EINVAL; + goto error; + } + gotdata = 1; + break; + } else { + while(chunk > (int)sizeof(buffer)) { + len = fread(buffer, 1, sizeof(buffer), play->fp); + chunk -= sizeof(buffer); + } + if (chunk) + len = fread(buffer, 1, chunk, play->fp); + } + } + + if (!gotfmt || !gotdata) { + PDEBUG(DWAVE, DEBUG_ERROR, "WAVE error: Missing 'data' or 'fmt' chunk!\n"); + rc = -EINVAL; + goto error; + } + + if (fmt.format != 1) { + PDEBUG(DWAVE, DEBUG_ERROR, "WAVE error: We support only PCM files!\n"); + rc = -EINVAL; + goto error; + } + if (*channels_p == 0) + *channels_p = fmt.channels; + if (fmt.channels != *channels_p) { + PDEBUG(DWAVE, DEBUG_ERROR, "WAVE error: We expect %d cannel(s), but wave file only has %d channel(s)\n", *channels_p, fmt.channels); + rc = -EINVAL; + goto error; + } + if (*samplerate_p == 0) + *samplerate_p = fmt.sample_rate; + if ((int)fmt.sample_rate != *samplerate_p) { + PDEBUG(DWAVE, DEBUG_ERROR, "WAVE error: The WAVE file's sample rate (%d) does not match our sample rate (%d)!\n", fmt.sample_rate, *samplerate_p); + rc = -EINVAL; + goto error; + } + if ((int)fmt.data_rate != 2 * *channels_p * *samplerate_p) { + PDEBUG(DWAVE, DEBUG_ERROR, "WAVE error: The WAVE file's data rate is only %d bytes per second, but we expect %d bytes per second (2 bytes per sample * channels * samplerate)!\n", fmt.data_rate, 2 * *channels_p * *samplerate_p); + rc = -EINVAL; + goto error; + } + if (fmt.bytes_sample != 2 * *channels_p) { + PDEBUG(DWAVE, DEBUG_ERROR, "WAVE error: The WAVE file's bytes per sample is only %d, but we expect %d bytes sample (2 bytes per sample * channels)!\n", fmt.bytes_sample, 2 * *channels_p); + rc = -EINVAL; + goto error; + } + if (fmt.bits_sample != 16) { + PDEBUG(DWAVE, DEBUG_ERROR, "WAVE error: We support only 16 bit files!\n"); + rc = -EINVAL; + goto error; + } + + play->channels = *channels_p; + play->left = chunk / 2 / *channels_p; + + play->buffer_size = *samplerate_p * 2 * *channels_p; + play->buffer = calloc(play->buffer_size, 1); + if (!play->buffer) { + PDEBUG(DWAVE, DEBUG_ERROR, "No mem!\n"); + rc = -ENOMEM; + goto error; + } + + rc = pthread_create(&play->tid, NULL, playback_child, play); + if (rc < 0) { + PDEBUG(DWAVE, DEBUG_ERROR, "Failed to create thread to playback WAVE file! (errno %d)\n", errno); + goto error; + } + + PDEBUG(DWAVE, DEBUG_NOTICE, "*** Reading WAVE file from %s.\n", filename); + + return 0; + +error: + if (play->buffer) { + free(play->buffer); + play->buffer = NULL; + } + if (play->fp) { + fclose(play->fp); + play->fp = NULL; + } + return rc; +} + +int wave_write(wave_rec_t *rec, sample_t **samples, int length) +{ + double max_deviation = rec->max_deviation; + int32_t value; + int __attribute__((__unused__)) len; + int i, c; + int to_write; + + /* on error, don't write more */ + if (rec->finish) + return 0; + + /* how much space is in buffer */ + to_write = (rec->buffer_size + rec->buffer_readp - rec->buffer_writep - 1) % rec->buffer_size; + to_write /= 2 * rec->channels; + if (to_write < length) + PDEBUG(DWAVE, DEBUG_NOTICE, "Record WAVE buffer overflow.\n"); + else + to_write = length; + if (to_write == 0) + return 0; + + for (i = 0; i < to_write; i++) { + for (c = 0; c < rec->channels; c++) { + value = samples[c][i] / max_deviation * 32767.0; + if (value > 32767) + value = 32767; + else if (value < -32767) + value = -32767; + rec->buffer[rec->buffer_writep] = value; + rec->buffer_writep = (rec->buffer_writep + 1) % rec->buffer_size; + rec->buffer[rec->buffer_writep] = value >> 8; + rec->buffer_writep = (rec->buffer_writep + 1) % rec->buffer_size; + } + } + rec->written += to_write; + + return to_write; +} + +int wave_read(wave_play_t *play, sample_t **samples, int length) +{ + double max_deviation = play->max_deviation; + int16_t value; /* must be int16, so assembling bytes work */ + int __attribute__((__unused__)) len; + int i, c; + int to_read; + int got = 0; + + /* we have finished */ + if (play->left == 0) { + to_read = 0; +read_empty: + for (i = to_read; i < length; i++) { + for (c = 0; c < play->channels; c++) + samples[c][i] = 0; + } + return got; + } + + /* how much do we read from buffer */ + to_read = (play->buffer_size + play->buffer_writep - play->buffer_readp) % play->buffer_size; + to_read /= 2 * play->channels; + if (to_read > (int)play->left) + to_read = play->left; + if (to_read > length) + to_read = length; + + if (to_read == 0 && play->finish) { + if (play->left) { + PDEBUG(DWAVE, DEBUG_NOTICE, "*** Finished reading WAVE file. (short read)\n"); + play->left = 0; + } + goto read_empty; + } + + /* read from buffer */ + for (i = 0; i < to_read; i++) { + for (c = 0; c < play->channels; c++) { + value = play->buffer[play->buffer_readp]; + play->buffer_readp = (play->buffer_readp + 1) % play->buffer_size; + value |= play->buffer[play->buffer_readp] << 8; + play->buffer_readp = (play->buffer_readp + 1) % play->buffer_size; + samples[c][i] = (double)value / 32767.0 * max_deviation; + } + } + got += to_read; + play->left -= to_read; + + if (!play->left) + PDEBUG(DWAVE, DEBUG_NOTICE, "*** Finished reading WAVE file.\n"); + + if (to_read < length) + goto read_empty; + + return got; +} + +void wave_destroy_record(wave_rec_t *rec) +{ + uint8_t buffer[256]; + uint32_t size, wsize; + struct fmt fmt; + int __attribute__((__unused__)) len; + + if (!rec->fp) + return; + + /* on error, thread has terminated */ + if (rec->finish) { + fclose(rec->fp); + rec->fp = NULL; + return; + } + + /* finish thread */ + rec->finish = 1; + pthread_join(rec->tid, NULL); + + /* cue */ + fprintf(rec->fp, "cue %c%c%c%c%c%c%c%c", 4, 0, 0, 0, 0,0,0,0); + + /* LIST */ + fprintf(rec->fp, "LIST%c%c%c%cadtl", 4, 0, 0, 0); + + /* go to header */ + fseek(rec->fp, 0, SEEK_SET); + + size = 2 * rec->written * rec->channels; + wsize = 4 + 8 + sizeof(fmt) + 8 + size + 8 + 4 + 8 + 4; + + /* RIFF */ + fprintf(rec->fp, "RIFF%c%c%c%c", wsize & 0xff, (wsize >> 8) & 0xff, (wsize >> 16) & 0xff, wsize >> 24); + + /* WAVE */ + fprintf(rec->fp, "WAVE"); + + /* fmt */ + fprintf(rec->fp, "fmt %c%c%c%c", (uint8_t)sizeof(fmt), 0, 0, 0); + fmt.format = 1; + fmt.channels = rec->channels; + fmt.sample_rate = rec->samplerate; /* samples/sec */ + fmt.data_rate = rec->samplerate * 2 * rec->channels; /* full data rate */ + fmt.bytes_sample = 2 * rec->channels; /* all channels */ + fmt.bits_sample = 16; /* one channel */ + buffer[0] = fmt.format; + buffer[1] = fmt.format >> 8; + buffer[2] = fmt.channels; + buffer[3] = fmt.channels >> 8; + buffer[4] = fmt.sample_rate; + buffer[5] = fmt.sample_rate >> 8; + buffer[6] = fmt.sample_rate >> 16; + buffer[7] = fmt.sample_rate >> 24; + buffer[8] = fmt.data_rate; + buffer[9] = fmt.data_rate >> 8; + buffer[10] = fmt.data_rate >> 16; + buffer[11] = fmt.data_rate >> 24; + buffer[12] = fmt.bytes_sample; + buffer[13] = fmt.bytes_sample >> 8; + buffer[14] = fmt.bits_sample; + buffer[15] = fmt.bits_sample >> 8; + len = fwrite(buffer, 1, sizeof(fmt), rec->fp); + + /* data */ + fprintf(rec->fp, "data%c%c%c%c", size & 0xff, (size >> 8) & 0xff, (size >> 16) & 0xff, size >> 24); + + free(rec->buffer); + rec->buffer = NULL; + fclose(rec->fp); + rec->fp = NULL; + + PDEBUG(DWAVE, DEBUG_NOTICE, "*** WAVE file written.\n"); +} + +void wave_destroy_playback(wave_play_t *play) +{ + if (!play->fp) + return; + + /* finish thread if not already */ + play->finish = 1; + pthread_join(play->tid, NULL); + + free(play->buffer); + play->buffer = NULL; + fclose(play->fp); + play->fp = NULL; +} + diff --git a/src/libwave/wave.h b/src/libwave/wave.h new file mode 100644 index 0000000..064f914 --- /dev/null +++ b/src/libwave/wave.h @@ -0,0 +1,37 @@ + +typedef struct wave_rec { + FILE *fp; + int channels; + double max_deviation; + int samplerate; + uint32_t written; /* how much samples written */ + /* thread stuff */ + pthread_t tid; /* file io thread id */ + int finish; /* indicates end of thread */ + uint8_t *buffer; /* buffer to store sample data */ + int buffer_size; /* size of buffer in bytes */ + int buffer_readp; /* read pointer to next byte in buffer */ + int buffer_writep; /* write pointer to next byte in buffer */ +} wave_rec_t; + +typedef struct wave_play { + FILE *fp; + int channels; + double max_deviation; + uint32_t left; /* how much samples left */ + /* thread stuff */ + pthread_t tid; /* file io thread id */ + int finish; /* indicates end of thread */ + uint8_t *buffer; /* buffer to store sample data */ + int buffer_size; /* size of buffer in bytes */ + int buffer_readp; /* read pointer to next byte in buffer */ + int buffer_writep; /* write pointer to next byte in buffer */ +} wave_play_t; + +int wave_create_record(wave_rec_t *rec, const char *filename, int samplerate, int channels, double max_deviation); +int wave_create_playback(wave_play_t *play, const char *filename, int *samplerate_p, int *channels_p, double max_deviation); +int wave_read(wave_play_t *play, sample_t **samples, int length); +int wave_write(wave_rec_t *rec, sample_t **samples, int length); +void wave_destroy_record(wave_rec_t *rec); +void wave_destroy_playback(wave_play_t *play); +