[comp.sys.ibm.pc.hardware] Sound Blaster Voice Program

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