/* Easy Sound */

/// "includes"
#include <exec/types.h>
#include <exec/memory.h>
#include <devices/audio.h>
#include <stdio.h>
#include "EasySound.h"

#define CLOCK_CONSTANT 3579545

/* An IOAudio pointer to each sound channel: */
struct IOAudio *IOA[4] = {
    NULL, NULL, NULL, NULL
};

typedef LONG Fixed;
typedef struct {
    ULONG oneShotHiSamples;     /* #samples in the high octave 1-shot part */
    ULONG repeatHiSamples;      /* #samples in the high octave repeat part */
    ULONG samplesPerHiCycle;    /* #samples/cycle in high octave, else 0   */
    UWORD samplesPerSec;        /* Data sampling rate */
    UBYTE ctOctave;             /* Number of octaves of waveforms */
    UBYTE sCompression;         /* Data compression technique used */
    Fixed volume;               /* Playback volume from 0 to 0x10000 */
} Voice8Header;

/* Declare the functions we are going to use: */

BOOL ESPlaySound (struct SoundInfo *info, UWORD volume, UBYTE channel,
                  BYTE priority, WORD delta_rate, UWORD repeat,
                  ULONG start, ULONG time, BOOL wait);

void ESStopSound (UBYTE channel);

BOOL ESPrepareIOA (UWORD period, UWORD volume, UWORD cycles, UBYTE channel,
                   BYTE priority, struct SoundInfo *info, ULONG start,
                   ULONG time);

void ESRemoveSound (struct SoundInfo *info);

struct SoundInfo *ESPrepareSound (STRPTR file);

UWORD ESLoadSound (STRPTR filename, struct SoundInfo *info);

ULONG ESGetSize (STRPTR filename);

ULONG ESSizeIFF (STRPTR filename);

UWORD ESReadIFF (STRPTR filename, struct SoundInfo *info);

BOOL ESMoveTo (STRPTR check_string, FILE * file_ptr);
///
/// "ESPlaySound"
/* ESPlaySound()                                                         */
/* ESPlaySound() plays one already prepared sound effect. You can decide */
/* what volume, which channel, what rate, which priority, how many times */
/* the sound should be played and so on...                               */
/*                                                                       */
/* Synopsis: ok = PlaySound( ptr, vol, cha, pri, drate, times, start,    */
/*                           time, wait );                               */
/*                                                                       */
/* ok:    (BOOL) If the sound was played successfully TRUE is            */
/*        returned, else FALSE.                                          */
/*                                                                       */
/* ptr:   (struct SoundInfo *) Pointer to a SoundInfo structure. This    */
/*        pointer was returned by PrepareSound().                        */
/*                                                                       */
/* vol:   (UWORD) Volume, 0 to 64.                                       */
/*                                                                       */
/* cha:   (UBYTE) Which channel should be used. (LEFT0, RIGHT0,          */
/*        RIGHT1 or LEFT1)                                               */
/*                                                                       */
/* pri:   (BYTE) What priority should be used. See header file for a     */
/*        complete list of recommended priorities.                       */
/*                                                                       */
/* drate: (WORD) Delta rate. When the sound is prepared, the record      */
/*        rate is automatically stored in the SoundInfo structure,       */
/*        so if you do not want to change the rate, write NORMALRATE.    */
/*                                                                       */
/* times: (UWORD) How many times the sound should be played. If you      */
/*        want to play the sound forever, write NONSTOP. (To stop a      */
/*        sound call the function StopSound().)                          */
/*                                                                       */
/* start: (ULONG) Where in the sound data we should start to play. (If   */
/*        you want to start at the beginning of the sound data set start */
/*        to 0.)                                                         */
/*                                                                       */
/* time:  (ULONG) For how long time the sound should be played. If you   */
/*        want to play all of the sound data set time to 0.              */
/*                                                                       */
/* wait:  (BOOL) Set this parameter to WAIT if you want to wait for the  */
/*        sound to be completed. If you set it to DO_NOT_WAIT, your      */
/*        program will continue to run while the sound is played.        */
/*        NOTE! Do not try to play a sound continiously (times set to    */
/*        NONSTOP) and at the same time set this field to TRUE! The      */
/*        request will then never be completed and your program will     */
/*        come to a dead lock!!                                          */

