2015-07-27 01:17:23 +00:00
|
|
|
|
|
|
|
//#define DEBUG 1
|
|
|
|
|
|
|
|
|
2015-07-27 00:35:07 +00:00
|
|
|
//
|
|
|
|
// This file is part of Dire Wolf, an amateur radio packet TNC.
|
|
|
|
//
|
2016-12-17 01:12:38 +00:00
|
|
|
// Copyright (C) 2013, 2014, 2015, 2016 John Langner, WB2OSZ
|
2015-07-27 00:35:07 +00:00
|
|
|
//
|
|
|
|
// This program is free software: you can redistribute it and/or modify
|
|
|
|
// it under the terms of the GNU General Public License as published by
|
|
|
|
// the Free Software Foundation, either version 2 of the License, or
|
|
|
|
// (at your option) any later version.
|
|
|
|
//
|
|
|
|
// This program is distributed in the hope that it will be useful,
|
|
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
// GNU General Public License for more details.
|
|
|
|
//
|
|
|
|
// You should have received a copy of the GNU General Public License
|
|
|
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
//
|
|
|
|
|
|
|
|
/*------------------------------------------------------------------
|
|
|
|
*
|
|
|
|
* Module: dtmf.c
|
|
|
|
*
|
|
|
|
* Purpose: Decoder for DTMF, commonly known as "touch tones."
|
|
|
|
*
|
|
|
|
* Description: This uses the Goertzel Algorithm for tone detection.
|
|
|
|
*
|
|
|
|
* References: http://eetimes.com/design/embedded/4024443/The-Goertzel-Algorithm
|
|
|
|
* http://www.ti.com/ww/cn/uprogram/share/ppt/c5000/17dtmf_v13.ppt
|
|
|
|
*
|
2016-12-23 16:28:39 +00:00
|
|
|
* Revisions: 1.4 - Added transmit capability.
|
|
|
|
*
|
2015-07-27 00:35:07 +00:00
|
|
|
*---------------------------------------------------------------*/
|
|
|
|
|
2016-07-03 22:09:34 +00:00
|
|
|
#include "direwolf.h"
|
2015-07-27 00:35:07 +00:00
|
|
|
|
|
|
|
#include <stdlib.h>
|
|
|
|
#include <stdio.h>
|
|
|
|
#include <math.h>
|
|
|
|
#include <assert.h>
|
2016-12-17 01:12:38 +00:00
|
|
|
#include <string.h>
|
2015-07-27 00:35:07 +00:00
|
|
|
|
|
|
|
#include "dtmf.h"
|
2015-09-07 23:56:20 +00:00
|
|
|
#include "hdlc_rec.h" // for dcd_change
|
2016-12-17 01:12:38 +00:00
|
|
|
#include "textcolor.h"
|
|
|
|
#include "gen_tone.h"
|
2015-07-27 00:35:07 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#if DTMF_TEST
|
|
|
|
#define TIMEOUT_SEC 1 /* short for unit test below. */
|
2016-07-03 22:09:34 +00:00
|
|
|
#define DEBUG 1 // Don't remove this. We want more output for test.
|
2015-07-27 00:35:07 +00:00
|
|
|
#else
|
|
|
|
#define TIMEOUT_SEC 5 /* for normal operation. */
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
|
|
#define NUM_TONES 8
|
|
|
|
static int const dtmf_tones[NUM_TONES] = { 697, 770, 852, 941, 1209, 1336, 1477, 1633 };
|
|
|
|
|
|
|
|
/*
|
2015-07-27 01:17:23 +00:00
|
|
|
* Current state of the DTMF decoding.
|
2015-07-27 00:35:07 +00:00
|
|
|
*/
|
|
|
|
|
2015-07-27 01:17:23 +00:00
|
|
|
static struct dd_s { /* Separate for each audio channel. */
|
|
|
|
|
2015-07-27 00:35:07 +00:00
|
|
|
int sample_rate; /* Samples per sec. Typ. 44100, 8000, etc. */
|
|
|
|
int block_size; /* Number of samples to process in one block. */
|
|
|
|
float coef[NUM_TONES];
|
|
|
|
|
2015-07-27 01:17:23 +00:00
|
|
|
int n; /* Samples processed in this block. */
|
|
|
|
float Q1[NUM_TONES];
|
|
|
|
float Q2[NUM_TONES];
|
|
|
|
char prev_dec;
|
|
|
|
char debounced;
|
|
|
|
char prev_debounced;
|
|
|
|
int timeout;
|
|
|
|
|
|
|
|
} dd[MAX_CHANS];
|
2015-07-27 00:35:07 +00:00
|
|
|
|
|
|
|
|
2016-12-17 01:12:38 +00:00
|
|
|
static int s_amplitude = 100; // range of 0 .. 100
|
2015-07-27 00:35:07 +00:00
|
|
|
|
|
|
|
|
2016-12-17 01:12:38 +00:00
|
|
|
static void push_button (int chan, char button, int ms);
|
|
|
|
|
2015-07-27 00:35:07 +00:00
|
|
|
|
|
|
|
/*------------------------------------------------------------------
|
|
|
|
*
|
|
|
|
* Name: dtmf_init
|
|
|
|
*
|
|
|
|
* Purpose: Initialize the DTMF decoder.
|
|
|
|
* This should be called once at application start up time.
|
|
|
|
*
|
2015-07-27 01:17:23 +00:00
|
|
|
* Inputs: p_audio_config - Configuration for audio interfaces.
|
|
|
|
*
|
|
|
|
* All we care about is:
|
|
|
|
*
|
|
|
|
* samples_per_sec - Audio sample frequency, typically
|
|
|
|
* 44100, 22050, 8000, etc.
|
|
|
|
*
|
|
|
|
* This is a associated with the soundcard.
|
|
|
|
* In version 1.2, we can have multiple soundcards
|
|
|
|
* with potentially different sample rates.
|
2015-07-27 00:35:07 +00:00
|
|
|
*
|
2016-12-17 01:12:38 +00:00
|
|
|
* amp - Signal amplitude, for transmit, on scale of 0 .. 100.
|
|
|
|
*
|
2016-12-23 16:28:39 +00:00
|
|
|
* 100 will produce maximum amplitude of +-32k samples.
|
2016-12-17 01:12:38 +00:00
|
|
|
*
|
2015-07-27 00:35:07 +00:00
|
|
|
* Returns: None.
|
|
|
|
*
|
|
|
|
*----------------------------------------------------------------*/
|
|
|
|
|
2015-07-27 01:17:23 +00:00
|
|
|
|
2016-12-17 01:12:38 +00:00
|
|
|
void dtmf_init (struct audio_s *p_audio_config, int amp)
|
2015-07-27 00:35:07 +00:00
|
|
|
{
|
|
|
|
int j; /* Loop over all tones frequencies. */
|
|
|
|
int c; /* Loop over all audio channels. */
|
2015-07-27 01:17:23 +00:00
|
|
|
|
2015-07-27 00:35:07 +00:00
|
|
|
|
2016-12-17 01:12:38 +00:00
|
|
|
s_amplitude = amp;
|
|
|
|
|
2015-07-27 00:35:07 +00:00
|
|
|
/*
|
2015-07-27 01:17:23 +00:00
|
|
|
* Pick a suitable processing block size.
|
2015-07-27 00:35:07 +00:00
|
|
|
* Larger = narrower bandwidth, slower response.
|
|
|
|
*/
|
2015-07-27 01:17:23 +00:00
|
|
|
|
|
|
|
for (c=0; c<MAX_CHANS; c++) {
|
|
|
|
struct dd_s *D = &(dd[c]);
|
|
|
|
int a = ACHAN2ADEV(c);
|
2016-12-17 01:12:38 +00:00
|
|
|
|
|
|
|
D->sample_rate = p_audio_config->adev[a].samples_per_sec;
|
|
|
|
|
2015-07-27 01:17:23 +00:00
|
|
|
if (p_audio_config->achan[c].dtmf_decode != DTMF_DECODE_OFF) {
|
2015-07-27 00:35:07 +00:00
|
|
|
|
|
|
|
#if DEBUG
|
2016-12-17 01:12:38 +00:00
|
|
|
text_color_set(DW_COLOR_DEBUG);
|
2015-07-27 01:17:23 +00:00
|
|
|
dw_printf ("channel %d:\n", c);
|
2015-07-27 00:35:07 +00:00
|
|
|
#endif
|
2015-07-27 01:17:23 +00:00
|
|
|
D->block_size = (205 * D->sample_rate) / 8000;
|
|
|
|
|
|
|
|
#if DEBUG
|
|
|
|
dw_printf (" freq k coef \n");
|
|
|
|
#endif
|
|
|
|
for (j=0; j<NUM_TONES; j++) {
|
|
|
|
float k;
|
2015-07-27 00:35:07 +00:00
|
|
|
|
|
|
|
|
|
|
|
// Why do some insist on rounding k to the nearest integer?
|
|
|
|
// That would move the filter center frequency away from ideal.
|
|
|
|
// What is to be gained?
|
|
|
|
// More consistent results for all the tones when k is not rounded off.
|
|
|
|
|
2015-07-27 01:17:23 +00:00
|
|
|
k = D->block_size * (float)(dtmf_tones[j]) / (float)(D->sample_rate);
|
2015-07-27 00:35:07 +00:00
|
|
|
|
2016-12-17 01:12:38 +00:00
|
|
|
D->coef[j] = 2.0f * cosf(2.0f * (float)M_PI * (float)k / (float)(D->block_size));
|
2015-07-27 00:35:07 +00:00
|
|
|
|
2016-12-17 01:12:38 +00:00
|
|
|
assert (D->coef[j] > 0.0f && D->coef[j] < 2.0f);
|
2015-07-27 00:35:07 +00:00
|
|
|
#if DEBUG
|
2015-07-27 01:17:23 +00:00
|
|
|
dw_printf ("%8d %5.1f %8.5f \n", dtmf_tones[j], k, D->coef[j]);
|
2015-07-27 00:35:07 +00:00
|
|
|
#endif
|
2015-07-27 01:17:23 +00:00
|
|
|
}
|
|
|
|
}
|
2015-07-27 00:35:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
for (c=0; c<MAX_CHANS; c++) {
|
2015-07-27 01:17:23 +00:00
|
|
|
struct dd_s *D = &(dd[c]);
|
|
|
|
D->n = 0;
|
2015-07-27 00:35:07 +00:00
|
|
|
for (j=0; j<NUM_TONES; j++) {
|
2015-07-27 01:17:23 +00:00
|
|
|
D->Q1[j] = 0;
|
|
|
|
D->Q2[j] = 0;
|
2015-07-27 00:35:07 +00:00
|
|
|
}
|
2015-07-27 01:17:23 +00:00
|
|
|
D->prev_dec = ' ';
|
|
|
|
D->debounced = ' ';
|
|
|
|
D->prev_debounced = ' ';
|
|
|
|
D->timeout = 0;
|
2015-07-27 00:35:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
/*------------------------------------------------------------------
|
|
|
|
*
|
|
|
|
* Name: dtmf_sample
|
|
|
|
*
|
|
|
|
* Purpose: Process one audio sample from the sound input source.
|
|
|
|
*
|
|
|
|
* Inputs: c - Audio channel number.
|
|
|
|
* This can process multiple channels in parallel.
|
|
|
|
* input - Audio sample.
|
|
|
|
*
|
|
|
|
* Returns: 0123456789ABCD*# for a button push.
|
|
|
|
* . for nothing happening during sample interval.
|
|
|
|
* $ after several seconds of inactivity.
|
|
|
|
* space between sample intervals.
|
|
|
|
*
|
|
|
|
*
|
|
|
|
*----------------------------------------------------------------*/
|
|
|
|
|
|
|
|
__attribute__((hot))
|
|
|
|
char dtmf_sample (int c, float input)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
float Q0;
|
|
|
|
float output[NUM_TONES];
|
|
|
|
char decoded;
|
|
|
|
char ret;
|
2015-07-27 01:17:23 +00:00
|
|
|
struct dd_s *D;
|
2015-07-27 00:35:07 +00:00
|
|
|
static const char rc2char[16] = { '1', '2', '3', 'A',
|
|
|
|
'4', '5', '6', 'B',
|
|
|
|
'7', '8', '9', 'C',
|
|
|
|
'*', '0', '#', 'D' };
|
2015-07-27 01:17:23 +00:00
|
|
|
|
|
|
|
D = &(dd[c]);
|
|
|
|
|
2015-07-27 00:35:07 +00:00
|
|
|
for (i=0; i<NUM_TONES; i++) {
|
2015-07-27 01:17:23 +00:00
|
|
|
Q0 = input + D->Q1[i] * D->coef[i] - D->Q2[i];
|
|
|
|
D->Q2[i] = D->Q1[i];
|
|
|
|
D->Q1[i] = Q0;
|
2015-07-27 00:35:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Is it time to process the block?
|
|
|
|
*/
|
2015-07-27 01:17:23 +00:00
|
|
|
D->n++;
|
|
|
|
if (D->n == D->block_size) {
|
2015-07-27 00:35:07 +00:00
|
|
|
int row, col;
|
|
|
|
|
|
|
|
for (i=0; i<NUM_TONES; i++) {
|
2015-07-27 01:17:23 +00:00
|
|
|
output[i] = sqrt(D->Q1[i] * D->Q1[i] + D->Q2[i] * D->Q2[i] - D->Q1[i] * D->Q2[i] * D->coef[i]);
|
|
|
|
D->Q1[i] = 0;
|
|
|
|
D->Q2[i] = 0;
|
2015-07-27 00:35:07 +00:00
|
|
|
}
|
2015-07-27 01:17:23 +00:00
|
|
|
D->n = 0;
|
2015-07-27 00:35:07 +00:00
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
* The input signal can vary over a couple orders of
|
|
|
|
* magnitude so we can't set some absolute threshold.
|
|
|
|
*
|
|
|
|
* See if one tone is stronger than the sum of the
|
|
|
|
* others in the same group multiplied by some factor.
|
|
|
|
*
|
|
|
|
* For perfect synthetic signals this needs to be in
|
|
|
|
* the range of about 1.33 (very senstive) to 2.15 (very fussy).
|
|
|
|
*
|
|
|
|
* Too low will cause false triggers on random noise.
|
|
|
|
* Too high will won't decode less than perfect signals.
|
|
|
|
*
|
|
|
|
* Use the mid point 1.74 as our initial guess.
|
|
|
|
* It might need some fine tuning for imperfect real world signals.
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
2016-12-17 01:12:38 +00:00
|
|
|
#define THRESHOLD 1.74f
|
2015-07-27 00:35:07 +00:00
|
|
|
|
|
|
|
if (output[0] > THRESHOLD * ( output[1] + output[2] + output[3])) row = 0;
|
|
|
|
else if (output[1] > THRESHOLD * (output[0] + output[2] + output[3])) row = 1;
|
|
|
|
else if (output[2] > THRESHOLD * (output[0] + output[1] + output[3])) row = 2;
|
|
|
|
else if (output[3] > THRESHOLD * (output[0] + output[1] + output[2] )) row = 3;
|
|
|
|
else row = -1;
|
|
|
|
|
|
|
|
if (output[4] > THRESHOLD * ( output[5] + output[6] + output[7])) col = 0;
|
|
|
|
else if (output[5] > THRESHOLD * (output[4] + output[6] + output[7])) col = 1;
|
|
|
|
else if (output[6] > THRESHOLD * (output[4] + output[5] + output[7])) col = 2;
|
|
|
|
else if (output[7] > THRESHOLD * (output[4] + output[5] + output[6] )) col = 3;
|
|
|
|
else col = -1;
|
|
|
|
|
|
|
|
for (i=0; i<NUM_TONES; i++) {
|
|
|
|
#if DEBUG
|
|
|
|
dw_printf ("%5.0f ", output[i]);
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
if (row >= 0 && col >= 0) {
|
|
|
|
decoded = rc2char[row*4+col];
|
|
|
|
}
|
|
|
|
else {
|
2015-07-27 01:17:23 +00:00
|
|
|
decoded = ' ';
|
2015-07-27 00:35:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Consider valid only if we get same twice in a row.
|
|
|
|
|
2015-07-27 01:17:23 +00:00
|
|
|
if (decoded == D->prev_dec) {
|
|
|
|
D->debounced = decoded;
|
2015-09-07 23:56:20 +00:00
|
|
|
|
|
|
|
// Update Data Carrier Detect Indicator.
|
|
|
|
|
2016-12-17 01:12:38 +00:00
|
|
|
#ifndef DTMF_TEST
|
2015-11-29 15:44:30 +00:00
|
|
|
dcd_change (c, MAX_SUBCHANS, 0, decoded != ' ');
|
2016-12-17 01:12:38 +00:00
|
|
|
#endif
|
2015-09-07 23:56:20 +00:00
|
|
|
|
2015-07-27 00:35:07 +00:00
|
|
|
/* Reset timeout timer. */
|
|
|
|
if (decoded != ' ') {
|
2015-07-27 01:17:23 +00:00
|
|
|
D->timeout = ((TIMEOUT_SEC) * D->sample_rate) / D->block_size;
|
2015-07-27 00:35:07 +00:00
|
|
|
}
|
|
|
|
}
|
2015-07-27 01:17:23 +00:00
|
|
|
D->prev_dec = decoded;
|
2015-07-27 00:35:07 +00:00
|
|
|
|
|
|
|
// Return only new button pushes.
|
|
|
|
// Also report timeout after period of inactivity.
|
|
|
|
|
|
|
|
ret = '.';
|
2015-07-27 01:17:23 +00:00
|
|
|
if (D->debounced != D->prev_debounced) {
|
|
|
|
if (D->debounced != ' ') {
|
|
|
|
ret = D->debounced;
|
2015-07-27 00:35:07 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
if (ret == '.') {
|
2015-07-27 01:17:23 +00:00
|
|
|
if (D->timeout > 0) {
|
|
|
|
D->timeout--;
|
|
|
|
if (D->timeout == 0) {
|
2015-07-27 00:35:07 +00:00
|
|
|
ret = '$';
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2015-07-27 01:17:23 +00:00
|
|
|
D->prev_debounced = D->debounced;
|
2015-07-27 00:35:07 +00:00
|
|
|
|
|
|
|
#if DEBUG
|
2015-07-27 01:17:23 +00:00
|
|
|
dw_printf (" dec=%c, deb=%c, ret=%c, to=%d \n",
|
|
|
|
decoded, D->debounced, ret, D->timeout);
|
2015-07-27 00:35:07 +00:00
|
|
|
#endif
|
|
|
|
return (ret);
|
|
|
|
}
|
|
|
|
|
|
|
|
return (' ');
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2016-12-17 01:12:38 +00:00
|
|
|
|
|
|
|
/*-------------------------------------------------------------------
|
2015-07-27 00:35:07 +00:00
|
|
|
*
|
2016-12-17 01:12:38 +00:00
|
|
|
* Name: dtmf_send
|
2015-07-27 00:35:07 +00:00
|
|
|
*
|
2016-12-17 01:12:38 +00:00
|
|
|
* Purpose: Generate DTMF tones from text string.
|
2015-07-27 00:35:07 +00:00
|
|
|
*
|
2016-12-17 01:12:38 +00:00
|
|
|
* Inputs: chan - Radio channel number.
|
|
|
|
* str - Character string to send. 0-9, A-D, *, #
|
|
|
|
* speed - Number of tones per second. Range 1 to 10.
|
|
|
|
* txdelay - Delay (ms) from PTT to start.
|
2016-12-23 16:28:39 +00:00
|
|
|
* txtail - Delay (ms) from end to PTT off.
|
|
|
|
*
|
2016-12-17 01:12:38 +00:00
|
|
|
* Returns: Total number of milliseconds to activate PTT.
|
|
|
|
* This includes delays before the first tone
|
|
|
|
* and after the last to avoid chopping off part of it.
|
2015-07-27 01:17:23 +00:00
|
|
|
*
|
2016-12-17 01:12:38 +00:00
|
|
|
* Description: xmit_thread calls this instead of the usual hdlc_send
|
|
|
|
* when we have a special packet that means send DTMF.
|
|
|
|
*
|
|
|
|
*--------------------------------------------------------------------*/
|
|
|
|
|
|
|
|
int dtmf_send (int chan, char *str, int speed, int txdelay, int txtail)
|
|
|
|
{
|
|
|
|
char *p;
|
|
|
|
int len_ms; // Length of tone or gap between.
|
2016-12-23 16:28:39 +00:00
|
|
|
|
2016-12-17 01:12:38 +00:00
|
|
|
len_ms = (int) ( ( 500.0f / (float)speed ) + 0.5f);
|
|
|
|
|
|
|
|
push_button (chan, ' ', txdelay);
|
|
|
|
|
|
|
|
for (p = str; *p != '\0'; p++) {
|
|
|
|
|
|
|
|
push_button (chan, *p, len_ms);
|
|
|
|
push_button (chan, ' ', len_ms);
|
|
|
|
}
|
|
|
|
|
|
|
|
push_button (chan, ' ', txtail);
|
|
|
|
|
|
|
|
#ifndef DTMF_TEST
|
|
|
|
audio_flush(ACHAN2ADEV(chan));
|
|
|
|
#endif
|
|
|
|
return (txdelay +
|
|
|
|
(int) (1000.0f * (float)strlen(str) / (float)speed + 0.5f) +
|
|
|
|
txtail);
|
|
|
|
|
|
|
|
} /* end dtmf_send */
|
2015-07-27 00:35:07 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
2016-12-17 01:12:38 +00:00
|
|
|
/*------------------------------------------------------------------
|
|
|
|
*
|
|
|
|
* Name: push_button
|
|
|
|
*
|
|
|
|
* Purpose: Generate DTMF tone for a button push.
|
|
|
|
*
|
|
|
|
* Inputs: chan - Radio channel number.
|
|
|
|
*
|
|
|
|
* button - One of 0-9, A-D, *, #. Others result in silence.
|
|
|
|
* '?' is a special case used only for unit testing.
|
|
|
|
*
|
|
|
|
* ms - Duration in milliseconds.
|
|
|
|
* Use 50 ms for tone and 50 ms of silence for max rate of 10 per second.
|
|
|
|
*
|
|
|
|
* Outputs: Audio is sent to radio.
|
|
|
|
*
|
|
|
|
*----------------------------------------------------------------*/
|
|
|
|
|
|
|
|
static void push_button (int chan, char button, int ms)
|
2015-07-27 00:35:07 +00:00
|
|
|
{
|
2016-12-17 01:12:38 +00:00
|
|
|
float phasea = 0;
|
|
|
|
float phaseb = 0;
|
|
|
|
float fa = 0;
|
|
|
|
float fb = 0;
|
2015-07-27 00:35:07 +00:00
|
|
|
int i;
|
2016-12-17 01:12:38 +00:00
|
|
|
float dtmf; // Audio. Sum of two sine waves.
|
|
|
|
#if DTMF_TEST
|
2015-07-27 00:35:07 +00:00
|
|
|
char x;
|
|
|
|
static char result[100];
|
|
|
|
static int result_len = 0;
|
2016-12-17 01:12:38 +00:00
|
|
|
#endif
|
2015-07-27 00:35:07 +00:00
|
|
|
|
|
|
|
switch (button) {
|
|
|
|
case '1': fa = dtmf_tones[0]; fb = dtmf_tones[4]; break;
|
|
|
|
case '2': fa = dtmf_tones[0]; fb = dtmf_tones[5]; break;
|
|
|
|
case '3': fa = dtmf_tones[0]; fb = dtmf_tones[6]; break;
|
2016-12-17 01:12:38 +00:00
|
|
|
case 'a':
|
2015-07-27 00:35:07 +00:00
|
|
|
case 'A': fa = dtmf_tones[0]; fb = dtmf_tones[7]; break;
|
2016-12-17 01:12:38 +00:00
|
|
|
|
2015-07-27 00:35:07 +00:00
|
|
|
case '4': fa = dtmf_tones[1]; fb = dtmf_tones[4]; break;
|
|
|
|
case '5': fa = dtmf_tones[1]; fb = dtmf_tones[5]; break;
|
|
|
|
case '6': fa = dtmf_tones[1]; fb = dtmf_tones[6]; break;
|
2016-12-17 01:12:38 +00:00
|
|
|
case 'b':
|
2015-07-27 00:35:07 +00:00
|
|
|
case 'B': fa = dtmf_tones[1]; fb = dtmf_tones[7]; break;
|
2016-12-17 01:12:38 +00:00
|
|
|
|
2015-07-27 00:35:07 +00:00
|
|
|
case '7': fa = dtmf_tones[2]; fb = dtmf_tones[4]; break;
|
|
|
|
case '8': fa = dtmf_tones[2]; fb = dtmf_tones[5]; break;
|
|
|
|
case '9': fa = dtmf_tones[2]; fb = dtmf_tones[6]; break;
|
2016-12-17 01:12:38 +00:00
|
|
|
case 'c':
|
2015-07-27 00:35:07 +00:00
|
|
|
case 'C': fa = dtmf_tones[2]; fb = dtmf_tones[7]; break;
|
2016-12-17 01:12:38 +00:00
|
|
|
|
2015-07-27 00:35:07 +00:00
|
|
|
case '*': fa = dtmf_tones[3]; fb = dtmf_tones[4]; break;
|
|
|
|
case '0': fa = dtmf_tones[3]; fb = dtmf_tones[5]; break;
|
|
|
|
case '#': fa = dtmf_tones[3]; fb = dtmf_tones[6]; break;
|
2016-12-17 01:12:38 +00:00
|
|
|
case 'd':
|
2015-07-27 00:35:07 +00:00
|
|
|
case 'D': fa = dtmf_tones[3]; fb = dtmf_tones[7]; break;
|
|
|
|
|
2016-12-17 01:12:38 +00:00
|
|
|
#if DTMF_TEST
|
|
|
|
|
|
|
|
case '?': /* check result */
|
2015-07-27 01:17:23 +00:00
|
|
|
|
2015-07-27 00:35:07 +00:00
|
|
|
if (strcmp(result, "123A456B789C*0#D123$789$") == 0) {
|
2016-12-17 01:12:38 +00:00
|
|
|
text_color_set(DW_COLOR_REC);
|
2015-07-27 00:35:07 +00:00
|
|
|
dw_printf ("\nSuccess!\n");
|
|
|
|
}
|
2015-07-27 01:17:23 +00:00
|
|
|
else if (strcmp(result, "123A456B789C*0#D123789") == 0) {
|
2016-12-17 01:12:38 +00:00
|
|
|
text_color_set(DW_COLOR_ERROR);
|
2015-07-27 01:17:23 +00:00
|
|
|
dw_printf ("\n * Time-out failed, otherwise OK *\n");
|
|
|
|
dw_printf ("\"%s\"\n", result);
|
2016-12-17 01:12:38 +00:00
|
|
|
exit (EXIT_FAILURE);
|
2015-07-27 01:17:23 +00:00
|
|
|
}
|
2015-07-27 00:35:07 +00:00
|
|
|
else {
|
2016-12-17 01:12:38 +00:00
|
|
|
text_color_set(DW_COLOR_ERROR);
|
2015-07-27 00:35:07 +00:00
|
|
|
dw_printf ("\n *** TEST FAILED ***\n");
|
|
|
|
dw_printf ("\"%s\"\n", result);
|
2016-12-17 01:12:38 +00:00
|
|
|
exit (EXIT_FAILURE);
|
2015-07-27 00:35:07 +00:00
|
|
|
}
|
|
|
|
break;
|
2016-12-17 01:12:38 +00:00
|
|
|
#endif
|
2015-07-27 00:35:07 +00:00
|
|
|
}
|
|
|
|
|
2016-12-17 01:12:38 +00:00
|
|
|
//dw_printf ("push_button (%d, '%c', %d), fa=%.0f, fb=%.0f. %d samples\n", chan, button, ms, fa, fb, (ms*dd[chan].sample_rate)/1000);
|
|
|
|
|
|
|
|
for (i = 0; i < (ms*dd[chan].sample_rate)/1000; i++) {
|
2015-07-27 00:35:07 +00:00
|
|
|
|
2016-12-17 01:12:38 +00:00
|
|
|
// This could be more efficient with a precomputed sine wave table
|
|
|
|
// but I'm not that worried about it.
|
|
|
|
// With a Raspberry Pi, model 2, default 1200 receiving takes about 14% of one CPU core.
|
|
|
|
// When transmitting tones, it briefly shoots up to about 33%.
|
|
|
|
|
|
|
|
if (fa > 0 && fb > 0) {
|
|
|
|
dtmf = sinf(phasea) + sinf(phaseb);
|
|
|
|
phasea += 2.0f * (float)M_PI * fa / dd[chan].sample_rate;
|
|
|
|
phaseb += 2.0f * (float)M_PI * fb / dd[chan].sample_rate;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
dtmf = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
#if DTMF_TEST
|
2015-07-27 00:35:07 +00:00
|
|
|
|
|
|
|
/* Make sure it is insensitive to signal amplitude. */
|
2016-12-17 01:12:38 +00:00
|
|
|
/* (Uncomment each of below when testing.) */
|
2015-07-27 00:35:07 +00:00
|
|
|
|
2016-12-17 01:12:38 +00:00
|
|
|
x = dtmf_sample (0, dtmf);
|
|
|
|
//x = dtmf_sample (0, dtmf * 1000);
|
|
|
|
//x = dtmf_sample (0, dtmf * 0.001);
|
2015-07-27 00:35:07 +00:00
|
|
|
|
|
|
|
if (x != ' ' && x != '.') {
|
|
|
|
result[result_len] = x;
|
|
|
|
result_len++;
|
|
|
|
result[result_len] = '\0';
|
|
|
|
}
|
2016-12-17 01:12:38 +00:00
|
|
|
#else
|
|
|
|
|
|
|
|
// 'dtmf' can be in range of +-2.0 because it is sum of two sine waves.
|
|
|
|
// Amplitude of 100 would use full +-32k range.
|
|
|
|
|
|
|
|
int sam = (int)(dtmf * 16383.0f * (float)s_amplitude / 100.0f);
|
|
|
|
gen_tone_put_sample (chan, ACHAN2ADEV(chan), sam);
|
|
|
|
|
|
|
|
#endif
|
2015-07-27 00:35:07 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-12-17 01:12:38 +00:00
|
|
|
|
|
|
|
/*------------------------------------------------------------------
|
|
|
|
*
|
|
|
|
* Name: main
|
|
|
|
*
|
|
|
|
* Purpose: Unit test for functions above.
|
|
|
|
*
|
|
|
|
* Usage: rm a.exe ; gcc -DDTMF_TEST dtmf.c textcolor.c ; ./a.exe
|
|
|
|
* or
|
|
|
|
* make dtmftest
|
|
|
|
*
|
|
|
|
*----------------------------------------------------------------*/
|
|
|
|
|
|
|
|
#if DTMF_TEST
|
|
|
|
|
2015-07-27 01:17:23 +00:00
|
|
|
static struct audio_s my_audio_config;
|
|
|
|
|
|
|
|
|
2016-12-17 01:12:38 +00:00
|
|
|
int main ()
|
2015-07-27 00:35:07 +00:00
|
|
|
{
|
2016-12-17 01:12:38 +00:00
|
|
|
int c = 0; // radio channel.
|
2015-07-27 00:35:07 +00:00
|
|
|
|
2015-07-27 01:17:23 +00:00
|
|
|
memset (&my_audio_config, 0, sizeof(my_audio_config));
|
2016-12-17 01:12:38 +00:00
|
|
|
my_audio_config.adev[ACHAN2ADEV(c)].defined = 1;
|
|
|
|
my_audio_config.adev[ACHAN2ADEV(c)].samples_per_sec = 44100;
|
2019-05-13 10:25:12 +00:00
|
|
|
my_audio_config.achan[c].medium = MEDIUM_RADIO;
|
2016-12-17 01:12:38 +00:00
|
|
|
my_audio_config.achan[c].dtmf_decode = DTMF_DECODE_ON;
|
2015-07-27 01:17:23 +00:00
|
|
|
|
2016-12-17 01:12:38 +00:00
|
|
|
dtmf_init(&my_audio_config, 50);
|
2015-07-27 00:35:07 +00:00
|
|
|
|
2016-12-17 01:12:38 +00:00
|
|
|
text_color_set(DW_COLOR_INFO);
|
2015-07-27 00:35:07 +00:00
|
|
|
dw_printf ("\nFirst, check all button tone pairs. \n\n");
|
|
|
|
/* Max auto dialing rate is 10 per second. */
|
|
|
|
|
2016-12-17 01:12:38 +00:00
|
|
|
push_button (c, '1', 50); push_button (c, ' ', 50);
|
|
|
|
push_button (c, '2', 50); push_button (c, ' ', 50);
|
|
|
|
push_button (c, '3', 50); push_button (c, ' ', 50);
|
|
|
|
push_button (c, 'A', 50); push_button (c, ' ', 50);
|
2015-07-27 00:35:07 +00:00
|
|
|
|
2016-12-17 01:12:38 +00:00
|
|
|
push_button (c, '4', 50); push_button (c, ' ', 50);
|
|
|
|
push_button (c, '5', 50); push_button (c, ' ', 50);
|
|
|
|
push_button (c, '6', 50); push_button (c, ' ', 50);
|
|
|
|
push_button (c, 'B', 50); push_button (c, ' ', 50);
|
2015-07-27 00:35:07 +00:00
|
|
|
|
2016-12-17 01:12:38 +00:00
|
|
|
push_button (c, '7', 50); push_button (c, ' ', 50);
|
|
|
|
push_button (c, '8', 50); push_button (c, ' ', 50);
|
|
|
|
push_button (c, '9', 50); push_button (c, ' ', 50);
|
|
|
|
push_button (c, 'C', 50); push_button (c, ' ', 50);
|
2015-07-27 00:35:07 +00:00
|
|
|
|
2016-12-17 01:12:38 +00:00
|
|
|
push_button (c, '*', 50); push_button (c, ' ', 50);
|
|
|
|
push_button (c, '0', 50); push_button (c, ' ', 50);
|
|
|
|
push_button (c, '#', 50); push_button (c, ' ', 50);
|
|
|
|
push_button (c, 'D', 50); push_button (c, ' ', 50);
|
2015-07-27 00:35:07 +00:00
|
|
|
|
2016-12-17 01:12:38 +00:00
|
|
|
text_color_set(DW_COLOR_INFO);
|
2015-07-27 00:35:07 +00:00
|
|
|
dw_printf ("\nShould reject very short pulses.\n\n");
|
|
|
|
|
2016-12-17 01:12:38 +00:00
|
|
|
push_button (c, '1', 20); push_button (c, ' ', 50);
|
|
|
|
push_button (c, '1', 20); push_button (c, ' ', 50);
|
|
|
|
push_button (c, '1', 20); push_button (c, ' ', 50);
|
|
|
|
push_button (c, '1', 20); push_button (c, ' ', 50);
|
|
|
|
push_button (c, '1', 20); push_button (c, ' ', 50);
|
2015-07-27 00:35:07 +00:00
|
|
|
|
2016-12-17 01:12:38 +00:00
|
|
|
text_color_set(DW_COLOR_INFO);
|
2015-07-27 00:35:07 +00:00
|
|
|
dw_printf ("\nTest timeout after inactivity.\n\n");
|
|
|
|
/* For this test we use 1 second. */
|
2015-07-27 01:17:23 +00:00
|
|
|
/* In practice, it will probably more like 5. */
|
2015-07-27 00:35:07 +00:00
|
|
|
|
2016-12-17 01:12:38 +00:00
|
|
|
push_button (c, '1', 250); push_button (c, ' ', 500);
|
|
|
|
push_button (c, '2', 250); push_button (c, ' ', 500);
|
|
|
|
push_button (c, '3', 250); push_button (c, ' ', 1200);
|
2015-07-27 00:35:07 +00:00
|
|
|
|
2016-12-17 01:12:38 +00:00
|
|
|
push_button (c, '7', 250); push_button (c, ' ', 500);
|
|
|
|
push_button (c, '8', 250); push_button (c, ' ', 500);
|
|
|
|
push_button (c, '9', 250); push_button (c, ' ', 1200);
|
2015-07-27 00:35:07 +00:00
|
|
|
|
|
|
|
/* Check for expected results. */
|
|
|
|
|
2016-12-17 01:12:38 +00:00
|
|
|
push_button (c, '?', 0);
|
|
|
|
|
|
|
|
exit (EXIT_SUCCESS);
|
2015-07-27 00:35:07 +00:00
|
|
|
|
|
|
|
} /* end main */
|
|
|
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
/* end dtmf.c */
|
|
|
|
|