[comp.sys.ibm.pc.hardware] My last Sound Blaster Software contribution...

tjw@unix.cis.pitt.edu (TJ Wood WA3VQJ) (09/01/90)

For this week, anyway! ;-)

I purchased MSC 5.1, so that I could join the civilized world and I've
made a couple of significant changes to the code I wrote to interface
the Sound Blaster AD/DA program loadable driver to the C language.

First, I got rid of some very klugie code that overwrote some segments.
No more "swapping" everytime I want to use printf.

Of course, I introduced something that is almost as klugie:  I allocate
64K bytes for the driver (which is less than 3K bytes long).  Given
enough free time, I'll get "malloc" or "halloc" to work and I will not
have to play these games.  (Oh, if only I didn't have to work for a
living...)  

In a "real" application, I could use the extra space I've set aside for
the driver for buffering the data from/to the AD/DA.  I just wasn't
concerned about efficiency.

Other changes that I made include a new `syntax' for the record command:
"RECORD fn.ext digi-rate speaker-status".  To record to the file ANY.SNG
at 12KHz and to have the speaker turned on during recording, you'd type:
"RECORD ANY.SNG 12000 1".  My old Soundblaster user's guide (and the
developer's kit) state that you should NOT have the speaker turned on
during recording.  So naturally, I did it, just to see why.  We'll there
is a lot of whistling and chirping that can be heard during the recording,
but I'd rather be able to monitor what the soundblaster is recording,
even if it isn't "perfect" quality.

I'm also now passing the address of the driver down to the SBn()
routines.  It's always the last (or only) argument of the routines.
Functions 1-12 (of the 13 possible) are now supported.  I also now call
SB0 (get driver version number) and SB9 (uninstall driver) as part of
the startup/shutdown of the programs.

In the past week, I've received a couple of questions worth mentioning
here.  One was "Why can't your play.c program play VOC files".  The
answer is that VOC files have a header on them.  To play a VOC file, all
one needs to do is strip off the header.  My RECORD.C has a function
called EXIT_VOICE which strips off the header off of the EXIT.VOC file.
Since what I really wanted to do was record entire songs, not just
a couple of seconds of voice, I really didn't think about putting
headers on the files (or about stripping those headers off).  If you
want to play VOC files, copy the for loop out of EXIT_VOICE and use
it to strip off that header.

Another question was: "Why did you write programs that already exist?",
referring to VPLAY and VREC that (now) come with the sound blaster.
Well, my old package didn't have these.  I guess great minds think alike.

Lastly, a very good question was: "How'd you figure all this out?".
Well, my old user manual contained information not in the new manual.
The driver and the Creative Voice Format was documented.  This is now in
the developer's kit.  I thought everyone got this.  That's why I didn't
really comment the code.

On the topic of the developer's kit:  So far it looks pretty good!
There is a great deal of technical information as well as listings of
the code that Creative Lab's has created.  The code is in C and duplicated
in BASIC for those that still use it!  I've only played a bit with these
examples.  I modified one for the text-to-speech interface  and got it
working in short order.  There are descriptions of how the other
drivers work and some hardware information, too.

There's quite a bit to be learned (on my part) about MIDI interfaces,
etc, before I can take advantage of everything that's in there.

Other thoughts:  I purchased my soundblaster at the "old" price ($199).
As of yesterday, Electronics Boutique (and everyone else at the mall)
was selling it for $179.  However, I got lucky.  My "old" soundblaster
has those 2 extra chips!  I got a fully populated board.  

Finally:  If you've read this far, you must be interested in all of
this.  Drop me a line and say "Hello" if you get the chance.  I'd like
hear what other folks are doing with the soundblaster.


Now for the code:  Here's the deal.  You can do anything you want with
this code.  It's yours to play with (or even sell if you can find
someone who'd buy it).   The only thing I ask is that if you do develop
something "neat" with it, you'd send me a copy.  Sound fair to you?
Obviously, there's no warranty to this code.  It's in the public domain
and you get it "as is".  I disown it! ;-)

Compile the follwing with MSC V5.0 (or later I guess) using the Huge
memory model (/AH switch).  I also build it for use with codeview.
You'll also need to use your CT-VOICE.DRV file and copy TV4.VOC to
EXIT.VOC once again.

--------------------------------Cut Here--------------------------------
/* The new Play.c */

#include <stdio.h>

char huge SB_DRIVER[65536];
unsigned int far SB_STATUS_WORD;

unsigned long int array_size = 65536;
char huge array1[65536];
char huge array2[65536];

extern  int far SB0();      /* Get version of driver                        */
extern  int far SB1();      /* Set Base I/O Address                         */
extern  int far SB2();      /* Set Interrupt number (2,3,5, or 7)           */
extern  int far SB3();      /* Init Driver                                  */
extern  int far SB4();      /* Turn speaker off (0) or (1) On               */
extern  int far SB5();      /* Tell Driver the address of the Status Word   */
extern  int far SB6();      /* Play voice                                   */
extern  int far SB7();      /* Record Voice                                 */
extern  int far SB8();      /* Stop Voice Process                           */
extern  int far SB9();      /* Uninstall Driver (clean up, etc)             */
extern  int far SB10();     /* Pause playing voice                          */
extern  int far SB11();     /* Continue playing voice                       */
extern  int far SB12();     /* Break-out looping voice                      */

unsigned int c;
unsigned long int count;
unsigned int SB_DRIVER_SIZE;
unsigned long int start_seg_addr;
char far *ptr;


main(argc,argv)
int argc;
char *argv[];
{
    FILE *fp;

    unsigned long  int i;
    unsigned int j;
    unsigned short int k;
    unsigned int digi_rate;
    unsigned int version;

    int LOAD_DRIVER();

    int now_at_end=0;
    int now_playing=0;



    if (argc != 2){
        printf("Usage:  Play fn.ext\n");
        exit(1);
    }

    if ( (fp = fopen(argv[1],"rb") ) == NULL) { /* Open input file */
        printf("PLAY: error opening %s , file not found\n",argv[1]);
        exit(2);
    }

    printf("Press any key to EXIT\n\n");    

    k = LOAD_DRIVER();      /* Read the VOXKIT driver into memory */

    version = SB0(SB_DRIVER);     /* Get version number of driver */
    printf("CT-DRIVER version is %d.%d\n\n",(version & 0xFF00) >> 8,version & 0x00FF);

    k = SB3(SB_DRIVER);              /* Init */
    if( k != 0){
        printf("Init FAILS %d\n",k);
    }

    k = SB5(&SB_STATUS_WORD,SB_DRIVER);   /* Assign the status word */
    if( k != 0){
        printf("Set status word FAILS %d\n",k);
    }


    k = SB4(1,SB_DRIVER);             /* Speaker On */
    if( k != 0){
        printf("Speaker on FAILS %d\n",k);
    }


    while(now_at_end == 0){

        printf("loading buffer 1\r");

        for(i=0; i < array_size; i++){
            c = fgetc(fp);
            if(c != EOF){
                array1[i] = c;
            }
            else {
                if(i == 0){
                    now_at_end = 1;
                    break;
                }
                else {
                    now_at_end = 2;
                    for (j = i; j < array_size; j++){
                        array1[j] = 0;
                    }
                }
                break;
            }
        }

        if(now_playing == 1){

            printf("                \r");

            SB_WAIT();  /* Wait for sb to finish playing array2 */

            if(kbhit() != 0){   /* If any key is pressed, exit */
                k = getche();
                break;
            }

        }

        k = SB6(array1,SB_DRIVER);

        if( k != 0){
            printf("Voice out FAILS %d\n",k);
        }

        now_playing = 1;    /* raise the flag */

        if (now_at_end != 0){
            break;
        }


        printf("loading buffer 2\r");

        for(i=0; i < array_size; i++){
            c = fgetc(fp);
            if(c != EOF){
                array2[i] = c;
            }
            else {
                if(i == 0){
                    now_at_end = 1;
                    break;
                }
                else {
                    now_at_end = 2;
                    for (j = i; j < array_size; j++){
                        array2[j] = 0;
                    }
                }
                break;
            }
        }

        printf("                \r");


        SB_WAIT();  /* Wait for sb to finish playing array1 */

        if(kbhit() != 0){   /* If any key is pressed, exit */
            k = getche();
            break;
        }

        if(now_at_end != 0){
            break;
        }

        k = SB6(array2,SB_DRIVER);

        if( k != 0){
            printf("Voice out array2 FAILS %d\n",k);
        }

    }

    fclose(fp);

    k = SB4(0,SB_DRIVER);
    if( k != 0){
        printf("Speaker off FAILS %d\n",k);
    }


    k = SB9(SB_DRIVER);
    if( k != 0){
        printf("Uninstall driver FAILS %d\n",k);
    }


}

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;

    if( (fp = fopen("CT-VOICE.DRV","rb")) == NULL){
        return(1);
    }

        
    SB_DRIVER_SIZE = 0;

    while ( (c = fgetc(fp)) != EOF){

        SB_DRIVER[SB_DRIVER_SIZE] = c;

        SB_DRIVER_SIZE++;
    }

    fclose(fp);

    return(0);
}

----------------------------------Cut Here--------------------------------
/* The new Record.C */


#include <stdio.h>

char huge SB_DRIVER[65536];
unsigned int far SB_STATUS_WORD;

unsigned long int array_size = 65536;
char huge array1[65536];
char huge array2[65536];

extern  int far SB0();      /* Get version of driver                        */
extern  int far SB1();      /* Set Base I/O Address                         */
extern  int far SB2();      /* Set Interrupt number (2,3,5, or 7)           */
extern  int far SB3();      /* Init Driver                                  */
extern  int far SB4();      /* Turn speaker off (0) or (1) On               */
extern  int far SB5();      /* Tell Driver the address of the Status Word   */
extern  int far SB6();      /* Play voice                                   */
extern  int far SB7();      /* Record Voice                                 */
extern  int far SB8();      /* Stop Voice Process                           */
extern  int far SB9();      /* Uninstall Driver (clean up, etc)             */
extern  int far SB10();     /* Pause playing voice                          */
extern  int far SB11();     /* Continue playing voice                       */
extern  int far SB12();     /* Break-out looping voice                      */

unsigned int c;
unsigned long int count;
unsigned int SB_DRIVER_SIZE;
unsigned long int start_seg_addr;
char far *ptr;


main(argc,argv)
int argc;
char *argv[];
{

    FILE *fp;

    unsigned long  int i;
    unsigned int j;
    unsigned short int k;
    unsigned int digi_rate;
    unsigned int speaker_setting;

    unsigned int version;

    int LOAD_DRIVER();


    if (argc < 2){
        printf("Usage:  RECORD fn.ext\n");
        exit(1);
    }


    if ( (fp = fopen(argv[1],"wb") ) == NULL) { /* Open output file */
        printf("RECORD: error opening %s\n",argv[1]);
        exit(2);
    }

    if (argc >= 3){
        sscanf(argv[2],"%d",&digi_rate);
    }
    else {
        printf("Rate to Scan? (4000 to 12000) >");
        scanf("%d",&digi_rate);
        printf("\n\n");
    }

    if (argc >= 4){
        sscanf(argv[3],"%d",&speaker_setting);
    }
    else {
        speaker_setting = 0;
    }

    k = LOAD_DRIVER();

    version = SB0(SB_DRIVER);
    printf("CT-DRIVER version is %d.%d\n\n",(version & 0xFF00) >> 8,version & 0x00FF);


    k = SB3(SB_DRIVER);
    if( k != 0){
        printf("Init FAILS %d\n",k);
    }

    k = SB5(&SB_STATUS_WORD,SB_DRIVER);
    if( k != 0){
        printf("Set status word FAILS %d\n",k);
    }

    k = SB4(speaker_setting,SB_DRIVER);
    if( k != 0){
        printf("Speaker control FAILS %d\n",k);
    }

    printf("Press any key to BEGIN Recording...");

    while (kbhit() == 0);
    k = getche();

    printf("\rPress any key to STOP Recording... ");

    k = SB7(array1,digi_rate,array_size,SB_DRIVER);

    if( k != 0){
        printf("Voice in FAILS %d\n",k);
    }


    while (1 == 1){

        SB_WAIT();

        if(kbhit() != 0){
            k = getche();
            break;
        }

        k = SB7(array2,digi_rate,array_size,SB_DRIVER);

        for(i=0; i < array_size; i++){  /* Write first buffer to file */
            fputc(array1[i],fp);
        }

        SB_WAIT();


        k = SB7(array1,digi_rate,array_size,SB_DRIVER);

        if( k != 0){
            printf("Voice in FAILS %d\n",k);
        }

        for(i=0; i < array_size; i++){  /* Write second buffer to file */
            fputc(array2[i],fp);
        }
    }

    for(i=0; i < array_size; i++){  /* Write first buffer to file */
        fputc(array1[i],fp);
    }

    fclose(fp);

    k = SB4(1,SB_DRIVER);
    if( k != 0){
        printf("Speaker on FAILS %d\n",k);
    }


    printf("\rYour recording is complete...      ");

    k = EXIT_VOICE();    

    k = SB6(array1,SB_DRIVER);

    SB_WAIT();

    if( k != 0){
        printf("Voice out FAILS %d\n",k);
    }

    k = SB4(0,SB_DRIVER);
    if( k != 0){
        printf("Speaker off FAILS %d\n",k);
    }

    k = SB9(SB_DRIVER);
    if( k != 0){
        printf("Uninstall driver FAILS %d\n",k);
    }

}

int EXIT_VOICE()
{
    FILE *fp;

    unsigned long int i;

    if( (fp = fopen("EXIT.VOC","rb")) == NULL){
        return(1);
    }

    for(i=0; i < 0x1a; i++){ /*Strip off the header */
        c = fgetc(fp);
    }

    i=0;
    while ( (c = fgetc(fp)) != EOF){
        array1[i] = c;
        i++;
    }

    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;

    if( (fp = fopen("CT-VOICE.DRV","rb")) == NULL){
        return(1);
    }

        
    SB_DRIVER_SIZE = 0;

    while ( (c = fgetc(fp)) != EOF){

        SB_DRIVER[SB_DRIVER_SIZE] = c;

        SB_DRIVER_SIZE++;
    }

    fclose(fp);

    return(0);
}

---------------------------------Cut Here---------------------------------

;New SB.ASM


PGROUP  GROUP   INTERFACE
.MODEL HUGE
;    INTERFACE SEGMENT   BYTE PUBLIC 'PROG'
    INTERFACE SEGMENT   BYTE PUBLIC 'DATA'

    ASSUME  CS:INTERFACE


    PUBLIC _SB0          ;Get Version number  Returns: AH - Major  AL - Minor
_SB0 PROC    FAR

    push    bp          ;save the original base pointer
    mov     bp,sp       ;store the beginning of the stack in bp
                        ;push (in order) any additional registers here

    mov     bx,0        ;Init Function code

    call    dword ptr [bp+6]    ;call the driver

    pop     bp

    ret

_SB0 ENDP

    PUBLIC _SB1          ;Set Base I/O Address
_SB1 PROC    FAR

    push    bp          ;save the original base pointer
    mov     bp,sp       ;store the beginning of the stack in bp
                        ;push (in order) any additional registers here

    mov     bx,1        ;Init Function code
    mov     ax,[bp+6]   ;Set AX to be the base address (210x - 260x)

    call    dword ptr [bp+8]    ;call the driver

    pop     bp

    ret

_SB1 ENDP

    PUBLIC _SB2          ;Set Interrupt number
_SB2 PROC    FAR

    push    bp          ;save the original base pointer
    mov     bp,sp       ;store the beginning of the stack in bp
                        ;push (in order) any additional registers here

    mov     bx,2        ;Function code

    mov     ax,[bp+6]   ;Set AX to be the interrupt number for the DMA (2,3,5 or 7)

    call    dword ptr [bp+8]    ;call the driver

    pop     bp

    ret

_SB2 ENDP


    PUBLIC _SB3         ;Init Sound Blaster
_SB3 PROC    FAR
                        ;Returns: 0 - OK!;                  1 - Voice Card Fails
                        ;         2 - I/O read/write fails; 3 -Interrupt for DMA Fails

    push    bp          ;save the original base pointer
    mov     bp,sp       ;store the beginning of the stack in bp
                        ;push (in order) any additional registers here

    mov     bx,3        ;Init Function code

    call    dword ptr [bp+6]    ;call the driver

    pop     bp

    ret

_SB3 ENDP

    PUBLIC _SB4          ;Speaker off(0)/on(1) 
_SB4 PROC    FAR


    push    bp          ;save the original base pointer
    mov     bp,sp       ;store the beginning of the stack in bp
                        ;push (in order) any additional registers here

    mov     bx,4
    mov     ax,[bp+6]   ;Function code

    call    dword ptr [bp+8]    ;call the driver

    pop     bp

    ret


_SB4 ENDP

    PUBLIC _SB5          ;Set status word address 
_SB5 PROC    FAR


    push    bp          ;save the base pointer
    mov     bp,sp       ;store the beginning of the stack in bp

    push    di

    mov     bx,5        ;function code 5

    mov     di,[bp+6]   ;offset address of status word into reg di
    mov     es,[bp+8]   ;Segment address

    call    dword ptr [bp+10]    ;call the driver

    pop     di          ;restore the value of the di register
    pop     bp          ;restore the base pointer
    ret                 ;"Clem Clone! Back to the shadows again! (Appologies to The Firesign Theatre)


_SB5 ENDP

    PUBLIC _SB6          ;Output voice
_SB6 PROC    FAR

    push    bp          ;save the base pointer
    mov     bp,sp       ;store the beginning of the stack in bp

    push    di

    mov     bx,6        ;function code 6

    mov     di,[bp+6]   ;offset address of sound buffer into reg di
    mov     es,[bp+8]   ;Segment address

    call    dword ptr [bp+10]    ;call the driver

    pop     di          ;restore the value of the di register
    pop     bp          ;restore the base pointer
    ret                 ;"Clem Clone! Back to the shadows again! (Appologies to The Firesign Theatre)

_SB6 ENDP


    PUBLIC _SB7          ;Input voice 
_SB7 PROC    FAR


    push    bp          ;save the original base pointer
    mov     bp,sp       ;store the beginning of the stack in bp

    push    di          ;push (in order) any additional registers here

    mov     bx,7
    mov     di,[bp+6]  ;offset address of sound buffer into reg di
    mov     es,[bp+8]  ;Segment address
    mov     ax,[bp+10] ;Sampling rate
    mov     cx,[bp+12] ;ditto
    mov     dx,[bp+14] ;Size of buffer


    call    dword ptr [bp+16]    ;call the driver


    pop     di          ;restore the value of the di register 
    pop     bp          ;restore the base pointer
    ret                 ;"Clem Clone! Back to the shadows again! (Appologies to The Firesign Theatre)

_SB7 ENDP




    PUBLIC _SB8          ;Stop Voice Process 
_SB8 PROC    FAR

    push    bp          ;save the original base pointer
    mov     bp,sp       ;store the beginning of the stack in bp
                        ;push (in order) any additional registers here

    mov     bx,8        ;Init Function code

    call    dword ptr [bp+6]    ;call the driver

    pop     bp

    ret

_SB8 ENDP

    PUBLIC _SB9          ;Uninstall Driver
_SB9 PROC    FAR

    push    bp          ;save the original base pointer
    mov     bp,sp       ;store the beginning of the stack in bp
                        ;push (in order) any additional registers here

    mov     bx,9        ;Init Function code

    call    dword ptr [bp+6]    ;call the driver

    pop     bp

    ret

_SB9 ENDP

    PUBLIC _SB10        ;Pause Output Voice
_SB10 PROC    FAR       ;Returns: 0 - OK!;  1- Voice output not in progress

    push    bp          ;save the original base pointer
    mov     bp,sp       ;store the beginning of the stack in bp
                        ;push (in order) any additional registers here

    mov     bx,10        ;Init Function code

    call    dword ptr [bp+6]    ;call the driver

    pop     bp

    ret

_SB10 ENDP

    PUBLIC _SB11        ;Continue Output Voice
_SB11 PROC    FAR
                        ;Returns: 0 - OK!;  1- No Voice to continue

    push    bp          ;save the original base pointer
    mov     bp,sp       ;store the beginning of the stack in bp
                        ;push (in order) any additional registers here

    mov     bx,11        ;Init Function code

    call    dword ptr [bp+6]    ;call the driver

    pop     bp

    ret

_SB11 ENDP

    PUBLIC _SB12          ;Break-out Looping Voice
_SB12 PROC    FAR
                          ;Returns: 0 - OK!;  1- No voice in loop

    push    bp          ;save the original base pointer
    mov     bp,sp       ;store the beginning of the stack in bp
                        ;push (in order) any additional registers here

    mov     bx,12
    mov     ax,[bp+6]   ;load Parameter into ax

    call    dword ptr [bp+8]    ;call the driver

    pop     bp

    ret

_SB12 ENDP

    INTERFACE ENDS

    END

---------------------------------Cut Here-----------------------------------
-- 
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