BOOL ESPlaySound (struct SoundInfo *info, UWORD volume, UBYTE channel,
                  BYTE priority, WORD delta_rate, UWORD times, ULONG start,
                  ULONG time, BOOL wait) {
    /* 
     * Before we may play the sound, we must make sure that the sound is not 
     * already being played. We will therefore call the function 
     * ISStopSound(), in order to stop the sound if it is playing:       
     */
    ESStopSound (channel);

    if (ESPrepareIOA (CLOCK_CONSTANT/info->RecordRate + delta_rate, volume, times, channel, priority, info, start, time)) {

        BeginIO (IOA[channel]);
        if (wait)
            WaitIO (IOA[channel]);

        return (TRUE);          /* OK! */
    }
    else
        return (FALSE);         /* ERROR! */
}
///
/// "ESStopSound"
/* ESStopSound()                                                       */
/* ESStopSound() will stop the specified audio channel from continuing */
/* to play the sound. It will also close all devices and ports that    */
/* have been opened, and deallocate some memory that have been         */
/* allocated.                                                          */
/*                                                                     */
/* Synopsis: ISStopSound( channel );                                   */
/* channel:  (UBYTE) The audio channel that should be stopped. (LEFT0, */
/*           LEFT1, RIGHT0 or RIGHT1.)                                 */

void ESStopSound (UBYTE channel)
{
    /* Check if the IOAudio structure exist: */
    if (IOA[channel]) {
        /* 1. Stop the sound: */
        AbortIO (IOA[channel]);

        /* 2. If there exist a Sound Device, close it: */
        if (IOA[channel]->ioa_Request.io_Device)
            CloseDevice (IOA[channel]);

        /* 3. If there exist a Message Port, delete it: */
        if (IOA[channel]->ioa_Request.io_Message.mn_ReplyPort)
            DeletePort (IOA[channel]->ioa_Request.io_Message.mn_ReplyPort);

        /* 4. Remove the request block: */
        DeleteExtIO (IOA[channel], sizeof (struct IOAudio));

        /* 5. Set the pointer to NULL so we know that this */
        /*    request has now been deleted:                */
        IOA[channel] = NULL;
    }
}

///
/// "ESPrepareIOA"
/* ESPrepareIOA()                                                       */
/* ESPrepareIOA() allocates and initializes an IOAudio structure.       */
/*                                                                      */
/* Synopsis: ok = ISPrepareIOA( per, vol, cyc, cha, ptr, start, time ); */
/*                                                                      */
/* ok:    (BOOL) If the IOAudio structure was allocated and             */
/*        initialized successfully, TRUE is returned, else FALSE.       */
/* per:   (UWORD) Period time.                                          */
/* vol:   (UWORD) Volume, 0 to 64.                                      */
/* cyc:   (UWORD) How many times the sound should be played.            */
/*        (0 : forever)                                                 */
/* cha:   (UBYTE) Which channel should be used. (LEFT0, RIGHT0,         */
/*        RIGHT1 or LEFT1)                                              */
/* ptr:   (struct SoundInfo *) Pointer to a SoundInfo structure.        */
/* start: (ULONG) Where in the sound data we should start to play.      */
/* time:  (ULONG) For how long time the sound should be played.         */

