/*      midasfx.c
 *
 * MIDAS sound effect library
 *
 * $Id: midasfx.c,v 1.3 1996/09/28 08:12:40 jpaana Exp $
 *
 * Copyright 1996 Petteri Kangaslampi and Jarno Paananen
 *
 * This file is part of the MIDAS Sound System, and may only be
 * used, modified and distributed under the terms of the MIDAS
 * Sound System license, LICENSE.TXT. By continuing to use,
 * modify or distribute this file you indicate that you have
 * read the license and understand and accept it fully.
*/


#include "lang.h"
#include "mtypes.h"
#include "errors.h"
#include "mmem.h"
#include "sdevice.h"
#include "midasfx.h"
#include "file.h"


/* Magic constant that is used to identify playing handles that refer to
   sounds on automatic effect channels: */
#define AUTOMAGIC 0x100

/* Maximum playing handle: */
#define MAXHANDLE 0xFFFE

/* Magic channel number - not found: */
#define NOCHANNEL 0xFFFF



/****************************************************************************\
*       enum fxFunctIDs
*       ---------------
* Description:  Function IDs for sound effect library functions
\****************************************************************************/

enum fxFunctIDs
{
    ID_fxInit = ID_fx,
    ID_fxClose,
    ID_fxLoadRawSample,
    ID_fxFreeSample,
    ID_fxSetAutoChannels,
    ID_fxPlaySample,
    ID_fxStopSample,
    ID_fxSetSampleRate,
    ID_fxSetSampleVolume,
    ID_fxSetSamplePanning,
    ID_fxSetSamplePriority
};



static unsigned numAutoChannels;        /* Number of auto FX channels */
static fxChannel *autoChannels;         /* Automatic effect channels */
static unsigned nextHandle;             /* Next sample playing handle */
static SoundDevice *SD;                 /* Sound Device for the effects */
static unsigned nextChannel;            /* Next automatic channel */



/****************************************************************************\
*
* Function:     int fxInit(void)
*
* Description:  Initializes the sound effect library
*
* Input:        SoundDevice *SD         Pointer to the Sound Device that will
*                                       be used for playing the effects
*
* Returns:      MIDAS error code
*
\****************************************************************************/

int CALLING fxInit(SoundDevice *_SD)
{
    /* Remember the Sound Device: */
    SD = _SD;

    /* We don't have any automatic channels: */
    numAutoChannels = 0;
    autoChannels = NULL;

    nextHandle = AUTOMAGIC;
    nextChannel = 0;

    return OK;
}




/****************************************************************************\
*
* Function:     int fxClose(void)
*
* Description:  Uninitializes the sound effect library
*
* Returns:      MIDAS error code
*
\****************************************************************************/

int CALLING fxClose(void)
{
    int         error;
    unsigned    i;
    unsigned    chan;

    /* Check if we have automatic effect channels. If yes, clear the sounds
       on them and free the channel structures: */
    if ( numAutoChannels != 0 )
    {
        for ( i = 0; i < numAutoChannels; i++ )
        {
            chan = autoChannels[i].sdChannel;

            if ( (error = SD->StopSound(chan)) != OK )
                PASSERROR(ID_fxClose);
        }

        if ( (error = memFree(autoChannels)) != OK )
            PASSERROR(ID_fxClose);
    }

    return OK;
}




/****************************************************************************\
*
* Function:     int fxLoadRawSample(char *fileName, unsigned sampleType,
*                   int loopSample, unsigned *sampleHandle)
*
* Description:  Loads a raw sample file into memory and adds the sample to
*               the sound device.
*
* Input:        char *fileName          sample file name
*               unsigned sampleType     sample type, see enum sdSampleType
*               int loopSample          1 if sample is looped, 0 if not
*               unsigned *sampleHandle  pointer to sample handle variable
*
* Returns:      MIDAS error code. The sample handle for the sample will be
*               written to *sampleHandle.
*
\****************************************************************************/

