Path: gmd.de!Germany.EU.net!EU.net!howland.reston.ans.net!cs.utexas.edu!geraldo.cc.utexas.edu!grumpy.cc.utexas.edu!not-for-mail From: tone@grumpy.cc.utexas.edu (Minor or Mucho) Newsgroups: alt.2600 Subject: Re: generating red box tones Date: 5 Feb 1994 19:49:31 -0600 Organization: The University of Texas - Austin Lines: 794 Message-ID: <2j1ibb$3tu@grumpy.cc.utexas.edu> References: <2ibv4cINN6up@srvr1.engin.umich.edu> <2ioskoINNh9@srvr1.engin.umich.edu> NNTP-Posting-Host: grumpy.cc.utexas.edu A few years ago, before any of the current crop of Adlib blue boxes appeared, I decided to write a blue box program for the PC. I first thought of using the Adlib FM synthesis section of the card, but I quickly discovered that while it could do multiple pure tones at once, it could not synthesize any frequency exactly. The average error was (if I remember right) 1 or 2 hertz. This is probably within the various systems tolerances, but I decided against it. Instead I decided to generate samples digitally into a buffer and then play them back. This program generates a huge set of sounds into arrays of pointers to sound buffers, then plays them back. This is bad/ugly code, for several reasons: * The Synth routine is grossly inefficient. You **WILL NEED** a math coprocessor if you want it to initialize in under a few hours. (it should take 1-2 minutes on a 486-33). [Onkel, did you ever get that fast synth routine?] * The Do______ functions are not modular at all. Textbook example of what not to do. I do not even know if this program works. Well, I know it generates tones, but I've never actually tested one. The only sort of verification I have is from the QUARTER.VOC I made and distributed using this program -- I have heard accounts of it working. The sound quality leaves something to be desired. Some sounds have "noise". Sampling rate has some relationship to it, and it isn't as simple as "faster is better". It may also be that my algorithm is imprecise. Maybe harmonics has something to do with it? Math/physics people fill me in. It should also be a simple matter to re-write the program to use 16 bit samples; I imagine that would improve the quality drastically, but I only had a soundblaster when I wrote this. I am also unsure of the specs for the various international dialing systems, so there's a good chance some of the tones here are wrong. I'm sure some Dutch readers are muttering "lame" in a variety of languages... The sound drivers being called here are from The Audio Solution. Since it is a commercial, I'm not going to include it. If you really want to use this code, keep the synth and loadvals routines, generate the tones, then write them to disk. Convert the raw 8 bit samples to the format of your choice (.WAV, .VOC). If you want to do real-time dialing, get a real program. Of course as soon as I wrote this, several excellent Adlib blue boxes appeared, including Onkel's. Have Phun! /** **/ /** Babe -- The Blue Box **/ /** (c) 1992 Mucho Maas **/ /** All Rights Reserved **/ /** Experimental Blue Box Prototype **/ /** **/ // Need a routine to save and load generated soundsets... // sndfile = fopen("test.snd","wb"); // fwrite(sound,sizeof(byte)*duration,1,sndfile); // fclose(sndfile); // Need a routine to deal with non 8-bit raw sound drivers (PC speaker, // Adlib, Disney Sound Source..) #include #include #include #include #include #include #include #include #include "babe.h" #define PI 3.1415926 #define TWOPI 6.2831852 #define VERSION "0.5 Beta" // tone1, tone2, milliseconds, etc... static int CCITT[48] = { 2600, 2400, 500, // Hangup 700, 900, 55, // 1 700, 1100, 55, // 2 900, 1100, 55, // 3 700, 1300, 55, // 4 900, 1300, 55, // 5 1100,1300, 55, // 6 700, 1500, 55, // 7 900, 1500, 55, // 8 1100, 1500, 55, // 9 1300, 1500, 55, // 0 700, 1700, 100, // Code 11 900, 1700, 100, // Code 12 1100, 1700, 100, // KP1 1300, 1700, 100, // KP2 1500, 1700, 100, // ST } ; char *CCITT_names[] = { "Hangup", "1", "2", "3", "4", "5", "6", "7", "8", "9", "0", "Code 11", "Code 12", "KP 1", "KP 2", "ST", } ; char CCITT_keys[] = { 'H', // Hangup '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', 'C', // Code 11 'K', // Code 12 'P', // KP 1 'E', // KP 2 'S', // ST } ; static int R2[48] = { 3825, 3825, 500, // Hangup 1380, 1500, 60, // 1 1380, 1620, 60, // 2 1500, 1620, 60, // 3 1380, 1740, 60, // 4 1980, 1740, 60, // 5 1620, 1740, 60, // 6 1380, 1860, 60, // 7 1500, 1860, 60, // 8 1620, 1860, 60, // 9 1620, 1980, 60, // * 1740, 1860, 60, // 0 1740, 1980, 60, // # 1380, 1980, 100, // KP2E 1500, 1980, 100, // KP2 (with echo? how much??) 1860, 1980, 100, // ST } ; char *R2_names[] = { "Hangup", "1", "2", "3", "4", "5", "6", "7", "8", "9", "*", "0", "#", "KP2E", "KP2", "ST", } ; char R2_keys[] = { 'H', // Hangup '1', '2', '3', '4', '5', '6', '7', '8', '9', '*', '0', '#', 'K', // KP2E 'P', // KP2 'S', // ST } ; static int DTMF[48] = { 1209, 697, 60, // 1 1336, 697, 60, // 2 1477, 697, 60, // 3 1209, 770, 60, // 4 1336, 770, 60, // 5 1477, 770, 60, // 6 1209, 852, 60, // 7 1336, 852, 60, // 8 1477, 852, 60, // 9 1209, 941, 60, // * 1336, 941, 60, // 0 1477, 941, 60, // # 1633, 697, 60, // A 1633, 770, 60, // B 1633, 852, 60, // C 1633, 941, 60, // D } ; char *DTMF_names[] = { "1", "2", "3", "4", "5", "6", "7", "8", "9", "*", "0", "#", "A", "B", "C", "D", } ; char DTMF_keys[] = { '1', '2', '3', '4', '5', '6', '7', '8', '9', '*', '0', '#', 'A', 'B', 'C', 'D', } ; static int GERMAN[60] = { // Tone lengths here are just guesses... 1400, 1800, 60, // 1 1400, 2200, 60, // 2 1800, 2200, 60, // 3 1400, 2600, 60, // 4 1800, 2600, 60, // 5 2200, 2600, 60, // 6 1400, 3000, 60, // 7 1800, 3000, 60, // 8 2200, 3000, 60, // 9 2600, 3000, 60, // 0 1800, 2400, 100, // STP 1400, 2400, 100, // SM 2600, 2400, 100, // KP+ 2600, 2400, 60, // KP+ (60 ms) 3000, 2400, 100, // ST 3000, 2400, 60, // ST (60 ms) 2200, 2400, 100, // KP 2200, 2400, 60, // KP (60 ms) 4800, 5200, 100, // Trunk 4800, 4800, 100, // Size (Seize??) } ; char *GERMAN_names[] = { "1", "2", "3", "4", "5", "6", "7", "8", "9", "0", "STP", "SM", "KP+", "KP+ (60 ms)", "ST", "ST (60 ms)", "KP", "KP (60 ms)", "Trunk", "Seize", } ; char GERMAN_keys[] = { '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', 'S', // STP 'M', // SM 'K', // KP+ 'L', // KP+ (60 ms) 'T', // ST 'U', // ST (60 ms) 'K', // KP 'J', // KP (60 ms) 'T', // Trunk 'Z', // Seize } ; static int PIECES[60] = { 1700, 2200, 33, // Quarter piece 0, 0, 33, // Quarter silence 1700, 2200, 66, // Nickel & Dime piece 0, 0, 66, // Nickel & Dime silence 350, 440, 500, // Dialtone (continuous) 480, 620, 500, // Busy (500 msec on, 500 off) (filter 1100) 0, 0, 100, // 100 msec silence 0, 0, 200, // 200 msec silence 0, 0, 300, // 300 msec silence 0, 0, 500, // 500 msec silence 440, 480, 500, // Ringback (2 sec on, 4 sec off) // and PBX Ringback (1 sec on, 3 off) 480, 620, 200, // Congestion, toll (200 msec on, 300 off) 480, 620, 300, // Reorder (local) (300 msec on, 200 off) 1400, 2060, 100, // Part 1 of receiver off-hook. (100 on, 100 off) 2450, 2600, 100, // Part 2 of receiver off-hook. 1633, 697, 100, // A 1633, 770, 100, // B 1633, 852, 100, // C 1633, 941, 100, // D 2600, 2400, 300, // Just to annoy them } ; char *PIECES_names[] = { "Nickel", "Dime", "Quarter", "Dialtone", "Busy", "Ring", "Congestion", "Reorder", "Offhook", "A", "B", "C", "D", "2600 hz", } ; char PIECES_keys[] = { 'N', // Nickel 'D', // Dime 'Q', // Quarter 'T', // Dialtone 'B', // Busy 'R', // USA Domestic Ring 'C', // Congestion 'O', // Reorder (fast busy) 'H', // Off-hook '1', // DTMF A '2', // DTMF B '3', // DTMF C '4', // DTMF D '^', // 2600 (Ah, Cat's Meow...) } ; float sintable[3600]; FILE *sndfile; typedef struct SNDSTRUCT { char far *sound; // address of audio data. unsigned int sndlen; // Length of audio sample. int far *IsPlaying; // Address of play status flag. int frequency; // Playback frequency. } ; struct SNDSTRUCT CCITTptrs[60]; struct SNDSTRUCT R2ptrs[60]; struct SNDSTRUCT DTMFptrs[60]; struct SNDSTRUCT GERMptrs[60]; struct SNDSTRUCT PIECESptrs[57]; void loadsintable(void); void fastsynth(struct SNDSTRUCT *sndplay, int freq[8], int amplitude[8], int numfreq, int milliseconds, int sampfreq); void synth(struct SNDSTRUCT *sndplay, int freq[8], int amplitude[8], int numfreq, int milliseconds, int sampfreq); void loadvals(struct SNDSTRUCT *sndpoints, int *sndvalues, int sndcount, int *freq, int *amplitude, int sampfreq, char *name); void freevals(struct SNDSTRUCT *sndpoints, int sndcount); void DoGerman(void); void DoCCITT(void); void DoDTMF(void); void DoR2(void); void DoPieces(void); int main(void) { int freq[8]; // Frequencies in a given tone int numfreq; // Number of frequencies int sampfreq; // Master sampling frequency int millisecs; // # of milliseconds tone lasts register int x,y,z,c; // counters char num; char inchar; static int amplitude[8] = { 128, 128, 128, 128, 128, 128, 128, 128 }; clrscr(); printf("Babe - The Blue Box\n"); printf("Version %s (%s)\n",VERSION,__DATE__); printf("Copyright 1992 Mucho Maas\n\n"); if (CheckIn() == 0) { cputs("\nYou need to load a digitized sound driver first (i.e. SBLASTER.COM)\n"); exit(1); } for (x=0; x < 4; ++x) freq[x] = 0; cputs("Enter sampling frequency (22050, 11025, etc.) : "); scanf("%d",&sampfreq); loadsintable(); loadvals(CCITTptrs, CCITT, sizeof(CCITT), freq, amplitude, sampfreq, "CCITT #5"); loadvals(R2ptrs, R2, sizeof(R2), freq, amplitude, sampfreq, "CCITT #5 R2"); loadvals(GERMptrs, GERMAN, sizeof(GERMAN), freq, amplitude, sampfreq, "German"); loadvals(DTMFptrs, DTMF, sizeof(DTMF), freq, amplitude, sampfreq, "DTMF"); loadvals(PIECESptrs, PIECES, sizeof(PIECES), freq, amplitude, sampfreq, "Assorted"); freq[0] = 1400; freq[1] = 2060; freq[2] = 2450; freq[3] = 2600; synth(&PIECESptrs[13], freq, amplitude, 4, 100, sampfreq); // Off-hook sound // freq[0] = 480; // freq[1] = 620; // freq[2] = -1100; // synth(&PIECESptrs[5], freq, amplitude, 3, 500, sampfreq); // Busy - filter 1100. /* Filtering sounds lousy... I dunno */ BadCode: cputs("\n\rD)TMF\n\r"); cputs("C)CITT\n\r"); cputs("R)2 CCITT\n\r"); cputs("G)erman\n\r"); cputs("B)its & Pieces\n\r"); cputs("Q)uit\n\r"); cputs(": "); inchar = getch(); inchar = toupper(inchar); printf("%c",inchar); switch (inchar) { case 'C': DoCCITT(); break; case 'D': DoDTMF(); break; case 'G': DoGerman(); break; case 'R': DoR2(); break; case 'B': DoPieces(); break; case 'Q': break; default: goto BadCode; } if (inchar != 'Q') goto BadCode; freevals(CCITTptrs, sizeof(CCITT)); freevals(R2ptrs, sizeof(R2)); freevals(GERMptrs, sizeof(GERMAN)); freevals(DTMFptrs, sizeof(DTMF)); freevals(PIECESptrs, sizeof(PIECES)); return(0); } // These menus aren't as modular as they could or should be, but they // are intended to be disposable when a decent front-end gets written. // // Pieces will need to remain a custom module, however, since it is // a bunch of exceptions. void DoGerman(void) { register int x,z; char inchar; clrscr(); cputs("German Frequencies:\n\r\n\r"); for (x=0, z=0; x < 20, z < 60; x++, z+=3) printf("%c : %4.0d %4.0d %4.0d %s\n",GERMAN_keys[x],GERMAN[z],GERMAN[z+1],GERMAN[z+2],GERMAN_names[x]); cputs("Q : Quit\n\r"); cputs("Dial: "); for (;;) { inchar = getch(); inchar = toupper(inchar); if (inchar == 'Q') { cputs("\n\r"); break; } for (x=0; x < 20; x++) if (inchar == GERMAN_keys[x]) { DigPlay2(&GERMptrs[x]); printf("%c",inchar); } } } void DoCCITT(void) { register int x,z; char inchar; clrscr(); cputs("CCITT Frequencies:\n\r\n\r"); for (x=0, z=0; x < 16, z < 48; x++, z+=3) printf("%c : %4.0d %4.0d %4.0d %s\n",CCITT_keys[x],CCITT[z],CCITT[z+1],CCITT[z+2],CCITT_names[x]); cputs("Q : Quit\n\r"); cputs("Dial: "); for (;;) { inchar = getch(); inchar = toupper(inchar); if (inchar == 'Q') { cputs("\n\r"); break; } for (x=0; x < 16; x++) if (inchar == CCITT_keys[x]) { DigPlay2(&CCITTptrs[x]); printf("%c",inchar); } } } void DoDTMF(void) { register int x,z; char inchar; clrscr(); cputs("DTMF Frequencies:\n\r\n\r"); for (x=0, z=0; x < 16, z < 48; x++, z+=3) printf("%c : %4.0d %4.0d %4.0d %s\n",DTMF_keys[x],DTMF[z],DTMF[z+1],DTMF[z+2],DTMF_names[x]); cputs("Q : Quit\n\r"); cputs("Dial: "); for (;;) { inchar = getch(); inchar = toupper(inchar); if (inchar == 'Q') { cputs("\n\r"); break; } for (x=0; x < 16; x++) if (inchar == DTMF_keys[x]) { DigPlay2(&DTMFptrs[x]); printf("%c",inchar); } } } void DoR2(void) { register int x,z; char inchar; clrscr(); cputs("CCITT R2 Frequencies:\n\r\n\r"); for (x=0, z=0; x < 16, z < 48; x++, z+=3) printf("%c : %4.0d %4.0d %4.0d %s\n",R2_keys[x],R2[z],R2[z+1],R2[z+2],R2_names[x]); cputs("Q : Quit\n\r"); cputs("Dial: "); for (;;) { inchar = getch(); inchar = toupper(inchar); if (inchar == 'Q') { cputs("\n\r"); break; } for (x=0; x < 16; x++) if (inchar == R2_keys[x]) { DigPlay2(&R2ptrs[x]); printf("%c",inchar); } } } void DoPieces(void) { register int x,z,count; char inchar; clrscr(); cputs("Bits & Pieces:\n\r\n\r"); for (x=0; x < 14; x++) printf("%c : %s\n",PIECES_keys[x],PIECES_names[x]); cputs("X : Exit\n\r"); cputs("Dial: "); for (;;) { inchar = getch(); if (!isdigit(inchar)) inchar = toupper(inchar); if (inchar == 'X') { cputs("\n\r"); break; } for (x=0; x < 16; x++) if (inchar == PIECES_keys[x]) { switch (inchar) { case 'Q': for (count = 0; count < 5; count++) { DigPlay2(&PIECESptrs[0]); DigPlay2(&PIECESptrs[1]); } break; case 'D' : DigPlay2(&PIECESptrs[2]); DigPlay2(&PIECESptrs[3]); DigPlay2(&PIECESptrs[2]); break; case 'N' : DigPlay2(&PIECESptrs[2]); break; case 'T' : DigPlay2(&PIECESptrs[4]); break; case 'B' : for (count=0; count < 10; count++) { DigPlay2(&PIECESptrs[5]); DigPlay2(&PIECESptrs[9]); } break; case 'R' : DigPlay2(&PIECESptrs[10]); DigPlay2(&PIECESptrs[10]); for (count=0; count < 8; count++) // 4 second delay DigPlay2(&PIECESptrs[9]); break; case 'C' : for (count=0; count < 10; count++) { DigPlay2(&PIECESptrs[11]); DigPlay2(&PIECESptrs[8]); } break; case 'O' : for (count=0; count < 10; count++) { DigPlay2(&PIECESptrs[12]); DigPlay2(&PIECESptrs[7]); } break; case 'H' : for (count=0; count < 10; count++) { DigPlay2(&PIECESptrs[13]); DigPlay2(&PIECESptrs[6]); } break; case '1' : DigPlay2(&PIECESptrs[15]); break; case '2' : DigPlay2(&PIECESptrs[16]); break; case '3' : DigPlay2(&PIECESptrs[17]); break; case '4' : DigPlay2(&PIECESptrs[18]); break; case '^' : DigPlay2(&PIECESptrs[19]); break; default: break; } // DigPlay2(&PIECESptrs[x]); printf("%c",inchar); } } } void loadsintable(void) { register int x; for (x=0; x < 3600; x++) sintable[x] = sin((float)x/(float)10); } // Loadvals() takes the array of frequencies and lengths and makes calls // with them to Synth() to generate waveforms for each of the digits in // the tone set. void loadvals(struct SNDSTRUCT *sndpoints, int *sndvalues, int sndcount, int *freq, int *amplitude, int sampfreq, char *name) { register int x,c; int millisecs, numfreq; int digits; // Number of digits/keys.. they come in triads printf("Generating %s waveforms",name); digits = sndcount / (3 * sizeof(int)); for (x=0, c=0; c < digits; c++, x+=3) { freq[0] = sndvalues[x]; freq[1] = sndvalues[x+1]; millisecs = sndvalues[x+2]; numfreq = 2; // Hard wired for now; should be flexible synth(&sndpoints[c], freq, amplitude, numfreq, millisecs, sampfreq); cputs("."); } cputs("\r\n"); } // Freevals() Releases the memory used by a soundset. void freevals(struct SNDSTRUCT *sndpoints, int sndcount) { register int c; int digits; struct SNDSTRUCT *sndplay; digits = sndcount / (3 * sizeof(int)); for (c=0; c < digits; c++) { sndplay = &sndpoints[c]; free(sndplay->sound); } } // Synth composes and mixes the waveform, putting the raw 8 bit // data into the soundstructure passed as a pointer to sndplay. // Negative frequencies are filtered (subtracted). // Optimization is desperately needed here, but it won't help much // without some sort of fast sin table. void synth(struct SNDSTRUCT *sndplay, int *freq, int *amplitude, int numfreq, int milliseconds, int sampfreq) { byte far *sndptr; float point[8], flopoint; register int x,z; // Counters unsigned int duration; // Duration in samples duration = milliseconds * (sampfreq/1000); if ((sndplay->sound = malloc(duration)) == NULL) { printf("\nUnable to allocate memory for a sound!\n") ; exit(1); } sndptr = sndplay->sound; sndplay->sndlen = duration; sndplay->frequency = sampfreq; memset(sndplay->sound,(char) 128, duration-1); // make the buffer into silence if (freq[0] != 0) // If first frequency is 0, this is a silent buffer, so leave it as silence. for (x=1; x <= duration; ++x) { flopoint = 0; for (z=0; z < numfreq; ++z) { point[z] = (amplitude[z] * sin(2 * PI * freq[z] * ((float)1/(float)sampfreq) * x)); // Simple harmonic motion if (freq[z] > 0) flopoint += point[z]; // Normal freqency; add it in else flopoint -= point[z]; // Filtered frequency; subtract it } *sndptr = (byte) ((flopoint / numfreq) + 128); sndptr++; } }