BOOL ESPrepareIOA (UWORD period, UWORD volume, UWORD cycles, UBYTE channel,
                   BYTE priority, struct SoundInfo *info, ULONG start,
                   ULONG time)
{
    /* Store error numbers here: */
    BYTE error;

    /* Declare a pointer to a MsgPort structure: */
    struct MsgPort *port;

    /* Get a reply port: (No name, normal priority) */
    port = (struct MsgPort *)
        CreatePort (NULL, 0);

    /* Did we get a reply port? */
    if (!port) {
        /* Bad news! We did not get a reply port. */

        /* Return with an error value: */
        return (FALSE);
    }

    /* Create an IOAudio request: */
    IOA[channel] = (struct IOAudio *)
        CreateExtIO (port, sizeof (struct IOAudio));

    /* Could we allocate enough memory? */
    if (!IOA[channel]) {
        /* Tough luck! We did not get a request block. */

        /* Close the reply port: */
        DeletePort (port);

        /* Return with an error value: */
        return (FALSE);
    }

    /* Initialize the IOAudion structure: */

    /* Set priority: */
    IOA[channel]->ioa_Request.io_Message.mn_Node.ln_Pri = priority;

    /* Set channel: (This program tries to reserve one specific */
    /* audio channel. It does not support an allocation array   */
    /* with several options.)                                   */
    info->channel_bit = 1 << channel;

    /* Give the request block a pointer to our simple allocation array: */
    IOA[channel]->ioa_Data = &(info->channel_bit);

    /* Set the length of our allocation array: */
    IOA[channel]->ioa_Length = sizeof (UBYTE);

    /* Open Audio Device: */
    error = OpenDevice (AUDIONAME, 0, IOA[channel], 0);

    /* Have wee successfully opened it? */
    if (error) {
        /* Hard times! Could not open the device! */

        /* Delete the request block: */
        DeleteExtIO (IOA[channel], sizeof (struct IOAudio));

        /* Close the reply port: */
        DeletePort (port);

        /* Set audio pointer to NULL so we know that */
        /* we do not have any request block here:    */
        IOA[channel] = NULL;

        /* Return with an error value: */
        return (FALSE);         /* ERROR! */
    }

    /* We now have a reply port, a request block, one  */
    /* audio channel reserved and the audio device has */
    /* been opened. Prepare to play:                   */

    /* Initialize the request block with the users requirements: */
    IOA[channel]->ioa_Request.io_Flags = ADIOF_PERVOL;

    /* We want to play sound (write data to the audio device): */
    IOA[channel]->ioa_Request.io_Command = CMD_WRITE;

    /* Set period value: */
    IOA[channel]->ioa_Period = period;

    /* Set volume: */
    IOA[channel]->ioa_Volume = volume;

    /* Number of times the sound wave should be played: */
    IOA[channel]->ioa_Cycles = cycles;

    /* If the user has specified a play time we use */
    /* it, else we play the complete sound:         */
    if (time)
        IOA[channel]->ioa_Length = time;
    else
        IOA[channel]->ioa_Length = info->FileLength;

    /* Set start position in the waveform: */
    IOA[channel]->ioa_Data = info->SoundBuffer + start;

    /* Everything has been prepared, return with OK: */
    return (TRUE);
}
///
/// "ESRemoveSound"
/* ESRemoveSound()                                                        */
/* ESRemoveSound() will stop playing the sound, and deallocate all memory */
/* that was allocated by the PrepareSound() function. Before your program */
/* terminates, all soundeffects that have been prepared, MUST be removed. */
/*                                                                        */
/* IMPORTANT! Each channel that is currently playing the sound must be    */
/* stopped before you may remove the sound!!!! (Use the ESStopSound()     */
/* function.)                                                             */
/*                                                                        */
/* Synopsis: ESRemoveSound( pointer );                                    */
/*                                                                        */
/* pointer:  (struct SoundInfo *) Pointer to a SoundInfo structure.       */

void ESRemoveSound (struct SoundInfo *info)
{
    /* IMPORTANT! The sound must have been */
    /* stopped before you may remove it!!! */

    /* Have we allocated a SoundInfo structure? */
    if (info) {
        /* Deallocate the sound buffer: */
        FreeMem (info->SoundBuffer, info->FileLength);

        /* Deallocate the SoundInfo structure: */
        FreeMem (info, sizeof (struct SoundInfo));
        info = NULL;
    }
}

///
/// "ESPrepareSound"
/* ESPrepareSound()

ESPrepareSound() loads a sampled sound file (IFF or FutureSound) into a 
buffer that is automatically allocated. All information about the sound 
(record rate, length, buffersize etc) is put into an SoundInfo structure. 
If ESPrepareSound() has successfully prepared the sound it returns a 
pointer to a SoundInfo structure, otherwise it returns NULL.

Synopsis: pointer = ESPrepareSound( filename );

pointer:  (struct SoundInfo *) Pointer to a SoundInfo structure.

filename: (STRPTR) Pointer to a string containing the name of the
          sound file. For example "df0:Explosion.snd".
*/

struct SoundInfo *ESPrepareSound (STRPTR file) {

    struct SoundInfo *info;

    /* 
     * Allocate memory for a SoundInfo structure: (The memory can be of 
     * any type, and should be cleared.)            
     */
    info = (struct SoundInfo *)AllocMem (sizeof (struct SoundInfo), MEMF_CLEAR);
    if (!info) {
        return (NULL);
    }

    /* 
     * Get the size of the sound file, and store it in the SoundInfo 
     * structure:            
     */
    info->FileLength = ESGetSize (file);
    if (info->FileLength == 0) {
        FreeMem (info, sizeof (struct SoundInfo));
        return (NULL);
    }

    /* 
     * Allocate enough memory for the sampled sound, and store a pointer to 
     * the buffer in the SoundInfo structure: (Remember that sound data must 
     * be in chip memory.)                   
     */
    info->SoundBuffer = (BYTE *) AllocMem (info->FileLength, MEMF_CHIP | MEMF_CLEAR);
    if (!info->SoundBuffer) {
        FreeMem (info, sizeof (struct SoundInfo));
        return (NULL);
    }

    info->RecordRate = ESLoadSound (file, info);
    if (info->RecordRate == 0) {
        FreeMem (info->SoundBuffer, info->FileLength);
        FreeMem (info, sizeof (struct SoundInfo));
        return (NULL);
    }

