tjw@unix.cis.pitt.edu (TJ Wood WA3VQJ) (08/15/90)
Here's the demo program I promised! What it is: This program will first output a pre-recorded voice that it reads from a disk file. Then it will digitize a few seconds of input and loop 10 times playing it. Jeez, Louise! It took me the better part of a week to just do that!?! ;-) Hardware Required: This program works with the Creative Labs, Inc. SOUND BLASTER card. Development tools: I used Microsoft C Version 5.0 and Microsoft Assembler 4.0 in the development of these programs. If you don't have either of these, but want the object files, send me e-mail and we'll figure out how to get them to you. The code view debugger was a great help (now that I know how to use it) in working with this code. If you use code view with this code and set break points, be sure to set the break points AFTER the my SB_WAIT() function completes. I suspect that I've not got the SB5() (set status word address) routine down 100%. The status word doesn't seem to change unless I'm actively reading it. (Something wrong with the way I set the address I'll bet). Anyway, it does work if I'm looking at it. Required files: CT-VOICE.DRV, and TV.BIN (made from TV.VOC) The program requires the CT-VOICE.DRV file that the VOXKIT.EXE and VOUT.EXE use. You should already have a copy of this file working with these programs. Be sure that VOXKIT and/or VOUT work before trying my program. Copy CT-VOICE.DRV into a subdirectory along with my programs. I'm not sending a copy of my CT-VOICE.DRV file, since my hardware addresses may not match yours. I used a text editor to cut off the "header" of the file TV.VOC and created my TV.BIN file. In retrospect, I can see that I should have just let the program read the first 0x1A bytes and trashed them. Then I could have used the TV.VOC (or any other VOC file) directly. You can modify the TV_VOICE() code yourself or wait for my next hacking session. Or you can just comment out the first part of my demo program and get right to digitizing and playing back your own stuff. Disclaimer: I hacked this together in under a week of evenings. My spouse was at a conference and I had a few spare hours on my hands. However, I don't write "C" programs on PC's for a living. (Hey, somebody has to write FORTRAN on VMS systems!) Also, up until last week, I had never written an assembly program on a PC. I believe that assembly language should be seen and not heard (just like little kids) ;-) I'm doing some really nasty things in this code (like ANDing pointer addresses to get my driver pointer (ptr) to point to location 0 of a segment). I don't know how to convince the linker to stop locating my driver area at hex addresses "A" or "E", so I forced the issue and just overwrote whatever code was written at the top of the segment. This tended to cause the program to crash after I did a "printf" and tried another driver function (small wonder, eh?). Code view tells me that some of my instructions are not legal after I do a "printf", so something is writing into the driver area. So, I wrote another kluge to save the current state of the driver before a "printf" and I restore it directly after the "printf". I would much rather have the source code for the driver so that the assembler could make it relocatable and then I wouldn't care where it was loaded. But, as long as I'm reading the binary driver from disk to memory, this will have to do. In the demo program I'm only doing the Save and Restore of the driver around one "printf" as the other "printf"s are only going to execute if something is wrong. The Save and Restore should be around EVERY "printf". Don't be alarmed by a couple of compiler warnings about different levels of indirection. This is where I'm ANDing the pointer and if I were a "C" compiler, I'd complain about this too! ;-) Seriously, if someone could look at this code and tell me how to do this in a cleaner way, I'd love to hear from them. Assembly Routines: I wrote routines SB3 thru SB12 which correspond with the functions found in Appendix A of the SOUND BLASTER user reference manual. I'll get around to writing SB0 thru SB2 as well as SB13 someday. They aren't really needed at the moment. Sound Input: I'm digitizing from my Sanyo CD player at volume level 2. I recommend using the TANNAHILL WEAVERS "Dancing Feet" CD. Bagpipe music digitizes best. ;-) Of course, 2 LIVE CREW might be fun, too. COPYRIGHT: Get real. Copyright this crud? This code is now in the public domain. Use it at your own risk. If you develop anything out of it, send it to me so I can have fun with it. If you make a million+ dollars, send me 10%. The Code: "out1.c" --------------------CUT HERE-------------------------------------------------- #include <stdio.h> extern char far SB_DRIVER; extern char far SB_BUFFER; extern unsigned int far SB_STATUS_WORD; extern int far SB3(); extern int far SB4(); extern int far SB5(); extern int far SB6(); extern int far SB7(); extern int far SB8(); extern int far SB9(); extern int far SB10(); extern int far SB11(); extern int far SB12(); unsigned int c; unsigned int count; unsigned int SB_DRIVER_SIZE; unsigned long int start_seg_addr; char far *ptr; unsigned char SB_DRIVER_CODE[3000]; main() { FILE *fp; unsigned long int i; unsigned int j; unsigned short int k; int TV_VOICE(); int LOAD_DRIVER(); k = TV_VOICE(); k = LOAD_DRIVER(); k = SB3(); if( k != 0){ printf("Init FAILS %d\n",k); } k = SB5(); if( k != 0){ printf("Set status word FAILS %d\n",k); } k = SB4(1); if( k != 0){ printf("Speaker on FAILS %d\n",k); } k = SB6(); SB_WAIT(); if( k != 0){ printf("Voice out FAILS %d\n",k); } k = SB4(0); if( k != 0){ printf("Speaker off FAILS %d\n",k); } k = SB7(5000,0,30000); SB_WAIT(); if( k != 0){ printf("Voice in FAILS %d\n",k); } k = SB4(1); if( k != 0){ printf("Speaker on FAILS %d\n",k); } for(i=0; i < 10; i++){ /* Play the newly recorded voice 10 times */ k = SB6(); SB_WAIT(); if( k != 0){ printf("Voice out FAILS %d\n",k); } else { SAVE_DRIVER(); printf("Voice has been output\n"); RESTORE_DRIVER(); } } } int TV_VOICE() { FILE *fp; if( (fp = fopen("TV4.BIN","rb")) == NULL){ return(1); } ptr = &SB_BUFFER; count = 0; while ( (c = fgetc(fp)) != EOF){ *(ptr+count) = c; count++; } fclose(fp); return(0); } int SB_WAIT() /* Waits until sound board is done */ { unsigned int j; do { /* Loop until sound board is done */ j = SB_STATUS_WORD; } while(j != 0); } int LOAD_DRIVER() /* Load driver from disk file */ { FILE *fp; ptr = &SB_DRIVER; start_seg_addr = ptr; start_seg_addr = start_seg_addr & 0xFFFF0000; ptr = start_seg_addr; if( (fp = fopen("CT-VOICE.DRV","rb")) == NULL){ return(1); } SB_DRIVER_SIZE = 0; while ( (c = fgetc(fp)) != EOF){ *(ptr+SB_DRIVER_SIZE) = c; SB_DRIVER_SIZE++; } fclose(fp); return(0); } int SAVE_DRIVER() /* Save current state of driver from memory */ { int i; ptr = start_seg_addr; for (i=0; i < SB_DRIVER_SIZE; i++){ SB_DRIVER_CODE[i] = *(ptr+i); } } int RESTORE_DRIVER() /* Restore driver back into memory */ { int i; ptr = start_seg_addr; for (i=0; i < SB_DRIVER_SIZE; i++){ *(ptr+i) = SB_DRIVER_CODE[i]; } } --------------------CUT HERE-------------------------------------------------- ASSEMBLY routines: "sb.asm" --------------------CUT HERE-------------------------------------------------- PGROUP GROUP INTERFACE INTERFACE SEGMENT BYTE PUBLIC 'DATA' ASSUME CS:INTERFACE PUBLIC _sb_driver _SB_DRIVER PROC FAR db 2500 dup (0) _SB_DRIVER ENDP PUBLIC _SB_STATUS_WORD _SB_STATUS_WORD PROC FAR dw 0 _SB_STATUS_WORD ENDP PUBLIC _SB_BUFFER _SB_BUFFER PROC FAR db 32000 dup (0) _SB_BUFFER ENDP PUBLIC _SB3 ;Init Sound Board _SB3 PROC FAR mov bx,3 ;Init Function code jmp [si] ;Go to top of segment (where the driver starts) _SB3 ENDP PUBLIC _SB4 ;Speaker off(0)/on(1) Sound Board Interface _SB4 PROC FAR push bp ;save the original base pointer mov bp,sp ;store the beginning of the sack in bp mov bx,4 mov ax,[bp+6] ;Function code pop bp ;restore the base pointer jmp [si] ;Go to top of segment (where the driver starts) _SB4 ENDP PUBLIC _SB5 ;Set status word address Sound Board Interface _SB5 PROC FAR mov bx,5 lea di,_SB_STATUS_WORD ;load address of status word into reg di jmp [si] ;Go to top of segment (where the driver starts) _SB5 ENDP PUBLIC _SB6 ;Output voice - Sound Board Interface _SB6 PROC FAR mov bx,6 lea di,_SB_BUFFER ;load address of sount buffer into reg di jmp [si] ;Go to top of segment (where the driver starts) _SB6 ENDP PUBLIC _SB7 ;Input voice Sound Board Interface _SB7 PROC FAR push bp ;save the original base pointer mov bp,sp ;store the beginning of the sack in bp mov bx,7 mov ax,[bp+6] ;Sampling rate mov dx,[bp+8] ;Size of buffer mov cx,[bp+10] ;ditto lea di,_SB_BUFFER ;load address of sound buffer into reg di pop bp ;restore the base pointer jmp [si] ;Go to top of segment (where the driver starts) _SB7 ENDP PUBLIC _SB8 ;Stop Voice Process Sound Board Interface _SB8 PROC FAR mov bx,8 jmp [si] ;Go to top of segment (where the driver starts) _SB8 ENDP PUBLIC _SB9 ;Uninstall Driver - Sound Board Interface _SB9 PROC FAR mov bx,9 jmp [si] ;Go to top of segment (where the driver starts) _SB9 ENDP PUBLIC _SB10 ;Pause output - Sound Board Interface _SB10 PROC FAR mov bx,10 jmp [si] ;Go to top of segment (where the driver starts) _SB10 ENDP PUBLIC _SB11 ;Continue output - Sound Board Interface _SB11 PROC FAR mov bx,11 jmp [si] ;Go to top of segment (where the driver starts) _SB11 ENDP PUBLIC _SB12 ;Break-out looping voice - Sound Board Interface _SB12 PROC FAR push bp ;save the original base pointer mov bp,sp ;store the beginning of the sack in bp mov bx,12 mov ax,[bp+6] ;load Parameter into ax pop bp ;restore the base pointer jmp [si] ;Go to top of segment (where the driver starts) _SB12 ENDP INTERFACE ENDS END --------------------CUT HERE-------------------------------------------------- Please let me know if you get this working on your machine and send me copies of anything you make out of it, so I can have fun with it too! Terry -- INTERNET: tjw@unix.cis.pitt.edu BITNET: TJW@PITTVMS CC-NET: 33802::tjw UUCP: {decwrl!decvax!idis, allegra, bellcore}!pitt!unix.cis.pitt.edu!tjw And if dreams could come true, I'd still be there with you, On the banks of cold waters at the close of the day. - Craig Johnson