parent
8f920659d5
commit
8c66ccbd71
@ -0,0 +1,7 @@
|
||||
AM_CPPFLAGS = -Wall -Wextra -g $(all_includes)
|
||||
|
||||
noinst_LIBRARIES = libdebug.a
|
||||
|
||||
libdebug_a_SOURCES = \
|
||||
debug.c
|
||||
|
@ -0,0 +1,330 @@
|
||||
/* Simple debug functions for level and category filtering
|
||||
*
|
||||
* (C) 2016 by Andreas Eversberg <jolly@eversberg.eu>
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
#include <errno.h>
|
||||
#include <math.h>
|
||||
#include <time.h>
|
||||
#include <pthread.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/time.h>
|
||||
#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" },
|
||||
{ "golay", "\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[1;34m" },
|
||||
{ "ph", "\033[0;33m" },
|
||||
{ "dcf77", "\033[1;34m" },
|
||||
{ "jitter", "\033[0;36m" },
|
||||
{ NULL, NULL }
|
||||
};
|
||||
|
||||
int debuglevel = DEBUG_INFO;
|
||||
int debug_date = 0;
|
||||
uint64_t debug_mask[2] = { ~0, ~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 = 0; // make GCC happy
|
||||
|
||||
if (debuglevel > level)
|
||||
return;
|
||||
|
||||
if (!(debug_mask[cat >> 6] & ((uint64_t)1 << (cat & 63))))
|
||||
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: %s\033[0;39m", debug_cat[cat].color, file, line, debug_cat[cat].name, 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 <level> | <level>,<category>[,<category>[,...]] | 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)
|
||||
memset(debug_mask, 0, sizeof(debug_mask));
|
||||
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[i >> 6] |= ((uint64_t)1 << (i & 63));
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
@ -0,0 +1,86 @@
|
||||
|
||||
#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 DGOLAY 15
|
||||
#define DFUENF 16
|
||||
#define DFRAME 17
|
||||
#define DCALL 18
|
||||
#define DCC 19
|
||||
#define DDB 20
|
||||
#define DTRANS 21
|
||||
#define DDMS 22
|
||||
#define DSMS 23
|
||||
#define DSDR 24
|
||||
#define DUHD 25
|
||||
#define DSOAPY 26
|
||||
#define DWAVE 27
|
||||
#define DRADIO 28
|
||||
#define DAM791X 29
|
||||
#define DUART 30
|
||||
#define DDEVICE 31
|
||||
#define DDATENKLO 32
|
||||
#define DZEIT 33
|
||||
#define DSIM1 34
|
||||
#define DSIM2 35
|
||||
#define DSIMI 36
|
||||
#define DSIM7 37
|
||||
#define DMTP2 38
|
||||
#define DMTP3 39
|
||||
#define DMUP 40
|
||||
#define DROUTER 41
|
||||
#define DSTDERR 42
|
||||
#define DSS5 43
|
||||
#define DISDN 44
|
||||
#define DMISDN 45
|
||||
#define DDSS1 46
|
||||
#define DSIP 47
|
||||
#define DTEL 48
|
||||
#define DUK0 49
|
||||
#define DPH 50
|
||||
#define DDCF77 51
|
||||
#define DJITTER 52
|
||||
//NOTE: increment mask array, if 127 is exceeded
|
||||
|
||||
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);
|
||||
|
@ -0,0 +1,7 @@
|
||||
AM_CPPFLAGS = -Wall -Wextra -g $(all_includes)
|
||||
|
||||
noinst_LIBRARIES = libdtmf.a
|
||||
|
||||
libdtmf_a_SOURCES = \
|
||||
dtmf_encode.c \
|
||||
dtmf_decode.c
|
@ -0,0 +1,250 @@
|
||||
/* DTMF coder
|
||||
*
|
||||
* (C) 2016 by Andreas Eversberg <jolly@eversberg.eu>
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <math.h>
|
||||
#include "../libsample/sample.h"
|
||||
#include "../libdebug/debug.h"
|
||||
#include "dtmf_decode.h"
|
||||
|
||||
//#define DEBUG
|
||||
|
||||
#define level2db(level) (20 * log10(level))
|
||||
#define db2level(db) pow(10, (double)db / 20.0)
|
||||
|
||||
#define DTMF_LOW_1 697.0
|
||||
#define DTMF_LOW_2 770.0
|
||||
#define DTMF_LOW_3 852.0
|
||||
#define DTMF_LOW_4 941.0
|
||||
#define DTMF_HIGH_1 1209.0
|
||||
#define DTMF_HIGH_2 1336.0
|
||||
#define DTMF_HIGH_3 1477.0
|
||||
#define DTMF_HIGH_4 1633.0
|
||||
|
||||
static const char dtmf_digit[] = " 123A456B789C*0#D";
|
||||
|
||||
int dtmf_decode_init(dtmf_dec_t *dtmf, void *priv, void (*recv_digit)(void *priv, char digit, dtmf_meas_t *meas), int samplerate, double max_amplitude, double min_amplitude)
|
||||
{
|
||||
int rc;
|
||||
|
||||
memset(dtmf, 0, sizeof(*dtmf));
|
||||
dtmf->priv = priv;
|
||||
dtmf->recv_digit = recv_digit;
|
||||
dtmf->samplerate = samplerate;
|
||||
dtmf->freq_margin = 1.03; /* 1.8 .. 3.5 % */
|
||||
dtmf->max_amplitude = max_amplitude;
|
||||
dtmf->min_amplitude = min_amplitude;
|
||||
dtmf->forward_twist = db2level(4.0);
|
||||
dtmf->reverse_twist = db2level(8.0);
|
||||
dtmf->time_detect = (int)(0.025 * (double)samplerate);
|
||||
dtmf->time_meas = (int)(0.015 * (double)samplerate);
|
||||
dtmf->time_pause = (int)(0.010 * (double)samplerate);
|
||||
|
||||
/* init fm demodulator */
|
||||
rc = fm_demod_init(&dtmf->demod_low, (double)samplerate, (DTMF_LOW_1 + DTMF_LOW_4) / 2.0, DTMF_LOW_4 - DTMF_LOW_1);
|
||||
if (rc < 0)
|
||||
goto error;
|
||||
rc = fm_demod_init(&dtmf->demod_high, (double)samplerate, (DTMF_HIGH_1 + DTMF_HIGH_4) / 2.0, DTMF_HIGH_4 - DTMF_HIGH_1);
|
||||
if (rc < 0)
|
||||
goto error;
|
||||
|
||||
/* use fourth order (2 iter) filter, since it is as fast as second order (1 iter) filter */
|
||||
iir_lowpass_init(&dtmf->freq_lp[0], 100.0, samplerate, 2);
|
||||
iir_lowpass_init(&dtmf->freq_lp[1], 100.0, samplerate, 2);
|
||||
|
||||
return 0;
|
||||
|
||||
error:
|
||||
dtmf_decode_exit(dtmf);
|
||||
return rc;
|
||||
}
|
||||
|
||||
void dtmf_decode_exit(dtmf_dec_t *dtmf)
|
||||
{
|
||||
fm_demod_exit(&dtmf->demod_low);
|
||||
fm_demod_exit(&dtmf->demod_high);
|
||||
}
|
||||
|
||||
void dtmf_decode_reset(dtmf_dec_t *dtmf)
|
||||
{
|
||||
dtmf->detected = 0;
|
||||
dtmf->count = 0;
|
||||
}
|
||||
|
||||
void dtmf_decode_filter(dtmf_dec_t *dtmf, sample_t *samples, int length, sample_t *frequency_low, sample_t *frequency_high, sample_t *amplitude_low, sample_t *amplitude_high)
|
||||
{
|
||||
sample_t I_low[length], Q_low[length];
|
||||
sample_t I_high[length], Q_high[length];
|
||||
int i;
|
||||
|
||||
fm_demodulate_real(&dtmf->demod_low, frequency_low, length, samples, I_low, Q_low);
|
||||
fm_demodulate_real(&dtmf->demod_high, frequency_high, length, samples, I_high, Q_high);
|
||||
/* peak amplitude is the length of I/Q vector
|
||||
* since we filter out the unwanted modulation product, the vector is only half of length */
|
||||
for (i = 0; i < length; i++) {
|
||||
amplitude_low[i] = sqrt(I_low[i] * I_low[i] + Q_low[i] * Q_low[i]) * 2.0;
|
||||
amplitude_high[i] = sqrt(I_high[i] * I_high[i] + Q_high[i] * Q_high[i]) * 2.0;
|
||||
}
|
||||
iir_process(&dtmf->freq_lp[0], frequency_low, length);
|
||||
iir_process(&dtmf->freq_lp[1], frequency_high, length);
|
||||
}
|
||||
void dtmf_decode(dtmf_dec_t *dtmf, sample_t *samples, int length)
|
||||
{
|
||||
sample_t frequency_low[length], amplitude_low[length];
|
||||
sample_t frequency_high[length], amplitude_high[length];
|
||||
double margin, min_amplitude, max_amplitude, forward_twist, reverse_twist, f1, f2;
|
||||
int time_detect, time_meas, time_pause;
|
||||
int low = 0, high = 0;
|
||||
char detected, digit;
|
||||
int count;
|
||||
int amplitude_ok, twist_ok;
|
||||
int i;
|
||||
|
||||
margin = dtmf->freq_margin;
|
||||
min_amplitude = dtmf->min_amplitude;
|
||||
max_amplitude = dtmf->max_amplitude;
|
||||
forward_twist = dtmf->forward_twist;
|
||||
reverse_twist = dtmf->reverse_twist;
|
||||
time_detect = dtmf->time_detect;
|
||||
time_meas = dtmf->time_meas;
|
||||
time_pause = dtmf->time_pause;
|
||||
detected = dtmf->detected;
|
||||
count = dtmf->count;
|
||||
|
||||
/* FM/AM demod */
|
||||
dtmf_decode_filter(dtmf, samples, length, frequency_low, frequency_high, amplitude_low, amplitude_high);
|
||||
|
||||
for (i = 0; i < length; i++) {
|
||||
#ifdef DEBUG
|
||||
// printf("%s %.5f\n", debug_amplitude(samples[i]/2.0), samples[i]/2.0);
|
||||
#endif
|
||||
/* get frequency of low frequencies, correct amplitude drop at cutoff point */
|
||||
f1 = frequency_low[i] + (DTMF_LOW_1 + DTMF_LOW_4) / 2.0;
|
||||
if (f1 >= DTMF_LOW_1 / margin && f1 <= DTMF_LOW_1 * margin) {
|
||||
/* cutoff point */
|
||||
amplitude_low[i] /= 0.7071;
|
||||
low = 1;
|
||||
f1 -= DTMF_LOW_1;
|
||||
} else
|
||||
if (f1 >= DTMF_LOW_2 / margin && f1 <= DTMF_LOW_2 * margin) {
|
||||
amplitude_low[i] /= 1.0734;
|
||||
low = 2;
|
||||
f1 -= DTMF_LOW_2;
|
||||
} else
|
||||
if (f1 >= DTMF_LOW_3 / margin && f1 <= DTMF_LOW_3 * margin) {
|
||||
amplitude_low[i] /= 1.0389;
|
||||
low = 3;
|
||||
f1 -= DTMF_LOW_3;
|
||||
} else
|
||||
if (f1 >= DTMF_LOW_4 / margin && f1 <= DTMF_LOW_4 * margin) {
|
||||
/* cutoff point */
|
||||
amplitude_low[i] /= 0.7071;
|
||||
low = 4;
|
||||
f1 -= DTMF_LOW_4;
|
||||
} else
|
||||
low = 0;
|
||||
/* get frequency of high frequencies, correct amplitude drop at cutoff point */
|
||||
f2 = frequency_high[i] + (DTMF_HIGH_1 + DTMF_HIGH_4) / 2.0;
|
||||
if (f2 >= DTMF_HIGH_1 / margin && f2 <= DTMF_HIGH_1 * margin) {
|
||||
/* cutoff point */
|
||||
amplitude_high[i] /= 0.7071;
|
||||
high = 1;
|
||||
f2 -= DTMF_HIGH_1;
|
||||
} else
|
||||
if (f2 >= DTMF_HIGH_2 / margin && f2 <= DTMF_HIGH_2 * margin) {
|
||||
amplitude_high[i] /= 1.0731;
|
||||
high = 2;
|
||||
f2 -= DTMF_HIGH_2;
|
||||
} else
|
||||
if (f2 >= DTMF_HIGH_3 / margin && f2 <= DTMF_HIGH_3 * margin) {
|
||||
amplitude_high[i] /= 1.0372;
|
||||
high = 3;
|
||||
f2 -= DTMF_HIGH_3;
|
||||
} else
|
||||
if (f2 >= DTMF_HIGH_4 / margin && f2 <= DTMF_HIGH_4 * margin) {
|
||||
/* cutoff point */
|
||||
amplitude_high[i] /= 0.7071;
|
||||
high = 4;
|
||||
f2 -= DTMF_HIGH_4;
|
||||
} else
|
||||
high = 0;
|
||||
digit = 0;
|
||||
amplitude_ok = 0;
|
||||
twist_ok = 0;
|
||||
if (low && high) {
|
||||
digit = dtmf_digit[low*4+high];
|
||||
/* check for limits */
|
||||
if (amplitude_low[i] <= max_amplitude && amplitude_low[i] >= min_amplitude && amplitude_high[i] <= max_amplitude && amplitude_high[i] >= min_amplitude) {
|
||||
amplitude_ok = 1;
|
||||
#ifdef DEBUG
|
||||
printf("%.1f %.1f (limits %.1f .. %.1f) %.1f\n", level2db(amplitude_low[i]), level2db(amplitude_high[i]), level2db(min_amplitude), level2db(max_amplitude), level2db(amplitude_high[i] / amplitude_low[i]));
|
||||
#endif
|
||||
if (amplitude_high[i] / amplitude_low[i] <= forward_twist && amplitude_low[i] / amplitude_high[i] <= reverse_twist)
|
||||
twist_ok = 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (!detected) {
|
||||
if (digit && amplitude_ok && twist_ok) {
|
||||
if (count == 0) {
|
||||
memset(&dtmf->meas, 0, sizeof(dtmf->meas));
|
||||
}
|
||||
if (count >= time_meas) {
|
||||
dtmf->meas.frequency_low += f1;
|
||||
dtmf->meas.frequency_high += f2;
|
||||
dtmf->meas.amplitude_low += amplitude_low[i];
|
||||
dtmf->meas.amplitude_high += amplitude_high[i];
|
||||
dtmf->meas.count++;
|
||||
}
|
||||
count++;
|
||||
if (count >= time_detect) {
|
||||
detected = digit;
|
||||
dtmf->meas.frequency_low /= dtmf->meas.count;
|
||||
dtmf->meas.frequency_high /= dtmf->meas.count;
|
||||
dtmf->meas.amplitude_low /= dtmf->meas.count;
|
||||
dtmf->meas.amplitude_high /= dtmf->meas.count;
|
||||
dtmf->meas.count = 1;
|
||||
dtmf->recv_digit(dtmf->priv, digit, &dtmf->meas);
|
||||
}
|
||||
} else
|
||||
count = 0;
|
||||
} else {
|
||||
if (!digit || digit != detected || !amplitude_ok || !twist_ok) {
|
||||
count++;
|
||||
if (count >= time_pause) {
|
||||
detected = 0;
|
||||
#ifdef DEBUG
|
||||
printf("lost!\n");
|
||||
#endif
|
||||
}
|
||||
} else
|
||||
count = 0;
|
||||
}
|
||||
#ifdef DEBUG
|
||||
if (digit)
|
||||
printf("DTMF tone='%c' diff frequency=%.1f %.1f amplitude=%.1f %.1f dB (%s) twist=%.1f dB (%s)\n", digit, f1, f2, level2db(amplitude_low[i]), level2db(amplitude_high[i]), (amplitude_ok) ? "OK" : "nok", level2db(amplitude_high[i] / amplitude_low[i]), (twist_ok) ? "OK" : "nok");
|
||||
#endif
|
||||
|
||||
dtmf->detected = detected;
|
||||
dtmf->count = count;
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,36 @@
|
||||
#include "../libfm/fm.h"
|
||||
|
||||
typedef struct ftmf_meas {
|
||||
double frequency_low;
|
||||
double frequency_high;
|
||||
double amplitude_low;
|
||||
double amplitude_high;
|
||||
int count;
|
||||
} dtmf_meas_t;
|
||||
|
||||
typedef struct dtmf_dec {
|
||||
void *priv;
|
||||
void (*recv_digit)(void *priv, char digit, dtmf_meas_t *meas);
|
||||
int samplerate; /* samplerate */
|
||||
double freq_margin; /* +- limit of frequency deviation (percent) valid tone*/
|
||||
double min_amplitude; /* minimum amplitude relative to 0 dBm */
|
||||
double max_amplitude; /* maximum amplitude relative to 0 dBm */
|
||||
double forward_twist; /* how much do higher frequencies are louder than lower frequencies */
|
||||
double reverse_twist; /* how much do lower frequencies are louder than higher frequencies */
|
||||
int time_detect;
|
||||
int time_meas;
|
||||
int time_pause;
|
||||
fm_demod_t demod_low; /* demodulator for low frequencies */
|
||||
fm_demod_t demod_high; /* demodulator for high frequencies */
|
||||
iir_filter_t freq_lp[2]; /* low pass to filter the frequency result */
|
||||
char detected; /* currently detected DTMF digit or 0 for no detection */
|
||||
int count; /* counter to count detection or loss (pause) of signal */
|
||||
dtmf_meas_t meas; /* measurements */
|
||||
} dtmf_dec_t;
|
||||
|
||||
int dtmf_decode_init(dtmf_dec_t *dtmf, void *priv, void (*recv_digit)(void *priv, char digit, dtmf_meas_t *meas), int samplerate, double max_amplitude, double min_amplitude);
|
||||
void dtmf_decode_exit(dtmf_dec_t *dtmf);
|
||||
void dtmf_decode_reset(dtmf_dec_t *dtmf);
|
||||
void dtmf_decode(dtmf_dec_t *dtmf, sample_t *samples, int length);
|
||||
void dtmf_decode_filter(dtmf_dec_t *dtmf, sample_t *samples, int length, sample_t *frequency_low, sample_t *frequency_high, sample_t *amplitude_low, sample_t *amplitude_high);
|
||||
|
@ -0,0 +1,130 @@
|
||||
/* DTMF coder
|
||||
*
|
||||
* (C) 2016 by Andreas Eversberg <jolly@eversberg.eu>
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <math.h>
|
||||
#include "../libsample/sample.h"
|
||||
#include "dtmf_encode.h"
|
||||
|
||||
#define PI M_PI
|
||||
|
||||
#define PEAK_DTMF_LOW 0.2818 /* -11 dBm, relative to 0 dBm level */
|
||||
#define PEAK_DTMF_HIGH 0.3548 /* -9 dBm, relative to 0 dBm level */
|
||||
|
||||
void dtmf_encode_init(dtmf_enc_t *dtmf, int samplerate, double dBm_level)
|
||||
{
|
||||
int i;
|
||||
|
||||
memset(dtmf, 0, sizeof(*dtmf));
|
||||
dtmf->samplerate = samplerate;
|
||||
|
||||
for (i = 0; i < 65536; i++) {
|
||||
dtmf->sine_low[i] = sin((double)i / 65536.0 * 2.0 * PI) * PEAK_DTMF_LOW * dBm_level;
|
||||
dtmf->sine_high[i] = sin((double)i / 65536.0 * 2.0 * PI) * PEAK_DTMF_HIGH * dBm_level;
|
||||
}
|
||||
}
|
||||
|
||||
/* set dtmf tone */
|
||||
int dtmf_encode_set_tone(dtmf_enc_t *dtmf, char tone, double on_duration, double off_duration)
|
||||
{
|
||||
double f1, f2;
|
||||
|
||||
switch(tone) {
|
||||
case '1': f1 = 697.0; f2 = 1209.0; break;
|
||||
case '2': f1 = 697.0; f2 = 1336.0; break;
|
||||
case '3': f1 = 697.0; f2 = 1477.0; break;
|
||||
case'a':case 'A': f1 = 697.0; f2 = 1633.0; break;
|
||||
case '4': f1 = 770.0; f2 = 1209.0; break;
|
||||
case '5': f1 = 770.0; f2 = 1336.0; break;
|
||||
case '6': f1 = 770.0; f2 = 1477.0; break;
|
||||
case'b':case 'B': f1 = 770.0; f2 = 1633.0; break;
|
||||
case '7': f1 = 852.0; f2 = 1209.0; break;
|
||||
case '8': f1 = 852.0; f2 = 1336.0; break;
|
||||
case '9': f1 = 852.0; f2 = 1477.0; break;
|
||||
case'c':case 'C': f1 = 852.0; f2 = 1633.0; break;
|
||||
case '*': f1 = 941.0; f2 = 1209.0; break;
|
||||
case '0': f1 = 941.0; f2 = 1336.0; break;
|
||||
case '#': f1 = 941.0; f2 = 1477.0; break;
|
||||
case'd':case 'D': f1 = 941.0; f2 = 1633.0; break;
|
||||
default:
|
||||
dtmf->tone = 0;
|
||||
return -1;
|
||||
}
|
||||
dtmf->tone = tone;
|
||||
dtmf->pos = 0;
|
||||
dtmf->on = (int)((double)dtmf->samplerate * on_duration);
|
||||
dtmf->off = dtmf->on + (int)((double)dtmf->samplerate * off_duration);
|
||||
dtmf->phaseshift65536[0] = 65536.0 / ((double)dtmf->samplerate / f1);
|
||||
dtmf->phaseshift65536[1] = 65536.0 / ((double)dtmf->samplerate / f2);
|
||||
dtmf->phase65536[0] = 0.0;
|
||||
dtmf->phase65536[1] = 0.0;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Generate audio stream from DTMF tone.
|
||||
* Keep phase for next call of function.
|
||||
* Stop, if tone has finished and return only the samples that were used.
|
||||
*/
|
||||
int dtmf_encode(dtmf_enc_t *dtmf, sample_t *samples, int length)
|
||||
{
|
||||
double *phaseshift, *phase;
|
||||
sample_t *sine_low, *sine_high;
|
||||
int count = 0;
|
||||
int i;
|
||||
|
||||
/* if no tone */
|
||||
if (!dtmf->tone)
|
||||
return 0;
|
||||
|
||||
sine_low = dtmf->sine_low;
|
||||
sine_high = dtmf->sine_high;
|
||||
phaseshift = dtmf->phaseshift65536;
|
||||
phase = dtmf->phase65536;
|
||||
|
||||
for (i = 0; i < length; i++) {
|
||||
*samples++ = sine_low[(uint16_t)phase[0]]
|
||||
+ sine_high[(uint16_t)phase[1]];
|
||||
phase[0] += phaseshift[0];
|
||||
if (phase[0] >= 65536.0)
|
||||
phase[0] -= 65536.0;
|
||||
phase[1] += phaseshift[1];
|
||||
if (phase[1] >= 65536.0)
|
||||
phase[1] -= 65536.0;
|
||||
|
||||
dtmf->pos++;
|
||||
/* tone ends */
|
||||
if (dtmf->pos == dtmf->on) {
|
||||
phaseshift[0] = 0.0;
|
||||
phaseshift[1] = 0.0;
|
||||
phase[0] = 0.0;
|
||||
phase[1] = 0.0;
|
||||
}
|
||||
/* pause ends */
|
||||
if (dtmf->pos == dtmf->off) {
|
||||
dtmf->tone = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
count += i;
|
||||
|
||||
return count;
|
||||
}
|
||||
|
@ -0,0 +1,17 @@
|
||||
|
||||
typedef struct dtmf_enc {
|
||||
int samplerate; /* samplerate */
|
||||
char tone; /* current tone to be played */
|
||||
int on, off; /* samples to turn on and afterwards off */
|
||||
int pos; /* sample counter for tone */
|
||||
int max; /* max number of samples for tone duration */
|
||||
double phaseshift65536[2]; /* how much the phase of sine wave changes per sample */
|
||||
double phase65536[2]; /* current phase */
|
||||
sample_t sine_low[65536]; /* sine tables at individual levels */
|
||||
sample_t sine_high[65536];
|
||||
} dtmf_enc_t;
|
||||
|
||||
void dtmf_encode_init(dtmf_enc_t *dtmf, int samplerate, double dBm_level);
|
||||
int dtmf_encode_set_tone(dtmf_enc_t *dtmf, char tone, double on_duration, double off_duration);
|
||||
int dtmf_encode(dtmf_enc_t *dtmf, sample_t *samples, int length);
|
||||
|
@ -0,0 +1,8 @@
|
||||
AM_CPPFLAGS = -Wall -Wextra -g $(all_includes)
|
||||
|
||||
noinst_LIBRARIES = libfilter.a
|
||||
|
||||
libfilter_a_SOURCES = \
|
||||
iir_filter.c \
|
||||
fir_filter.c
|
||||
|
@ -0,0 +1,197 @@
|
||||
/* FIR filter
|
||||
*
|
||||
* (C) 2017 by Andreas Eversberg <jolly@eversberg.eu>
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <math.h>
|
||||
#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;
|
||||
}
|
||||
|
@ -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 */
|
||||
|
@ -0,0 +1,204 @@
|
||||
/* cut-off filter (biquad) based on Nigel Redmon (www.earlevel.com)
|
||||
*
|
||||
* (C) 2016 by Andreas Eversberg <jolly@eversberg.eu>
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <math.h>
|
||||
#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
|
@ -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 */
|
@ -0,0 +1,6 @@
|
||||
AM_CPPFLAGS = -Wall -Wextra -g $(all_includes)
|
||||
|
||||
noinst_LIBRARIES = libfm.a
|
||||
|
||||
libfm_a_SOURCES = \
|
||||
fm.c
|
@ -0,0 +1,413 @@
|
||||
/* FM modulation processing
|
||||
*
|
||||
* (C) 2017 by Andreas Eversberg <jolly@eversberg.eu>
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include <math.h>
|
||||
#include "../libsample/sample.h"
|
||||
#include "fm.h"
|
||||
|
||||
static int has_init = 0;
|
||||
static int fast_math = 0;
|
||||
static float *sin_tab = NULL, *cos_tab = NULL;
|
||||
|
||||
/* global init */
|
||||
int fm_init(int _fast_math)
|
||||
{
|
||||
fast_math = _fast_math;
|
||||
|
||||
if (fast_math) {
|
||||
int i;
|
||||
|
||||
sin_tab = calloc(65536+16384, sizeof(*sin_tab));
|
||||
if (!sin_tab) {
|
||||
fprintf(stderr, "No mem!\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
cos_tab = sin_tab + 16384;
|
||||
|
||||
/* generate sine and cosine */
|
||||
for (i = 0; i < 65536+16384; i++)
|
||||
sin_tab[i] = sin(2.0 * M_PI * (double)i / 65536.0);
|
||||
}
|
||||
|
||||
has_init = 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* global exit */
|
||||
void fm_exit(void)
|
||||
{
|
||||
if (sin_tab) {
|
||||
free(sin_tab);
|
||||
sin_tab = cos_tab = NULL;
|
||||
}
|
||||
|
||||
has_init = 0;
|
||||
}
|
||||
|
||||
/* init FM modulator */
|
||||
int fm_mod_init(fm_mod_t *mod, double samplerate, double offset, double amplitude)
|
||||
{
|
||||
int i;
|
||||
|
||||
if (!has_init) {
|
||||
fprintf(stderr, "libfm was not initialized, please fix!\n");
|
||||
abort();
|
||||
}
|
||||
|
||||
memset(mod, 0, sizeof(*mod));
|
||||
mod->samplerate = samplerate;
|
||||
mod->offset = offset;
|
||||
mod->amplitude = amplitude;
|
||||
|
||||
mod->ramp_length = samplerate * 0.001;
|
||||
mod- |