/* * Program: FREQ.C * Author: Philip VanBaren & Emil LAURENTIU * Date: 15 August 1993 * Last modified: Tuesday, 05 August 1997 * * Description: This program samples data from a sound card, performs an FFT, * and displays the result. * Can handle up to 2048 points (actually any size is possible * with a little fiddling of buffers to get around 64k limits). * (This restriction is given freq.h, and may be changed.) * On a 486/33 this code can perform and plot 1024-point and * below in nearly real-time at 44100kHz. (1024 FFT=31ms) * * The DOS portion of this code was written for Borland C, but should work * with other compilers if the graphics and console-io base functions are * changed appropriately. * * The source for specific graphics environments and sound cards may require * other packages. Refer to the specific files for more details. * * Most changes are required only in the sc_*.c and gr_*.c files. * * Copyright (C) 1995 Philip VanBaren (C) Emil LAURENTIU */ #include #include #include #include #include "freq.h" #include "fft.h" #include "extern.h" #include "display.h" /* * Table for approximating the logarithm. * These values are round(log2(index/16)*8192) for index=0:31 */ long ln[] = { -131072L, -32768L, -24576L, -19784L, -16384L, -13747L, -11592L, -9770L, -8192L, -6800L, -5555L, -4428L, -3400L, -2454L, -1578L, -763L, 0L, 716L, 1392L, 2031L, 2637L, 3214L, 3764L, 4289L, 4792L, 5274L, 5738L, 6184L, 6614L, 7029L, 7429L, 7817L }; int f_dtmf[8] = {697, 770, 852, 941, 1209, 1336, 1477, 1633}; int o_dtmf[8] = {18, 20, 22, 24, 31, 34, 37, 42}; int sb_f_dtmf[8] = {457, 505, 558, 617, 396, 438, 484, 535}; char matrix_dtmf[4][4] = { {'1', '2', '3', 'A'}, {'4', '5', '6', 'B'}, {'7', '8', '9', 'C'}, {'*', '0', '#', 'D'} }; int on_dtmf[8]; char dtmf_nr[121]; int p_dtmf; int last_i = -1; int active_freq, active_dtmf = 0; int active_ctcss = 0; int ctcss_nr; double f_ctcss[] = { 67.0, 69.4 , 71.9, 74.4, 77.0, 79.7, 82.5, 85.4, 88.5, 91.5, 94.8, 97.4, 100.0, 103.5, 107.2, 110.9, 114.8, 118.8, 123.0, 127.3, 131.8, 136.5, 141.3, 146.2, 151.4, 156.7, 159.8, 162.2, 165.5, 167.9, 171.3, 173.8, 177.3, 179.9, 183.5, 186.2, 189.9, 192.8, 196.6, 199.5, 203.5, 206.5, 210.7, 218.1, 225.7, 229.1, 233.6, 241.8, 250.3, 254.1 }; unsigned long ctcss_act1 = 0xABFFFFFD; unsigned long ctcss_act2 = 0x0001DD2A; long m_ctcss; char sline[80]; volatile int flag[BUFFERS]; /* Array of flags indicating fullness of * buffers */ volatile int record_buffer; /* Pointer to next buffer to be filled */ int queue_buffer; /* Pointer to next buffer to be queued */ int process_buffer; /* Pointer to next buffer to be FFTed */ short *fftdata; /* Array for FFT data */ short *wind; /* Array storing windowing function */ int x[WINDOW_RIGHT - WINDOW_LEFT + 1]; /* Array of bin #'s * displayed */ int x2[WINDOW_RIGHT - WINDOW_LEFT + 1]; /* Array of terminal bin * #'s */ int lasty[WINDOW_RIGHT - WINDOW_LEFT + 1]; /* Last y position for * FFT bins */ unsigned int yscale[WINDOW_RIGHT - WINDOW_LEFT + 1]; /* Scaling factors */ long *ybase; /* Scaling offset for log calculations */ long *displayval; int shift = 0; /* Number of bits for gain shift */ double shiftscale = 1; /* Multiplication factor that does this shift */ float log_scalefactor; /* Scaling factor for log values */ float disp_scalefactor; /* Display scalefactor for log values */ void far *buffer[BUFFERS]; /* Buffers for gathering data */ short *p1, *p2; /* Various indexing pointers */ int *bri; long *pDisplayval; int *pLasty; long *pYbase; unsigned int *pYscale; int *pX, *pX2; unsigned char far *sample8; short far *sample16; long a2, root, mask; /* Variables for computing Sqrt/Log of * Amplitude^2 */ long peak_amp; /* Peak amplitude found */ int peak_index; /* Bin number of the peak amplitude */ long back1, back2; /* Variables for differencing */ char ini_file[100]; /* Filename for the ini file */ int done = 0; /* Flag indicating program should exit */ int main( int argc, char *argv[], char *environ[] ) { int i, j, ind; int padX, padY; long y; int first; int key = 0; draw_init( ); DOUT( "Getting the command line arguments" ); /* * Check if the first parameter is an ini file name */ if ( argc > 1 && argv[1][0] != '-' && argv[1][0] != '/' ) { strncpy( ini_file, argv[1], sizeof( ini_file ) ); first = 2; } else { strncpy( ini_file, "dtmf_fft.ini", sizeof( ini_file ) ); first = 1; } /* * Parse the ini file and command line */ DOUT( "Parsing the ini file" ); parse_ini_file( ); DOUT( "Parsing the command line" ); parse_command( ( argc - first ), &argv[first], environ ); /* * Initialize the buffer info for the maximum size we will encounter */ DOUT( "Allocating the buffer space" ); setup_buffers( MAX_LEN ); DOUT( "Computing the window functions" ); compute_window_function( ); /* * Set up the required arrays in the FFT code. */ DOUT( "Initializing the FFT code" ); InitializeFFT( fftlen ); /* * Initialize the graphics to 640x480 VGA mode */ DOUT( "Setting the graphics mode" ); setup_graphics( ); setnormalpalette( ); draw_fontcolor( TEXT_COLOR ); draw_rectangle( WINDOW_LEFT - 2, WINDOW_TOP - 2, WINDOW_RIGHT + 2, WINDOW_BOTTOM + 2, BORDER_COLOR ); DOUT( "Resetting the sound card" ); reset_soundcard( ); /* * Initalize the graph scales */ setup_xscale( ); amplitude_scale( ); DOUT( "Drawing the header information" ); update_header( ); /* * Keep getting data and plotting it. A space will pause, a second space * will continue. Any other key will quit. */ DOUT( "Entering data loop" ); while ( !done ) { /* Wait for current buffer to fill up, and check for an input */ int input; key = draw_getkey( ); #ifdef DEBUG_MODE i = 0; while ( i++ < 500 && !key ) #else while ( ( !flag[process_buffer] || freeze ) && !key ) #endif key = draw_getkey( ); input = key; while ( input ) { /* Grab and count repeated keystrokes */ int repetitions = 1; key = input; input = draw_getkey( ); while ( input == key ) { repetitions++; input = draw_getkey( ); } done |= process_input( key, repetitions ); } if ( !key && !freeze ) { int clip = 0; /* * Perform windowing on the data */ p1 = fftdata; p2 = wind; if ( sample_size == 8 ) { sample8 = ( unsigned char far * ) buffer[process_buffer]; for ( i = 0; i < fftlen; i++ ) { #ifdef DEBUG_MODE *sample8 = ( char ) rand( ); #endif if ( ( *sample8 == 0 ) || ( *sample8 == 255 ) ) clip = 1; *p1 = ( short ) ( ( ( ( long ) ( *sample8 ) - 128L ) * ( long ) ( *p2 ) ) >> 7 ); sample8++; p1++; p2++; } } else { sample16 = ( short far * ) buffer[process_buffer]; for ( i = 0; i < fftlen; i++ ) { #ifdef DEBUG_MODE *sample16 = rand( ); #endif if ( ( *sample16 == 32767 ) || ( *sample16 == -32768L ) ) clip = 1; *p1 = ( short ) ( ( ( long ) ( *sample16 ) * ( long ) ( *p2 ) ) >> 15 ); sample16++; p1++; p2++; } } if ( clip ) draw_setpalette( 0, warn.red, warn.green, warn.blue ); else draw_setpalette( 0, background.red, background.green, background.blue ); /* Free up the buffer we just processed. */ flag[process_buffer] = 0; if ( ++process_buffer >= BUFFERS ) process_buffer = 0; /* Now that we have processed the buffer, queue it up again. */ recordblock( buffer[queue_buffer] ); if ( ++queue_buffer >= BUFFERS ) queue_buffer = 0; /* The real meat of the code lies elsewhere! */ RealFFT( fftdata ); /* Use pointers for indexing to speed things up a bit. */ bri = BitReversed; pDisplayval = displayval; for ( i = 0; i < fftlen / 2; i++ ) { /* Compute the magnitude */ register long re = fftdata[*bri]; register long im = fftdata[( *bri ) + 1]; register long root; if ( ( a2 = re * re + im * im ) < 0 ) a2 = 0; /* Watch for possible overflow */ /* Use higher resolution only for small values */ if ( a2 > 4194304L ) { root = 32; do { mask = a2 / root; root = ( root + mask ) >> 1; } while ( labs( root - mask ) > 1 ); root *= 16; } else { root = 512; a2 *= 256; do { mask = a2 / root; root = ( root + mask ) >> 1; } while ( labs( root - mask ) > 1 ); } *pDisplayval = root; bri++; pDisplayval++; } } if ( dtmf_mode ) { active_freq = 0; for ( i = 0; i < 8; i++ ) { /* ind = (int)( (double)(f_dtmf[i])*fftlen/SampleRate +.5 ); */ if ( displayval[o_dtmf[i]] > 524288.0 * threshold_level ) { on_dtmf[i] = 1; active_freq += ( i < 4 ) ? 1 : 10; } else on_dtmf[i] = 0; } if ( active_freq == 11 ) /* DTMF means 2 freq's active */ { padX = 1 * on_dtmf[4] + 2 * on_dtmf[5] + 3 * on_dtmf[6] + 4 * on_dtmf[7] - 1; padY = 1 * on_dtmf[0] + 2 * on_dtmf[1] + 3 * on_dtmf[2] + 4 * on_dtmf[3] - 1; i = padX + 4 * padY; if( log_mode && !active_dtmf ) fprintf( log_file, "%s - '", time_stamp() ); if ( i != last_i || !active_dtmf ) { dtmf_nr[p_dtmf] = matrix_dtmf[padY][padX]; dtmf_nr[p_dtmf + 1] = 0; last_i = i; draw_fontcolor( LABEL_COLOR ); draw_text_left( 156 + 8 * ( p_dtmf % 40 ), 60 + 10 * ( p_dtmf / 40 ), &dtmf_nr[p_dtmf] ); if( log_mode ) fprintf( log_file, "%c", dtmf_nr[p_dtmf] ); p_dtmf++; if ( p_dtmf == 120 ) { p_dtmf = 0; draw_bar( 156, 60, 476, 90, 0 ); } } active_dtmf = 1; } else { if( log_mode && active_dtmf ) fprintf( log_file, "'\n" ); active_dtmf = 0; } } if ( ctcss_mode ) { draw_bar( 200, 60, 240, 70, 0 ); draw_fontcolor( LABEL_COLOR ); draw_text_left( 200, 60, "off" ); m_ctcss = 0L; for ( i = 0; i < CTCSS_MAX; i++ ) { if( ( i < 32 ? ctcss_act1 >> i : ctcss_act2 >> (i-32) ) & 1 ) { ind = ( int ) ( f_ctcss[i] * fftlen / SampleRate + .5 ); if ( displayval[ind] > m_ctcss ) { m_ctcss = displayval[ind]; ctcss_nr = i; } } } if ( m_ctcss > 524288.0 * threshold_level ) { draw_bar( 124, 60, 240, 70, 0 ); draw_fontcolor( GRAPH_COLOR ); sprintf( sline, "%5.1lf Hz", f_ctcss[ctcss_nr] ); draw_text_left( 124, 60, sline ); draw_fontcolor( LABEL_COLOR ); draw_text_left( 200, 60, "on" ); if( log_mode && (last_i != ctcss_nr || !active_ctcss ) ) { fprintf( log_file, "%s - %5.1lf Hz\n", time_stamp(), f_ctcss[ctcss_nr] ); last_i = ctcss_nr; } active_ctcss = 1; } else active_ctcss = 0; } { /* * Next, put this data up on the display */ setup_vga( ); /* Prepare VGA for video update */ pLasty = lasty; pX = x; pX2 = x2; pYscale = yscale; peak_amp = 0; peak_index = 0; y = WINDOW_BOTTOM; /* For linear amplitude mode */ { int index, xval; for ( i = WINDOW_LEFT; i < WINDOW_RIGHT + 1; i++ ) { /* * If this line is the same as the previous one, just use the * previous y value. Else go ahead and compute the value. */ index = *pX; if ( index != -1 ) { register long dv = displayval[index]; if ( *pX2 ) /* Take the maximum of a set of bins */ { for ( xval = index; xval < *pX2; xval++ ) { if ( displayval[xval] > dv ) { dv = displayval[xval]; index = xval; } } } y = ( WINDOW_BOTTOM ) - ( ( dv * *pYscale ) >> shift ); if ( y < WINDOW_TOP ) y = WINDOW_TOP; if ( dv > peak_amp ) { peak_amp = dv; peak_index = *pX; } } if ( y > *pLasty ) { /* Draw a black line */ unsigned char bit = ~( 0x80 >> ( i & 0x07 ) ); unsigned int endbase = ( unsigned int ) ( y * 80 ); unsigned int base = ( unsigned int ) ( *pLasty * 80 + ( i >> 3 ) ); while ( base < endbase ) { screen( base ) &= bit; base += 80; } } else { /* Draw a blue line. */ unsigned char bit = 0x80 >> ( i & 0x07 ); unsigned int endbase = ( unsigned int ) ( ( *pLasty + 1 ) * 80 ); unsigned int base = ( unsigned int ) ( y * 80 + ( i >> 3 ) ); while ( base < endbase ) { screen( base ) |= bit; base += 80; } } *pLasty = ( unsigned int ) y; pDisplayval++; pX++; pX2++; pLasty++; pYscale++; } } cleanup_vga( ); /* Reset VGA for normal functions */ } if ( display_peak ) { char ach[20]; sprintf( ach, "%7.1f", ( double ) SampleRate * peak_index / fftlen ); draw_bar( PKX, PKY - 1, PKX + 63, PKY + _font_height, 0 ); draw_text_left( PKX, PKY, ach ); } } /* * Shut down the DMA system. */ cleanup_soundcard( ); cleanup_graphics( ); if( log_mode ) fclose( log_file ); printf( "You have been using DTMF_FFT v 1.10 (dtmf & ctcss) " ); #ifdef SC_SB8 if ( Soundcard == SC_SB8 ) printf( " in Soundblaster 8-bit mode." ); #endif #ifdef SC_SB16 if ( Soundcard == SC_SB16 ) printf( " in Soundblaster 16-bit mode." ); #endif printf( "\nCopyright (C) 1996 Philip VanBaren & " "(C) 1997 Emil Laurentiu (YO3GGH)" ); return ( 0 ); }