    /* 
     * The sound has now successfully been loaded! 
     */

    /* 
     * Old FutureSound files were saved in kHz. If the record rate is less 
     * than one hundered, we know it is an old FutureSound file, and simply 
     * multiply the rate with one thousand:       
     */
    if (info->RecordRate < 100)
        info->RecordRate *= 1000;

    /* Return a pointer to the SoundInfo structure: */
    return (info);
}
///
/// "ESLoadSound"
/* ESLoadSound()                                                       */
/* ESLoadSound() will load sampled sound that was either saved in IFF  */
/* or FutureSound format.                                              */
/*                                                                     */
/* Synopsis: rate = ESLoadSound( filename, pointer );                  */
/*                                                                     */
/* rate:     (UWORD) The record rate is returned if the sound was      */
/*           successfully loaded, else 0.                              */
/*                                                                     */
/* filename: (STRPTR) Pointer to a string containing the name of the   */
/*           sound file. For example "df0:Explosion.snd".              */
/*                                                                     */
/* pointer:  (struct SoundInfo *) Pointer to a SoundInfo structure.    */

UWORD ESLoadSound (STRPTR filename, struct SoundInfo * info)
{
    FILE *file_ptr;             /* Pointer to a file. */
    ULONG length;               /* Data Length. */
    UWORD record_rate;          /* Record rate. */

    /* Check if it is an IFF File: */
    if (ESSizeIFF (filename)) {
        /* Yes, it is an IFF file. Read it: */
        return (ESReadIFF (filename, info));
    }
    else {
        /* No, then it is probably a FutureSound file. */
        /* Open the file so we can read it:            */
        if ((file_ptr = fopen (filename, "r")) == 0)
            return (0);         /* ERROR! Could not open the file! */

        /* Read the data length: */
        if (fread ((char *) &length, sizeof (ULONG), 1, file_ptr) == 0) {
            /* ERROR! Could not read the data length! */
            /* Close the file, and return zero:       */
            fclose (file_ptr);
            return (0);
        }

        /* Read the record rate: */
        if (fread ((char *) &record_rate, sizeof (UWORD), 1, file_ptr) == 0) {
            /* ERROR! Could not read the record rate! */
            /* Close the file, and return zero:       */
            fclose (file_ptr);
            return (0);
        }

        /* Read the sampled sound data into the buffer: */
        if (fread ((char *) info->SoundBuffer, length, 1, file_ptr) == 0) {
            /* ERROR! Could not read the data!  */
            /* Close the file, and return zero: */
            fclose (file_ptr);
            return (0);
        }

        /* Close the file: */
        fclose (file_ptr);

        /* Return the record rate: */
        return (record_rate);
    }
}
///
/// "ESGetSize"
/* ESGetSize()                                                        */
/* ESGetSize() returns the size of the file which was saved in either */
/* IFF or FutureSound format.                                         */
/*                                                                    */
/* Synopsis: length = ESGetSize( filename );                          */
/*                                                                    */
/* length:   (ULONG) Data length.                                     */
/*                                                                    */
/* filename: (STRPTR) Pointer to a string containing the name of the  */
/*           sound file. For example "df0:Explosion.snd".             */

ULONG ESGetSize (STRPTR filename)
{
    FILE *file_ptr;             /* Pointer to a file. */
    ULONG length;               /* Data length. */

    /* Check if it is an IFF File: */
    if ((length = ESSizeIFF (filename)) == 0) {
        /* No, then it is probably a FutureSound file. */
        /* Open the file so we can read it:            */
        if ((file_ptr = fopen (filename, "r")) == 0)
            return (0);         /* ERROR! Could not open the file! */

        /* Read the data length: */
        if (fread ((char *) &length, sizeof (ULONG), 1, file_ptr) == 0) {
            /* ERROR! Could not read the data length! */
            /* Close the file, and return zero:       */
            fclose (file_ptr);
            return (0);
        }

        /* Close the file: */
        fclose (file_ptr);
    }
    return (length);
}
///
/// "ESSizeIFF"
/* ESSizeIFF()                                                       */
/* ESSizeIFF() returns the size of an IFF file, or zero if something */
/* went wrong (for example, It was not an IFF file).                 */
/*                                                                   */
/* Synopsis: length = ESSizeIFF( filename );                         */
/*                                                                   */
/* length:   (ULONG) Data length.                                    */
/*                                                                   */
/* filename: (STRPTR) Pointer to a string containing the name of the */
/*           IFF file. For example "df0:Explosion.snd".              */