int CALLING fxLoadRawSample(char *fileName, unsigned sampleType,
    int loopSample, unsigned *sampleHandle)
{
    int         error;
    static char *buffer;
    static fileHandle f;
    static long len;
    static sdSample smp;


    /* Open the sound effect file: */
    if ( (error = fileOpen(fileName, fileOpenRead, &f)) != OK )
        PASSERROR(ID_fxLoadRawSample);

    /* Get file size: */
    if ( (error = fileGetSize(f, &len)) != OK )
    {
        fileClose(f);
        PASSERROR(ID_fxLoadRawSample);
    }

    /* Allocate sample loading buffer: */
    if ( (error = memAlloc(len, (void**) &buffer)) != OK )
    {
        fileClose(f);
        PASSERROR(ID_fxLoadRawSample);
    }

    /* Read the sample: */
    if ( (error = fileRead(f, buffer, len)) != OK )
    {
        fileClose(f);
        memFree(buffer);
        PASSERROR(ID_fxLoadRawSample);
    }

    /* Close the sample file: */
    if ( (error = fileClose(f)) != OK )
    {
        memFree(buffer);
        PASSERROR(ID_fxLoadRawSample);
    }

    /* Build Sound Device sample structure for the sample: */
    smp.sample = buffer;
    smp.samplePos = sdSmpConv;
    smp.sampleType = sampleType;
    smp.sampleLength = len;

    if ( loopSample )
    {
        /* Loop the whole sample: */
        smp.loopMode = sdLoop1;
        smp.loop1Start = 0;
        smp.loop1End = len;
        smp.loop1Type = loopUnidir;
    }
    else
    {
        /* No loop: */
        smp.loopMode = sdLoopNone;
        smp.loop1Start = smp.loop1End = 0;
        smp.loop1Type = loopNone;
    }

    /* No loop 2: */
    smp.loop2Start = smp.loop2End = 0;
    smp.loop2Type = loopNone;

    /* Add the sample to the Sound Device: */
    if ( (error = SD->AddSample(&smp, 1, sampleHandle)) != OK )
    {
        memFree(buffer);
        PASSERROR(ID_fxLoadRawSample);
    }

    /* Deallocate the buffer: */
    if ( (error = memFree(buffer)) != OK )
        PASSERROR(ID_fxLoadRawSample);

    return OK;
}




/****************************************************************************\
*
* Function:     int fxFreeSample(unsigned sample)
*
* Description:  Deallocates a sample and frees it from the Sound Device.
*
* Input:        unsigned sample         sample handle for the sample to be
*                                       deallocated.
*
* Returns:      MIDAS error code
*
\****************************************************************************/

int CALLING fxFreeSample(unsigned sample)
{
    int         error;

    /* Just free the sample from the Sound Device: */
    if ( (error = SD->RemoveSample(sample)) != OK )
        PASSERROR(ID_fxFreeSample);

    return OK;
}




/****************************************************************************\
*
* Function:     int fxSetAutoChannels(int numChannels,
*                   unsigned *channelNumbers)
*
* Description:  Sets the channel numbers that can be used as automatic effect
*               channels.
*
* Input:        int numChannels             number of channels that can be
*                                           used
*               unsigned *channelNumbers    pointer to a table that contains
*                                           the channels that can be used
*
* Returns:      MIDAS error code
*
\****************************************************************************/

int CALLING fxSetAutoChannels(int numChannels, unsigned *channelNumbers)
{
    int         error;
    unsigned    i;
    unsigned    chan;
    fxChannel   *fxchan;

    /* Check if there are previous automatic effect channels. If yes, clear
       them and free the channel structures: */
    if ( numAutoChannels != 0 )
    {
        for ( i = 0; i < numAutoChannels; i++ )
        {
            chan = autoChannels[i].sdChannel;

            if ( (error = SD->StopSound(chan)) != OK )
                PASSERROR(ID_fxClose);
        }

        if ( (error = memFree(autoChannels)) != OK )
            PASSERROR(ID_fxClose);
    }

    if ( numChannels )
    {
        /* Allocate memory for channel structures: */
        numAutoChannels = numChannels;
        if ( (error = memAlloc(numChannels * sizeof(fxChannel),
            (void**) &autoChannels)) != OK )
            PASSERROR(ID_fxSetAutoChannels);

        /* Initialize channels: */
        for ( i = 0; i < (unsigned) numChannels; i++ )
        {
            fxchan = &autoChannels[i];
            fxchan->sdChannel = channelNumbers[i];
            fxchan->sampleHandle = 0;
            fxchan->playHandle = 0;
            fxchan->priority = 0;
        }
    }
    else
    {
        /* There are no automatic effect channels: */
        numAutoChannels = 0;
        autoChannels = NULL;
    }

    nextHandle = AUTOMAGIC;
    nextChannel = 0;

    return OK;
}




