/*
 * wave - crudely play Windows WAV files while preemptive multithreading
 *      - if -sb option given, outputs directly through soundblaster card
 *      - demonstrates hooking the timer tick
 *      - demonstrates messaging between ISR and a thread
 *
 * Copyright (c) 1999 Erick Engelke
 *
 * This is a very crude program which reads Windows WAV files and
 * attempts to convert them to 1-BIT speaker output, or 8-BIT SoundBlaster.
 *
 * NOTE: only 8 bit WAV files supported.
 */
#include <stdio.h>
#include <mem.h>
#include <process.h>
#include <ctype.h>
#include <conio.h>
#include <dos.h>
#include <rtos.h>
#include <string.h>

/*------------------ structure of a WAV file ------------------------*/
typedef struct {
    BYTE  wav_riff[4];       // "RIFF"
    DWORD wav_chunksize;
    BYTE  wav_wave[4];       // "WAVE"
} wav_hdr;

typedef struct {
    char    bID[4];
    DWORD   dwSize;
} chunk_str;

typedef struct {
    WORD    wFormatTag;     // 1 = WAVE_FORMAT_PCM
    WORD    wChannels;      // 1 = mono
    DWORD   dwSamplesPerSec;    // sampling rate
    DWORD   dwAvgBYTEsPerSec;   // for buffer estimation
    WORD    wBlockAlign;        // data block size
} fmt_str;

/*--------------------- global variables ----------------------------*/
WORD soundskip;
WORD soundposn;
BYTE *sounddata;
WORD soundlength;
BYTE soundthreshhold = 120;

/*--------------------- global functions ----------------------------*/

void settimer( void (*x)() )
{
    disable();
    k_user_int8 = x;
    enable();
}

/*------------------ Sound Blaster Functions --------------------------*/
int use_sb = 0;
WORD sb_base = 0;

/*
 * Offsets relative to base I/O address.
 */
#define SB_DSP_RESET		0x06
#define SB_DSP_READ_DATA	0x0A
#define SB_DSP_WRITE_DATA	0x0C
#define SB_DSP_WRITE_STATUS	0x0C
#define SB_DSP_DATA_AVAIL	0x0E

/* DSP Commands */
#define SB_DIRECT_8_BIT_DAC	0x10
#define SB_SPEAKER_ON		0xD1
#define SB_SPEAKER_OFF		0xD3
#define SB_DSP_ID		0xE0
#define SB_DSP_VER		0xE1


void sb_writedac(BYTE x)
{
    while(inportb(sb_base + SB_DSP_WRITE_STATUS) & 0x80);
    outportb(sb_base + SB_DSP_WRITE_DATA, x);
}

void sb_init( void )
{
    WORD port;
    WORD count, count2;

    for ( port = 0x210 ; port <= 0x260 ; port += 0x10 ) {
        count = 10;
        while ( count != 0 ) {
            outportb( port + 0x6, 1 );        /* reset SB */
            for ( count2 = 0 ; count2 < 100 ; ++count2 );
            outportb( port + 0x6, 0 );

            for ( count2 = 50 ;
                  (count2 !=0 ) && ( inportb( port + 0xe ) < 128 ) ;
                  count2-- );

            if ( ( count2 == 0 ) || ( inportb( port + 0xa ) != 0xaa )) {
                count--;
                if ( count != 0 ) continue;
                else break;
            } else {
                sb_base = port;
                cprintf("Sound Blaster found found at port 0x%x\r\n", port );

                /* enable speaker */
                sb_writedac( SB_SPEAKER_ON );
                return;
            }
        }
    }
}



void sb_code( BYTE value )
{
    sb_writedac( 0x10 );
    sb_writedac( value );
}
/*------------------ support functions ------------------------------*/

int read_wav_hdr( FILE *f, wav_hdr *wav )
{
    if ( fread( wav, sizeof( wav_hdr ), 1 , f ) < 1 ) {
        cputs( "unable to read the header\r\n");
        return( 0 );
    }
    /* see if RIFF format */
    if ( memcmp( wav->wav_riff , "RIFF", 4 ) != 0 ) {
        cputs( "not RIFF format\r\n");
        return( 0 );
    }
    /* see if WAVE subformat */
    if ( memcmp( wav->wav_wave, "WAVE", 4 ) != 0 ) {
        cputs( "not WAVE subformat\r\n");
        return( 0 );
    }
    return( 1 );
}

