├── .gitignore ├── DtmfDetector.cpp ├── DtmfDetector.hpp ├── DtmfGenerator.cpp ├── DtmfGenerator.hpp ├── Makefile ├── README.md ├── detect-au.cpp ├── example.cpp ├── scripts ├── README.md ├── au_file.py ├── goertzel.py ├── plot_T.png ├── plot_T.py ├── plot_au.png ├── plot_au.py └── tonegen.py ├── test-data ├── Dtmf-.au ├── Dtmf0.au ├── Dtmf1.au ├── Dtmf2.au ├── Dtmf3.au ├── Dtmf4.au ├── Dtmf5.au ├── Dtmf6.au ├── Dtmf7.au ├── Dtmf8.au ├── Dtmf9.au ├── DtmfA.au ├── DtmfB.au ├── DtmfC.au ├── DtmfD.au ├── DtmfStar.au └── README.txt └── types_cpp.hpp /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files 2 | *.slo 3 | *.lo 4 | *.o 5 | 6 | # Compiled Dynamic libraries 7 | *.so 8 | 9 | # Compiled Static libraries 10 | *.lai 11 | *.la 12 | *.a 13 | -------------------------------------------------------------------------------- /DtmfDetector.cpp: -------------------------------------------------------------------------------- 1 | /** Author: Plyashkevich Viatcheslav 2 | * 3 | * This program is free software; you can redistribute it and/or modify 4 | * it under the terms of the GNU Lesser General Public License 5 | * All rights reserved. 6 | */ 7 | 8 | #include 9 | #include "DtmfDetector.hpp" 10 | 11 | #if DEBUG 12 | #include 13 | #endif 14 | 15 | // This is the same function as in DtmfGenerator.cpp 16 | static inline INT32 MPY48SR(INT16 o16, INT32 o32) 17 | { 18 | UINT32 Temp0; 19 | INT32 Temp1; 20 | Temp0 = (((UINT16)o32 * o16) + 0x4000) >> 15; 21 | Temp1 = (INT16)(o32 >> 16) * o16; 22 | return (Temp1 << 1) + Temp0; 23 | } 24 | 25 | // The Goertzel algorithm. 26 | // For a good description and walkthrough, see: 27 | // https://sites.google.com/site/hobbydebraj/home/goertzel-algorithm-dtmf-detection 28 | // 29 | // Koeff0 Coefficient for the first frequency. 30 | // Koeff1 Coefficient for the second frequency. 31 | // arraySamples Input samples to process. Must be COUNT elements long. 32 | // Magnitude0 Detected magnitude of the first frequency. 33 | // Magnitude1 Detected magnitude of the second frequency. 34 | // COUNT The number of elements in arraySamples. Always equal to 35 | // SAMPLES in practice. 36 | static void goertzel_filter(INT16 Koeff0, INT16 Koeff1, const INT16 arraySamples[], INT32 *Magnitude0, INT32 *Magnitude1, UINT32 COUNT) 37 | { 38 | INT32 Temp0, Temp1; 39 | UINT16 ii; 40 | // Vk1_0 prev (first frequency) 41 | // Vk2_0 prev_prev (first frequency) 42 | // 43 | // Vk1_1 prev (second frequency) 44 | // Vk2_0 prev_prev (second frequency) 45 | INT32 Vk1_0 = 0, Vk2_0 = 0, Vk1_1 = 0, Vk2_1 = 0; 46 | 47 | // Iterate over all the input samples 48 | // For each sample, process the two frequencies we're interested in: 49 | // output = Input + 2*coeff*prev - prev_prev 50 | // N.B. bit-shifting to the left achieves the multiplication by 2. 51 | for(ii = 0; ii < COUNT; ++ii) 52 | { 53 | Temp0 = MPY48SR(Koeff0, Vk1_0 << 1) - Vk2_0 + arraySamples[ii], 54 | Temp1 = MPY48SR(Koeff1, Vk1_1 << 1) - Vk2_1 + arraySamples[ii]; 55 | Vk2_0 = Vk1_0, 56 | Vk2_1 = Vk1_1; 57 | Vk1_0 = Temp0, 58 | Vk1_1 = Temp1; 59 | } 60 | 61 | // Magnitude: prev_prev**prev_prev + prev*prev - coeff*prev*prev_prev 62 | 63 | // TODO: what does shifting by 10 bits to the right achieve? Probably to 64 | // make room for the magnitude calculations. 65 | Vk1_0 >>= 10, 66 | Vk1_1 >>= 10, 67 | Vk2_0 >>= 10, 68 | Vk2_1 >>= 10; 69 | Temp0 = MPY48SR(Koeff0, Vk1_0 << 1), 70 | Temp1 = MPY48SR(Koeff1, Vk1_1 << 1); 71 | Temp0 = (INT16)Temp0 * (INT16)Vk2_0, 72 | Temp1 = (INT16)Temp1 * (INT16)Vk2_1; 73 | Temp0 = (INT16)Vk1_0 * (INT16)Vk1_0 + (INT16)Vk2_0 * (INT16)Vk2_0 - Temp0; 74 | Temp1 = (INT16)Vk1_1 * (INT16)Vk1_1 + (INT16)Vk2_1 * (INT16)Vk2_1 - Temp1; 75 | *Magnitude0 = Temp0, 76 | *Magnitude1 = Temp1; 77 | return; 78 | } 79 | 80 | 81 | // This is a GSM function, for concrete processors she may be replaced 82 | // for same processor's optimized function (norm_l) 83 | // This is a GSM function, for concrete processors she may be replaced 84 | // for same processor's optimized function (norm_l) 85 | // 86 | // This function is used for normalization. TODO: how exactly does it work? 87 | static inline INT16 norm_l(INT32 L_var1) 88 | { 89 | INT16 var_out; 90 | 91 | if (L_var1 == 0) 92 | { 93 | var_out = 0; 94 | } 95 | else 96 | { 97 | if (L_var1 == (INT32)0xffffffff) 98 | { 99 | var_out = 31; 100 | } 101 | else 102 | { 103 | if (L_var1 < 0) 104 | { 105 | L_var1 = ~L_var1; 106 | } 107 | 108 | for(var_out = 0; L_var1 < (INT32)0x40000000; var_out++) 109 | { 110 | L_var1 <<= 1; 111 | } 112 | } 113 | } 114 | 115 | return(var_out); 116 | } 117 | 118 | const UINT32 DtmfDetectorInterface::NUMBER_OF_BUTTONS; 119 | const unsigned DtmfDetector::COEFF_NUMBER; 120 | // These frequencies are slightly different to what is in the generator. 121 | // More importantly, they are also different to what is described at: 122 | // http://en.wikipedia.org/wiki/Dual-tone_multi-frequency_signaling 123 | // 124 | // Some frequencies seem to match musical notes: 125 | // http://www.geocities.jp/pyua51113/artist_data/ki_setsumei.html 126 | // http://en.wikipedia.org/wiki/Piano_key_frequencies 127 | // 128 | // It seems this is done to simplify harmonic detection. 129 | // 130 | const INT16 DtmfDetector::CONSTANTS[COEFF_NUMBER] = { 131 | 27860, // 0: 706Hz, harmonics include: 78Hz, 235Hz, 3592Hz 132 | 26745, // 1: 784Hz, apparently a high G, harmonics: 78Hz 133 | 25529, // 2: 863Hz, harmonics: 78Hz 134 | 24216, // 3: 941Hz, harmonics: 78Hz, 235Hz, 314Hz 135 | 19747, // 4: 1176Hz, harmonics: 78Hz, 235Hz, 392Hz, 3529Hz 136 | 16384, // 5: 1333Hz, harmonics: 78Hz 137 | 12773, // 6: 1490Hz, harmonics: 78Hz, 2980Hz 138 | 8967, // 7: 1547Hz, harmonics: 314Hz, 392Hz 139 | // The next coefficients correspond to frequencies of harmonics of the 140 | // near-DTMF frequencies above, as well as of other frequencies listed 141 | // below. 142 | 21319, // 1098Hz 143 | 29769, // 549Hz 144 | // 549Hz is: 145 | // - half of 1098Hz (see above) 146 | // - 1/3 of 1633Hz, a real DTMF frequency (see DtmfGenerator) 147 | 32706, // 78Hz, a very low D# on a piano 148 | // 78Hz is a very convenient frequency, since its (approximately): 149 | // - 1/3 of 235Hz (not a DTMF frequency, but we do detect it, see below) 150 | // - 1/4 of 314Hz (not a DTMF frequency, but we do detect it, see below) 151 | // - 1/5 of 392Hz (not a DTMF frequency, but we do detect it, see below) 152 | // - 1/7 of 549Hz 153 | // - 1/9 of 706Hz 154 | // - 1/10 of 784Hz 155 | // - 1/11 of 863Hz 156 | // - 1/12 of 941Hz 157 | // - 1/14 of 1098Hz (not a DTMF frequency, but we do detect it, see above) 158 | // - 1/15 of 1176Hz 159 | // - 1/17 of 1333Hz 160 | // - 1/19 of 1490Hz 161 | 32210, // 235Hz 162 | // 235Hz is: 163 | // - 1/3 of 706Hz 164 | // - 1/4 of 941Hz 165 | // - 1/5 of 1176Hz 166 | // - 1/15 of 3529Hz (not a DTMF frequency, but we do detect it, see below) 167 | 31778, // 314Hz 168 | // 314Hz is: 169 | // - 1/3 of 941Hz 170 | // - 1/5 of 1547Hz 171 | // - 1/8 of 2510Hz (not a DTMF frequency, but we do detect it, see below) 172 | 31226, // 392Hz, apparently a middle-2 G 173 | // 392Hz is: 174 | // - 1/2 of 794Hz 175 | // - 1/3 of 1176Hz 176 | // - 1/4 of 1547Hz 177 | // - 1/9 of 3529Hz 178 | -1009, // 2039Hz TODO: why is this frequency useful? 179 | -12772, // 2510Hz, which is 8*314Hz 180 | -22811, // 2980Hz, which is 2*1490Hz 181 | -30555 // 3529Hz, 3*1176Hz, 5*706Hz 182 | }; 183 | INT32 DtmfDetector::powerThreshold = 328; 184 | INT32 DtmfDetector::dialTonesToOhersTones = 16; 185 | INT32 DtmfDetector::dialTonesToOhersDialTones = 6; 186 | const INT32 DtmfDetector::SAMPLES = 102; 187 | //-------------------------------------------------------------------- 188 | DtmfDetector::DtmfDetector(INT32 frameSize_): frameSize(frameSize_) 189 | { 190 | // 191 | // This array is padded to keep the last batch, which is smaller 192 | // than SAMPLES, from the previous call to dtmfDetecting. 193 | // 194 | pArraySamples = new INT16 [frameSize + SAMPLES]; 195 | internalArray = new INT16 [SAMPLES]; 196 | frameCount = 0; 197 | prevDialButton = ' '; 198 | permissionFlag = 0; 199 | } 200 | //--------------------------------------------------------------------- 201 | DtmfDetector::~DtmfDetector() 202 | { 203 | delete [] pArraySamples; 204 | delete [] internalArray; 205 | } 206 | 207 | void DtmfDetector::dtmfDetecting(INT16 input_array[]) 208 | { 209 | // ii Variable for iteration 210 | // temp_dial_button A tone detected in part of the input_array 211 | UINT32 ii; 212 | char temp_dial_button; 213 | 214 | // Copy the input array into the middle of pArraySamples. 215 | // I think the first frameCount samples contain the last batch from the 216 | // previous call to this function. 217 | for(ii=0; ii < frameSize; ii++) 218 | { 219 | pArraySamples[ii + frameCount] = input_array[ii]; 220 | } 221 | 222 | frameCount += frameSize; 223 | // Read index into pArraySamples that corresponds to the current batch. 224 | UINT32 temp_index = 0; 225 | // If don't have enough samples to process an entire batch, then don't 226 | // do anything. 227 | if(frameCount >= SAMPLES) 228 | { 229 | // Process samples while we still have enough for an entire 230 | // batch. 231 | while(frameCount >= SAMPLES) 232 | { 233 | // Determine the tone present in the current batch 234 | temp_dial_button = DTMF_detection(&pArraySamples[temp_index]); 235 | 236 | // Determine if we should register it as a new tone, or 237 | // ignore it as a continuation of a previously 238 | // registered tone. 239 | // 240 | // This seems buggy. Consider a sequence of three 241 | // tones, with each tone corresponding to the dominant 242 | // tone in a batch of SAMPLES samples: 243 | // 244 | // SILENCE TONE_A TONE_B will get registered as TONE_B 245 | // 246 | // TONE_A will be ignored. 247 | if(permissionFlag) 248 | { 249 | if(temp_dial_button != ' ') 250 | { 251 | dialButtons[indexForDialButtons++] = temp_dial_button; 252 | // NUL-terminate the string. 253 | dialButtons[indexForDialButtons] = 0; 254 | // If we've gone out of bounds, wrap around. 255 | if(indexForDialButtons >= 64) 256 | indexForDialButtons = 0; 257 | } 258 | permissionFlag = 0; 259 | } 260 | 261 | // If we've gone from silence to a tone, set the flag. 262 | // The tone will be registered in the next iteration. 263 | if((temp_dial_button != ' ') && (prevDialButton == ' ')) 264 | { 265 | permissionFlag = 1; 266 | } 267 | 268 | // Store the current tone. In light of the above 269 | // behaviour, all that really matters is whether it was 270 | // a tone or silence. Finally, move on to the next 271 | // batch. 272 | prevDialButton = temp_dial_button; 273 | 274 | temp_index += SAMPLES; 275 | frameCount -= SAMPLES; 276 | } 277 | 278 | // 279 | // We have frameCount samples left to process, but it's not 280 | // enough for an entire batch. Shift these left-over 281 | // samples to the beginning of our array and deal with them 282 | // next time this function is called. 283 | // 284 | for(ii=0; ii < frameCount; ii++) 285 | { 286 | pArraySamples[ii] = pArraySamples[ii + temp_index]; 287 | } 288 | } 289 | 290 | } 291 | //----------------------------------------------------------------- 292 | // Detect a tone in a single batch of samples (SAMPLES elements). 293 | char DtmfDetector::DTMF_detection(INT16 short_array_samples[]) 294 | { 295 | INT32 Dial=32, Sum; 296 | char return_value=' '; 297 | unsigned ii; 298 | Sum = 0; 299 | 300 | // Dial TODO: what is this? 301 | // Sum Sum of the absolute values of samples in the batch. 302 | // return_value The tone detected in this batch (can be silence). 303 | // ii Iteration variable 304 | 305 | // Quick check for silence. 306 | for(ii = 0; ii < SAMPLES; ii ++) 307 | { 308 | if(short_array_samples[ii] >= 0) 309 | Sum += short_array_samples[ii]; 310 | else 311 | Sum -= short_array_samples[ii]; 312 | } 313 | Sum /= SAMPLES; 314 | if(Sum < powerThreshold) 315 | return ' '; 316 | 317 | //Normalization 318 | // Iterate over each sample. 319 | // First, adjusting Dial to an appropriate value for the batch. 320 | for(ii = 0; ii < SAMPLES; ii++) 321 | { 322 | T[0] = static_cast(short_array_samples[ii]); 323 | if(T[0] != 0) 324 | { 325 | if(Dial > norm_l(T[0])) 326 | { 327 | Dial = norm_l(T[0]); 328 | } 329 | } 330 | } 331 | 332 | Dial -= 16; 333 | 334 | // Next, utilize Dial for scaling and populate internalArray. 335 | for(ii = 0; ii < SAMPLES; ii++) 336 | { 337 | T[0] = short_array_samples[ii]; 338 | internalArray[ii] = static_cast(T[0] << Dial); 339 | } 340 | 341 | 342 | //Frequency detection 343 | goertzel_filter(CONSTANTS[0], CONSTANTS[1], internalArray, &T[0], &T[1], SAMPLES); 344 | goertzel_filter(CONSTANTS[2], CONSTANTS[3], internalArray, &T[2], &T[3], SAMPLES); 345 | goertzel_filter(CONSTANTS[4], CONSTANTS[5], internalArray, &T[4], &T[5], SAMPLES); 346 | goertzel_filter(CONSTANTS[6], CONSTANTS[7], internalArray, &T[6], &T[7], SAMPLES); 347 | goertzel_filter(CONSTANTS[8], CONSTANTS[9], internalArray, &T[8], &T[9], SAMPLES); 348 | goertzel_filter(CONSTANTS[10], CONSTANTS[11], internalArray, &T[10], &T[11], SAMPLES); 349 | goertzel_filter(CONSTANTS[12], CONSTANTS[13], internalArray, &T[12], &T[13], SAMPLES); 350 | goertzel_filter(CONSTANTS[14], CONSTANTS[15], internalArray, &T[14], &T[15], SAMPLES); 351 | goertzel_filter(CONSTANTS[16], CONSTANTS[17], internalArray, &T[16], &T[17], SAMPLES); 352 | 353 | #if DEBUG 354 | for (ii = 0; ii < COEFF_NUMBER; ++ii) 355 | printf("%d ", T[ii]); 356 | printf("\n"); 357 | #endif 358 | 359 | INT32 Row = 0; 360 | INT32 Temp = 0; 361 | // Row Index of the maximum row frequency in T 362 | // Temp The frequency at the maximum row/column (gets reused 363 | // below). 364 | //Find max row(low frequences) tones 365 | for(ii = 0; ii < 4; ii++) 366 | { 367 | if(Temp < T[ii]) 368 | { 369 | Row = ii; 370 | Temp = T[ii]; 371 | } 372 | } 373 | 374 | // Column Index of the maximum column frequency in T 375 | INT32 Column = 4; 376 | Temp = 0; 377 | //Find max column(high frequences) tones 378 | for(ii = 4; ii < 8; ii++) 379 | { 380 | if(Temp < T[ii]) 381 | { 382 | Column = ii; 383 | Temp = T[ii]; 384 | } 385 | } 386 | 387 | Sum=0; 388 | //Find average value dial tones without max row and max column 389 | for(ii = 0; ii < 10; ii++) 390 | { 391 | Sum += T[ii]; 392 | } 393 | Sum -= T[Row]; 394 | Sum -= T[Column]; 395 | // N.B. Divide by 8 396 | Sum >>= 3; 397 | 398 | // N.B. looks like avoiding a divide by zero. 399 | if(!Sum) 400 | Sum = 1; 401 | 402 | //If relations max row and max column to average value 403 | //are less then threshold then return 404 | // This means the tones are too quiet compared to the other, non-max 405 | // DTMF frequencies. 406 | if(T[Row]/Sum < dialTonesToOhersDialTones) 407 | return ' '; 408 | if(T[Column]/Sum < dialTonesToOhersDialTones) 409 | return ' '; 410 | 411 | // Next, check if the volume of the row and column frequencies 412 | // is similar. If they are different, then they aren't part of 413 | // the same tone. 414 | // 415 | // In the literature, this is known as "twist". 416 | //If relations max colum to max row is large then 4 then return 417 | if(T[Row] < (T[Column] >> 2)) return ' '; 418 | //If relations max colum to max row is large then 4 then return 419 | // The reason why the twist calculations aren't symmetric is that the 420 | // allowed ratios for normal and reverse twist are different. 421 | if(T[Column] < ((T[Row] >> 1) - (T[Row] >> 3))) return ' '; 422 | 423 | // N.B. looks like avoiding a divide by zero. 424 | for(ii = 0; ii < COEFF_NUMBER; ii++) 425 | if(T[ii] == 0) 426 | T[ii] = 1; 427 | 428 | //If relations max row and max column to all other tones are less then 429 | //threshold then return 430 | // Check for the presence of strong harmonics. 431 | for(ii = 10; ii < COEFF_NUMBER; ii ++) 432 | { 433 | if(T[Row]/T[ii] < dialTonesToOhersTones) 434 | return ' '; 435 | if(T[Column]/T[ii] < dialTonesToOhersTones) 436 | return ' '; 437 | } 438 | 439 | 440 | //If relations max row and max column tones to other dial tones are 441 | //less then threshold then return 442 | for(ii = 0; ii < 10; ii ++) 443 | { 444 | // TODO: 445 | // The next two nested if's can be collapsed into a single 446 | // if-statement. Basically, he's checking that the current 447 | // tone is NOT the maximum tone. 448 | // 449 | // A simpler check would have been (ii != Column && ii != Row) 450 | // 451 | if(T[ii] != T[Column]) 452 | { 453 | if(T[ii] != T[Row]) 454 | { 455 | if(T[Row]/T[ii] < dialTonesToOhersDialTones) 456 | return ' '; 457 | if(Column != 4) 458 | { 459 | // Column == 4 corresponds to 1176Hz. 460 | // TODO: what is so special about this frequency? 461 | if(T[Column]/T[ii] < dialTonesToOhersDialTones) 462 | return ' '; 463 | } 464 | else 465 | { 466 | if(T[Column]/T[ii] < (dialTonesToOhersDialTones/3)) 467 | return ' '; 468 | } 469 | } 470 | } 471 | } 472 | 473 | //We are choosed a push button 474 | // Determine the tone based on the row and column frequencies. 475 | switch (Row) 476 | { 477 | case 0: 478 | switch (Column) 479 | { 480 | case 4: 481 | return_value='1'; 482 | break; 483 | case 5: 484 | return_value='2'; 485 | break; 486 | case 6: 487 | return_value='3'; 488 | break; 489 | case 7: 490 | return_value='A'; 491 | break; 492 | }; 493 | break; 494 | case 1: 495 | switch (Column) 496 | { 497 | case 4: 498 | return_value='4'; 499 | break; 500 | case 5: 501 | return_value='5'; 502 | break; 503 | case 6: 504 | return_value='6'; 505 | break; 506 | case 7: 507 | return_value='B'; 508 | break; 509 | }; 510 | break; 511 | case 2: 512 | switch (Column) 513 | { 514 | case 4: 515 | return_value='7'; 516 | break; 517 | case 5: 518 | return_value='8'; 519 | break; 520 | case 6: 521 | return_value='9'; 522 | break; 523 | case 7: 524 | return_value='C'; 525 | break; 526 | }; 527 | break; 528 | case 3: 529 | switch (Column) 530 | { 531 | case 4: 532 | return_value='*'; 533 | break; 534 | case 5: 535 | return_value='0'; 536 | break; 537 | case 6: 538 | return_value='#'; 539 | break; 540 | case 7: 541 | return_value='D'; 542 | break; 543 | } 544 | } 545 | 546 | return return_value; 547 | } 548 | -------------------------------------------------------------------------------- /DtmfDetector.hpp: -------------------------------------------------------------------------------- 1 | /** Author: Plyashkevich Viatcheslav 2 | * 3 | * This program is free software; you can redistribute it and/or modify 4 | * it under the terms of the GNU Lesser General Public License 5 | * All rights reserved. 6 | */ 7 | 8 | 9 | #ifndef DTMF_DETECTOR 10 | #define DTMF_DETECTOR 11 | 12 | #include "types_cpp.hpp" 13 | 14 | 15 | typedef Types::Int32 INT32; 16 | typedef Types::Uint32 UINT32; 17 | typedef Types::Int16 INT16; 18 | typedef Types::Uint16 UINT16; 19 | 20 | 21 | // DTMF detector object 22 | 23 | // N.B. Not sure why this interface is necessary, as the only class to 24 | // implement it is the DtmfDetector. 25 | class DtmfDetectorInterface 26 | { 27 | public: 28 | // The maximum number of detected tones 29 | static const UINT32 NUMBER_OF_BUTTONS = 65; 30 | // A fixed-size array to store the detected tones. The array is 31 | // NUL-terminated. 32 | char dialButtons[NUMBER_OF_BUTTONS]; 33 | // A constant pointer to expose dialButtons for read-only access 34 | char *const pDialButtons; 35 | // The number of tones detected (stored in dialButtons) 36 | mutable INT16 indexForDialButtons; 37 | public: 38 | INT32 getIndexDialButtons() // The number of detected push buttons, max number = 64 39 | const 40 | { 41 | return indexForDialButtons; 42 | } 43 | char *getDialButtonsArray() // Address of array, where store detected push buttons 44 | const 45 | { 46 | return pDialButtons; 47 | } 48 | void zerosIndexDialButton() // Zeros of a detected button array 49 | const 50 | { 51 | indexForDialButtons = 0; 52 | } 53 | 54 | DtmfDetectorInterface():indexForDialButtons(0), pDialButtons(dialButtons) 55 | { 56 | dialButtons[0] = 0; 57 | } 58 | }; 59 | 60 | class DtmfDetector : public DtmfDetectorInterface 61 | { 62 | protected: 63 | // These coefficients include the 8 DTMF frequencies plus 10 harmonics. 64 | static const unsigned COEFF_NUMBER=18; 65 | // A fixed-size array to hold the coefficients 66 | static const INT16 CONSTANTS[COEFF_NUMBER]; 67 | // This array keeps the entire buffer PLUS a single batch. 68 | INT16 *pArraySamples; 69 | // The magnitude of each coefficient in the current frame. Populated 70 | // by goertzel_filter 71 | INT32 T[COEFF_NUMBER]; 72 | // An array of size SAMPLES. Used as input to the Goertzel function. 73 | INT16 *internalArray; 74 | // The size of the entire buffer used for processing samples. 75 | // Specified at construction time. 76 | const INT32 frameSize; //Size of a frame is measured in INT16(word) 77 | // The number of samples to utilize in a single call to Goertzel. 78 | // This is referred to as a frame. 79 | static const INT32 SAMPLES; 80 | // This gets used for a variety of purposes. Most notably, it indicates 81 | // the start of the circular buffer at the start of ::dtmfDetecting. 82 | INT32 frameCount; 83 | // The tone detected by the previous call to DTMF_detection. 84 | char prevDialButton; 85 | // This flag is used to aggregate adjacent tones and spaces, i.e. 86 | // 87 | // 111111 222222 -> 12 (where a space represents silence) 88 | // 89 | // 1 means that during the previous iteration, we entered a tone 90 | // (gone from silence to non-silence). 91 | // 0 means otherwise. 92 | // 93 | // While this is a member variable, it can by all means be a local 94 | // variable in dtmfDetecting. 95 | // 96 | // N.B. seems to not work. In practice, you get this: 97 | // 98 | // 111111 222222 -> 1111122222 99 | char permissionFlag; 100 | 101 | // Used for quickly determining silence within a batch. 102 | static INT32 powerThreshold; 103 | // 104 | // dialTonesToOhersTone is the higher ratio. 105 | // dialTonesToOhersDialTones is the lower ratio. 106 | // 107 | // It seems like the aim of this implementation is to be more tolerant 108 | // towards strong "dial tones" than "tones". The latter include 109 | // harmonics. 110 | // 111 | static INT32 dialTonesToOhersTones; 112 | static INT32 dialTonesToOhersDialTones; 113 | 114 | // This protected function determines the tone present in a single frame. 115 | char DTMF_detection(INT16 short_array_samples[]); 116 | public: 117 | 118 | // frameSize_ - input frame size 119 | DtmfDetector(INT32 frameSize_); 120 | ~DtmfDetector(); 121 | 122 | void dtmfDetecting(INT16 inputFrame[]); // The DTMF detection. 123 | // The size of a inputFrame must be equal of a frameSize_, who 124 | // was set in constructor. 125 | }; 126 | 127 | #endif 128 | -------------------------------------------------------------------------------- /DtmfGenerator.cpp: -------------------------------------------------------------------------------- 1 | /** Author: Plyashkevich Viatcheslav 2 | * 3 | * This program is free software; you can redistribute it and/or modify 4 | * it under the terms of the GNU Lesser General Public License 5 | * All rights reserved. 6 | */ 7 | 8 | #include "DtmfGenerator.hpp" 9 | 10 | // Multiplicaton of two fixed-point numbers 11 | static inline INT32 MPY48SR(INT16 o16, INT32 o32) 12 | { 13 | // http://stackoverflow.com/questions/12864216/why-perform-multiplication-in-this-way 14 | UINT32 Temp0; 15 | INT32 Temp1; 16 | // A1. get the lower 16 bits of the 32-bit param 17 | // A2. multiply them with the 16-bit param 18 | // A3. add 16384 19 | // A4. bitshift to the right by 15 (TODO: why 15?) 20 | Temp0 = (((UINT16)o32 * o16) + 0x4000) >> 15; 21 | // B1. Get the higher 16 bits of the 32-bit param 22 | // B2. Multiply them with the 16-bit param 23 | Temp1 = (INT16)(o32 >> 16) * o16; 24 | // 1. Shift B to the left (TODO: why do this?) 25 | // 2. Combine with A and return 26 | return (Temp1 << 1) + Temp0; 27 | } 28 | 29 | // Generate a dual-tone multiple frequency signal and write it to a buffer. 30 | // 31 | // Coeff0 A coefficient for the first frequency 32 | // Coeff1 A coefficient for the second frequency 33 | // y The output buffer 34 | // COUNT Size of the output buffer 35 | // y1_0 ? 36 | // y1_1 37 | // y2_0 38 | // y2_1 39 | static void frequency_oscillator(INT16 Coeff0, INT16 Coeff1, 40 | INT16 y[], UINT32 COUNT, 41 | INT32 *y1_0, INT32 *y1_1, 42 | INT32 *y2_0, INT32 *y2_1) 43 | { 44 | // the register keyword isn't really useful and achieves little. 45 | // http://www.drdobbs.com/keywords-that-arent-or-comments-by-anoth/184403859 46 | register INT32 Temp1_0, Temp1_1, Temp2_0, Temp2_1, Temp0, Temp1, Subject; 47 | UINT16 ii; 48 | 49 | // Write the parameters to the registers. 50 | // As far as I can tell, using commas instead of the semicolon does not 51 | // change the program semantically. 52 | // http://en.wikipedia.org/wiki/Comma_operator 53 | Temp1_0 = *y1_0, 54 | Temp1_1 = *y1_1, 55 | Temp2_0 = *y2_0, 56 | Temp2_1 = *y2_1, 57 | // TODO: what is the purpose of Subject? 58 | Subject = Coeff0 * Coeff1; 59 | for(ii = 0; ii < COUNT; ++ii) 60 | { 61 | Temp0 = MPY48SR(Coeff0, Temp1_0 << 1) - Temp2_0, 62 | Temp1 = MPY48SR(Coeff1, Temp1_1 << 1) - Temp2_1; 63 | Temp2_0 = Temp1_0, 64 | Temp2_1 = Temp1_1; 65 | Temp1_0 = Temp0, 66 | Temp1_1 = Temp1, 67 | Temp0 += Temp1; 68 | // "X >>= Y" means: "X = X >> Y", i.e. shift X right by Y bits. 69 | // http://en.wikipedia.org/wiki/Operators_in_C_and_C%2B%2B 70 | if(Subject) 71 | Temp0 >>= 1; 72 | y[ii] = (INT16)Temp0; 73 | } 74 | 75 | *y1_0 = Temp1_0, 76 | *y1_1 = Temp1_1, 77 | *y2_0 = Temp2_0, 78 | *y2_1 = Temp2_1; 79 | } 80 | 81 | // These frequencies match what is described on: 82 | // http://en.wikipedia.org/wiki/Dual-tone_multi-frequency_signaling 83 | const INT16 DtmfGenerator::tempCoeff[8] = { 84 | //Low frequencies (row) 85 | 27980, // 697Hz 86 | 26956, // 770Hz 87 | 25701, // 852Hz 88 | 24218, // 941Hz 89 | //High frequencies (column) 90 | 19073, // 1209Hz 91 | 16325, // 1335Hz 92 | 13085, // 1477Hz 93 | 9315 // 1633Hz 94 | }; 95 | 96 | DtmfGenerator::DtmfGenerator(INT32 FrameSize, INT32 DurationPush, INT32 DurationPause) 97 | { 98 | // N.B. bit-shifting to the right corresponds to a multiplication by 8. 99 | // Determine the number of buffers each tone and silence should occupy. 100 | countDurationPushButton = (DurationPush << 3)/FrameSize + 1; 101 | countDurationPause = (DurationPause << 3)/FrameSize + 1; 102 | sizeOfFrame = FrameSize; 103 | readyFlag = 1; 104 | countLengthDialButtonsArray = 0; 105 | } 106 | 107 | // The destructor does nothing. 108 | DtmfGenerator::~DtmfGenerator() 109 | { 110 | } 111 | 112 | void DtmfGenerator::dtmfGenerating(INT16 y[]) 113 | { 114 | if(readyFlag) return; 115 | 116 | // Iterate over all the tones we've been instructed to generate 117 | while(countLengthDialButtonsArray > 0) 118 | { 119 | // If we're starting a new tone, then determine the 120 | // coefficients for it. Otherwise, we're mid-tone, so we can 121 | // just use whatever is already set. 122 | if(countDurationPushButton == tempCountDurationPushButton) 123 | { 124 | // N.B. y2_1 and y2_2 always seem to be 31000 125 | switch(pushDialButtons[count]) 126 | { 127 | case '1': 128 | tempCoeff1 = tempCoeff[0]; 129 | tempCoeff2 = tempCoeff[4]; 130 | y1_1 = tempCoeff[0]; 131 | y2_1 = 31000; 132 | y1_2 = tempCoeff[4]; 133 | y2_2 = 31000; 134 | break; 135 | case '2': 136 | tempCoeff1 = tempCoeff[0]; 137 | tempCoeff2 = tempCoeff[5]; 138 | y1_1 = tempCoeff[0]; 139 | y2_1 = 31000; 140 | y1_2 = tempCoeff[5]; 141 | y2_2 = 31000; 142 | break; 143 | case '3': 144 | tempCoeff1 = tempCoeff[0]; 145 | tempCoeff2 = tempCoeff[6]; 146 | y1_1 = tempCoeff[0]; 147 | y2_1 = 31000; 148 | y1_2 = tempCoeff[6]; 149 | y2_2 = 31000; 150 | break; 151 | case 'A': 152 | tempCoeff1 = tempCoeff[0]; 153 | tempCoeff2 = tempCoeff[7]; 154 | y1_1 = tempCoeff[0]; 155 | y2_1 = 31000; 156 | y1_2 = tempCoeff[7]; 157 | y2_2 = 31000; 158 | break; 159 | case '4': 160 | tempCoeff1 = tempCoeff[1]; 161 | tempCoeff2 = tempCoeff[4]; 162 | y1_1 = tempCoeff[1]; 163 | y2_1 = 31000; 164 | y1_2 = tempCoeff[4]; 165 | y2_2 = 31000; 166 | break; 167 | case '5': 168 | tempCoeff1 = tempCoeff[1]; 169 | tempCoeff2 = tempCoeff[5]; 170 | y1_1 = tempCoeff[1]; 171 | y2_1 = 31000; 172 | y1_2 = tempCoeff[5]; 173 | y2_2 = 31000; 174 | break; 175 | case '6': 176 | tempCoeff1 = tempCoeff[1]; 177 | tempCoeff2 = tempCoeff[6]; 178 | y1_1 = tempCoeff[1]; 179 | y2_1 = 31000; 180 | y1_2 = tempCoeff[6]; 181 | y2_2 = 31000; 182 | break; 183 | case 'B': 184 | tempCoeff1 = tempCoeff[1]; 185 | tempCoeff2 = tempCoeff[7]; 186 | y1_1 = tempCoeff[1]; 187 | y2_1 = 31000; 188 | y1_2 = tempCoeff[7]; 189 | y2_2 = 31000; 190 | break; 191 | case '7': 192 | tempCoeff1 = tempCoeff[2]; 193 | tempCoeff2 = tempCoeff[4]; 194 | y1_1 = tempCoeff[2]; 195 | y2_1 = 31000; 196 | y1_2 = tempCoeff[4]; 197 | y2_2 = 31000; 198 | break; 199 | case '8': 200 | tempCoeff1 = tempCoeff[2]; 201 | tempCoeff2 = tempCoeff[5]; 202 | y1_1 = tempCoeff[2]; 203 | y2_1 = 31000; 204 | y1_2 = tempCoeff[5]; 205 | y2_2 = 31000; 206 | break; 207 | case '9': 208 | tempCoeff1 = tempCoeff[2]; 209 | tempCoeff2 = tempCoeff[6]; 210 | y1_1 = tempCoeff[2]; 211 | y2_1 = 31000; 212 | y1_2 = tempCoeff[6]; 213 | y2_2 = 31000; 214 | break; 215 | case 'C': 216 | tempCoeff1 = tempCoeff[2]; 217 | tempCoeff2 = tempCoeff[7]; 218 | y1_1 = tempCoeff[2]; 219 | y2_1 = 31000; 220 | y1_2 = tempCoeff[7]; 221 | y2_2 = 31000; 222 | break; 223 | case '*': 224 | tempCoeff1 = tempCoeff[3]; 225 | tempCoeff2 = tempCoeff[4]; 226 | y1_1 = tempCoeff[3]; 227 | y2_1 = 31000; 228 | y1_2 = tempCoeff[4]; 229 | y2_2 = 31000; 230 | break; 231 | case '0': 232 | tempCoeff1 = tempCoeff[3]; 233 | tempCoeff2 = tempCoeff[5]; 234 | y1_1 = tempCoeff[3]; 235 | y2_1 = 31000; 236 | y1_2 = tempCoeff[5]; 237 | y2_2 = 31000; 238 | break; 239 | case '#': 240 | tempCoeff1 = tempCoeff[3]; 241 | tempCoeff2 = tempCoeff[6]; 242 | y1_1 = tempCoeff[3]; 243 | y2_1 = 31000; 244 | y1_2 = tempCoeff[6]; 245 | y2_2 = 31000; 246 | break; 247 | case 'D': 248 | tempCoeff1 = tempCoeff[3]; 249 | tempCoeff2 = tempCoeff[7]; 250 | y1_1 = tempCoeff[3]; 251 | y2_1 = 31000; 252 | y1_2 = tempCoeff[7]; 253 | y2_2 = 31000; 254 | break; 255 | default: 256 | tempCoeff1 = tempCoeff2 = 0; 257 | y1_1 = 0; 258 | y2_1 = 0; 259 | y1_2 = 0; 260 | y2_2 = 0; 261 | } 262 | } 263 | // We've determined the coefficients for the current tone. 264 | // Now determine whether we're in the middle of a tone or 265 | // a pause. In either case, we fill up the output buffer 266 | // and return. 267 | while(tempCountDurationPushButton>0) 268 | { 269 | // Handle the dial tone. 270 | --tempCountDurationPushButton; 271 | 272 | frequency_oscillator(tempCoeff1, tempCoeff2, 273 | y, sizeOfFrame, 274 | &y1_1, &y1_2, 275 | &y2_1, &y2_2 276 | ); 277 | return; 278 | } 279 | 280 | while(tempCountDurationPause>0) 281 | { 282 | // Handle silence. Simply zeros the buffer. 283 | --tempCountDurationPause; 284 | for(INT32 ii=0; ii 20) countLengthDialButtonsArray = 20; 324 | for(INT32 ii=0; ii 2 | * 3 | * This program is free software; you can redistribute it and/or modify 4 | * it under the terms of the GNU Lesser General Public License 5 | * All rights reserved. 6 | */ 7 | 8 | 9 | 10 | #ifndef _DTMF_GENERATOR_ 11 | #define _DTMF_GENERATOR_ 12 | 13 | #include "types_cpp.hpp" 14 | 15 | 16 | typedef Types::Int32 INT32; 17 | typedef Types::Uint32 UINT32; 18 | typedef Types::Int16 INT16; 19 | typedef Types::Uint16 UINT16; 20 | 21 | 22 | 23 | // Class DtmfGenerator is used for generating of DTMF 24 | // frequences, corresponding push buttons. 25 | 26 | class DtmfGenerator 27 | { 28 | // The coefficients for each of the 8 frequencies. 29 | // The four low frequencies come first, followed by the four high 30 | // frequencies. 31 | // The coefficients are fixed for a sampling rate of 8KHz. 32 | static const INT16 tempCoeff[8]; 33 | // Number of buffers a single tone should occupy. 34 | // Initialized in the constructor. 35 | INT32 countDurationPushButton; 36 | // Number of buffers a single silence should occupy. 37 | // Initialized in the constructor. 38 | INT32 countDurationPause; 39 | // Number of buffers we have to write to complete the current tone. 40 | INT32 tempCountDurationPushButton; 41 | // Number of buffers we have to write to complete the current silence. 42 | INT32 tempCountDurationPause; 43 | // Set to 0 while there is still something left to output, i.e. not all 44 | // of the tones in pushDialButtons have been completely output. This 45 | // means: "please wait until I'm done before sending me more input." 46 | // Set to 1 when we've output everything we've been asked to. This 47 | // means: "please give me more input". 48 | INT32 readyFlag; 49 | // A fixed-size array of dial tones to generate. 50 | char pushDialButtons[20]; 51 | // The number of tones we still have to generate (gets decremented 52 | // each time a tone is generated). 53 | UINT32 countLengthDialButtonsArray; 54 | // The index of the current tone we're generating (gets incremented 55 | // each time a tone is generated). 56 | UINT32 count; 57 | // The size of the output buffer we will be writing to. 58 | INT32 sizeOfFrame; 59 | 60 | short tempCoeff1, tempCoeff2; 61 | INT32 y1_1, y1_2, y2_1, y2_2; 62 | 63 | public: 64 | 65 | // FrameSize - Size of frame, DurationPush - duration pushed button in ms 66 | // DurationPause - duration pause between pushed buttons in ms 67 | DtmfGenerator(INT32 FrameSize, INT32 DurationPush=70, INT32 DurationPause=50); 68 | ~DtmfGenerator(); 69 | 70 | //That function will be run on each outcoming frame 71 | // 72 | // This function performs the actual generation of the signal. 73 | // 74 | // The size of out (the buffer to which the generated signal will 75 | // be output to) is SizeOfFrame (specified in constructor. Does 76 | // nothing if ready_flag is non-zero. 77 | void dtmfGenerating(INT16 out[]); 78 | 79 | // If transmitNewDialButtonsArray return 1 then the dialButtonsArray will be transmitted 80 | // if 0, transmit is not possible and is needed to wait (nothing will be transmitted) 81 | // Warning! lengthDialButtonsArray must to be < 21 and != 0, if lengthDialButtonsArray will be > 20 82 | // will be transmitted only first 20 dial buttons 83 | // if lengthDialButtonsArray == 0 will be returned 1 and nothing will be transmitted 84 | INT32 transmitNewDialButtonsArray(char dialButtonsArray[], UINT32 lengthDialButtonsArray); 85 | 86 | //Reset generation 87 | void dtmfGeneratorReset() 88 | { 89 | countLengthDialButtonsArray = 0; 90 | count = 0; 91 | readyFlag = 1; 92 | } 93 | 94 | 95 | //If getReadyFlag return 1 then a new button's array may be transmitted 96 | // if 0 transmit is not possible and is needed to wait 97 | INT32 getReadyFlag() const 98 | { 99 | return readyFlag?1:0; 100 | } 101 | }; 102 | 103 | /* Example: 104 | 105 | DtmfGenerator dtmfGen( 256, // frame size 106 | 60, // duration in ms of a pressure of a button 107 | 50 // duration pause between pressures of buttons 108 | ); 109 | 110 | // |dtmf generation 60 ms| pause 50 ms |dtmf generation 60 ms| pause 50 ms | and so on... 111 | 112 | INT16 y[256]; // outcoming frame 113 | 114 | char pushButtons[20] = {'1', '2', '#', '*', '0', 'D'....}; // array of push buttons, 115 | // will be generate dtmf frequencies corresponding of push buttons 116 | // 1, 2, #, *, 0, D and so on. 117 | 118 | volatile int break_current_action = 0; 119 | while(1) 120 | { 121 | dtmfGen.transmitNewDialButtonsArray(pushButtons, 20); // new generation 122 | while(!dtmfGen.getReadyFlag()) 123 | { 124 | dtmfGen.dtmfGenerating(y); // in the y will be writed 256 (this size define in constructor) 125 | // INT16 dtmf samples, it samples will be replaced with new 256 samples in each iteration, 126 | // after that this array may be transfered to peripherals or to auxialiry 127 | // processing 128 | if(break_current_action) // some extern event occur (interrupt for example) 129 | dtmfGen.dtmfGeneratorReset(); // stop current generation 130 | } 131 | } 132 | 133 | */ 134 | 135 | #endif 136 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # 2 | # http://www.gnu.org/software/make/manual/make.html 3 | # 4 | CPP=g++ 5 | INCLUDES= 6 | CFLAGS=-Wall -ggdb 7 | LDFLAGS= 8 | EXE=example.out detect-au.out 9 | SRC=DtmfDetector.cpp DtmfGenerator.cpp 10 | OBJ=$(patsubst %.cpp,obj/%.o,$(SRC)) 11 | 12 | # 13 | # This is here to prevent Make from deleting secondary files. 14 | # 15 | .SECONDARY: 16 | 17 | debug: CFLAGS += -DDEBUG=1 18 | debug: all 19 | 20 | # 21 | # $< is the first dependency in the dependency list 22 | # $@ is the target name 23 | # 24 | all: dirs $(addprefix bin/, $(EXE)) tags 25 | 26 | 27 | dirs: 28 | mkdir -p obj 29 | mkdir -p bin 30 | 31 | tags: *.cpp *.hpp 32 | ctags *.cpp *.hpp 33 | 34 | bin/%.out: obj/%.o $(OBJ) 35 | $(CPP) $(CFLAGS) $< $(OBJ) $(LDFLAGS) -o $@ 36 | 37 | obj/%.o : %.cpp 38 | $(CPP) $(CFLAGS) $< $(INCLUDES) -c -o $@ 39 | 40 | clean: 41 | rm -f obj/* 42 | rm -f bin/* 43 | rm -f tags 44 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | dtmf-cpp 2 | ======== 3 | 4 | C++ DTMF detector and generator classes 5 | 6 | The original code was written by Plyashkevich Viatcheslav 7 | and is available in its original form at http://sourceforge.net/projects/dtmf/ 8 | 9 | Main features: 10 | 11 | - Portable fixed-point implementation 12 | - Detection of DTMF tones from 8KHz PCM8 signal 13 | 14 | Installation 15 | ------------ 16 | 17 | git clone https://github.com/mpenkov/dtmf-cpp.git 18 | cd dtmf-cpp 19 | make 20 | bin/detect-au.out test-data/Dtmf0.au -------------------------------------------------------------------------------- /detect-au.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Utilize the DtmfDetector to detect tones in an AU file. 3 | // The file must be 8KHz, PCM encoded, mono. 4 | // 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #include 15 | 16 | #include "DtmfDetector.hpp" 17 | 18 | // 19 | // The string ".snd" in big-endian byte ordering. This identifies the file as 20 | // an AU sound file. 21 | // 22 | #define AU_MAGIC 0x2e736e64 23 | 24 | // 25 | // The size of the buffer we use for reading & processing the audio samples. 26 | // 27 | #define BUFLEN 256 28 | 29 | using namespace std; 30 | 31 | // 32 | // Swap the endianness of an 4-byte integer. 33 | // 34 | uint32_t 35 | swap32(uint32_t a) 36 | { 37 | char b1, b2, b3, b4 = 0; 38 | b1 = a; 39 | b2 = a >> 8; 40 | b3 = a >> 16; 41 | b4 = a >> 24; 42 | return (b1 << 24) + (b2 << 16) + (b3 << 8) + b4; 43 | } 44 | 45 | struct au_header 46 | { 47 | uint32_t magic; 48 | uint32_t header_size; 49 | uint32_t nsamples; 50 | uint32_t encoding; 51 | uint32_t sample_rate; 52 | uint32_t nchannels; 53 | }; 54 | 55 | string 56 | au_header_tostr(au_header &h) 57 | { 58 | stringstream ss; 59 | ss << h.header_size << " header bytes, " << 60 | h.nsamples << " samples, encoding type: " << h.encoding << ", " 61 | << h.sample_rate << "Hz, " << h.nchannels << " channels"; 62 | return ss.str(); 63 | } 64 | 65 | int 66 | main(int argc, char **argv) 67 | { 68 | if (argc != 2) 69 | { 70 | cerr << "usage: " << argv[0] << " filename.au" << endl; 71 | return 1; 72 | } 73 | 74 | ifstream fin(argv[1], ios::binary); 75 | if (!fin.good()) 76 | { 77 | cerr << argv[1] << ": unable to open file" << endl; 78 | return 1; 79 | } 80 | au_header header; 81 | fin.read((char *)&header, sizeof(au_header)); 82 | 83 | // 84 | // The data in the AU file header is stored in big-endian byte ordering. 85 | // If we're on a little-endian machine, we need to reorder the bytes. 86 | // While other arrangements (e.g. middle-endian) are also technically 87 | // possible, this example does not support them, since they are relatively 88 | // rare. 89 | // 90 | if (header.magic == AU_MAGIC) 91 | { 92 | } 93 | else if (header.magic == swap32(AU_MAGIC)) 94 | { 95 | header.header_size = swap32(header.header_size); 96 | header.nsamples = swap32(header.nsamples); 97 | header.encoding = swap32(header.encoding); 98 | header.sample_rate = swap32(header.sample_rate); 99 | header.nchannels = swap32(header.nchannels); 100 | } 101 | else 102 | { 103 | cerr << "bad magic number: " << hex << header.magic << endl; 104 | return 1; 105 | } 106 | 107 | cout << argv[1] << ": " << au_header_tostr(header) << endl; 108 | // 109 | // This example only supports a specific type of AU format: 110 | // 111 | // - no additional data in the header 112 | // - linear PCM encoding 113 | // - 8KHz sample rate 114 | // - mono 115 | // 116 | if 117 | ( 118 | header.header_size != 24 119 | || 120 | header.encoding != 2 121 | || 122 | header.sample_rate != 8000 123 | || 124 | header.nchannels != 1 125 | ) 126 | { 127 | cerr << argv[1] << ": unsupported AU format" << endl; 128 | return 1; 129 | } 130 | 131 | char cbuf[BUFLEN]; 132 | short sbuf[BUFLEN]; 133 | DtmfDetector detector(BUFLEN); 134 | for (uint32_t i = 0; i < header.nsamples; i += BUFLEN) 135 | { 136 | fin.read(cbuf, BUFLEN); 137 | // 138 | // Promote our 8-bit samples to 16 bits, since that's what the detector 139 | // expects. Shift them left during promotion, since the decoder won't 140 | // pick them up otherwise (volume too low). 141 | // 142 | for (int j = 0; j < BUFLEN; ++j) 143 | sbuf[j] = cbuf[j] << 8; 144 | detector.zerosIndexDialButton(); 145 | detector.dtmfDetecting(sbuf); 146 | cout << i << ": `" << detector.getDialButtonsArray() << "'" << endl; 147 | } 148 | cout << endl; 149 | fin.close(); 150 | 151 | return 0; 152 | } 153 | -------------------------------------------------------------------------------- /example.cpp: -------------------------------------------------------------------------------- 1 | /** Author: Plyashkevich Viatcheslav 2 | * 3 | * This program is free software; you can redistribute it and/or modify 4 | * it under the terms of the GNU Lesser General Public License 5 | * All rights reserved. 6 | */ 7 | 8 | 9 | #include 10 | #include "DtmfDetector.hpp" 11 | #include "DtmfGenerator.hpp" 12 | 13 | const unsigned FRAME_SIZE = 160; 14 | 15 | char dialButtons[16]; 16 | short int samples[100000]; 17 | 18 | DtmfDetector dtmfDetector(FRAME_SIZE); 19 | DtmfGenerator dtmfGenerator(FRAME_SIZE, 40, 20); 20 | 21 | int main() 22 | { 23 | dialButtons[0] = '1'; 24 | dialButtons[1] = '2'; 25 | dialButtons[2] = '3'; 26 | dialButtons[3] = 'A'; 27 | dialButtons[4] = '4'; 28 | dialButtons[5] = '5'; 29 | dialButtons[6] = '6'; 30 | dialButtons[7] = 'B'; 31 | dialButtons[8] = '7'; 32 | dialButtons[9] = '8'; 33 | dialButtons[10] = '9'; 34 | dialButtons[11] = 'C'; 35 | dialButtons[12] = '*'; 36 | dialButtons[13] = '0'; 37 | dialButtons[14] = '#'; 38 | dialButtons[15] = 'D'; 39 | 40 | 41 | while(true) 42 | { 43 | static int framenumber = 0; 44 | ++framenumber; 45 | dtmfGenerator.dtmfGeneratorReset(); 46 | dtmfDetector.zerosIndexDialButton(); 47 | dtmfGenerator.transmitNewDialButtonsArray(dialButtons, 16); 48 | while(!dtmfGenerator.getReadyFlag()) 49 | { 50 | dtmfGenerator.dtmfGenerating(samples); // Generating of a 16 bit's little-endian's pcm samples array 51 | dtmfDetector.dtmfDetecting(samples); // Detecting from 16 bit's little-endian's pcm samples array 52 | } 53 | 54 | if(dtmfDetector.getIndexDialButtons() != 16) 55 | { 56 | printf("Error of a number of detecting buttons \n"); 57 | continue; 58 | } 59 | 60 | for(int ii = 0; ii < dtmfDetector.getIndexDialButtons(); ++ii) 61 | { 62 | if(dtmfDetector.getDialButtonsArray()[ii] != dialButtons[ii]) 63 | { 64 | printf("Error of a detecting button \n"); 65 | continue; 66 | } 67 | } 68 | printf("Success in frame: %d \n", framenumber); 69 | } 70 | 71 | return 0; 72 | } 73 | -------------------------------------------------------------------------------- /scripts/README.md: -------------------------------------------------------------------------------- 1 | Utility Scripts 2 | =============== 3 | 4 | These scripts are used for testing and debugging. For full options, run: 5 | 6 | python scriptname.py --help 7 | 8 | DTMF Tone Sequence Generator 9 | ---------------------------- 10 | 11 | To generate a sequence of DTMF tones: 12 | 13 | python tonegen.py "1 2 3 4 5 6 7 8 9 0 A B C D * #" test.au 14 | 15 | You can then play back the generated file in any media player. 16 | 17 | DTMF Detector 18 | ------------- 19 | 20 | To detect DTMF tones in an audio file: 21 | 22 | python goertzel.py test.au 23 | 24 | You will see output like: 25 | 26 | test.au: 8000Hz 27 | 0 1 3439407.55 3377800.10 28 | 120 1 3797085.89 3783684.74 29 | 240 1 1528250.62 1507809.07 30 | 360 . 0.00 0.00 31 | 480 . 0.00 0.00 32 | 600 2 1623529.03 1600404.12 33 | 720 2 3504532.93 3498308.97 34 | 840 2 3502300.73 3478325.32 35 | ... 36 | 37 | The left-most column indicates the sample number of the first frame that got processed. 38 | The second column shows the detected DTMF tone ("." means nothing was detected). 39 | Currently, this simple detector doesn't apply any logic to the tones it detects -- it merely indicates 40 | the tone detected at each frame. 41 | 42 | Waveform Plotter 43 | ---------------- 44 | 45 | To quickly plot the waveform stored in an AU file: 46 | 47 | python plot_au.py test.au 48 | 49 | This requires matplotlib. Alternatively, use an editor like http://audacity.sourceforge.net/ 50 | 51 | Result: 52 | ![Alt text](https://raw.github.com/mpenkov/dtmf-cpp/master/scripts/plot_au.png) 53 | 54 | Goertzel Output Plotter 55 | ----------------------- 56 | 57 | If you build the detector in debug mode: 58 | 59 | cd .. 60 | make clean 61 | make debug 62 | 63 | Then you can visualize the strength of each magnitude at each frame: 64 | 65 | ../bin/detect-au.out test.au | python plot_T.py 66 | 67 | Result: 68 | ![Alt text](https://raw.github.com/mpenkov/dtmf-cpp/master/scripts/plot_T.png) 69 | -------------------------------------------------------------------------------- /scripts/au_file.py: -------------------------------------------------------------------------------- 1 | # create a soundfile in AU format playing a sine wave 2 | # of a given frequency, duration and volume 3 | # tested with Python25 by vegaseat 29jan2008 4 | # 5 | # from http://www.daniweb.com/software-development/python/code/217024/creating-and-playing-a-sine-wave-sound-python# 6 | # 7 | from struct import pack 8 | from math import sin, pi 9 | SAMPLE_RATE = 8000 10 | def au_file(name='test.au', freq=440, dur=1000, vol=0.5): 11 | """ 12 | creates an AU format audio file of a sine wave 13 | of frequency freq (Hz) 14 | for duration dur (milliseconds) 15 | at volume vol (max is 1.0) 16 | """ 17 | fout = open(name, 'wb') 18 | nsamples = SAMPLE_RATE*dur/1000 19 | # header needs size, encoding=2, sampling_rate=8000, channel=1 20 | fout.write('.snd' + pack('>5L', 24, nsamples, 2, SAMPLE_RATE, 1)) 21 | factor = 2 * pi * freq/SAMPLE_RATE 22 | # write data 23 | for seg in range(nsamples): 24 | # sine wave calculations 25 | sin_seg = sin(seg * factor) 26 | fout.write(pack('b', vol * 127 * sin_seg)) 27 | fout.close() 28 | 29 | # test the module ... 30 | if __name__ == '__main__': 31 | import sys 32 | if len(sys.argv) != 3: 33 | print >> sys.stderr, "usage: python %s fname.au freq" % __file__ 34 | sys.exit(-1) 35 | name = sys.argv[1] 36 | freq = float(sys.argv[2]) 37 | 38 | au_file(name=name, freq=freq, dur=128, vol=0.8) 39 | 40 | # if you have Windows, you can test the audio file 41 | # otherwise comment this code out 42 | #import os 43 | #os.startfile('sound800.au') 44 | -------------------------------------------------------------------------------- /scripts/goertzel.py: -------------------------------------------------------------------------------- 1 | """ 2 | Implementation of the Goertzel algorithm. 3 | 4 | Based on notes from: 5 | 6 | https://sites.google.com/site/hobbydebraj/home/goertzel-algorithm-dtmf-detection 7 | """ 8 | from math import pi, cos 9 | 10 | from plot_au import read_au 11 | from tonegen import ROW_FREQ, COL_FREQ 12 | 13 | # 14 | # Mapping of dual tones (lo, hi) to their symbols. 15 | # 16 | FREQ_TO_TONE = dict() 17 | FREQ_TO_TONE[ (697, 1209) ] = "1" 18 | FREQ_TO_TONE[ (697, 1336) ] = "2" 19 | FREQ_TO_TONE[ (697, 1477) ] = "3" 20 | FREQ_TO_TONE[ (697, 1633) ] = "A" 21 | FREQ_TO_TONE[ (770, 1209) ] = "4" 22 | FREQ_TO_TONE[ (770, 1336) ] = "5" 23 | FREQ_TO_TONE[ (770, 1477) ] = "6" 24 | FREQ_TO_TONE[ (770, 1633) ] = "B" 25 | FREQ_TO_TONE[ (852, 1209) ] = "7" 26 | FREQ_TO_TONE[ (852, 1336) ] = "8" 27 | FREQ_TO_TONE[ (852, 1477) ] = "9" 28 | FREQ_TO_TONE[ (852, 1633) ] = "C" 29 | FREQ_TO_TONE[ (941, 1209) ] = "*" 30 | FREQ_TO_TONE[ (941, 1336) ] = "0" 31 | FREQ_TO_TONE[ (941, 1477) ] = "#" 32 | FREQ_TO_TONE[ (941, 1633) ] = "D" 33 | 34 | def create_parser(): 35 | """Create an object to use for the parsing of command-line arguments.""" 36 | from optparse import OptionParser 37 | usage = "usage: %s filename.au [options]" % __file__ 38 | parser = OptionParser(usage) 39 | parser.add_option( 40 | "--threshold", 41 | "-t", 42 | dest="threshold", 43 | default=1000, 44 | type="int", 45 | help="Specify the cutoff threshold") 46 | parser.add_option( 47 | "--buflen", 48 | "-b", 49 | dest="buflen", 50 | default=120, 51 | type="int", 52 | help="Specify the detection buffer length") 53 | return parser 54 | 55 | class Goertzel: 56 | """This class implements DTMF detection by using the Goertzel algorithm.""" 57 | def __init__(self, sample_rate, threshold): 58 | self.sample_rate = sample_rate 59 | self.threshold = threshold 60 | self.coeff = dict() 61 | 62 | for f in ROW_FREQ + COL_FREQ: 63 | theta = 2*pi/sample_rate * f 64 | self.coeff[f] = 2*cos(theta) 65 | #print f, theta, self.coeff[f] 66 | 67 | def process(self, samples): 68 | """Process a block of samples.""" 69 | # 70 | # Calculate the Z-transforms for each frequency. 71 | # 72 | prev_prev = dict() 73 | prev = dict() 74 | for i,x in enumerate(samples): 75 | mag = dict() 76 | for f in ROW_FREQ + COL_FREQ: 77 | try: 78 | p = prev[f] 79 | except KeyError: 80 | p = 0 81 | try: 82 | pp = prev_prev[f] 83 | except KeyError: 84 | pp = 0 85 | 86 | output = x + self.coeff[f]*p - pp 87 | prev_prev[f] = p 88 | prev[f] = output 89 | 90 | for f in ROW_FREQ + COL_FREQ: 91 | mag[f] = prev_prev[f]**2 + prev[f]**2 - self.coeff[f]*prev_prev[f]*prev[f] 92 | 93 | # 94 | # Get the most likely tone candidate. 95 | # 96 | strong = sorted(mag, key=lambda f: mag[f], reverse=True) 97 | try: 98 | tone = FREQ_TO_TONE[tuple(sorted(strong[:2]))] 99 | except KeyError: 100 | tone = "." 101 | 102 | # 103 | # Reject the candidate if the magnitudes aren't strong enough. 104 | # 105 | mag1, mag2 = mag[strong[0]], mag[strong[1]] 106 | if not (mag1 > self.threshold and mag2 > self.threshold): 107 | tone = "." 108 | 109 | return tone, mag1, mag2 110 | 111 | def main(): 112 | parser = create_parser() 113 | options, args = parser.parse_args() 114 | if len(args) != 1: 115 | parser.error("invalid number of arguments") 116 | sample_rate, samples = read_au(args[0]) 117 | print "%s: %dHz" % (args[0], sample_rate) 118 | goertzel = Goertzel(sample_rate, options.threshold) 119 | for i in range(0, len(samples) - options.buflen, options.buflen): 120 | tone, mag1, mag2 = goertzel.process(samples[i:i+options.buflen]) 121 | print "%8d %s %.2f %.2f" % (i, tone, mag1, mag2) 122 | 123 | if __name__ == "__main__": 124 | import sys 125 | sys.exit(main()) 126 | -------------------------------------------------------------------------------- /scripts/plot_T.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mpenkov/dtmf-cpp/ce63d4bb8096de31a762e0b293ae84e96c921e79/scripts/plot_T.png -------------------------------------------------------------------------------- /scripts/plot_T.py: -------------------------------------------------------------------------------- 1 | """Plot the magnitudes of each frequency detected at each frame. 2 | 3 | usage: detect-au.out file.au | python plot_T.py 4 | 5 | Requires that the detector was built with make debug. 6 | """ 7 | import sys 8 | import csv 9 | import matplotlib.pyplot as plt 10 | 11 | def from_fp(fp): 12 | """Convert from fixed-point to float.""" 13 | return float(fp)/2**15 14 | 15 | # 16 | # ignore the first and second columns -- the frame number and the tone, 17 | # respectively. 18 | # 19 | coeff = list() 20 | for row in csv.reader(sys.stdin, delimiter=" "): 21 | if len(row) != 18+1: # last column is empty string, ignore it 22 | continue 23 | coeff.append(map(from_fp, row[:-1])) 24 | xval = range(len(coeff)) 25 | 26 | leg = { 0: ("r", "o", "706Hz"), # row freq 27 | 1: ("g", "o", "784Hz"), 28 | 2: ("b", "o", "863Hz"), 29 | 3: ("c", "o", "941Hz"), 30 | 4: ("m", "s", "1176Hz"), # col freq 31 | 5: ("y", "s", "1333Hz"), 32 | 6: ("orange", "s", "1490Hz"), 33 | 7: ("pink", "s", "1547Hz"), 34 | 8: ("black", "o", "1098Hz"), # unknown 35 | 9: ("black", "s", "549Hz"), 36 | 10: ("r", "+", "78Hz"), # harmonics 37 | 11: ("g", "+", "235Hz"), 38 | 12: ("b", "+", "314Hz"), 39 | 13: ("c", "+", "392Hz"), 40 | 14: ("m", "x", "2039Hz"), 41 | 15: ("y", "x", "2510Hz"), 42 | 16: ("orange", "x", "2980Hz"), 43 | 17: ("pink", "x", "3529Hz") } 44 | 45 | for i, col in enumerate(zip(*coeff)): 46 | c, m, l = leg[i] 47 | plt.plot(xval, col, color=c, marker=m, label=l); 48 | plt.xlabel("frame number") 49 | plt.ylabel("T") 50 | plt.legend(bbox_to_anchor=(1.05, 1), loc=2, borderaxespad=0.) 51 | plt.subplots_adjust(left=0.1, right=0.75, top=0.9, bottom=0.1) 52 | plt.show() 53 | -------------------------------------------------------------------------------- /scripts/plot_au.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mpenkov/dtmf-cpp/ce63d4bb8096de31a762e0b293ae84e96c921e79/scripts/plot_au.png -------------------------------------------------------------------------------- /scripts/plot_au.py: -------------------------------------------------------------------------------- 1 | """Plot the waveform of the signal in the specified AU file.""" 2 | 3 | import sys 4 | from struct import unpack, calcsize 5 | 6 | def read_au(fname): 7 | """Read the AU file. Return the sampling rate and samples in a list.""" 8 | fin = open(fname, "rb") 9 | payload = fin.read() 10 | 11 | assert payload[:4] == ".snd" 12 | header_len = calcsize(">5L") 13 | _, nsamples, encoding, sample_rate, _ = unpack(">5L", payload[4:4+header_len]) 14 | samples = unpack("%db" % nsamples, payload[4+header_len:]) 15 | return sample_rate, samples 16 | 17 | if __name__ == "__main__": 18 | import matplotlib.pyplot as plt 19 | sample_rate, samples = read_au(sys.argv[1]) 20 | plt.title("%s (%dHz)" % (sys.argv[1], sample_rate)) 21 | plt.plot(range(len(samples)), samples) 22 | plt.xlabel("sample number") 23 | plt.ylabel("value") 24 | plt.show() 25 | -------------------------------------------------------------------------------- /scripts/tonegen.py: -------------------------------------------------------------------------------- 1 | """Generate a sequence of DTMF tones.""" 2 | 3 | # 4 | # http://docs.python.org/library/struct.html 5 | # 6 | from struct import pack 7 | from math import sin, pi 8 | 9 | ROW_FREQ = (697, 770, 852, 941) 10 | COL_FREQ = (1209, 1336, 1477, 1633) 11 | 12 | def create_parser(): 13 | """Create an object to use for the parsing of command-line arguments.""" 14 | from optparse import OptionParser 15 | usage = "usage: %s \"1 2 3 4 5 6 7 8 9\" filename.au [options]" % __file__ 16 | parser = OptionParser(usage) 17 | parser.add_option( 18 | "--sample-rate", 19 | "-s", 20 | dest="sample_rate", 21 | default=8000, 22 | type="int", 23 | help="Use the specified sample rate in Hz") 24 | parser.add_option( 25 | "--volume", 26 | "-v", 27 | dest="volume", 28 | default=0.5, 29 | type="float", 30 | help="Specify signal volume in the range [0,1]") 31 | parser.add_option( 32 | "--duration", 33 | "-d", 34 | dest="duration", 35 | default=40, 36 | type="int", 37 | help="Specify the tone duration in ms") 38 | return parser 39 | 40 | def tone2(f1, f2, sample_rate, dur): 41 | """ 42 | Generate a tone composed of two frequencies, f1 and f2, of the 43 | specified sample rate (Hz), duration (ms) and volume. 44 | """ 45 | # 46 | # Check that we're using valid DTMF frequencies. 47 | # 48 | assert f1 in ROW_FREQ 49 | assert f2 in COL_FREQ 50 | nsamples = sample_rate*dur/1000 51 | factor1 = 2*pi*f1/sample_rate 52 | factor2 = 2*pi*f2/sample_rate 53 | return [ (sin(x*factor1) + sin(x*factor2))/2 for x in range(nsamples) ] 54 | 55 | def silence(sample_rate, dur): 56 | """Generate silence of the specified duration, in ms.""" 57 | return [ 0 for i in range(sample_rate*dur/1000) ] 58 | 59 | def save_au(fname, samples, sample_rate, vol): 60 | """Save the samples to the specified file in AU format.""" 61 | fout = open(fname, "wb") 62 | nsamples = len(samples) 63 | # header needs size, encoding=2, sampling_rate=8000, channel=1 64 | fout.write(".snd" + pack(">5L", 24, nsamples, 2, sample_rate, 1)) 65 | for y in samples: 66 | fout.write(pack("b", vol * 127 * y)) 67 | fout.close() 68 | 69 | def main(): 70 | parser = create_parser() 71 | options, args = parser.parse_args() 72 | if len(args) != 2: 73 | parser.error("invalid number of arguments") 74 | 75 | # 76 | # Don't support frequencies below 4KHz 77 | # 78 | assert options.sample_rate >= 4000 79 | 80 | # 81 | # http://en.wikipedia.org/wiki/Dual-tone_multi-frequency_signaling#Keypad 82 | # 83 | tones = { "1" : tone2(697, 1209, options.sample_rate, options.duration), 84 | "2" : tone2(697, 1336, options.sample_rate, options.duration), 85 | "3" : tone2(697, 1477, options.sample_rate, options.duration), 86 | "A" : tone2(697, 1633, options.sample_rate, options.duration), 87 | "4" : tone2(770, 1209, options.sample_rate, options.duration), 88 | "5" : tone2(770, 1336, options.sample_rate, options.duration), 89 | "6" : tone2(770, 1477, options.sample_rate, options.duration), 90 | "B" : tone2(770, 1633, options.sample_rate, options.duration), 91 | "7" : tone2(852, 1209, options.sample_rate, options.duration), 92 | "8" : tone2(852, 1336, options.sample_rate, options.duration), 93 | "9" : tone2(852, 1477, options.sample_rate, options.duration), 94 | "C" : tone2(852, 1633, options.sample_rate, options.duration), 95 | "*" : tone2(941, 1209, options.sample_rate, options.duration), 96 | "0" : tone2(941, 1336, options.sample_rate, options.duration), 97 | "#" : tone2(941, 1477, options.sample_rate, options.duration), 98 | "D" : tone2(941, 1633, options.sample_rate, options.duration), 99 | " " : silence(options.sample_rate, options.duration) } 100 | # 101 | # for convenient command-line generation 102 | # 103 | tones["S"] = tones["*"] 104 | tones["H"] = tones["#"] 105 | 106 | samples = list() 107 | for ch in args[0]: 108 | samples += tones[ch] 109 | save_au(args[1], samples, options.sample_rate, options.volume) 110 | 111 | if __name__ == "__main__": 112 | main() 113 | -------------------------------------------------------------------------------- /test-data/Dtmf-.au: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mpenkov/dtmf-cpp/ce63d4bb8096de31a762e0b293ae84e96c921e79/test-data/Dtmf-.au -------------------------------------------------------------------------------- /test-data/Dtmf0.au: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mpenkov/dtmf-cpp/ce63d4bb8096de31a762e0b293ae84e96c921e79/test-data/Dtmf0.au -------------------------------------------------------------------------------- /test-data/Dtmf1.au: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mpenkov/dtmf-cpp/ce63d4bb8096de31a762e0b293ae84e96c921e79/test-data/Dtmf1.au -------------------------------------------------------------------------------- /test-data/Dtmf2.au: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mpenkov/dtmf-cpp/ce63d4bb8096de31a762e0b293ae84e96c921e79/test-data/Dtmf2.au -------------------------------------------------------------------------------- /test-data/Dtmf3.au: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mpenkov/dtmf-cpp/ce63d4bb8096de31a762e0b293ae84e96c921e79/test-data/Dtmf3.au -------------------------------------------------------------------------------- /test-data/Dtmf4.au: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mpenkov/dtmf-cpp/ce63d4bb8096de31a762e0b293ae84e96c921e79/test-data/Dtmf4.au -------------------------------------------------------------------------------- /test-data/Dtmf5.au: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mpenkov/dtmf-cpp/ce63d4bb8096de31a762e0b293ae84e96c921e79/test-data/Dtmf5.au -------------------------------------------------------------------------------- /test-data/Dtmf6.au: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mpenkov/dtmf-cpp/ce63d4bb8096de31a762e0b293ae84e96c921e79/test-data/Dtmf6.au -------------------------------------------------------------------------------- /test-data/Dtmf7.au: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mpenkov/dtmf-cpp/ce63d4bb8096de31a762e0b293ae84e96c921e79/test-data/Dtmf7.au -------------------------------------------------------------------------------- /test-data/Dtmf8.au: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mpenkov/dtmf-cpp/ce63d4bb8096de31a762e0b293ae84e96c921e79/test-data/Dtmf8.au -------------------------------------------------------------------------------- /test-data/Dtmf9.au: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mpenkov/dtmf-cpp/ce63d4bb8096de31a762e0b293ae84e96c921e79/test-data/Dtmf9.au -------------------------------------------------------------------------------- /test-data/DtmfA.au: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mpenkov/dtmf-cpp/ce63d4bb8096de31a762e0b293ae84e96c921e79/test-data/DtmfA.au -------------------------------------------------------------------------------- /test-data/DtmfB.au: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mpenkov/dtmf-cpp/ce63d4bb8096de31a762e0b293ae84e96c921e79/test-data/DtmfB.au -------------------------------------------------------------------------------- /test-data/DtmfC.au: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mpenkov/dtmf-cpp/ce63d4bb8096de31a762e0b293ae84e96c921e79/test-data/DtmfC.au -------------------------------------------------------------------------------- /test-data/DtmfD.au: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mpenkov/dtmf-cpp/ce63d4bb8096de31a762e0b293ae84e96c921e79/test-data/DtmfD.au -------------------------------------------------------------------------------- /test-data/DtmfStar.au: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mpenkov/dtmf-cpp/ce63d4bb8096de31a762e0b293ae84e96c921e79/test-data/DtmfStar.au -------------------------------------------------------------------------------- /test-data/README.txt: -------------------------------------------------------------------------------- 1 | Files downloaded from: 2 | 3 | http://en.wikipedia.org/wiki/Dual-tone_multi-frequency_signaling. 4 | 5 | The originals were in Vorbis (.ogg) format. I converted them to AU using ffmpeg. 6 | 7 | for f in ~/Downloads/*.ogg; do ffmpeg -i $f -acodec pcm_s8 -ar 8000 test-data/$(basename "$f" .ogg).au; done 8 | -------------------------------------------------------------------------------- /types_cpp.hpp: -------------------------------------------------------------------------------- 1 | // Author: Plyashkevich Viatcheslav 2 | // 3 | 4 | // This program is free software; you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation; either version 2 of the License, or 7 | // (at your option) any later version. 8 | //-------------------------------------------------------------------------- 9 | // All rights reserved. 10 | 11 | 12 | #if !_BASE_TYPES_ 13 | #define _BASE_TYPES_ 1 14 | 15 | // http://stackoverflow.com/questions/12863738/what-is-this-c-code-attempting-to-achieve 16 | template class Types; 17 | template <> class Types<5, 4, 2, 1> 18 | { 19 | public: 20 | typedef long int Int40; 21 | typedef unsigned long int Uint40; 22 | typedef int Int32; 23 | typedef unsigned int Uint32; 24 | typedef short int Int16; 25 | typedef unsigned short int Uint16; 26 | typedef char Int8; 27 | typedef unsigned char Uint8; 28 | }; 29 | template <> class Types<8, 4, 2, 1> 30 | { 31 | public: 32 | typedef long int Int64; 33 | typedef unsigned long int Uint64; 34 | typedef int Int32; 35 | typedef unsigned int Uint32; 36 | typedef short int Int16; 37 | typedef unsigned short int Uint16; 38 | typedef char Int8; 39 | typedef unsigned char Uint8; 40 | }; 41 | template <> class Types<4, 4, 2, 1> 42 | { 43 | public: 44 | typedef int Int32; 45 | typedef unsigned int Uint32; 46 | typedef short int Int16; 47 | typedef unsigned short int Uint16; 48 | typedef char Int8; 49 | typedef unsigned char Uint8; 50 | }; 51 | 52 | // For 16bit chars 53 | template <> class Types<2, 1, 1, 1> 54 | { 55 | public: 56 | typedef long int Int32; 57 | typedef unsigned long int Uint32; 58 | typedef short int Int16; 59 | typedef unsigned short int Uint16; 60 | }; 61 | 62 | #endif 63 | --------------------------------------------------------------------------------