/****************************************************************************\
*
* Function:     int fxPlaySample(unsigned channel, unsigned sample,
*                   int priority, unsigned rate, unsigned volume, int panning,
*                   unsigned *playHandle)
*
* Description:  Starts playing a sound effect sample on a channel
*
* Input:        unsigned channel        channel number, or fxAutoChannel for
*                                       automatic selection
*               unsigned sample         sample handle
*               int priority            effect priority, the higher the value
*                                       the higher the priority
*               unsigned rate           effect initial sample rate
*               unsigned volume         effect initial volume (0-64)
*               int panning             effect initial panning, see enum
*                                       sdPanning
*               unsigned *playHandle    effect playing handle variable
*
* Returns:      MIDAS error code. The playing handle for the effect will be
*               written to *playHandle.
*
\****************************************************************************/

int CALLING fxPlaySample(unsigned channel, unsigned sample, int priority,
    unsigned rate, unsigned volume, int panning, unsigned *playHandle)
{
    int         error;
    unsigned    chan;
    unsigned    handle;

    if ( channel == fxAutoChannel )
    {
        /* We should select the channel automatically */

        /* Check that we do have automatic channels: */
        if ( numAutoChannels == 0 )
        {
            ERROR(errNoChannels, ID_fxPlaySample);
            return errNoChannels;
        }

        handle = nextHandle;

        /* We'll just use the next automatic channel: */
        autoChannels[nextChannel].sampleHandle = sample;
        autoChannels[nextChannel].priority = priority;
        autoChannels[nextChannel].playHandle = handle;
        chan = autoChannels[nextChannel].sdChannel;

        /* Go to the next channel: */
        nextChannel++;
        if ( nextChannel >= numAutoChannels )
            nextChannel = 0;

        /* Prepare next possible play handle: */
        nextHandle++;
        if ( (nextHandle < AUTOMAGIC) || (nextHandle > MAXHANDLE) )
            nextHandle = AUTOMAGIC;
    }
    else
    {
        /* Just use the channel given: */
        chan = channel;
        handle = chan;
    }

    /* The handle returned will be >= AUTOMAGIC if it refers to a sound on an
       automagic channel, otherwise it is simply the SD channel number */

    /* Stop any previous sound on the channel: */
    if ( (error = SD->StopSound(chan)) != OK )
        PASSERROR(ID_fxPlaySample);

    /* Set the sample to the channel: */
    if ( (error = SD->SetSample(chan, sample)) != OK )
        PASSERROR(ID_fxPlaySample);

    /* Set the new volume to the channel: */
    if ( (error = SD->SetVolume(chan, volume)) != OK )
        PASSERROR(ID_fxPlaySample);

    /* Set the new panning position: */
    if ( (error = SD->SetPanning(chan, panning)) != OK )
        PASSERROR(ID_fxPlaySample);

    /* Play the sound: */
    if ( (error = SD->PlaySound(chan, rate)) != OK )
        PASSERROR(ID_fxPlaySample);

    *playHandle = handle;

    return OK;
}



/****************************************************************************\
*
* Function:     unsigned FindChannel(unsigned playHandle)
*
* Description:  Finds the channel where a sound is being played
*
* Input:        unsigned playHandle     playing handle for the sound
*
* Returns:      Sound Device channel number for the sound, or NOCHANNEL if it
*               is not being played.
*
\****************************************************************************/

static unsigned FindChannel(unsigned playHandle)
{
    unsigned    i;

    if ( playHandle < AUTOMAGIC )
    {
        /* Just a simple channel number: */
        return playHandle;
    }

    /* Search through the channels and try to find the sound: */
    for ( i = 0; i < numAutoChannels; i++ )
    {
        if ( autoChannels[i].playHandle == playHandle )
            return autoChannels[i].sdChannel;
    }

    /* We couldn't find it: */
    return NOCHANNEL;
}



/****************************************************************************\
*
* Function:     int fxStopSample(unsigned playHandle)
*
* Description:  Stops playing a sample
*
* Input:        unsigned playHandle     sample playing handle (from
*                                       fxPlaySample)
*
* Returns:      MIDAS error code
*
\****************************************************************************/

