/* MIMIC.C Version 1.0 */ /* (c) 1990 Eletech Electronics, Inc. All rights UNreserved. */ /* */ /* Creation : 3-13-90 by Wei Lu. */ /* Function : Works with MINT.ASM to play Eletech Digicorder recorded voice */ /* file thru PC's internal speaker at specified sampling rate. */ /* Usage : MIMIC sampling_rate filename */ /* Compiler : Microsoft C 5.0 and up. */ /* Compiling Options: None. */ /* */ /* Operation: First MIMIC.C prepares for playback by: */ /* */ /* 1. allocating 64K bytes of play buffer memory from DOS, */ /* 2. loading voice data into play buffer bank 1, */ /* 3. re-directing (intercepting) timer 0 interrupt vector, */ /* 4. re-programming timer 0 to desired sampling rate. */ /* */ /* At this point MINT.ASM should start sending voice data to the */ /* speaker bit by bit upon each timer 0 interrupt. Meanwhile */ /* MIMIC.C will try to load more data into play buffer bank 2 */ /* and, if any key is pressed, stop playing. After bank 1 is */ /* played up, MIMIC.C switches playing to bank 2 and tries */ /* loading more data into bank 1. This goes on until all data are */ /* played and then MIMIC.C stops playing. */ /* */ /* When the playback is stopped (either by a key press or by */ /* running out of voice data) MIMIC restores timer 0 and its */ /* interrupt vector to normal operation. At this point, MINT.ASM */ /* is unhooked from timer 0 interrupt and its operation stopped. */ /* */ /* MIMIC.C communicates with MINT.ASM thru the following shared */ /* variables: */ /* */ /* play_addr - the far pointer points to the start address of */ /* the 64KB play buffer MIMIC.C allocates from DOS. */ /* Set by MIMIC.C and read by MINT.ASM to find out */ /* where the voice data are. Note that its offset */ /* address is always 0 since DOS allocates memory */ /* in paragraphs (16 bytes.) */ /* */ /* play_ptr - the pointer points to the currently-been-played */ /* byte. Starts from 0 and may go as high as 64K-1. */ /* Initially set by MIMIC.C and subsequently */ /* updated by MINT.ASM. */ /* */ /* play_length - number of bytes left to play. Initially set by */ /* MIMIC.C and subsequently updated by MINT.ASM. */ /* */ /* tack_count - number of "tacks" to wait before MINT.ASM does a */ /* real clock interrupt. Calculated and set by */ /* MIMIC.C and read by MINT.ASM. */ /* */ /* Here a "tack" is eight "ticks" and a "tick" is a timer 0 */ /* interrupt. When a system is powered up or reset, DOS sets up */ /* timer 0 for clock interrupt at about 18.2 times per second (so */ /* the tick rate is 18.2 Hz at this moment.) This is achieved by */ /* dividing 1.19 MHz (input clock rate) by 65536. MIMIC.C */ /* re-programs timer 0 so that the tick rate equals the sampling */ /* rate. This can be achieved by dividing 1.19 MHz by the integer */ /* part of 1190000/sampling_rate. This integer is kept in the */ /* variable "timer_div" and used to re-program timer 0. Although */ /* the tick rate has been changed, MINT.ASM still has to do 18.2 */ /* "real" clock interrupts every second to keep the clock */ /* running. A little calculation (65536*sampling_rate/1190000) */ /* yields the number of ticks to wait between "real" clock */ /* interrupts. Since MINT.ASM only checks this value at the end */ /* of every byte (or 8 ticks), this value is further divided by 8 */ /* to become number of "tacks" (variable tack_count.) */ /* */ /* Note that for most sampling rates the above calculations do */ /* not yield integers without remainders. If this is the case */ /* then the clock that DOS keeps will go slight faster (since we */ /* always round down the quotient) during the execution of the */ /* program. This deviation equals 1 - timer_div*tack_count/8192 */ /* and is usually less than 1%. This effect will accumulate with */ /* each run of the program until the system is reset (cold or */ /* warm), at that point DOS will reset the time by reading from */ /* either the hardware clock or the user, both not affected by */ /* this program, hopefully. */ /* */ /* */ /* There is actually a chance that the DOS clock goes slightly */ /* slower during the execution of the program. To explain this, */ /* we will have to first explain how MINT.ASM works. Upon each */ /* tick interrupt, MINT.ASM is activated and does one of the */ /* following: */ /* */ /* a. If there is no more data to play, do a "real" clock */ /* interrupt if it's time to, then terminate. */ /* */ /* Otherwise, */ /* */ /* b. If it does not need a new byte of data, output a bit and */ /* terminate. (This takes the least amount of time.) */ /* */ /* c. If it needs a new byte of data but it's not time yet to do */ /* a "real" clock interrupt, get a byte and output a bit and */ /* terminate. (This takes a little bit more time than a.) */ /* */ /* d. If it needs a new byte of data and it's also time to do a */ /* "real" clock interrupt, get a byte, output a bit, do a */ /* "real" clock interrupt and terminate. (This takes a lot of */ /* and the most amount of time.) */ /* */ /* So in normal operation the system has to complete the worst */ /* case (case d) within a sample period. If the system is slow so */ /* that it misses at least a tick interrupt while doing case d, */ /* an 18.2 Hz abnormality is introduced (which you can barely */ /* notice.) If the system is even slower that it misses at least */ /* a tick interrupt while doing case c, the playback tone will be */ /* slightly lower than normal due to slower effective playback */ /* rate. If the system is so slow as to miss at least a tick */ /* interrupt while doing case b, the tone will be significantly */ /* lower due to much slower effective playback rate. Case a is */ /* usually no problem except when it's time to do a "real" clock */ /* interrupt, at that time tick interrupts can be missed and the */ /* 18.2 Hz abnormality occurs. */ /* */ /* All these abnormalities work towards slowing down the DOS */ /* clock. Being slow to a certain extent (having only the 18.2 Hz */ /* abnormality) is sometimes a blessing since it works to offset */ /* the clock speedup effect as mentioned above. */ /* */ /* Also note that the playback is non-destructive, meaning that */ /* to play the same data again all you have to do is reset the */ /* play_ptr and play_length. Part or all of the recording can be */ /* instantly replayed in this manner. */ /* */ /* A final warning is that this program not only re-directs clock */ /* interrupt, but also borrowed software interrupt #255. Certain */ /* TSR programs hooking to these two interrupts may not work */ /* correctly after running this program. You can, of course, */ /* change the new clock interrupt to something other than #255 */ /* should conflict results. */ #include #include #include #define CLOCK_IRQ 8 /* old clock IRQ = vector #8 */ #define NEW_CLOCK_IRQ 255 /* new clock IRQ = vector #255 */ #define TIMER_COUNT 64 /* 8253 "load timer 0" I/O port */ #define TIMER_MODE 67 /* 8253 "write mode" I/O port */ main(argc, argv) int argc; char **argv; { extern void interrupt far int_handler(); /* new clock interrupt handler */ extern char far *play_addr; /* play buffer start address */ extern unsigned play_ptr; /* play byte pointer */ extern unsigned play_length; /* play length (in byte) */ extern unsigned char tack_count; /* how often we do old clock int */ void (interrupt far *old_vector1)(); /* where we put old timer vector */ void (interrupt far *old_vector2)(); /* where we put old vector #255 */ unsigned buffer_seg; /* play buffer segment address */ unsigned rate; /* rate = sampling_rate */ unsigned fhandle, bytes_read, timer_div, current_bank; char filename[80]; char far *p_addr1; /* points to start of bank 1 */ char far *p_addr2; /* points to start of bank 2 */ if (_dos_allocmem(4096, &buffer_seg)) /* allocate 64KB of play buffer */ { printf("MIMIC ERROR: can not allocate 64K bytes of buffer memory\n"); exit(0); } FP_SEG(play_addr) = FP_SEG(p_addr1) = FP_SEG(p_addr2) = buffer_seg; FP_OFF(play_addr) = FP_OFF(p_addr1) = 0; /* bank 1 from 0 to 32K */ FP_OFF(p_addr2) = 32768; /* bank 2 from 32K to 64K */ if (argc != 3) { printf("MIMIC USAGE: MIMIC sampling_rate filename\n"); exit(0); } if ((sscanf(argv[1], "%d", &rate) == 0) || (rate < 12) || (rate > 64)) { printf("MIMIC USAGE: sampling_rate must be an integer between 12 and 64\n"); exit(0); } if (_dos_open(argv[2], O_RDONLY, &fhandle)) { printf("MIMIC ERROR: can not open file '%s'\n", argv[2]); exit(0); } _dos_read(fhandle, (void far *)p_addr1, 32768, &bytes_read); current_bank = 1; play_ptr = 0; /* start from the first byte */ play_length = bytes_read; timer_div = 1190/rate; /* sampling_rate is in KHz */ tack_count = 8192*rate/1190; /* see below */ /* How often should we do an real clock interrupt? Once every 65536 ticks, */ /* or 8192 tacks, when the counting frequency is 1190 KHz. Now the counting */ /* frequency is "rate" in KHz, we should do it every 8192*rate/1190 tacks. */ old_vector1 = _dos_getvect(CLOCK_IRQ); /* get old clock int vector */ old_vector2 = _dos_getvect(NEW_CLOCK_IRQ); /* get old vector #255 */ _dos_setvect(NEW_CLOCK_IRQ, old_vector1); /* relocate old clock int handler*/ _dos_setvect(CLOCK_IRQ, int_handler); /* intercept clock interrupts */ outp(TIMER_MODE, 0x36); /* prepare to load new count */ outp(TIMER_COUNT, (char)timer_div); /* load low byte of new count */ outp(TIMER_COUNT, 0x0); /* high byte should be 0 */ /* MINT.ASM now starts sending data to speaker on each timer 0 interrupt */ while (bytes_read != 0) { if (bytes_read == 32768) /* there might be more data */ { if (current_bank == 1) _dos_read(fhandle, (void far *)p_addr2, 32768, &bytes_read); else _dos_read(fhandle, (void far *)p_addr1, 32768, &bytes_read); } else bytes_read = 0; /* no more data to play */ while (play_length != 0) { if (kbhit()) /* terminate if any key is hit */ break; /* get out of this small loop */ } if (kbhit()) { getch(); /* remove the unneeded "any key" */ break; /* get out of this big loop */ } if (current_bank == 1) { play_ptr = 32768; /* starts from bank 2 now */ current_bank = 2; } else { play_ptr = 0; /* restarts from bank 1 now */ current_bank = 1; } play_length = bytes_read; } outp(TIMER_MODE, 0x36); /* prepare to restore count */ outp(TIMER_COUNT, 0); /* old count = 0 (actually 65536)*/ outp(TIMER_COUNT, 0); _dos_setvect(CLOCK_IRQ, old_vector1); /* restore clock int vector */ _dos_setvect(NEW_CLOCK_IRQ, old_vector2); /* restore int vector #255 */ }