ULONG ESSizeIFF (STRPTR filename)
{
    FILE *file_ptr;             /* Pointer to a file. */
    STRPTR empty_string = "    ";       /* Four spaces. */
    LONG dummy;                 /* A dummy variable. */
    Voice8Header Header;        /* Voice8Header structure. */

    /* Try to open the file: */
    if (file_ptr = fopen (filename, "r")) {
        fread ((char *) empty_string, 4, 1, file_ptr);
        if (strcmp (empty_string, "FORM") == 0) {
            /* Read twice: */
            fread ((char *) empty_string, 4, 1, file_ptr);
            fread ((char *) empty_string, 4, 1, file_ptr);

            /* Check if it is a "8SVX" file, or not: */
            if (strcmp (empty_string, "8SVX") == 0) {
                ESMoveTo ("VHDR", file_ptr);
                fread ((char *) &dummy, sizeof (LONG), 1, file_ptr);
                fread ((char *) &Header, sizeof (Header), 1, file_ptr);

                /* Close the file, and return the length: */
                fclose (file_ptr);
                return (Header.oneShotHiSamples + Header.repeatHiSamples);
            }
        }
        /* Close the file: */
        fclose (file_ptr);
    }
    /* Return zero: (ERROR) */
    return (0);
}
///
/// "ESReadIFF"
/* ESReadIFF()                                                           */
/* ESReadIFF() reads an IFF file into the buffer, and returns the record */
/* rate.                                                                 */
/*                                                                       */
/* Synopsis: rate = ESReadIFF( filename, pointer );                      */
/*                                                                       */
/* rate:     (UWORD) The record rate is returned if the sound was        */
/*           successfully loaded, else 0.                                */
/*                                                                       */
/* filename: (STRPTR) Pointer to a string containing the name of the     */
/*           sound file. For example "df0:Explosion.snd".                */
/*                                                                       */
/* pointer:  (struct SoundInfo *) Pointer to a SoundInfo structure.      */

UWORD ESReadIFF (STRPTR filename, struct SoundInfo * info)
{
    FILE *file_ptr;             /* Pointer to a file. */
    STRPTR empty_string = "    ";       /* Four spaces. */
    LONG dummy;                 /* A dummy variable. */
    Voice8Header Header;        /* Voice8Header structure. */

    /* Try to open the file: */
    if (file_ptr = fopen (filename, "r")) {
        fread ((char *) empty_string, 4, 1, file_ptr);
        if (strcmp (empty_string, "FORM") == 0) {
            /* Read twice: */
            fread ((char *) empty_string, 4, 1, file_ptr);
            fread ((char *) empty_string, 4, 1, file_ptr);

            /* Check if it is a "8SVX" file, or not: */
            if (strcmp (empty_string, "8SVX") == 0) {
                ESMoveTo ("VHDR", file_ptr);
                fread ((char *) &dummy, sizeof (LONG), 1, file_ptr);
                fread ((char *) &Header, sizeof (Header), 1, file_ptr);

                ESMoveTo ("BODY", file_ptr);
                fread ((char *) &dummy, sizeof (LONG), 1, file_ptr);
                fread ((char *) info->SoundBuffer, Header.oneShotHiSamples +
                       Header.repeatHiSamples, 1, file_ptr);

                /* Close the file, and return the record rate: */
                fclose (file_ptr);
                return (Header.samplesPerSec);
            }
        }
        /* Close the file: */
        fclose (file_ptr);
    }
    /* Return zero: (ERROR) */
    return (0);
}
///
/// "ESMoveTo"
/* ESMoveTo()                                                  */
/* ESMoveTo() walks through an IFF file, and looks for chunks. */
/*                                                             */
/* Synopsis: ESMoveTo( chunk, file_ptr );                      */
/*                                                             */
/* chunk:    (STRPTR) The chunk we want to get to.             */
/*                                                             */
/* file_ptr: (FILE *) Pointer to an already opened file.       */

BOOL ESMoveTo (STRPTR check_string, FILE * file_ptr)
{
    STRPTR empty_string = "    ";       /* Four spaces. */
    int skip, loop;             /* How much data should be skiped. */
    LONG dummy;                 /* A dummy variable. */

    /* As long as we have not reached the EOF, continue: */
    while (!feof (file_ptr)) {
        fread ((char *) empty_string, 4, 1, file_ptr);

        /* Have we found the right chunk? */
        if (strcmp (check_string, empty_string) == 0)
            return (0);         /* YES! Return nothing. */

        /* Move foreward: */
        fread ((char *) &skip, sizeof (LONG), 1, file_ptr);
        for (loop = 0; loop < skip; loop++)
            fread ((char *) &dummy, 1, 1, file_ptr);
    }
}

///