int CALLING fxStopSample(unsigned playHandle)
{
    unsigned    chan;
    int         error;

    /* Find the channel number for the sound, exit if it's not being played:*/
    if ( (chan = FindChannel(playHandle)) == NOCHANNEL )
        return OK;

    /* Stop the sound: */
    if ( (error = SD->StopSound(chan)) != OK )
        PASSERROR(ID_fxStopSample);

    return OK;
}



/****************************************************************************\
*
* Function:     int fxSetSampleRate(unsigned playHandle, ulong rate)
*
* Description:  Changes the sample rate for a sample that is being played
*
* Input:        unsigned playHandle     sample playing handle (from
*                                       fxPlaySample)
*               ulong rate              new rate
*
* Returns:      MIDAS error code
*
\****************************************************************************/

int CALLING fxSetSampleRate(unsigned playHandle, ulong rate)
{
    unsigned    chan;
    int         error;

    /* Find the channel number for the sound, exit if it's not being played:*/
    if ( (chan = FindChannel(playHandle)) == NOCHANNEL )
        return OK;

    /* Set the new sample rate: */
    if ( (error = SD->SetRate(chan, rate)) != OK )
        PASSERROR(ID_fxSetSampleRate);

    return OK;
}




/****************************************************************************\
*
* Function:     int fxSetSampleVolume(unsigned playHandle, unsigned volume)
*
* Description:  Changes the volume for a sample that is being played
*
* Input:        unsigned playHandle     sample playing handle (from
*                                       fxPlaySample)
*               unsigned volume         new volume
*
* Returns:      MIDAS error code
*
\****************************************************************************/

int CALLING fxSetSampleVolume(unsigned playHandle, unsigned volume)
{
    unsigned    chan;
    int         error;

    /* Find the channel number for the sound, exit if it's not being played:*/
    if ( (chan = FindChannel(playHandle)) == NOCHANNEL )
        return OK;

    /* Set the new sample rate: */
    if ( (error = SD->SetVolume(chan, volume)) != OK )
        PASSERROR(ID_fxSetSampleVolume);

    return OK;
}




/****************************************************************************\
*
* Function:     int fxSetSamplePanning(unsigned playHandle, int panning)
*
* Description:  Changes the panning position for a sample that is being played
*
* Input:        unsigned playHandle     sample playing handle (from
*                                       fxPlaySample)
*               int panning             new panning position
*
* Returns:      MIDAS error code
*
\****************************************************************************/

int CALLING fxSetSamplePanning(unsigned playHandle, int panning)
{
    unsigned    chan;
    int         error;

    /* Find the channel number for the sound, exit if it's not being played:*/
    if ( (chan = FindChannel(playHandle)) == NOCHANNEL )
        return OK;

    /* Set the new sample rate: */
    if ( (error = SD->SetPanning(chan, panning)) != OK )
        PASSERROR(ID_fxSetSamplePanning);

    return OK;
}




/****************************************************************************\
*
* Function:     int fxSetSamplePriority(unsigned playHandle, int priority)
*
* Description:  Changes the priority for a sample that is being played
*
* Input:        unsigned playHandle     sample playing handle (from
*                                       fxPlaySample)
*               int priority            new playing priority
*
* Returns:      MIDAS error code
*
\****************************************************************************/

int CALLING fxSetSamplePriority(unsigned playHandle, int priority)
{
    unsigned    i;

    /* Check that the handle is for an automatic channel - priorities don't
       make sense otherwise: */
    if ( playHandle < AUTOMAGIC )
        return OK;

    /* Try to find the channel the sound is being played on, and set its
       priority if found: */
    for ( i = 0; i < numAutoChannels; i++ )
    {
        if ( autoChannels[i].playHandle == playHandle )
            autoChannels[i].priority = priority;
    }

    return OK;
}


/*
 * $Log: midasfx.c,v $
 * Revision 1.3  1996/09/28 08:12:40  jpaana
 * Fixed for Linux
 *
 * Revision 1.2  1996/09/25 18:36:41  pekangas
 * Fixed to compile in DOS without warnings
 *
 * Revision 1.1  1996/09/22 23:17:48  pekangas
 * Initial revision
 *
*/