int read_chunk( FILE *f, chunk_str *ch )
{
    if ( fread( ch, sizeof( chunk_str), 1 , f ) < 1 ) {
        cputs( "unable to read a chunk\r\n");
        return( 0 );
    }
    return( 1 );
}
int read_fmt( FILE *f, fmt_str *fmt )
{
    if ( fread( fmt, sizeof( fmt_str), 1 , f ) < 1 ) {
        cputs( "unable to read the fmt header\r\n");
        return( 0 );
    }
    if ( fmt->wFormatTag != 1 ) {
        cputs( "not WAVE_FORMAT_PCM \r\n");
        return( 0 );
    }
    return( 1 );
}

void interrupt_time_player( void )
{
    static int inside = 0;
    BYTE prevval, b;

    if ( inside == 0 ) {
        inside = 1;

        if ( soundposn < soundlength ) {
            b = sounddata[ soundposn ];
            if ( use_sb ) sb_code( b );
            else {
                prevval = inportb( 0x61 );
                b = (prevval & 0xFC) | (( b > soundthreshhold) ? 2 : 0) ;
                if ( b != prevval )
                    outportb( 0x61, b );
            }
            soundposn += soundskip;

            if ( soundposn >= soundlength )
            ksendmessage( kmainthread, 0, 0 );
        }
        inside = 0;
    }
}


void audio_play( WORD channels, DWORD rate, BYTE *data, WORD length )
{
    int value;
    DWORD tempdata;

    soundposn = 0;
    soundlength = length;
    sounddata = data;
    if ( rate > 10000 ) rate = 10000;   /* we are only accurate to 0.1 kHz */
    soundskip = channels;

    rt_timerfreq( rate );

    /* invoke the player */
    settimer( interrupt_time_player );

    /* wait until it's done */
    kreadmessage( &value, &tempdata );

    /* remove the player */
    settimer( NULL );
}

play( char *fname )
{
    FILE *f;
    wav_hdr wav;
    chunk_str chunk;
    fmt_str fmt;
    DWORD posn;
    BYTE *data;
    int samples_per_data;

    if ( (f = fopen( fname, "rb")) == NULL ) {
        cprintf( "unable to open WAV file %s", fname);
        return;
    }

    if ( ! read_wav_hdr( f, &wav )) return;

    do {
        if ( ! read_chunk( f, &chunk ))
            break;

        posn = ftell( f );
        if ( !memcmp(chunk.bID,"fmt ", 4 ))
            if ( ! read_fmt( f, &fmt ))
                break;

        if ( !memcmp(chunk.bID,"data", 4 )) {
            data = kcalloc( 1, chunk.dwSize );
            if ( data != NULL ) {
                fread( data, chunk.dwSize, 1 , f );

        samples_per_data = fmt.wChannels * fmt.wBlockAlign;
    cprintf("%u channels, %u align \n",
        fmt.wChannels, fmt.wBlockAlign);
                audio_play( samples_per_data , fmt.dwSamplesPerSec,
                        data, chunk.dwSize );
                kfree( data );
            }
            break;
        }
        fseek( f, posn + chunk.dwSize , SEEK_SET);
    } while ( 1 );
    fclose( f );
    return;
}

/*
 * consolethread - just dumps stuff to console for visual effect
 */
void consolethread( DWORD data )
{
    int i = 0;

    if ( data == 1 )
        kwindow( 1, 1, 40, 15 );
    else
        kwindow( 40, 1, 80, 15 );

    /* infinite loop */
    do {
        cprintf(" %u", i++ );
    } while ( 1 );
}

int main( int argc, char **argv )
{
    int i;

    kpreemptive = 1;
    kdebug = 1;
    rt_init( 100 );

    clrscr();
    kwindow( 1, 15, 80, 25 );

    sb_init();

    if ( argc < 2 ) {
        cputs("WAVE [-sb] wavefile1.WAV ... \r\n");
        cputs(" -sb means use SoundBlaster\r\n");
        return;
    }

    /* create a few threads for visual threading effect */
    rt_newthread( consolethread, 1, 4096, 0, "console thread #1" );
    rt_newthread( consolethread, 2, 4096, 0, "console thread #2" );


    for ( i = 1 ; i < argc ; ++i ) {
        if ( !stricmp( argv[i], "-sb")) {
            if (sb_base == 0 ) rt_halt("no SoundBlaster card, cannot use -sb option");
            use_sb = 1;
            cputs("Using SoundBlaster\r\n");
            continue;
        } else {
            cprintf("playing : %s ...", argv[i] );
            play( argv[i] );
            cputs("done\n\r");
        }
        rt_sleep( 1000 );
    }
    return( 0 );
}
