/*
    A bit more complicated example how to use OSS to play mixed 16-bit
    stereo 44100 kHz data. This time we install a signal handler and a pipe
    for inter-process communication. Threads might be better, but this
    approach works with all Unixes.

    Also included is the use of gettimeofday() to get the most precise
    timing for your demo/game. Press 'q' and enter to exit the program.

    - Marq/Fit 2000

    You may use the source as you wish.
 */

#include <sys/ioctl.h>
#include <fcntl.h>
#include <unistd.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <linux/soundcard.h>

#include <sys/time.h> /* Needed only for gettimeofday() */

#define BUFFER 1024 /* Bytes to mix at a time */

int comm_pipe[2]; /* The pipe used for inter-process communication */

void signal_handler(int param); /* Our signal handler */

int main()
{
    int handle,
        tmp,
        child;

    char sampledata[BUFFER];

    struct sigaction old_handler, /* Structs for signal handler */
                     new_handler;

    /* Open the device for writing */
    handle=open("/dev/dsp",O_WRONLY);
    if(handle==-1)
        return(EXIT_FAILURE);

    /* Set sound parameters. If you set them in a different order I can't
       promise it will work. ioctl() calls return -1 if the operation
       failed completely. If the parameter you passed to an ioctl()
       is modified, the device can't do something you asked. Didn't
       bother to check them... */

    /* Set fragment length (don't do if you don't care about latency).
       Small values give a small latency but will sound bad if the app
       won't get enough CPU time.
       (2<<16) - We want two fragments
       13      - The size of fragment is 2^13 = 8192 bytes (very small)
    */
    tmp=(2<<16)+13;
    ioctl(handle,SNDCTL_DSP_SETFRAGMENT,&tmp);

    /* Set output format. Some usual values are:
       AFMT_S16_LE - 16-bit signed
       AFMT_U8     - 8-bit unsigned */
    tmp=AFMT_S16_LE;
    ioctl(handle,SNDCTL_DSP_SETFMT,&tmp);

    /* Set number of channels. 0=mono, 1=stereo */
    tmp=1;
    ioctl(handle,SNDCTL_DSP_STEREO,&tmp);

    /* Set playing frequency. 44100, 22050 and 11025 are safe bets. */
    tmp=44100;
    ioctl(handle,SNDCTL_DSP_SPEED,&tmp);

    /* OK. Now the device should be initialised. Let's create a pipe
       for communication. */
    pipe(comm_pipe);

    /* Install a new handler for SIGCHLD signal. */
    new_handler.sa_handler=signal_handler;
    new_handler.sa_flags=0;
    sigaction(SIGCHLD,&new_handler,&old_handler);

    /* Now fork a child process that runs in the background and handles
       the audio writing. */
    if(!(child=fork())) /* Child process runs in this if-clause */
    {
        /* Child closes the output pipe. We don't need it. */
        close(comm_pipe[1]); 

        while(1)
        {
            /* Notify main process that we need sample data */
            kill(getppid(),SIGCHLD);

            /* Read audio data through pipe from the main process */
            read(comm_pipe[0],sampledata,BUFFER);

            /* Play the read audio data. Blocks until all bytes are
               in the OSS's buffer. */
            write(handle,(void *)sampledata,BUFFER);
        }
    }

    /* Main program closes the input pipe. We don't need it. */
    close(comm_pipe[0]);

    /* The main program continues here. Insert your megademo here ;v) */
    while(getchar()!='q')
        ;

    /* Kill the child process and close device */
    kill(child,SIGKILL);
    close(handle);

    /* Restore the old signal handler. Not really necessary since our
       program would exit anyway. */
    sigaction(SIGCHLD,&old_handler,&new_handler);

    return(EXIT_SUCCESS);
}

/* Our signal handler. Gets called when the child process sends a
   signal with kill() ie. new sample data must be mixed. */
void signal_handler(int param)
{
    char    mixbuffer[BUFFER];

    int n;

    struct timeval  system_time;    /* Needed only for gettimeofday() */
    struct timezone system_timezone;

    /* Put just some crap to the mixing buffer. We might as well mix
       some .mod or .mp3 here. By mixing small fragments you'll get
       better latency. */
    for(n=0;n<BUFFER;n++)
        mixbuffer[n]=n;

    /* Send sample data through the pipe to child.  */
    write(comm_pipe[1],mixbuffer,BUFFER);

    /* Here comes the precise timing with gettimeofday(). Not really
       related to the sample playing but it's useful to know anyway ;v)
       Struct members tv_sec and tv_usec contain the system time in
       seconds and microseconds */
    gettimeofday(&system_time,&system_timezone);
    fprintf(stderr,"%d.%06d\n",
            (int)system_time.tv_sec,(int)system_time.tv_usec);
}

/* EOS */
