/*=============================================================================
    Name    : SpeechEvent.c
    Purpose : Speech Event processor

    Created 10/01/1998 by salfreds
    Copyright Relic Entertainment, Inc.  All rights reserved.
=============================================================================*/

#include "SoundEventPrivate.h"
#include "SoundEvent.h"
#include "select.h"
#include "blobs.h"
#include "soundcmn.h"
#include "nis.h"
#include "soundmusic.h"
#include "fquant.h"
#include "chatting.h"
#include "singleplayer.h"
#include "bink.h"
#include "utility.h"

#define STATE_BREAK     3

#define MAX_BATTLES     20

#define Asteroids       0
#define Nebulae         1
#define GasClouds       2
#define DustClouds      3

#define NISSTREAM       0
#define AMBIENTSTREAM   1

#define STINGER_FADE_TIME   0.5f
#define MUSIC_FADE_FAST     0.5f
#define MUSIC_FADE_SLOW     1.0f

#define MUSIC_MIN_FADE  80

#define SPEECH_FLEET        0
#define SPEECH_STRIKECRAFT  1
#define SPEECH_CAPSHIPS     2
#define SPEECH_CONSTRUCTION 3
#define SPEECH_INTEL        4
#define SPEECH_PILOT1       1
#define SPEECH_PILOT2       2
#define SPEECH_PILOT3       3

#define NUM_SPEAKERS        5

#define SE_MAX_QUEUE        10

#define SPEECH_PAUSE        1.5f

#define MAX_RELIC_NAMES     33

#define SPEECH_SUPERNOVA_LEVEL  10

/*=============================================================================
    Structures
=============================================================================*/
typedef struct
{
    sdword      offset;
    sword       flags;
    sword       bitrate;
    ubyte       volume;
    ubyte       numchannels;
    sword       pad;
} MUSICSTREAM;

typedef struct
{
    sdword      ID;
    udword      checksum;
    sdword      numstreams;
    MUSICSTREAM mstreams[];
} MUSICHEADER;

#if 1
typedef struct
{
    sdword      handle;     // this is the handle for this event (might not be unique if linked)
    sdword      index;      // this is the index of linked events
    sdword      status;

    sdword      actornum;   // need this for linked speech events

    sdword      numlinks;


    Ship        *pShip;     // ship pointer associated with this event
    sdword      event;      // actual event, listed in speechevent.h
    sdword      variable;   // optional variable for this event
    sdword      variation;  // overriding variation to play
    uword       priority;   // priority of this event
    sword       volume;     // overriding volume

    real32      timein;     // at what time was this event put in the queue?
    real32      timeout;    // at what point is this going to timeout at?
} QUEUEEVENT;

typedef struct
{
    QUEUEEVENT  queue[SE_MAX_QUEUE];    // these are the queued events
    QUEUEEVENT  current;    // the event that is currently playing

    sdword      status;     // status of this speech channel
    sdword      nextevent;  // index into the queue of the next event

    bool        locked;     // set when deleting a bunch of stuff so another event doesn't play

    real32      timeover;   // what time will this event be over (approx)

    sdword      numqueued;

    sdword      lastpriority;
    sdword      laststarttick;
    sdword      lastendtick;
    sdword      lastevent;
    Ship        *lastship;
} SPEECHQUEUE;

typedef struct
{
    sdword      handle;
    sdword      status;
    sdword      tracknum;
    sdword      nexttrack;
    sword       targetvol;
    sword       pad;
} MUSICINFO;
#endif


/*=============================================================================
    Prototypes
=============================================================================*/

void speechPlayQueue(SPEECHQUEUE *pSQueue, sdword streamchannel);
sdword SEselectsentence(sdword actor, sdword event, sdword variable, sdword setvariation, sdword **pOffsets, udword *pDuration);
void speechQueueUpdate(void);
sdword SEreorderqueue(SPEECHQUEUE *pSQueue);
sdword SEcleanqueue(SPEECHQUEUE *pSQueue);


/*=============================================================================
    Private Data
=============================================================================*/

SPEECHQUEUE speechqueue[SE_NUM_ACTORS];
sdword  streamhandle[SE_NUM_ACTORS];
sdword  speechfilehandle;

sdword  numSinglePlayerEvents;

bool    MoShipMoving[MAX_MULTIPLAYER_PLAYERS] = {FALSE};

ubyte   *pspeechstream;

ubyte   *pspeechbuffer0;
ubyte   *pspeechbuffer1;
ubyte   *pspeechbuffer2;
ubyte   *pspeechbuffer3;
ubyte   *pspeechbuffer4;

Ship    *lastshiptospeak = NULL;
sdword  lastgrouptospeak = -1;

/* speech lookup tables */
SENTENCELUT *SentenceLUT;
PHRASELUT   *PhraseLUT;

/* special fx vars */
STREAMDELAY streamdelay[NUM_SPEAKERS];
STREAMEQ    streamEQ[NUM_SPEAKERS];
EFFECT      cleaneffect[NUM_SPEAKERS];
EFFECT      mixedeffect[NUM_SPEAKERS];
EFFECT      damageeffect;


/* music variables */
MUSICINFO   musicinfo[2];
sdword  musicstreamhandle[2];
sdword  musicfilehandle;
ubyte   *pmusicbuffer0;
ubyte   *pmusicbuffer1;
MUSICHEADER *musicheader;
sdword  levelTrack = -1;

char relicPlayerNames[MAX_RELIC_NAMES][MAX_PERSONAL_NAME_LEN] = {
    "Rupract",
    "SpankMaster3000",
    "SM3K",
    "Plaz",
    "dav",
    "Briscola",
    "EFMagnet",
    "Midnight",
    "Quinn_Deadly",
    "YourMammaStinks",
    "Wintermute",
    "Chimpden",
    "Lunarboy",
    "Unspacey",
    "Antonalogue",
    "=A=",
    "B1FF",
    "Hootchi",
    "rhithyn",
    "Psyco-Kitty",
    "Rasolf",
    "GargleTroth",
    "Flako",
    "Yonderboy",
    "DrakeBird",
    "TheDoctor",
    "Tender",
    "Killroy",
    "Rocketboy",
    "TheEmporersLover",
    "DrMagneto",
    "DrTongue",
    "NoWhere2Camp"
};

/* name of music header file */
#if defined(CGW)
char musicHeaderName[] = "CGW_Music.wxh";
#elif defined(Downloadable)
char musicHeaderName[] = "DL_Music.wxh";
#elif defined(DLPublicBeta)
char musicHeaderName[] = "PB_Music.wxh";
#elif defined(OEM)
char musicHeaderName[] = "OEM_Music.wxh";
#else
char musicHeaderName[] = "HW_Music.wxh";
#endif

/* externs */
extern bool soundpaused;

/* global volume settings */
extern real32 volSpeech;
extern real32 volMusic;
extern real32 volNIS;
extern bool bVolMusicNoFade;

/* global speech settings */
extern bool bActorOn[];
extern bool bCommandsOn;
extern bool bStatusOn;
extern bool bChatterOn;

extern sdword actorFlagsEnabled;

extern real32 nearestShipDistance;
extern real32 musicVolFactor;
extern real32 battleMusicVolFactor;
extern real32 musicInactiveFactor;
extern bool nearestShipMoving;

extern bool FalkosFuckedUpTutorialFlag;

/*=============================================================================
    Scripts
=============================================================================*/

scriptStructEntry streamEQScriptTable[] =
{
    { "flags", scriptSetSdwordCB, (udword)&streamEQ[0].flags, (udword)&streamEQ[0] },
    { "eq0", scriptSetReal32CB, (udword)&streamEQ[0].eq[0], (udword)&streamEQ[0] },
    { "eq1", scriptSetReal32CB, (udword)&streamEQ[0].eq[1], (udword)&streamEQ[0] },
    { "eq2", scriptSetReal32CB, (udword)&streamEQ[0].eq[2], (udword)&streamEQ[0] },
    { "eq3", scriptSetReal32CB, (udword)&streamEQ[0].eq[3], (udword)&streamEQ[0] },
    { "eq4", scriptSetReal32CB, (udword)&streamEQ[0].eq[4], (udword)&streamEQ[0] },
    { "eq5", scriptSetReal32CB, (udword)&streamEQ[0].eq[5], (udword)&streamEQ[0] },
    { "eq6", scriptSetReal32CB, (udword)&streamEQ[0].eq[6], (udword)&streamEQ[0] },
    { "eq7", scriptSetReal32CB, (udword)&streamEQ[0].eq[7], (udword)&streamEQ[0] },
    endEntry
};

scriptStructEntry streamDelaySciptTable[] =
{
    { "flags", scriptSetSdwordCB, (udword)&streamdelay[0].flags, (udword)&streamdelay[0] },
    { "level", scriptSetReal32CB, (udword)&streamdelay[0].level, (udword)&streamdelay[0] },
    { "duration", scriptSetUdwordCB, (udword)&streamdelay[0].duration, (udword)&streamdelay[0] },
    { "eq0", scriptSetReal32CB, (udword)&streamdelay[0].eq[0], (udword)&streamdelay[0] },
    { "eq1", scriptSetReal32CB, (udword)&streamdelay[0].eq[1], (udword)&streamdelay[0] },
    { "eq2", scriptSetReal32CB, (udword)&streamdelay[0].eq[2], (udword)&streamdelay[0] },
    { "eq3", scriptSetReal32CB, (udword)&streamdelay[0].eq[3], (udword)&streamdelay[0] },
    { "eq4", scriptSetReal32CB, (udword)&streamdelay[0].eq[4], (udword)&streamdelay[0] },
    { "eq5", scriptSetReal32CB, (udword)&streamdelay[0].eq[5], (udword)&streamdelay[0] },
    { "eq6", scriptSetReal32CB, (udword)&streamdelay[0].eq[6], (udword)&streamdelay[0] },
    { "eq7", scriptSetReal32CB, (udword)&streamdelay[0].eq[7], (udword)&streamdelay[0] },
    endEntry
};

scriptStructEntry streamEffectScriptTable[] =
{
    { "ClockCount", scriptSetUdwordCB, (udword)&cleaneffect[0].nClockCount, (udword)&cleaneffect[0] },
    { "FilterMinFreq", scriptSetUdwordCB, (udword)&cleaneffect[0].nFiltMinFreq, (udword)&cleaneffect[0] },
    { "FilterMaxFreq", scriptSetUdwordCB, (udword)&cleaneffect[0].nFiltMaxFreq, (udword)&cleaneffect[0] },
    { "ToneMinFreq", scriptSetUdwordCB, (udword)&cleaneffect[0].nToneMinFreq, (udword)&cleaneffect[0] },
    { "ToneMaxFreq", scriptSetUdwordCB, (udword)&cleaneffect[0].nToneMaxFreq, (udword)&cleaneffect[0] },
    { "ToneDuration", scriptSetUdwordCB, (udword)&cleaneffect[0].nToneDur, (udword)&cleaneffect[0] },
    { "ToneMute", scriptSetUdwordCB, (udword)&cleaneffect[0].nToneMute, (udword)&cleaneffect[0] },
    { "ToneCount", scriptSetUdwordCB, (udword)&cleaneffect[0].nToneCount, (udword)&cleaneffect[0] },
    { "BreakMaxRate", scriptSetUdwordCB, (udword)&cleaneffect[0].nBreakMaxRate, (udword)&cleaneffect[0] },
    { "BreakMaxDur", scriptSetUdwordCB, (udword)&cleaneffect[0].nBreakMaxDur, (udword)&cleaneffect[0] },
    { "QNoiseMaxRate", scriptSetUdwordCB, (udword)&cleaneffect[0].nQNoiseMaxRate, (udword)&cleaneffect[0] },
    { "QNoiseMaxDur", scriptSetUdwordCB, (udword)&cleaneffect[0].nQNoiseMaxDur, (udword)&cleaneffect[0] },
    { "ScaleLevel", scriptSetReal32CB, (udword)&cleaneffect[0].fScaleLev, (udword)&cleaneffect[0] },
    { "NoiseLevel", scriptSetReal32CB, (udword)&cleaneffect[0].fNoiseLev, (udword)&cleaneffect[0] },
    { "ToneLevel", scriptSetReal32CB, (udword)&cleaneffect[0].fToneLev, (udword)&cleaneffect[0] },
    { "LimitLevel", scriptSetReal32CB, (udword)&cleaneffect[0].fLimitLev, (udword)&cleaneffect[0] },
    { "PitchShift", scriptSetReal32CB, (udword)&cleaneffect[0].fPitchShift, (udword)&cleaneffect[0] },
    endEntry
};


/*=============================================================================
    Functions
=============================================================================*/
void SEloadscripts(void)
{
    sdword i;

    scriptSetStruct("SoundFX\\Scripts\\", "eq_fleet.script", streamEQScriptTable, (ubyte *)&streamEQ[SPEECH_FLEET]);
    scriptSetStruct("SoundFX\\Scripts\\", "eq_intel.script", streamEQScriptTable, (ubyte *)&streamEQ[SPEECH_INTEL]);
    scriptSetStruct("SoundFX\\Scripts\\", "eq_pilot1.script", streamEQScriptTable, (ubyte *)&streamEQ[SPEECH_PILOT1]);
    scriptSetStruct("SoundFX\\Scripts\\", "eq_pilot2.script", streamEQScriptTable, (ubyte *)&streamEQ[SPEECH_PILOT2]);
    scriptSetStruct("SoundFX\\Scripts\\", "eq_pilot3.script", streamEQScriptTable, (ubyte *)&streamEQ[SPEECH_PILOT3]);

    // set last unsused eq band to 1
    streamEQ[SPEECH_FLEET].eq[8]=1.0F;
    streamEQ[SPEECH_INTEL].eq[8]=1.0F;
    streamEQ[SPEECH_PILOT1].eq[8]=1.0F;
    streamEQ[SPEECH_PILOT2].eq[8]=1.0F;
    streamEQ[SPEECH_PILOT3].eq[8]=1.0F;

    scriptSetStruct("SoundFX\\Scripts\\", "delay_fleet.script", streamDelaySciptTable, (ubyte *)&streamdelay[SPEECH_FLEET]);
    scriptSetStruct("SoundFX\\Scripts\\", "delay_intel.script", streamDelaySciptTable, (ubyte *)&streamdelay[SPEECH_INTEL]);
    scriptSetStruct("SoundFX\\Scripts\\", "delay_strikecraft.script", streamDelaySciptTable, (ubyte *)&streamdelay[SPEECH_STRIKECRAFT]);
    scriptSetStruct("SoundFX\\Scripts\\", "delay_capships.script", streamDelaySciptTable, (ubyte *)&streamdelay[SPEECH_CAPSHIPS]);
    scriptSetStruct("SoundFX\\Scripts\\", "delay_construction.script", streamDelaySciptTable, (ubyte *)&streamdelay[SPEECH_CONSTRUCTION]);

    // set last unsused eq band to 1
    streamdelay[SPEECH_FLEET].eq[8]=1.0F;
    streamdelay[SPEECH_INTEL].eq[8]=1.0F;
    streamdelay[SPEECH_STRIKECRAFT].eq[8]=1.0F;
    streamdelay[SPEECH_CAPSHIPS].eq[8]=1.0F;
    streamdelay[SPEECH_CONSTRUCTION].eq[8]=1.0F;

    scriptSetStruct("SoundFX\\Scripts\\", "effect_fleet.script", streamEffectScriptTable, (ubyte *)&cleaneffect[SPEECH_FLEET]);
    scriptSetStruct("SoundFX\\Scripts\\", "effect_intel.script", streamEffectScriptTable, (ubyte *)&cleaneffect[SPEECH_INTEL]);
    scriptSetStruct("SoundFX\\Scripts\\", "effect_strikecraft.script", streamEffectScriptTable, (ubyte *)&cleaneffect[SPEECH_STRIKECRAFT]);
    scriptSetStruct("SoundFX\\Scripts\\", "effect_capships.script", streamEffectScriptTable, (ubyte *)&cleaneffect[SPEECH_CAPSHIPS]);

    scriptSetStruct("SoundFX\\Scripts\\", "effect_damage.script", streamEffectScriptTable, (ubyte *)&damageeffect);

    /* copy the unchanging effects to the mixed effect */
    for (i = 0;  i < SE_NUM_ACTORS; i++)
    {
        mixedeffect[i].nFiltMinFreq = cleaneffect[i].nFiltMinFreq;
        mixedeffect[i].nFiltMaxFreq = cleaneffect[i].nFiltMaxFreq;
        mixedeffect[i].fScaleLev = cleaneffect[i].fScaleLev;
        mixedeffect[i].fLimitLev = cleaneffect[i].fLimitLev;
    }

}


void SEinitdelayEQ(void)
{
    sdword i;

    for (i = 0; i < SE_NUM_ACTORS; i++)
    {
        streamdelay[i].flags = STREAM_FLAGS_NOEFFECT;
        streamdelay[i].level = 0.0f;
        streamdelay[i].duration = 0;
        memset(streamdelay[i].eq, 0, SOUND_EQ_SIZE * sizeof(real32));
//      streamEQ[i].flags = STREAM_FLAGS_NOEFFECT;
//      memset(streamEQ[i].eq, 0, SOUND_EQ_SIZE * sizeof(real32));
        fqInitE(&cleaneffect[i]);
        fqInitE(&mixedeffect[i]);
    }
    fqInitE(&damageeffect);
}


void soundEventDebugPrint(char *pszInformation)
{
#if SE_VERBOSE_LEVEL >= 1
    dbgMessagef("\n%s", pszInformation);
#endif
}


sdword speechEventInit(void)
{
    sdword buffersize;
    sdword streamersize;
    char loadfile[100];
    sdword i, j;
#if 0
    sdword numoffsets;
    sdword index;
#endif

    /* load the speech lookup tables */
    strcpy(loadfile, SOUNDFXDIR);
#ifdef CGW
    strcat(loadfile, "CGWsentence.lut");
#elif defined(Downloadable) || defined(DLPublicBeta)
    strcat(loadfile, "DLsentence.lut");
#else
    strcat(loadfile, "speechsentence_comp.lut");
#endif
    fileLoadAlloc(loadfile, &SentenceLUT, NonVolatile);

    strcpy(loadfile, SOUNDFXDIR);
#ifdef CGW
    strcat(loadfile, "CGWphrase.lut");
#elif defined(Downloadable) || defined(DLPublicBeta)
    strcat(loadfile, "DLphrase.lut");
#else
    strcat(loadfile, "speechphrase_comp.lut");
#endif
    fileLoadAlloc(loadfile, &PhraseLUT, NonVolatile);

    /* verify sentence and phrase luts */
    if (SentenceLUT->checksum != PhraseLUT->checksum)
    {
        dbgMessage("Sentence and Phrase lookup tables do not match.  Not from same generate.\n");
        dbgAssert(FALSE);
    }

#if 0
    for (i = 0; i < PhraseLUT->numsentences; i++)
    {
        numoffsets = PhraseLUT->lookup[(i * PhraseLUT->numcolumns) + 2];

        if (numoffsets > 20)
        {
            index = i;
#if SE_VERBOSE_LEVEL >= 1
            dbgMessagef("\nnumoffsets %d", numoffsets);
#endif
            break;
        }
    }

    for (i = 0; i < SentenceLUT->prioritylevels * SentenceLUT->numevents * SentenceLUT->numactors; i++)
    {
        if (index == SentenceLUT->lookup[(i*3)+2])
        {
#if SE_VERBOSE_LEVEL >= 1
            dbgMessagef("\nfound it at %d", i);
#endif
        }
    }
#endif
    soundstreamquery(SE_NUM_STREAMS, &buffersize, &streamersize);

    if (buffersize == 0)
    {
        memFree(SentenceLUT);
        memFree(PhraseLUT);
        /* soundstreamquery failed */
        return (SOUND_ERR);
    }

    pspeechstream = memAlloc(streamersize, "StreamStructures", NonVolatile);

    soundstreaminit(pspeechstream, streamersize, SE_NUM_STREAMS, soundEventDebugPrint);

    /* moved voice file from sfxdir to root */
    //  strcpy(loadfile, SOUNDFXDIR);

    /* open speech file and compare checksums */
    strcpy(loadfile,utyVoiceFilename);
    if (soundstreamopenfile(loadfile, &speechfilehandle) != SentenceLUT->checksum)
    {
        dbgMessage("Voice file does not match lookup tables.  Not from same generate.\n");
        dbgAssert(FALSE);
    }

    pspeechbuffer0 = memAlloc(buffersize, "SpeechBuffer0", NonVolatile);
        streamhandle[0] = soundstreamcreatebuffer(pspeechbuffer0, buffersize, SentenceLUT->compbitrate[0]);
    pspeechbuffer1 = memAlloc(buffersize, "SpeechBuffer1", NonVolatile);
        streamhandle[1] = soundstreamcreatebuffer(pspeechbuffer1, buffersize, SentenceLUT->compbitrate[1]);
    pspeechbuffer2 = memAlloc(buffersize, "SpeechBuffer2", NonVolatile);
        streamhandle[2] = soundstreamcreatebuffer(pspeechbuffer2, buffersize, SentenceLUT->compbitrate[2]);
#if defined(Downloadable) || defined(DLPublicBeta)
    pspeechbuffer4 = memAlloc(buffersize, "SpeechBuffer4", NonVolatile);
        streamhandle[4] = soundstreamcreatebuffer(pspeechbuffer4, buffersize, SentenceLUT->compbitrate[0]);
#else
    pspeechbuffer3 = memAlloc(buffersize, "SpeechBuffer3", NonVolatile);
        streamhandle[3] = soundstreamcreatebuffer(pspeechbuffer3, buffersize, SentenceLUT->compbitrate[3]);
    pspeechbuffer4 = memAlloc(buffersize, "SpeechBuffer4", NonVolatile);
        streamhandle[4] = soundstreamcreatebuffer(pspeechbuffer4, buffersize, SentenceLUT->compbitrate[0]);
#endif

    /* music stuff */
    /* load the music header file */
    strcpy(loadfile, SOUNDFXDIR);
    strcat(loadfile,musicHeaderName);
    fileLoadAlloc(loadfile, &musicheader, NonVolatile);

    /* open music file and compare checksums */
    strcpy(loadfile,utyMusicFilename);
    if (soundstreamopenfile(loadfile, &musicfilehandle) != musicheader->checksum)
    {
        dbgMessage("Music data file does not match lookup table.  Not from same generate.\n");
        dbgAssert(FALSE);
    }

    pmusicbuffer0 = memAlloc(buffersize, "MusicBuffer", NonVolatile);
        musicinfo[NISSTREAM].handle = soundstreamcreatebuffer(pmusicbuffer0, buffersize, (uword)FQ_BR88);
    pmusicbuffer1 = memAlloc(buffersize, "MusicBuffer", NonVolatile);
        musicinfo[AMBIENTSTREAM].handle = soundstreamcreatebuffer(pmusicbuffer1, buffersize, (uword)FQ_BR88);

    for (i = NISSTREAM; i <= AMBIENTSTREAM; i++)
    {
        musicinfo[i].status = SOUND_FREE;
        musicinfo[i].tracknum = SOUND_DEFAULT;
        musicinfo[i].targetvol = SOUND_DEFAULT;
    }

    SEinitdelayEQ();

    SEloadscripts();

    for (i = 0; i < SE_NUM_ACTORS; i++)
    {
        memset(&speechqueue[i], 0, sizeof(SPEECHQUEUE));
        speechqueue[i].current.handle = SOUND_NOTINITED;
        speechqueue[i].status = SOUND_STOPPED;
        speechqueue[i].nextevent = SOUND_NOTINITED;
        speechqueue[i].locked = FALSE;
        for (j = 0; j < SE_MAX_QUEUE; j++)
        {
            speechqueue[i].queue[j].handle = SOUND_NOTINITED;
            speechqueue[i].queue[j].variation = SOUND_NOTINITED;
        }
    }

    numSinglePlayerEvents = 0;

    return (SOUND_OK);
}


void speechEventClose(void)
{
    memFree(pmusicbuffer0);
    memFree(pmusicbuffer1);
    memFree(musicheader);

    memFree(pspeechstream);
    memFree(pspeechbuffer0);
    memFree(pspeechbuffer1);
    memFree(pspeechbuffer2);
#if defined(Downloadable) || defined(DLPublicBeta)
    memFree(pspeechbuffer4);
#else
    memFree(pspeechbuffer3);
    memFree(pspeechbuffer4);
#endif
    memFree(SentenceLUT);
    memFree(PhraseLUT);
}


/*-----------------------------------------------------------------------------
    Name        :
    Description :
    Inputs      :
    Outputs     :
    Return      :
----------------------------------------------------------------------------*/
void speechEventUpdate(void)
{
    /* must call this so the streamer gets updated */
    soundupdate();
    speechQueueUpdate();
}


/*-----------------------------------------------------------------------------
    Name        :
    Description :   this function updates the queues, starts playing new events,
                    reorders and cleans up the queue
    Inputs      :
    Outputs     :
    Return      :
----------------------------------------------------------------------------*/
#if 1
void speechQueueUpdate(void)
{
    sdword  i;
    SPEECHQUEUE *pSQueue;

    for (i = 0; i < SE_NUM_ACTORS; i++)
    {
        pSQueue = &speechqueue[i];

        /* is this channel playing something? */
        if (pSQueue->status >= SOUND_PLAYING)
        {
            /* is it over yet? */
            if (soundstreamover(streamhandle[i]))
            {
#if SE_VERBOSE_LEVEL >= 2
                dbgMessagef("\nQueue %d, stopped", i);
#endif
                /* was it a single player event? */
                if (pSQueue->current.event & SPEECH_SINGLEPLAYER_FLAG)
                {
                    numSinglePlayerEvents--;
                }
                if (pSQueue->nextevent >= SOUND_OK)
                {
                    if ((pSQueue->current.event == pSQueue->queue[pSQueue->nextevent].event) &&
                        (pSQueue->current.variable == pSQueue->queue[pSQueue->nextevent].variable))
                    {
                        memset(&pSQueue->queue[pSQueue->nextevent], 0, sizeof(QUEUEEVENT));
                        pSQueue->queue[pSQueue->nextevent].handle = SOUND_NOTINITED;
                        pSQueue->queue[pSQueue->nextevent].timeout = -1.0f;
                    }
                }
                pSQueue->current.event = SOUND_DEFAULT;
                pSQueue->status = SOUND_STOPPED;
                pSQueue->timeover = universe.totaltimeelapsed;
            }
            else
            {
                /* should it be shut off? */
            }
        }

        /* are we ready to play something? */
        if ((pSQueue->status == SOUND_STOPPED) && (pSQueue->nextevent >= SOUND_OK) && (!pSQueue->locked) &&
            (((universe.totaltimeelapsed - pSQueue->timeover) >= SPEECH_PAUSE) || !gameIsRunning || !binkDonePlaying || FalkosFuckedUpTutorialFlag))
        {
            /* find the event that we should play */

            /* remove it from the queue */
            memcpy(&pSQueue->current, &pSQueue->queue[pSQueue->nextevent], sizeof(QUEUEEVENT));

            memset(&pSQueue->queue[pSQueue->nextevent], 0, sizeof(QUEUEEVENT));
            pSQueue->queue[pSQueue->nextevent].handle = SOUND_NOTINITED;
            pSQueue->queue[pSQueue->nextevent].timeout = -1.0f;

            /* find the next event */

            /* play this one */
            speechPlayQueue(pSQueue, i);
            if (pSQueue->current.event & SPEECH_SINGLEPLAYER_FLAG)
            {
                numSinglePlayerEvents++;
            }
            SEreorderqueue(pSQueue);

            pSQueue->status = SOUND_PLAYING;
        }

        /* find the event that we should play */
        /* remove it from the queue */

        /* reorder the queue */
        /* clean up the queue */

    }

}


/*-----------------------------------------------------------------------------
    Name        :
    Description :
    Inputs      :
    Outputs     :
    Return      :
----------------------------------------------------------------------------*/
void speechPlayQueue(SPEECHQUEUE *pSQueue, sdword streamchannel)
{
    sdword i;
    sdword numoffsets;
    udword duration;
    sdword *phraseoffsets;

    EFFECT *fqeffect = NULL;
    STREAMEQ *pEQ = NULL;
    STREAMDELAY *pdelay = NULL;
    void *ppatch = NULL;
    sword level = 0;
    bool bookend = FALSE;
    ShipClass shipclass = 0;

    real32 vol = (real32)SOUND_VOL_MAX;
    sword pan = SOUND_PAN_CENTER;
    uword bitrate;
    sdword actor;
    sdword event;
    real32 damageratio = 0.0f;
    real32 mult;
    sdword actornum;

    event = pSQueue->current.event;
    actornum = pSQueue->current.actornum;

    /* figure out all the filtering etc */
    if ((event & ACTOR_PILOT_FLAG) && (!nisIsRunning))//gameIsRunning))
    {
        /* this is one of the Pilot actors */

        /* figure out the ship class */
        if (nisIsRunning)
        {
            shipclass = CLASS_Fighter;
        }
        else if (pSQueue->current.pShip != NULL)
        {
            shipclass = pSQueue->current.pShip->staticinfo->shipclass;

            damageratio = 1.0f - ((real32)pSQueue->current.pShip->health * (real32)pSQueue->current.pShip->staticinfo->oneOverMaxHealth);
        }

        /* get the appropriate EQ */
        pEQ = &streamEQ[actornum];

        if (((event & SPEECH_EVENT_MASK) >= (CHAT_Const_DuringBattle & SPEECH_EVENT_MASK)) &&
            ((event & SPEECH_EVENT_MASK) <= (CHAT_Const_More & SPEECH_EVENT_MASK)))
        {
            /* construction has a special delay */
            pdelay = &streamdelay[SPEECH_CONSTRUCTION];
            fqeffect = &cleaneffect[SPEECH_CAPSHIPS];
        }

        if ((shipclass == CLASS_Corvette) || (shipclass == CLASS_Fighter))
        {
            /* get the strike craft effects */
            pdelay = &streamdelay[SPEECH_STRIKECRAFT];

            /* figure out the damage */
            mult = damageratio * SPEECH_STIKEDAMAGE_MULT;

            mixedeffect[SPEECH_STRIKECRAFT].fScaleLev = cleaneffect[SPEECH_STRIKECRAFT].fScaleLev;
            mixedeffect[SPEECH_STRIKECRAFT].fNoiseLev = cleaneffect[SPEECH_STRIKECRAFT].fNoiseLev +
                            ((damageeffect.fNoiseLev - cleaneffect[SPEECH_STRIKECRAFT].fNoiseLev) * mult);
            mixedeffect[SPEECH_STRIKECRAFT].nBreakMaxRate = damageeffect.nBreakMaxRate;     //(udword)((real32)damageeffect.nBreakMaxRate * mult);
            mixedeffect[SPEECH_STRIKECRAFT].nBreakMaxDur = (udword)((real32)damageeffect.nBreakMaxDur - ((real32)damageeffect.nBreakMaxDur * mult));

            if (singlePlayerGameInfo.currentMission == SPEECH_SUPERNOVA_LEVEL)
            {
                /* use the quantize noise */
                mixedeffect[SPEECH_STRIKECRAFT].nQNoiseMaxRate = (udword)(damageeffect.nQNoiseMaxRate * SPEECH_STIKEDAMAGE_MULT);
                mixedeffect[SPEECH_STRIKECRAFT].nQNoiseMaxDur = (udword)(damageeffect.nQNoiseMaxDur * SPEECH_STIKEDAMAGE_MULT);
            }
            else
            {
                mixedeffect[SPEECH_STRIKECRAFT].nQNoiseMaxRate = 0;
                mixedeffect[SPEECH_STRIKECRAFT].nQNoiseMaxDur = 0;
            }

            fqeffect = &mixedeffect[SPEECH_STRIKECRAFT];
        }
        else    // if (shipclass != CLASS_Mothership)
        {
#if 0
            if (SPEECH_AMBIENT_ENABLE)
            {
                /* if we are adding background ambients to speech */
                if ((shipclass >= CLASS_HeavyCruiser) && (shipclass <= CLASS_Frigate))
                {
                    /* need to check if we should play calm or excited */
                    ppatch = SNDgetpatch(SpecialEffectBank, SpecEffectEventsLUT->lookup[GetPatch(SpecEffectEventsLUT, 0, Spec_IntCapShipCalm)]);
                }
                else if (shipclass == CLASS_Resource)
                {
                    /* resources play their bg ambient */
                    ppatch = SNDgetpatch(ShipBank, ShipCmnEventsLUT->lookup[GetPatch(ShipCmnEventsLUT, ResourceCollector, ShipCmn_Ambient)]);
                }
            }
#endif

            /* get the capital ship effects */
            pdelay = &streamdelay[SPEECH_CAPSHIPS];

            mult = damageratio * SPEECH_CAPDAMAGE_MULT;

            mixedeffect[SPEECH_CAPSHIPS].fScaleLev = cleaneffect[SPEECH_CAPSHIPS].fScaleLev;
            mixedeffect[SPEECH_CAPSHIPS].fNoiseLev = cleaneffect[SPEECH_CAPSHIPS].fNoiseLev +
                            ((damageeffect.fNoiseLev - cleaneffect[SPEECH_CAPSHIPS].fNoiseLev) * mult);
            mixedeffect[SPEECH_CAPSHIPS].nBreakMaxRate = damageeffect.nBreakMaxRate;    //(udword)((real32)damageeffect.nBreakMaxRate * mult);
            mixedeffect[SPEECH_CAPSHIPS].nBreakMaxDur = (udword)((real32)damageeffect.nBreakMaxDur - ((real32)damageeffect.nBreakMaxDur * mult));   //(udword)(damageeffect.nBreakMaxDur * mult);

            if (singlePlayerGameInfo.currentMission == SPEECH_SUPERNOVA_LEVEL)
            {
                /* use the quantize noise */
                mixedeffect[SPEECH_CAPSHIPS].nQNoiseMaxRate = (udword)(damageeffect.nQNoiseMaxRate * SPEECH_CAPDAMAGE_MULT);
                mixedeffect[SPEECH_CAPSHIPS].nQNoiseMaxDur = (udword)(damageeffect.nQNoiseMaxDur * SPEECH_CAPDAMAGE_MULT);
            }
            else
            {
                mixedeffect[SPEECH_CAPSHIPS].nQNoiseMaxRate = 0;
                mixedeffect[SPEECH_CAPSHIPS].nQNoiseMaxDur = 0;
            }

            /* sound the alarm */
            mixedeffect[SPEECH_CAPSHIPS].nToneMinFreq = damageeffect.nToneMinFreq;
            mixedeffect[SPEECH_CAPSHIPS].nToneMaxFreq = damageeffect.nToneMaxFreq;
            mixedeffect[SPEECH_CAPSHIPS].nToneDur = damageeffect.nToneDur;
            mixedeffect[SPEECH_CAPSHIPS].nToneMute = damageeffect.nToneMute;
            mixedeffect[SPEECH_CAPSHIPS].nToneCount = damageeffect.nToneCount;
            mixedeffect[SPEECH_CAPSHIPS].fToneLev = damageeffect.fToneLev * mult;

            fqeffect = &mixedeffect[SPEECH_CAPSHIPS];
        }

        if (fqeffect != NULL)
        {
            if (event & SPEECH_STATUS_FLAG)
            {
                fqeffect->fScaleLev *= SPEECH_STATUS_RATIO;
            }
            else if (event & SPEECH_CHATTER_FLAG)
            {
                fqeffect->fScaleLev *= SPEECH_CHATTER_RATIO;
            }
        }

        /* play radio bookends */
        bookend = TRUE;
    }
    else
    {
        /* this is a non-pilot actor */

        actor = (event & SPEECH_FLAG_MASK);

        switch (actor)
        {
            case ACTOR_FLEETCOMMAND_FLAG:
#if 0
                if (SPEECH_AMBIENT_ENABLE)
                {
                    ppatch = SNDgetpatch(SpecialEffectBank, SpecEffectEventsLUT->lookup[GetPatch(SpecEffectEventsLUT, 0, Spec_FleetBG)]);
                }
#endif
                pEQ = &streamEQ[SPEECH_FLEET];
                pdelay = &streamdelay[SPEECH_FLEET];
                fqeffect = &cleaneffect[SPEECH_FLEET];
                break;

            case ACTOR_FLEETINTEL_FLAG:
#if 0
                if (SPEECH_AMBIENT_ENABLE)
                {
                    ppatch = SNDgetpatch(SpecialEffectBank, SpecEffectEventsLUT->lookup[GetPatch(SpecEffectEventsLUT, 0, Spec_IntelBG)]);
                }
#endif
                pEQ = &streamEQ[SPEECH_INTEL];
                pdelay = &streamdelay[SPEECH_INTEL];
                fqeffect = &cleaneffect[SPEECH_INTEL];
                break;

            case ACTOR_ALLSHIPSENEMY_FLAG:
                bookend = TRUE;
                pdelay = &streamdelay[SPEECH_STRIKECRAFT];
                fqeffect = &cleaneffect[SPEECH_STRIKECRAFT];
                fqeffect->fScaleLev = 1.3f;
                break;

            case  ACTOR_PILOT_FLAG:
                if ((streamchannel > 0)  && (streamchannel < SentenceLUT->numactors))
                {
                    bookend = TRUE;
                }
                pdelay = &streamdelay[SPEECH_STRIKECRAFT];
                fqeffect = &cleaneffect[SPEECH_STRIKECRAFT];
#if SE_VERBOSE_LEVEL >= 2
                dbgMessage("Pilot\n");
#endif
                break;
            case ACTOR_AMBASSADOR_FLAG:
                pdelay = &streamdelay[SPEECH_STRIKECRAFT];
                fqeffect = &cleaneffect[SPEECH_STRIKECRAFT];
                fqeffect->fScaleLev = 2.5f;
                bookend = TRUE;
                pSQueue->current.actornum = 0;
#if SE_VERBOSE_LEVEL >= 2
                dbgMessage("Ambassador\n");
#endif
                break;
            case ACTOR_TRADERS_FLAG:
#if SE_VERBOSE_LEVEL >= 2
                dbgMessage("Traders\n");
#endif
                break;
            case ACTOR_PIRATES2_FLAG:
#if SE_VERBOSE_LEVEL >= 2
                dbgMessage("Pirates2\n");
#endif
                break;
            case ACTOR_NARRATOR_FLAG:
#if SE_VERBOSE_LEVEL >= 2
                dbgMessage("Narrator\n");
#endif
                break;
            case ACTOR_DEFECTOR_FLAG:
#if SE_VERBOSE_LEVEL >= 2
                dbgMessage("Defector\n");
#endif
                break;
            case ACTOR_EMPEROR_FLAG:
#if SE_VERBOSE_LEVEL >= 2
                dbgMessage("Emperor\n");
#endif
                break;
            case ACTOR_KHARSELIM_FLAG:
#if SE_VERBOSE_LEVEL >= 2
                dbgMessage("KharSelim\n");
#endif
                break;

            default:
                if ((event == N01_Chatter_Mid) || (event == N01_Chatter_BG))
                {
                    vol *= 1.5f;
                }
#if SE_VERBOSE_LEVEL >= 2
                dbgMessage("default\n");
#endif
                break;
        }
    }

    /* get the bitrate for this actor */
    if (streamchannel < SentenceLUT->numactors)
    {
        bitrate = SentenceLUT->compbitrate[streamchannel];
    }
    else
    {
        /* the extra channel always plays from actor 0 */
        bitrate = SentenceLUT->compbitrate[0];
    }

    /* get the sentence */
    numoffsets = SEselectsentence(actornum, (event & SPEECH_EVENT_MASK),
                                  pSQueue->current.variable, pSQueue->current.variation, &phraseoffsets, &duration);

    /* scale by the front end volume settings */
    vol *= volSpeech;

    /* if this is not a single player event and there is a single player event playing then turn it down */
    if ((numSinglePlayerEvents > 0) && !(event & SPEECH_SINGLEPLAYER_FLAG))
    {
        vol *= SPEECH_SINGLEPLAYER_RATIO;
    }

    /* scale the background ambience thats mixed into the speech */
    level = (sword)(vol * SPEECH_AMBIENT_LEVEL);

    /* queue the offsets */
    if ((numoffsets > 0) && (vol >= SPEECH_MIN_PERCEPTABLE_VOL))
    {
#if 0
        if (fqeffect != NULL)
        {
            dbgMessagef("\nspeechEvent: %d, channel: %d, volume: %f, scale: %f\n", event & SPEECH_EVENT_MASK, streamchannel, vol, fqeffect->fScaleLev);
        }
        else
        {
            dbgMessagef("\nspeechEvent: %d, channel: %d, volume: %f\n", event & SPEECH_EVENT_MASK, streamchannel, vol);
        }
#endif
        if (bookend && enableSFX)
        {
            soundstreamqueuewait(streamhandle[streamchannel], (sdword)UIBank,
                             (sdword)UIEventsLUT->lookup[GetPatch(UIEventsLUT, 0, UI_RadioBeepStart)],
                             SOUND_FLAGS_QUEUEPATCH, (sword)vol, pan, SOUND_MONO, SOUND_DEFAULT, NULL, NULL, NULL, event);

//          soundstreamqueueSilence(streamhandle[stream], SOUND_FLAGS_QUEUESILENCE, vol, pan, SOUND_MONO,
//                                  SentenceLUT->compbitrate, fqeffect, pEQ, pdelay, SEsilence());
        }

#if SE_VERBOSE_LEVEL >= 2
        dbgMessagef("\nspeechEvent: %d, channel: %d, volume: %d",
                event, streamchannel, vol);
#endif

        for (i = 0; i < numoffsets - 1; i++)
        {
            soundstreamqueuePatch(streamhandle[streamchannel], speechfilehandle, *phraseoffsets++, SOUND_FLAGS_QUEUESTREAM, (sword)vol, pan,
                                  SOUND_MONO, bitrate, fqeffect, pEQ, pdelay, ppatch, level, 0.0f, 0.0f, pSQueue->current.actornum, event, TRUE);
        }

        if (bookend && enableSFX)
        {
            soundstreamqueuePatch(streamhandle[streamchannel], speechfilehandle, *phraseoffsets++, SOUND_FLAGS_QUEUESTREAM, (sword)vol, pan,
                                  SOUND_MONO, bitrate, fqeffect, pEQ, pdelay, ppatch, level, 0.0f, 0.0f, pSQueue->current.actornum, event, TRUE);

//          soundstreamqueueSilence(streamhandle[stream], SOUND_FLAGS_QUEUESILENCE, vol, pan, SOUND_MONO,
//                                  SentenceLUT->compbitrate, fqeffect, pEQ, pdelay, SEsilence());

            soundstreamqueue(streamhandle[streamchannel], (sdword)UIBank,
                             (sdword)UIEventsLUT->lookup[GetPatch(UIEventsLUT, 0, UI_RadioBeepEnd)],
                             SOUND_FLAGS_QUEUEPATCH, (sword)vol, pan, SOUND_MONO, SOUND_DEFAULT, NULL, NULL, NULL, event);
        }
        else
        {
            soundstreamqueuePatch(streamhandle[streamchannel], speechfilehandle, *phraseoffsets++, SOUND_FLAGS_QUEUESTREAM, (sword)vol, pan,
                                  SOUND_MONO, bitrate, fqeffect, pEQ, pdelay, ppatch, level, 0.0f, 0.0f, pSQueue->current.actornum, event, FALSE);
        }

        /* figure out the last ship to speak var */
        if (pSQueue->current.pShip != NULL)
        {
            /* straight forward, this was just a ship */
            lastshiptospeak = pSQueue->current.pShip;
        }
        else if (event & SPEECH_GROUP_FLAG)
        {
            lastshiptospeak = (Ship *)-1;
            lastgrouptospeak = pSQueue->current.variable;
        }
    }
}


/*-----------------------------------------------------------------------------
    Name        : SERandomSequenceCreate
    Description : Creates a number which is a random BCD sequence numVariations
                    in length containing all numbers between 0 and numVarations
                    once and only once.
    Inputs      : numVariations - number of variations to encode.
                  lookupsy - the lookup table which contains probabilities
                  width - width of the lookup table
    Outputs     :
    Return      : BCD-encoded random series
----------------------------------------------------------------------------*/
udword SERandomSequenceCreate(sdword numVariations, sdword *lookupsy, sdword width)
{
    sdword index, whichIndex;
    udword sequence[16];
    ubyte probs[16], accumProb = 0;
    udword finalSequence;

    dbgAssert(numVariations <= 16);                         //can only fit 16 BCD's in a DWORD
    //make an order 0, 1, 2, 3, ... and a list of matching probabilities
    for (index = 0; index < numVariations; index++)
    {
        sequence[index] = index;
        accumProb += probs[index] = ((lookupinterp *)(lookupsy))->probability - accumProb;
        lookupsy += width;
    }
    //partially bubble sort this list (this will favor the most common variations first)
    for (index = 0; index < numVariations - 1; index++)     //!!! might want to bubble sort a different number of times
    {
        whichIndex = ranRandom(RAN_SoundBothThreads) % (numVariations - 1);
        if (probs[whichIndex] <= probs[whichIndex + 1])
        {
            swap(sequence[whichIndex], sequence[whichIndex + 1], finalSequence);
            swap(probs[whichIndex], probs[whichIndex + 1], accumProb);
        }
    }
    //create the final sequence DWORD
    finalSequence = 0;
    for (index = numVariations - 1; index >= 0; index--)
    {
        finalSequence <<= 4;                                //free up a new BCD digit
        finalSequence = (finalSequence & 0xfffffff0) | (sequence[index] + 1);//stick in the new digit
    }
    return(finalSequence);                                  //done
}

/*-----------------------------------------------------------------------------
    Name        : SENextVariationInSeries
    Description : Select a variation of a speech event based on a random sequence
    Inputs      : numVariations - how many variations of this event
                  lookupsy - the lookup table
                  width - width of the lookup table
    Outputs     :
    Return      :
----------------------------------------------------------------------------*/
sdword SENextVariationInSeries(sdword numVariations, sdword *lookupsy, sdword width)
{
    lookupinterp *interp = (lookupinterp *)(lookupsy);
    udword sequence;
    udword iVariation;

#if defined(Downloadable) || defined(DLPublicBeta)
    if (numVariations > 0xf)
    {                                                       //could be the case if this actor is disabled
        return(0);
    }
#endif
    dbgAssert(numVariations < 0xf);
    //get the random sequence into a dword
    sequence = (interp->high[1] << 24) + (interp->high[0] << 16) + (interp->low[1] << 8) + (interp->low[0]);

    if (sequence == 0)
    {                                                       //if no numbers left in sequence (tried all variations)
        sequence = SERandomSequenceCreate(numVariations, lookupsy, width);//start a new sequence
    }
    iVariation = (sequence & 0xf) - 1;                      //get a new variation
    sequence >>= 4;                                         //shift off those 4 bits
    dbgAssert(iVariation < numVariations);
    interp->high[1] = sequence >> 24;                       //put the sequence back in the high words
    interp->high[0] = (sequence >> 16) & 0xff;
    interp->low[1] = (sequence >> 8) & 0xff;
    interp->low[0] = (sequence) & 0xff;
#ifdef lmoloney
#if SE_VERBOSE_LEVEL >= 1
    dbgMessagef("\nSelected variation %d of %d for event at 0x%x of width %d", iVariation, numVariations, lookupsy, width);
#endif
#endif
    return(iVariation);                                     //return the variation we've found
}

/*-----------------------------------------------------------------------------
    Name        :
    Description :
    Inputs      :   actor - which actor?
                    event - which event?
                    variable - value of the variable for this event
                    setvariation - select a particular variation, -1 is random
    Outputs     :   pOffsets - sets a pointer to the first offset for the sentence, NULL if there is an error
                    pDuration - sets the approximate duration of this sentence
    Return      :   the number of offsets in this sentence, -1 if there is an error
----------------------------------------------------------------------------*/
sdword SEselectsentence(sdword actor, sdword event, sdword variable, sdword setvariation, sdword **pOffsets, udword *pDuration)
{
    uword numvariations;
    uword maxvariable;
    uword index;
    udword numoffsets;
    udword i;
    udword phrase;
    sword *pSentence;
    sdword *pPhrase;
    udword variation;
    static sdword lastBGvariation = 0;
    static sdword lastMIDvariation = 0;

    pSentence = &SentenceLUT->lookup[(actor * SentenceLUT->numevents + event) * SentenceLUT->numcolumns + 1];   // + 1 is to get past the priority

    numvariations = *pSentence++;

    if (numvariations == 0)
    {
        pOffsets = NULL;
        return (SOUND_ERR);
    }

    maxvariable = *pSentence++;
    index = *pSentence;

    if (variable < SOUND_OK)
    {                                                       //no variable specified
        variable = ranRandom(RAN_SoundBothThreads) % maxvariable;//!!! no heuristic on this one
    }
    else if (variable >= maxvariable)
    {                                                       //variable out of range
        pOffsets = NULL;
        return (SOUND_ERR);
    }

    if ((setvariation != SOUND_NOTINITED) && (setvariation < numvariations))
    {                                                       //if variation specified
        variation = setvariation;

        phrase = index + (variation * maxvariable) + variable;
    }
    else if (numvariations == 1)
    {                                                       //not spec'd but only 1 choice
        phrase = index + variable;
    }
    else
    {                                                       //no variation spec'd, choose one intelligently
        if (event == (N01_Chatter_Mid & SPEECH_EVENT_MASK))
        {                                                   //increment the variation if it's mid-ground chatter
            if (++lastMIDvariation >= numvariations)
            {
                lastMIDvariation = 0;
            }
            i = lastMIDvariation;
        }
        else if (event == (N01_Chatter_BG & SPEECH_EVENT_MASK))
        {                                                   //inclrement the variation if it's background chatter
            if (++lastBGvariation >= numvariations)
            {
                lastBGvariation = 0;
            }
            i = lastBGvariation;
        }
        else
        {                                                   //else it's a regular speech event; choose a random variation somehow
            i = SENextVariationInSeries(numvariations, &PhraseLUT->lookupsy[(index + variable) * PhraseLUT->numcolumns], maxvariable * PhraseLUT->numcolumns);
           /*
            for (i = 0; i < numvariations; i++)
            {
                if (PhraseLUT->lookupsy[(index + (i * maxvariable) + variable) * PhraseLUT->numcolumns + 1] >= variation)
                {
                    break;
                }
            }
            if (i >= numvariations)
            {
                i = numvariations - 1;
            }
            */
        }

        phrase = index + (i * maxvariable) + variable;
    }

    pPhrase = &PhraseLUT->lookupsy[phrase * PhraseLUT->numcolumns];

    *pDuration = *pPhrase++;
    pPhrase++;  // skip  the probability
    numoffsets = (*pPhrase++) & 0xff;

    *pOffsets = pPhrase;

    if (numoffsets > 0)
    {
        for (i = 0; i < numoffsets; i++)
        {
            if (*(pPhrase + i) == SOUND_NOTINITED)
            {
                // bad, this won't work
                pOffsets = NULL;
                numoffsets = 0;
                break;
            }
        }
    }

    return (numoffsets);
}


/*-----------------------------------------------------------------------------
    Name        :
    Description :   this function updates the actual speech that is playing,
                    decides when to fade volumes or cut off particular events
    Inputs      :
    Outputs     :
    Return      :
----------------------------------------------------------------------------*/
void speechUpdateSpeech(void)
{
    /* what is the most important speech? */
    /* should something else be shut off? */
    /* are we past the max # of events that should be playing? */


}


/*-----------------------------------------------------------------------------
    Name        :   speechEventQueue
    Description :   Adds speech events to the queue if there is space for them.  Does trivial rejection on
                    low priority speech events.
    Inputs      :   object - pointer to a ship object that this speech event is attached to (can be NULL for Fleet Command, etc)
                    event - event # to play, define in speechevent.h
                    var - variable that may be attached to an event (ie a build ship event would pass the ship variable #)
                    variation - only used if you want a specific variation of an event to play
                    actornum - used if you want to override the actor that the speech code has selected
                    playernum - the playerindex in the array player in the universe structure, only want to hear your own speech events
                    linkto - handle of the speech event that you want to tie this one to, will play this event directly after that event
                    timeout - amount of time this event can wait in the queue, 0 means play now, < 0 means wait indefinatly and don't drop this from the queue
                    volume - volume at which to play this event, if you want to override the code that figures that out
    Outputs     :
    Return      :   handle to this speech event, can be used to stop the event or link another event to this one
                    or SOUND_ERR (-1) if there is an error or the event can't be queued
----------------------------------------------------------------------------*/
sdword speechEventQueue(void *object, sdword event, sdword var, sdword variation, sdword actornum, sdword playernum, sdword linkto, real32 timeout, sword volume)
{
    sdword actor;
    sdword i, j;
    Ship *pShip = NULL;
    sdword handle = SOUND_ERR;
    SPEECHQUEUE *pSQueue;
    QUEUEEVENT *pQEvent;
    SpaceObj *spaceobject;
    sdword channel = 0;
    sdword ally = SOUND_ERR;

    if (!enableSpeech || soundpaused)
    {
        return (SOUND_ERR);
    }

    if ((event & SPEECH_EVENT_MASK) > SPEECH_LAST_EVENT)
    {
#if SE_VERBOSE_LEVEL >= 1
        dbgMessagef("\nspeechEventQueue: Tutorial speeech event %d, not implemented", event);
#endif
        return (SOUND_ERR);
    }

    /* make sure they're not asking for an actor that doesn't exist */
    if (actornum >= (SentenceLUT->numactors))
    {
        return (handle);
    }

    /* if an NIS is playing, only want to hear NIS events */
//  if (nisIsRunning && (event < SPEECH_FIRST_SP_EVENT))
    if (nisIsRunning && !(event & (SPEECH_NIS_FLAG + SPEECH_ANIMATIC_FLAG)))
    {
        return (handle);
    }

    /* if single player events are happening, don't want any chatter */
    if ((numSinglePlayerEvents > 0) && (event & SPEECH_CHATTER_FLAG))
    {
        return (handle);
    }

    /* toss out events the user doesn't want to hear */
    if ((event & SPEECH_CHATTER_FLAG) && !bChatterOn)
    {
        return (handle);
    }
    else if ((event & SPEECH_STATUS_FLAG) && !bStatusOn)
    {
        return (handle);
    }
    else if ((event & SPEECH_COMMAND_FLAG) && !bCommandsOn)
    {
        return (handle);
    }

#if 1       // enable when new speech is put in game
    if (!((event & SPEECH_FLAG_MASK) & actorFlagsEnabled))
    {
        return (handle);
    }
#endif

    if ((object != NULL) && !(event & ACTOR_FLEETCOMMAND_FLAG))
    {
        /* this is probably a ship, so this is one of the ALL SHIPS voices */
        spaceobject = (SpaceObj *)object;

        if (!univSpaceObjInRenderList(spaceobject) && ((event & SPEECH_STATUS_FLAG) || (event & SPEECH_CHATTER_FLAG)))
        {
            if (!(event & SPEECH_ALWAYSPLAY_FLAG))
            {
                return (SOUND_ERR);        // only do sound events for objects in current mission sphere we are viewing
            }
        }

        if (spaceobject->objtype != OBJ_ShipType)
        {
            return (SOUND_ERR);
        }

        pShip = (Ship *)object;

        if ((universe.curPlayerIndex != pShip->playerowner->playerIndex) && (!nisIsRunning))
        {
            return (SOUND_ERR);
        }

        if ((event == COMM_Selection) || (event == COMM_AssGrp_Select))
        {
            /* Probes and Proximity Sensors are unmanned and shouldn't speak when selected */
            if ((pShip->staticinfo->shiptype == Probe) || (pShip->staticinfo->shiptype == ProximitySensor))
            {
                /* play this sound event instead */
                soundEvent(pShip, Ship_SelectProbe);

                return (SOUND_OK);
            }
            else if ((pShip->staticinfo->maxfuel > 0.0f) && (pShip->fuel <= 0.0f))
            {
                if (event == COMM_Selection)
                {
                    var = SOUND_DEFAULT;
                }
                event = STAT_Strike_OutOfFuel;
            }
        }

        if (actornum < SOUND_OK)
        {
            if ((pShip->soundevent.actorNum <= SOUND_NOTINITED) || (pShip->soundevent.actorNum >= SentenceLUT->numactors))
            {
                actor = pShip->soundevent.actorNum = SEselectactor();
            }
            else
            {
                actor = pShip->soundevent.actorNum;
            }
        }
        else
        {
            actor = actornum;
        }
        channel = actor;
    }
    else
    {
        if (object != NULL)
        {
            spaceobject = (SpaceObj *)object;
            if (spaceobject->objtype == OBJ_ShipType)
            {
                pShip = (Ship *)object;
            }
        }

        if (((udword)playernum != universe.curPlayerIndex) && (!nisIsRunning) && (playernum != SOUND_EVENT_DEFAULT))
        {
            return (SOUND_ERR);
        }

        /* this is either Fleet Command, Fleet Intel, Traders, Pirates or NIS speech*/
//      if ()
//      {
//      }

        if (actornum < SOUND_OK)
        {
            actor = ACTOR_FLEETCOMMAND;
        }
        else
        {
            actor = actornum;
        }

        if (event == N01_Chatter_BG)
        {
            channel = 4;
        }
        else
        {
            channel = actor;
        }
    }

    /* figure out which speech channel to use */
    pSQueue = &speechqueue[channel];

    /* is the queue full? */
    if (pSQueue->numqueued == SE_MAX_QUEUE)
    {
        /* should see if I can bump one of these? */
        return (SOUND_ERR);
    }

    /* is this going to time out before it'll play? */
    if ((timeout > 0.0f) && (pSQueue->timeover > universe.totaltimeelapsed + timeout))
    {
        return (SOUND_ERR);
    }

    if (actor == SOUND_NOTINITED)
    {
        return (handle);
    }

    if ((event == STAT_Strike_OutOfFuel) && (var > SOUND_DEFAULT))
    {
        event = STAT_Group_OutOfFuel;
    }
    else if ((event == STAT_Strike_LowOnFuel) && (var > SOUND_DEFAULT))
    {
        event = STAT_Group_LowOnFuel;
    }

    if (var == SOUND_DEFAULT)
    {
        var = 0;
    }

    if ((pSQueue->current.event == event) && (pSQueue->current.variable == var))
    {
        return (SOUND_ERR);
    }

    /* special relic users events */
    if ((event == COMM_F_AllianceFormed) || (event == COMM_F_AllianceBroken))
    {
        ally = var;
        var = 0;

        for (i = 0; i < MAX_RELIC_NAMES; i++)
        {
            if (strnicmp(playerNames[ally], relicPlayerNames[i], MAX_PERSONAL_NAME_LEN) == 0)
            {
                if (event == COMM_F_AllianceFormed)
                {
                    event = COMM_F_RelicAllianceFormed;
                }
                else
                {
                    event = COMM_F_RelicAllianceBroken;
                }
                var = i;
                break;
            }
        }
    }
    else if (event == STAT_F_RelicPlayerDies)
    {
        for (i = 0; i < MAX_RELIC_NAMES; i++)
        {
            if (strnicmp(playerNames[var], relicPlayerNames[i], MAX_PERSONAL_NAME_LEN) == 0)
            {
                var = i;
                break;
            }
        }
        if ((i == MAX_RELIC_NAMES) || (i == universe.curPlayerIndex))
        {
            return (handle);
        }
    }

    if (gameIsRunning)
    {
        if (universe.curPlayerPtr->race == R2)
        {
            if (event == A09_Intel_Kushan)
            {
                event = A09_Intel_Taiidan;
            }
            else if (event == A10_Intel_Kushan)
            {
                event = A10_Intel_Taiidan;
            }
            else if (event == A12_Defector_Kushan)
            {
                event = A12_Defector_Taiidan;
            }
        }
    }

    for (i = 0; i < SE_MAX_QUEUE; i++)
    {
        if (pSQueue->queue[i].handle == SOUND_NOTINITED)
        {
            pQEvent = &pSQueue->queue[i];

            /* put it in this space */
            if (linkto < SOUND_OK)
            {
                handle = SNDcreatehandle(channel);
                pQEvent->index = 0;
            }
            else
            {
                handle = linkto;
                for (j = 0; j < SE_MAX_QUEUE; j++)
                {
                    if ((pSQueue->queue[j].handle == linkto) && (pSQueue->queue[j].index == 0))
                    {
                        break;
                    }
                }
                if (j < SE_MAX_QUEUE)
                {
                    pQEvent->index = pSQueue->queue[j].numlinks++;
                }
                else
                {
                    /* the one you wanted to link to is gone */
                    return (SOUND_ERR);
                }
            }

#if SE_VERBOSE_LEVEL >= 2
            dbgMessagef("Speech Event Queued.  Actor: %d, Event: %d\n", actor, event);
#endif
            pQEvent->status = SOUND_OK;
            pQEvent->actornum = actor;

            if (event & SPEECH_GROUP_FLAG)
            {
                pQEvent->pShip = NULL;
            }
            else
            {
                pQEvent->pShip = pShip;
            }
            pQEvent->event = event;
            pQEvent->variable = var;
            pQEvent->variation = variation;
            pQEvent->priority = SentenceLUT->lookup[(actor * SentenceLUT->numevents + (event & SPEECH_EVENT_MASK)) * SentenceLUT->numcolumns];
            pQEvent->volume = volume;

            pQEvent->timein = universe.totaltimeelapsed;
            if (timeout >= 0.0f)
            {
                pQEvent->timeout = universe.totaltimeelapsed + timeout;
            }
            else
            {
                pQEvent->timeout = timeout;
            }

            pQEvent->handle = handle;

            SEreorderqueue(pSQueue);
            break;
        }
    }

    return (handle);
}


/*-----------------------------------------------------------------------------
    Name        :   speechEventStop
    Description :   stops a speech event that is currently playing and deletes any
                    linked speech events from the queue.
    Inputs      :   handle - the handle for this event that was return by speechEventQueue
                    fadetime - the amount of time to fade out this speech event (in seconds)
                    addstatic - add static noise ass the speech fades out
    Outputs     :
    Return      :   SOUND_OK (0) if successful, SOUND_ERR (-1) if not
----------------------------------------------------------------------------*/
sdword speechEventStop(sdword handle, real32 fadetime, bool addstatic)
{
    /* is this event still playing? */
    /* are there any events linked to this one in the queue? */
    return (SOUND_OK);
}


/*-----------------------------------------------------------------------------
    Name        : speechEventActorStop
    Description : Stops all speech events from a particular actor.  Queued
                    events are discarded; playing events faded out.
    Inputs      : actorMask - bit fields for what actors' speech events to
                    delete.  Same as bit fields added to speech events.
                  fadeTime - amount of time to fade out over.
    Outputs     :
    Return      :
----------------------------------------------------------------------------*/
sdword speechEventActorStop(udword actorMask, real32 fadetime)
{
    sdword i;

    dbgAssert((actorMask & (~ACTOR_ALL_ACTORS)) == 0);

    for (i = 0; i < SE_MAX_QUEUE; i++)
    {
        if (speechqueue[0].queue[i].event & actorMask)
        {
            memset(&speechqueue[0].queue[i], 0, sizeof(QUEUEEVENT));
            speechqueue[0].queue[i].handle = SOUND_NOTINITED;
            speechqueue[0].queue[i].timeout = -1.0f;
        }
    }

    if (speechqueue[0].current.event & actorMask)
    {
        soundstreamvolume(speechqueue[0].current.handle, -1, fadetime);
    }

    return (SOUND_OK);
}


sdword speechEventStopAllSpecific(real32 fadetime, sdword speechType)
{
    sdword i, j;

    for (i = 0; i < SE_NUM_ACTORS; i++)
    {
        speechqueue[i].locked = TRUE;
        speechqueue[i].timeover = 0.0f;

        for (j = 0; j < SE_MAX_QUEUE; j++)
        {
            if (speechqueue[i].queue[j].event & speechType)
            {
                memset(&speechqueue[i].queue[j], 0, sizeof(QUEUEEVENT));
                speechqueue[i].queue[j].handle = SOUND_NOTINITED;
                speechqueue[i].queue[j].timeout = -1.0f;
            }
        }

        /* need to stop the currently playing one */
        if (speechqueue[i].current.event & speechType)
        {
            soundstreamvolume(speechqueue[i].current.handle, -1, fadetime);
        }

        speechqueue[i].locked = FALSE;
    }

    return (SOUND_OK);
}


sdword speechEventRemoveShip(Ship *pShip)
{
    sdword i, j;

    if (pShip == lastshiptospeak)
    {
        lastshiptospeak = NULL;
    }

    for (i = 0; i < SE_NUM_ACTORS; i++)
    {
        speechqueue[i].locked = TRUE;

        for (j = 0; j < SE_MAX_QUEUE; j++)
        {
            if (speechqueue[i].queue[j].pShip == pShip)
            {
                memset(&speechqueue[i].queue[j], 0, sizeof(QUEUEEVENT));
                speechqueue[i].queue[j].handle = SOUND_NOTINITED;
                speechqueue[i].queue[j].timeout = -1.0f;
                speechqueue[i].queue[j].pShip = NULL;
            }
        }

        /* need to stop the currently playing one */
        if (speechqueue[i].current.pShip == pShip)
        {
            soundstreamvolume(speechqueue[i].current.handle, -1, 100);
            speechqueue[i].current.pShip = NULL;
        }

        speechqueue[i].locked = FALSE;
    }

    return (SOUND_OK);
}


sdword SEreorderqueue(SPEECHQUEUE *pSQueue)
{
    sdword i;
    sdword highpriority = 0;
    bool bSomethingQueued = FALSE;
    /* need to get the next event */

    SEcleanqueue(pSQueue);

    for (i = 0; i < SE_MAX_QUEUE; i++)
    {
        if (pSQueue->queue[i].handle >= SOUND_OK)
        {
            bSomethingQueued = TRUE;
            if (pSQueue->queue[highpriority].handle < SOUND_OK)
            {
                highpriority = i;
            }
            else if ((pSQueue->queue[i].event & SPEECH_TYPE_MASK) > (pSQueue->queue[highpriority].event & SPEECH_TYPE_MASK))
            {
                highpriority = i;
            }
            else if ((pSQueue->queue[i].event & SPEECH_TYPE_MASK) == (pSQueue->queue[highpriority].event & SPEECH_TYPE_MASK))
            {
                if (pSQueue->queue[i].priority > pSQueue->queue[highpriority].priority)
                {
                    highpriority = i;
                }
                else if (pSQueue->queue[i].priority == pSQueue->queue[highpriority].priority)
                {
                    if (pSQueue->queue[i].timein < pSQueue->queue[highpriority].timein)
                    {
                        highpriority = i;
                    }
                }
            }
            else if (pSQueue->queue[i].priority > pSQueue->queue[highpriority].priority)
            {
                highpriority = i;
            }
            else if (pSQueue->queue[i].priority == pSQueue->queue[highpriority].priority)
            {
                if (pSQueue->queue[i].timein < pSQueue->queue[highpriority].timein)
                {
                    highpriority = i;
                }
            }
        }
    }

    if (bSomethingQueued)
    {
        pSQueue->nextevent = highpriority;
    }
    else
    {
        pSQueue->nextevent = SOUND_NOTINITED;
    }

    return (SOUND_OK);
}


sdword SEcleanqueue(SPEECHQUEUE *pSQueue)
{
    sdword i;

    for (i = 0; i < SE_MAX_QUEUE; i++)
    {
        if (pSQueue->queue[i].timeout >= 0.0f)
        {
            if ((pSQueue->queue[i].timeout < universe.totaltimeelapsed)
                && ((pSQueue->queue[i].event & SPEECH_TYPE_MASK) < SPEECH_TUTORIAL_FLAG))   // don't throw out Animatic, NIS, Single Player
            {                                                                               // or Tutorial speech events.
                /* get ride of this one */
                /* IS IT LINKED? */
                memset(&pSQueue->queue[i], 0, sizeof(QUEUEEVENT));
                pSQueue->queue[i].handle = SOUND_NOTINITED;
                pSQueue->queue[i].timeout = -1.0f;
            }
        }
    }
    return (SOUND_OK);
}


sdword speechEventCleanup(void)
{
    sdword i, j;

    numSinglePlayerEvents = 0;
    for (i = 0; i < SE_NUM_ACTORS; i++)
    {
        for (j = 0; j < SE_MAX_QUEUE; j++)
        {
            memset(&speechqueue[i].queue[j], 0, sizeof(QUEUEEVENT));
            speechqueue[i].queue[j].handle = SOUND_NOTINITED;
            speechqueue[i].queue[j].timeout = -1.0f;
        }

        if (speechqueue[i].current.handle > SOUND_NOTINITED)
        {
            soundstreamvolume(speechqueue[i].current.handle, -1, 0.5f);
        }

        memset(&speechqueue[i].current, 0, sizeof(QUEUEEVENT));
        speechqueue[i].current.handle = SOUND_NOTINITED;
        speechqueue[i].status = SOUND_STOPPED;
        speechqueue[i].nextevent = SOUND_NOTINITED;
        speechqueue[i].timeover = 0.0f;
        speechqueue[i].numqueued = 0;
    }
    return (SOUND_OK);
}

#else

/*-----------------------------------------------------------------------------
    Name        :
    Description :
    Inputs      :
    Outputs     :
    Return      :
----------------------------------------------------------------------------*/
sdword speechEventPlay(void *object, sdword event, sdword var, sdword playernum, sdword variation)
{
#if SPEECH
//    udword i;
    sdword actor = ACTOR_FLEETCOMMAND;
    sword vol = SOUND_VOL_MAX;
    sword pan = SOUND_PAN_CENTER;
    double dist;
    float damageratio = 0.0f;
    float distratio = (float)1.0;
    Ship *ship;
    SpaceObj *spaceobject;
    ShipStaticInfo *shipstatic;
    sdword curstream = SOUND_ERR;
    bool bookend = FALSE;
    ShipClass shipclass = 0;

    if (!enableSpeech)
    {
        return (SOUND_ERR);
    }

    if (event == STAT_Cap_ShipDocked)
    {
        return (SOUND_ERR);
    }

    /* figure out if there is a stream free to be played on */
//    for (i = 0; i < SE_NUM_STREAMS; i++)
//    {
//        if (soundstreamnumqueued(streamhandle[i]) == 0)
//        {
//            curstream = i;
//            break;
//        }
//    }
    if (soundstreamnumqueued(streamhandle[0]) == 0)
    {
        curstream = 0;
    }

    if (curstream == SOUND_ERR)
    {
        if ((event != COMM_Const_BuildCapShip) || (event != STAT_F_Research_CompletedShip))
        {
            return (SOUND_ERR);
        }

        curstream = 0;
    }

    if (object != NULL)
    {
        spaceobject = (SpaceObj *)object;

        if (spaceobject->objtype != OBJ_ShipType)
        {
            return (SOUND_ERR);
        }

        ship = (Ship *)object;
        shipstatic = (ShipStaticInfo *)ship->staticinfo;

        if (universe.curPlayerIndex != ship->playerowner->playerIndex)
        {
            return (SOUND_ERR);
        }

        if (!univSpaceObjInRenderList(spaceobject))
        {
            return (SOUND_ERR);        // only do sound events for objects in current mission sphere we are viewing
        }

//      if (event >= SPEECH_FIRST_SP_EVENT)
        if (event & SPEECH_SINGLEPLAYER_FLAG)
        {
            shipclass = CLASS_Fighter;
        }
        else
        {
            shipclass = ship->staticinfo->shipclass;
        }

        if (shipclass == CLASS_Mothership)
        {
            if (event == COMM_Selection)
            {
                event = COMM_F_MoShip_Selected;
            }
            playernum = universe.curPlayerIndex;
        }
        else
        {
            if ((ship->soundevent.actorNum <= SOUND_NOTINITED) || (ship->soundevent.actorNum >= SentenceLUT->numactors))
            {
                ship->soundevent.actorNum = actor = SEselectactor();
            }
            else
            {
                actor = ship->soundevent.actorNum;
            }

            damageratio = 1.0f - ((real32)ship->health * (real32)shipstatic->oneOverMaxHealth);      // (real32)shipstatic->maxhealth);

            dist = fsqrt(spaceobject->cameraDistanceSquared);

            vol = SOUND_VOL_MAX - (sword)(SPEECH_VOL_FACTOR * dist);
            if (vol < SPEECH_VOL_LOW)
            {
                vol = SPEECH_VOL_LOW;
            }
            else if (vol > SPEECH_VOL_MAX)
            {
                vol = SPEECH_VOL_MAX;
            }

            /* add the digital bleeps */
            bookend = TRUE;
        }
    }

    if (actor == ACTOR_FLEETCOMMAND)
    {
        if ((udword)playernum != universe.curPlayerIndex)
        {
            return (SOUND_ERR);
        }

        if (event == COMM_F_MoShip_Arrived)
        {
            if (MoShipMoving[universe.curPlayerIndex])
            {
                MoShipMoving[universe.curPlayerIndex] = FALSE;
            }
            else
            {
                return (SOUND_ERR);
            }
        }
        vol = SPEECH_VOL_MAX;
    }

    if (SEspeechevent(curstream, actor, event, var, vol, pan, 0.0f, 0.0f, variation, bookend, shipclass) == SOUND_OK)
    {
        /* set the last ship to speak variable so the hot key can be used later */
        if (actor == ACTOR_FLEETCOMMAND)
        {
            /* MAKE LAST SHIP BE THE MOTHERSHIP */
            if (universe.curPlayerPtr != NULL)
            {
                lastshiptospeak = universe.curPlayerPtr->PlayerMothership;
            }
            else
            {
                lastshiptospeak = NULL;
            }
        }
        else
        {
            lastshiptospeak = ship;
        }

    }


    if (event == COMM_Cloak_CloakingOn)
    {
        if (++actor >= SentenceLUT->numactors - 1)
        {
            actor = 1;
        }

        SEspeechevent(curstream, actor, STAT_Cloak_CloakingOn_Resp, var, vol, pan, 0.0f, 0.0f, variation, bookend, shipclass);
    }
    if ((event == STAT_AssGrp_UnderAttack) || (event == STAT_Grp_UnderAttack))
    {
        if (++actor >= SentenceLUT->numactors - 1)
        {
            actor = 1;
        }

        SEspeechevent(curstream, actor, STAT_Grp_UnderAttack_Rsp, var, vol, pan, 0.0f, 0.0f, variation, bookend, shipclass);
    }

    if (event == COMM_F_MoShip_Move)
    {
        MoShipMoving[universe.curPlayerIndex] = TRUE;
    }

    return (streamhandle[curstream]);
#endif
}


sdword SEspeechevent(sdword stream, sdword actor, sdword event, sdword var, sword vol, sword pan, double dist, float damageratio, sdword setVariation, bool bookend, ShipClass shipclass)
{
    udword duration = 0;
    udword variation = 0;
#if SPEECH
    uword numvariations;
    uword maxvar;
    uword index;
    udword numoffsets;
    udword i;
    udword phrase;
    EFFECT *fqeffect = NULL;
    sword priority = 0;
    sword *pSentence;
    sdword *pPhrase;
    STREAMEQ *pEQ = NULL;
    STREAMDELAY *pdelay = NULL;
    void *ppatch = NULL;
    sword level = 0;

// NEW SPEECH
    udword probability;

    pSentence = &SentenceLUT->lookup[(actor * SentenceLUT->numevents + event) * SentenceLUT->numcolumns];

    priority = *pSentence++;
    numvariations = *pSentence++;

#if 0
    for (i = 0; i < SentenceLUT->prioritylevels; i++)
    {
        pSentence = &SentenceLUT->lookup[((actor * SentenceLUT->numevents + event) * SentenceLUT->prioritylevels + i) * 3];
        if (*pSentence > 0)
        {
            numvariations = *pSentence++;
            break;
        }
    }
#endif
    if (numvariations == 0)
    {
        return (SOUND_ERR);
    }

    if (var < SOUND_OK)
    {
        var = 0;
//      return (SOUND_ERR);
    }

    maxvar = *pSentence++;
    index = *pSentence;

    if ((setVariation != SOUND_NOTINITED) && (setVariation < numvariations))
    {
        variation = setVariation;
    }
    else
    {
        variation = ranRandom(RAN_SoundGameThread) % numvariations;
    }

    if (event == COMM_F_Hyper_Engage)
    {
        variation = 1;
    }
    else if (event == STAT_Grav_Collapse)
    {
        variation = 1;
    }

    phrase = index + (variation * maxvar) + var;

    pPhrase = &PhraseLUT->lookup[phrase * PhraseLUT->numcolumns];

    duration = *pPhrase++;
// NEW SPEECH
    probability = *pPhrase++;
    numoffsets = *pPhrase++;

    if (numoffsets > 0)
    {
        for (i = 0; i < numoffsets; i++)
        {
            if (*(pPhrase + i) == SOUND_NOTINITED)
            {
                // bad, this won't work
                numoffsets = 0;
                break;
            }
        }
    }

    /* set up the effect structure */
//    fqInitE(&fqeffect);

    if (actor != ACTOR_FLEETCOMMAND)
    {
        pEQ = &streamEQ[SPEECH_PILOT1];

        if ((event >= CHAT_Const_DuringBattle) && (event <= CHAT_Const_More))
        {
            pdelay = &streamdelay[SPEECH_CONSTRUCTION];
        }

        if ((shipclass == CLASS_Corvette) || (shipclass == CLASS_Fighter))
        {
            /* filter the fighters */
            pdelay = &streamdelay[SPEECH_STRIKECRAFT];
            fqeffect = &cleaneffect[SPEECH_STRIKECRAFT];
//          vol = (sword)(vol * 1.5f);
//          if (vol > SOUND_VOL_MAX)
//          {
//              vol = SOUND_VOL_MAX;
//          }
        }
        if ((shipclass >= CLASS_HeavyCruiser) && (shipclass <= CLASS_Frigate))
        {
            ppatch = SNDgetpatch(SpecialEffectBank, SpecEffectEventsLUT->lookup[GetPatch(SpecEffectEventsLUT, 0, Spec_IntCapShipCalm)]);
            level = 128;
        }
        if (shipclass == CLASS_Resource)
        {
            ppatch = SNDgetpatch(ShipBank, ShipCmnEventsLUT->lookup[GetPatch(ShipCmnEventsLUT, ResourceCollector, ShipCmn_Ambient)]);
            level = 128;
        }
#if 0
        /* adjust frequency effects here */
        fqeffect.nFiltMinFreq = SPEECH_FILTER_LOW;
        fqeffect.nFiltMaxFreq = SPEECH_FILTER_HIGH;

        fqeffect.fNoiseLev = SPEECH_NOISE_LOW + ((float)dist * SPEECH_NOISE_FACTOR);    /// (float)150.0;
        if (fqeffect.fNoiseLev > SPEECH_NOISE_HIGH)
        {
            fqeffect.fNoiseLev = SPEECH_NOISE_HIGH;
        }
        fqeffect.fNoiseLev *= FQ_FNOISE;

        if (damageratio > SPEECH_BREAK_THRESHOLD)
        {
            fqeffect.nBreakMaxRate = SPEECH_BREAK_RATE_LOW + (udword)(SPEECH_BREAK_RATE_FACTOR * damageratio);
            if (fqeffect.nBreakMaxRate > SPEECH_BREAK_RATE_HIGH)
            {
                fqeffect.nBreakMaxRate = SPEECH_BREAK_RATE_HIGH;
            }
            fqeffect.nBreakMaxDur = SPEECH_BREAK_LENGTH_LOW + (udword)(SPEECH_BREAK_LENGTH_FACTOR * damageratio);
            if (fqeffect.nBreakMaxDur < SPEECH_BREAK_LENGTH_HIGH)
            {
                fqeffect.nBreakMaxDur = SPEECH_BREAK_LENGTH_HIGH;
            }
        }
#endif
    }
    else
    {
        pEQ = &streamEQ[SPEECH_FLEET];
    }

    if (numoffsets > 0)
    {
        if (bookend && enableSFX)
        {
            soundstreamqueue(streamhandle[stream], (sdword)SpecialEffectBank,
                             (sdword)SpecEffectEventsLUT->lookup[GetPatch(SpecEffectEventsLUT, 0, Spec_RadioBeepStart)],
                             SOUND_FLAGS_QUEUEPATCH, vol, pan, SOUND_MONO, SOUND_DEFAULT, NULL, NULL, NULL, pSQueue->current.event);

//          soundstreamqueueSilence(streamhandle[stream], SOUND_FLAGS_QUEUESILENCE, vol, pan, SOUND_MONO,
//                                  SentenceLUT->compbitrate, fqeffect, pEQ, pdelay, SEsilence());
        }

        for (i = 0; i < numoffsets; i++)
        {
//            soundstreamqueue(streamhandle[stream], speechfilehandle, *pPhrase++, 0, vol, pan, SOUND_MONO, SentenceLUT->compbitrate, &fqeffect, pEQ, pdelay);
//            soundstreamqueue(streamhandle[stream], speechfilehandle, *pPhrase++, SOUND_FLAGS_QUEUESTREAM, vol, pan, SOUND_MONO, SentenceLUT->compbitrate, fqeffect, pEQ, pdelay);
            soundstreamqueuePatch(streamhandle[stream], speechfilehandle, *pPhrase++, SOUND_FLAGS_QUEUESTREAM, vol, pan,
                                  SOUND_MONO, SentenceLUT->compbitrate, fqeffect, pEQ, pdelay, ppatch, level, 0.0f, actor);
        }

        if (bookend && enableSFX)
        {
//          soundstreamqueueSilence(streamhandle[stream], SOUND_FLAGS_QUEUESILENCE, vol, pan, SOUND_MONO,
//                                  SentenceLUT->compbitrate, fqeffect, pEQ, pdelay, SEsilence());

            soundstreamqueue(streamhandle[stream], (sdword)SpecialEffectBank,
                             (sdword)SpecEffectEventsLUT->lookup[GetPatch(SpecEffectEventsLUT, 0, Spec_RadioBeepEnd)],
                             SOUND_FLAGS_QUEUEPATCH, vol, pan, SOUND_MONO, SOUND_DEFAULT, NULL, NULL, NULL, pSQueue->current.event);
        }
    }
#endif

//    dbgMessagef("\nspeechEvent: %d, variable: %d, variation: %d, duration %d msec, text: %s",
//                event, var, variation, duration, aSpeechEventDebug[event]);

    return (SOUND_OK);
}

#endif

real32 SEsilence(void)
{
    return (0.5f);
}


sdword SEselectactor(void)
{
    sdword actor;

#if defined(Downloadable) || defined(DLPublicBeta)
    actor = (ranRandom(RAN_SoundGameThread) % 2) + 1;
#else
    actor = (ranRandom(RAN_SoundGameThread) % 3) + 1;
#endif

    if (!bActorOn[actor])
    {
        actor++;

#if defined(Downloadable) || defined(DLPublicBeta)
            if (actor > 2)
#else
            if (actor > 3)
#endif
        {
            actor = 1;
        }

        if (!bActorOn[actor])
        {
            actor++;

#if defined(Downloadable) || defined(DLPublicBeta)
            if (actor > 2)
#else
            if (actor > 3)
#endif
            {
                actor = 1;
            }

            if (!bActorOn[actor])
            {
                actor = SOUND_NOTINITED;
            }
        }
    }

    return (actor);
}


/*-----------------------------------------------------------------------------
    Name        :
    Description :
    Inputs      :
    Outputs     :
    Return      :
----------------------------------------------------------------------------*/
bool speechEventAttack(void)
{
    sdword objtype,
            i;
    Ship *ship;
    sdword  numFriendlies = 0,
            numEnemies = 0,
            resourceTypes = 0,
            numResources = 0;
    static bool lastreturn;

    if (keyIsHit(SHIFTKEY))
    {
        for (i = 0; i < selSelecting.numTargets; i++)
        {
            objtype = selSelecting.TargetPtr[i]->objtype;
            if (objtype == OBJ_ShipType)
            {
                ship = (Ship *)selSelecting.TargetPtr[i];
                if (ship->playerowner == universe.curPlayerPtr)
                {
                    numFriendlies++;
                }
                else
                {
                    numEnemies++;
                }
            }
            else if ((objtype >= OBJ_AsteroidType) && (objtype <= OBJ_DustType))
            {
                resourceTypes |= (1 << objtype);
                numResources++;
            }
        }

        // are there any friendlies
        if (numFriendlies > 0)
        {
            // do we attack?
            if (((ranRandom(RAN_SoundGameThread) % 100) <= SPEECH_DISOBEY_FORCEDATTACK) && (lastreturn))
            {
                speechEvent(selSelected.ShipPtr[0], COMM_AttackPlayerUnits_Cond, 0);
                lastreturn = FALSE;
                return (lastreturn);
            }
            speechEvent(selSelected.ShipPtr[0], COMM_AttackPlayerUnits, 0);
        }
        else if (numEnemies > 0)
        {
            //else its only enemies
            speechEvent(selSelected.ShipPtr[0], COMM_Attack, 0);
        }
        // are there any resources
        else if (numResources > 0)
        {
            if (resourceTypes == (1 << OBJ_AsteroidType))
            {
                speechEvent(selSelected.ShipPtr[0], COMM_AttackResources, Asteroids);
            }
            else if (resourceTypes == (1 << OBJ_NebulaType))
            {
                speechEvent(selSelected.ShipPtr[0], COMM_AttackResources, Nebulae);
            }
            else if (resourceTypes == (1 << OBJ_GasType))
            {
                speechEvent(selSelected.ShipPtr[0], COMM_AttackResources, GasClouds);
            }
            else if (resourceTypes == (1 << OBJ_DustType))
            {
                speechEvent(selSelected.ShipPtr[0], COMM_AttackResources, DustClouds);
            }
            else
            {
                speechEvent(selSelected.ShipPtr[0], COMM_AttackResourcesGeneric, 0);
            }
        }
    }
    else
    {
        speechEvent(selSelected.ShipPtr[0], COMM_Attack, 0);
    }

    lastreturn = TRUE;
    return (lastreturn);
}


/*-----------------------------------------------------------------------------
    Name        :
    Description :
    Inputs      :
    Outputs     :
    Return      :
----------------------------------------------------------------------------*/
void speechEventUnderAttack(Ship *target)
{
    if (target->playerowner == universe.curPlayerPtr)
    {
        if (target->shiptype == Mothership)
        {
            /* need to add some local warnings for MoShip */
            if (universe.totaltimeelapsed > (universe.curPlayerPtr->timeMoShipAttacked + SPEECH_MOSHIP_WARNING_TIME))
            {
                speechEventFleetSpec(target, STAT_F_MoShip_UnderAttack, 0, target->playerowner->playerIndex);
                universe.curPlayerPtr->timeMoShipAttacked = universe.totaltimeelapsed;
            }
        }
        else if (universe.totaltimeelapsed > (target->soundevent.timeLastStatus + SPEECH_WARNING_TIME))
        {
            if (target->shiptype == ResourceCollector)
            {
                speechEventFleetSpec(target, STAT_F_ResCol_Damaged, 0, target->playerowner->playerIndex);
                target->soundevent.timeLastStatus = universe.totaltimeelapsed;
            }
            else if (univSpaceObjInRenderList((SpaceObj *)target))
            {
                /* some local warnings */
                if (target->shiptype == Carrier)
                {
                    speechEvent(target, STAT_Carrier_Damaged, 0);
                    target->soundevent.timeLastStatus = universe.totaltimeelapsed;
                }
                else if (target->shiptype == HeavyCruiser)
                {
                    if ((target->health / target->staticinfo->maxhealth) < SEL_ShipHealthYellowFactor)
                    {
                        speechEvent(target, STAT_Cruiser_Damaged, 0);
                        target->soundevent.timeLastStatus = universe.totaltimeelapsed;
                    }
                }
                else if (target->shiptype == ResourceController)
                {
                    speechEvent(target, STAT_ResCol_Damaged, 0);
                    target->soundevent.timeLastStatus = universe.totaltimeelapsed;
                }
                else if (selAnyHotKeyTest(target) && (universe.totaltimeelapsed > (selHotKeyGroup[selHotKeyGroupNumberTest(target)].timeLastStatus + SPEECH_WARNING_TIME)))
                {
                    speechEvent(target, STAT_AssGrp_UnderAttack, selHotKeyGroupNumberTest(target));
                    selHotKeyGroup[selHotKeyGroupNumberTest(target)].timeLastStatus = universe.totaltimeelapsed;
                }
            }
            else
            {
                /* global warnings */
                if ((target->shiptype == Carrier) || (target->shiptype == HeavyCruiser))
                {
                    speechEventFleetSpec(target, STAT_F_SuperCap_Damaged, (target->shiptype == Carrier)?0:1, target->playerowner->playerIndex);
                    target->soundevent.timeLastStatus = universe.totaltimeelapsed;
                }
                else if (selAnyHotKeyTest(target) && (universe.totaltimeelapsed > (selHotKeyGroup[selHotKeyGroupNumberTest(target)].timeLastStatus + SPEECH_WARNING_TIME)))
                {
                    speechEventFleet(STAT_F_AssGrp_UnderAttack, selHotKeyGroupNumberTest(target), target->playerowner->playerIndex);
                    selHotKeyGroup[selHotKeyGroupNumberTest(target)].timeLastStatus = universe.totaltimeelapsed;
                }
            }
        }
    }
}


sdword musictranslatetracknum(sdword tracknum)
{
    sdword track;
#ifdef OEM
    switch (tracknum)
    {
        case AMB_Mission1:
            track = 0;
            break;
        case AMB_Mission2:
            track = 1;
            break;
        case AMB_Mission3:
            track = 2;
            break;
        case AMB_Mission5:
            track = 3;
            break;
        case AMB13_Tutorial:
            track = 4;
            break;
        case NIS01_R1:
            track = 5;
            break;
        case NIS01_R2:
            track = 6;
            break;
        case NIS02:
            track = 7;
            break;
        case NIS03:
            track = 8;
            break;
        case NIS04:
            track = 9;
            break;
        case ANIM00_Sierra:
            track = 10;
            break;
        case ANIM00_Relic:
            track = 11;
            break;
        case ANIM00_Opening:
            track = 12;
            break;
        case ANIM01_02:
            track = 13;
            break;
        case ANIM02_03:
            track = 14;
            break;
        case ANIM03_04:
            track = 15;
            break;
        case B01_TuranicRaidersLong:
            track = 16;
            break;
        case B02_TuranicRaidersShort:
            track = 17;
            break;
        case B04_EvilEmpire:
            track = 18;
            break;
        case RollCredits:
            track = 19;
            break;
        default:
            if ((tracknum >= MUS_FIRST_AMBIENT) && (tracknum <= MUS_LAST_BATTLE))
            {
                track = 0;
            }
            else
            {
                track = -1;
            }
            break;
    }
#elif defined(CGW)
    switch (tracknum)
    {
        case AMB_Mission1:
            track = 0;
            break;
        case AMB_Mission2:
            track = 1;
            break;
        case AMB12_FrontEnd:
            track = 2;
            break;
        case AMB13_Tutorial:
            track = 3;
            break;
        case NIS01_R1:
            track = 4;
            break;
        case NIS01_R2:
            track = 5;
            break;
        case NIS02:
            track = 6;
            break;
        case ANIM00_Sierra:
            track = 7;
            break;
        case ANIM00_Relic:
            track = 8;
            break;
        case ANIM00_Opening:
            track = 9;
            break;
        case ANIM01_02:
            track = 10;
            break;
        case ANIM02_03:
            track = 11;
            break;
        default:
            if ((tracknum >= MUS_FIRST_AMBIENT) && (tracknum <= MUS_LAST_BATTLE))
            {
                track = 0;
            }
            else
            {
                track = -1;
            }
            break;
    }
#elif defined(Downloadable)
    if ((tracknum >= MUS_FIRST_AMBIENT) && (tracknum <= MUS_LAST_BATTLE))
    {
        track = 0;
    }
    else if (tracknum == NIS01_R1)
    {
        track = 1;
    }
    else if (tracknum == NIS02)
    {
        track = 2;
    }
    else if (tracknum == ANIM01_02)
    {
        track = 3;
    }
    else
    {
        track = -1;
    }
#elif defined(DLPublicBeta)
    if ((tracknum >= MUS_FIRST_AMBIENT) && (tracknum <= MUS_LAST_BATTLE))
    {
        track = 0;
    }
    else
    {
        track = -1;
    }
#else
    track = tracknum;
#endif
    return (track);
}


void MEstarttrack(MUSICINFO *pinfo, MUSICSTREAM *pstream)
{
    if (pinfo->tracknum == RollCredits)
    {
        pinfo->targetvol = (sword)(pstream->volume * volNIS);
    }
    else
    {
        pinfo->targetvol = (sword)(pstream->volume * volMusic);
    }

    soundstreamqueue(pinfo->handle, musicfilehandle,
                    pstream->offset,
                    pstream->flags | SOUND_FLAGS_QUEUESTREAM,
                    pinfo->targetvol,
                    SOUND_PAN_CENTER,
                    (sword)pstream->numchannels,
                    (sword)pstream->bitrate,
                    NULL, NULL, NULL, -1);

    pinfo->status = SOUND_STARTING;
}


/*-----------------------------------------------------------------------------
    Name        :
    Description :
    Inputs      :
    Outputs     :
    Return      :
----------------------------------------------------------------------------*/
sdword musicEventPlay(sdword tracknum)
{
    MUSICINFO *pinfo;
    MUSICSTREAM *pmstream = &musicheader->mstreams[musictranslatetracknum(tracknum)];

    if (enableSpeech)
    {
        if ((tracknum <= MUS_LAST_LEVELTUNE) && (tracknum >= MUS_FIRST_AMBIENT))
        {
            levelTrack = tracknum;
        }

        if ((tracknum >= MUS_FIRST_AMBIENT) && (tracknum <= MUS_LAST_BATTLE))
        {
            pinfo = &musicinfo[AMBIENTSTREAM];

            // this is an ambient for the level or a battle track
            if (!soundstreamover(pinfo->handle))
            {
                // stop this track and start the next one
                pinfo->nexttrack = tracknum;
                pinfo->status = SOUND_RESTART;
                pinfo->targetvol = SOUND_VOL_AUTOSTOP;
                soundstreamvolume(pinfo->handle, pinfo->targetvol, MUSIC_FADE_SLOW);

                return (SOUND_OK);
            }

            pinfo->tracknum = tracknum;

            if (musicinfo[NISSTREAM].status > SOUND_FREE)
            {
                // if the NIS is still playing then wait till its shutting off to start this
                return (SOUND_OK);
            }

            MEstarttrack(pinfo, pmstream);

            return (SOUND_OK);
        }
        else if ((tracknum >= MUS_FIRST_NIS) && (tracknum <= MUS_LAST_ANIMATIC))
        {
            pinfo = &musicinfo[NISSTREAM];

            // this is an NIS or Animatic track
            if (!soundstreamover(pinfo->handle))
            {
                pinfo->nexttrack = tracknum;
                pinfo->status = SOUND_RESTART;
                pinfo->targetvol = SOUND_VOL_AUTOSTOP;
                soundstreamvolume(pinfo->handle, pinfo->targetvol, MUSIC_FADE_FAST);

                return (SOUND_OK);
            }

            if ((musicinfo[AMBIENTSTREAM].status == SOUND_PLAYING) || (musicinfo[AMBIENTSTREAM].status == SOUND_STARTING))
            {
                // shut off the ambient if its playing
                musicinfo[AMBIENTSTREAM].status = SOUND_STOPPING;
                soundstreamvolume(musicinfo[AMBIENTSTREAM].handle, SOUND_VOL_AUTOSTOP, MUSIC_FADE_FAST);
            }

            pinfo->tracknum = tracknum;

            MEstarttrack(pinfo, pmstream);

            if (pinfo->tracknum != RollCredits)
            {
                musicinfo[AMBIENTSTREAM].tracknum = SOUND_NOTINITED;
            }

            return (SOUND_OK);
        }
    }

    return (SOUND_ERR);
}


/*-----------------------------------------------------------------------------
    Name        :
    Description :
    Inputs      :
    Outputs     :
    Return      :
----------------------------------------------------------------------------*/
sdword musicEventStop(sdword tracknum, real32 fadetime)
{
    if (enableSpeech)
    {
        if (tracknum == SOUND_DEFAULT)
        {
            if ((musicinfo[AMBIENTSTREAM].status == SOUND_PLAYING) || (musicinfo[AMBIENTSTREAM].status == SOUND_STARTING))
            {
                tracknum = musicinfo[AMBIENTSTREAM].tracknum;
            }
            else if ((musicinfo[NISSTREAM].status == SOUND_PLAYING) || (musicinfo[NISSTREAM].status == SOUND_STARTING))
            {
                tracknum = musicinfo[NISSTREAM].tracknum;
            }
        }
        if ((tracknum >= MUS_FIRST_NIS) && (tracknum <= MUS_LAST_ANIMATIC))
        {
            /* this is an NIS */
            if (!soundstreamover(musicinfo[NISSTREAM].handle))
            {
                musicinfo[NISSTREAM].targetvol = SOUND_VOL_AUTOSTOP;
                soundstreamvolume(musicinfo[NISSTREAM].handle, musicinfo[NISSTREAM].targetvol, fadetime);
            }
            musicinfo[NISSTREAM].status = SOUND_STOPPING;

            speechEventStopAllSpecific(1.0f, SPEECH_NIS_FLAG);
        }
        else if ((tracknum >= MUS_FIRST_AMBIENT) && (tracknum <= MUS_LAST_BATTLE))
        {
            /* this is the AMBIENT track */
            if (tracknum != musicinfo[AMBIENTSTREAM].tracknum)
            {
                return (SOUND_ERR);
            }

            if ((musicinfo[AMBIENTSTREAM].status == SOUND_PLAYING) || (musicinfo[AMBIENTSTREAM].status == SOUND_STARTING))
            {
                musicinfo[AMBIENTSTREAM].targetvol = SOUND_VOL_AUTOSTOP;
                soundstreamvolume(musicinfo[AMBIENTSTREAM].handle, musicinfo[AMBIENTSTREAM].targetvol, fadetime);
                musicinfo[AMBIENTSTREAM].status = SOUND_STOPPING;
                musicinfo[AMBIENTSTREAM].tracknum = SOUND_ERR;
                if (tracknum > MUS_LAST_LEVELTUNE)
                {
                    musicEventPlay(levelTrack);
                }
            }
        }
    }

    return (SOUND_OK);
}


/*-----------------------------------------------------------------------------
    Name        :
    Description :
    Inputs      :
    Outputs     :
    Return      :
----------------------------------------------------------------------------*/
sdword musicEventUpdateVolume(void)
{
    sword volume;
    MUSICINFO *pinfo;
    MUSICSTREAM *pmstream;

    if (enableSpeech)
    {
        /* Check the AMBIENT */
        pinfo = &musicinfo[AMBIENTSTREAM];
        pmstream = &musicheader->mstreams[musictranslatetracknum(pinfo->tracknum)];

        if (pinfo->status == SOUND_STARTING)
        {
            if (soundstreamgetvol(pinfo->handle) == pinfo->targetvol)
            {
                pinfo->status = SOUND_PLAYING;
            }
            else if ((!soundstreamfading(pinfo->handle)) && (!soundstreamover(pinfo->handle)))
            {
                soundstreamvolume(pinfo->handle, pinfo->targetvol, MUSIC_FADE_FAST);
            }
        }
        else if (pinfo->status == SOUND_PLAYING)
        {
            if (!soundstreamover(pinfo->handle))
            {
                if (mrRenderMainScreen)
                {
                    if (pinfo->tracknum == AMB13_Tutorial)
                    {
                        /* we're in the tutorial, play the music at a constant volume */
                        volume = (sword)(pmstream->volume * MUSIC_TUTORIAL_VOL * volMusic);
                    }
                    else
                    {
                        /* we're in the game, volume depends on the distance from the nearst ship */
                        volume = (sword)(pmstream->volume * volMusic);

                        if (nearestShipDistance <= MUSIC_DISTANCE_SILENT)
                        {
                            if (pinfo->tracknum < MUS_FIRST_BATTLE)
                            {
                                if (nearestShipMoving)
                                {
                                    volume = (sword)((real32)volume * MUSIC_MINACTIVE_VOL);
                                }
                                else
                                {
                                    volume = (sword)((real32)volume * MUSIC_MININACTIVE_VOL);
                                }
                            }
                            else
                            {
                                volume = (sword)((real32)volume * MUSIC_MINBATTLE_VOL);
                            }
                        }
                        else if (nearestShipDistance <= MUSIC_DISTANCE_MAX)
                        {
                            if (pinfo->tracknum < MUS_FIRST_BATTLE)
                            {
                                if (nearestShipMoving)
                                {
                                    volume = (sword)((real32)volume * (((nearestShipDistance - MUSIC_DISTANCE_SILENT) * musicVolFactor) + MUSIC_MINACTIVE_VOL));
                                }
                                else
                                {
                                    volume = (sword)((real32)volume * (((nearestShipDistance - MUSIC_DISTANCE_SILENT) * musicInactiveFactor) + MUSIC_MININACTIVE_VOL));
                                }
                            }
                            else
                            {
                                volume = (sword)((real32)volume * (((nearestShipDistance - MUSIC_DISTANCE_SILENT) * battleMusicVolFactor) + MUSIC_MINBATTLE_VOL));
                            }
                        }
                        else
                        {
                            if (pinfo->tracknum < MUS_FIRST_BATTLE)
                            {
                                volume = (sword)((real32)volume * MUSIC_MAXGAME_VOL);
                            }
                            else
                            {
                                volume = (sword)((real32)volume * MUSIC_MAXBATTLE_VOL);
                            }
                        }


                        if (volume > pmstream->volume)
                        {
                            volume = pmstream->volume;
                        }
                        else if (volume < 0)
                        {
                            volume = 0;
                        }
                    }
                }
                else
                {
                    if (smSensorsActive)
                    {
                        /* we're in the sensors manager, play the music at the volume defined in the script */
                        if (pinfo->tracknum < MUS_FIRST_BATTLE)
                        {
                            volume = (sword)(pmstream->volume * MUSIC_SENSORS_VOL * volMusic);
                        }
                        else
                        {
                            volume = (sword)(pmstream->volume * MUSIC_MINBATTLE_VOL * volMusic);
                        }
                    }
                    else
                    {
                        /* we're in one of the other manager screens */
                        if (pinfo->tracknum < MUS_FIRST_BATTLE)
                        {
                            volume = (sword)(pmstream->volume * MUSIC_MANAGERS_VOL * volMusic);
                        }
                        else
                        {
                            volume = (sword)(pmstream->volume * MUSIC_MINBATTLE_VOL * volMusic);
                        }
                    }
                }

                if (!bVolMusicNoFade)
                {
                    soundstreamvolume(pinfo->handle, volume, MUSIC_FADE_TIME);
                }
                else
                {
                    soundstreamvolume(pinfo->handle, volume, 0.0f);
                    bVolMusicNoFade = FALSE;
                }
            }
            else
            {
                pinfo->status = SOUND_FREE;
                musicEventPlay(pinfo->tracknum);
            }
        }
        else if (pinfo->status == SOUND_STOPPING)
        {
            if (soundstreamover(pinfo->handle))
            {
                pinfo->status = SOUND_FREE;
            }
            else if ((pinfo->targetvol != SOUND_VOL_AUTOSTOP) ||
                (!soundstreamfading(pinfo->handle) &&
                (soundstreamgetvol(pinfo->handle) > SOUND_VOL_MIN)))
            {
                pinfo->targetvol = SOUND_VOL_AUTOSTOP;
                soundstreamvolume(pinfo->handle, pinfo->targetvol, SOUND_FADE_STOPTIME);
            }
        }
        else if (pinfo->status == SOUND_RESTART)
        {
            if (soundstreamover(pinfo->handle))
            {
                pinfo->tracknum = pinfo->nexttrack;
                pinfo->nexttrack = SOUND_NOTINITED;
                pmstream = &musicheader->mstreams[musictranslatetracknum(pinfo->tracknum)];

                MEstarttrack(pinfo, pmstream);
            }
            else if ((pinfo->targetvol != SOUND_VOL_AUTOSTOP) ||
                (!soundstreamfading(pinfo->handle) &&
                (soundstreamgetvol(pinfo->handle) > SOUND_VOL_MIN)))
            {
                pinfo->targetvol = SOUND_VOL_AUTOSTOP;
                soundstreamvolume(pinfo->handle, pinfo->targetvol, SOUND_FADE_STOPTIME);
            }
        }

        /* Check the NIS track */
        pinfo = &musicinfo[NISSTREAM];

        if (pinfo->status == SOUND_PLAYING)
        {
            if (soundstreamover(pinfo->handle) && !nisIsRunning)
            {
                pinfo->status = SOUND_STOPPING;
            }
            else if (pinfo->targetvol == SOUND_VOL_AUTOSTOP)
            {
                soundstreamvolume(pinfo->handle, pinfo->targetvol, SOUND_FADE_STOPTIME);
                pinfo->status = SOUND_STOPPING;
            }
        }
        else if (pinfo->status == SOUND_STARTING)
        {
            if (!soundstreamover(pinfo->handle))
                pinfo->status = SOUND_PLAYING;
        }
        else if (pinfo->status == SOUND_STOPPING)
        {
            if (soundstreamover(pinfo->handle))
            {
                pinfo->status = SOUND_FREE;
                if (pinfo->tracknum <= ANIM00_Relic)
                {
                    musicEventPlay(levelTrack);
                }
            }
            else if ((pinfo->targetvol != SOUND_VOL_AUTOSTOP) ||
                (!soundstreamfading(pinfo->handle) &&
                (soundstreamgetvol(pinfo->handle) > SOUND_VOL_MIN)))
            {
                pinfo->targetvol = SOUND_VOL_AUTOSTOP;
                soundstreamvolume(pinfo->handle, pinfo->targetvol, SOUND_FADE_STOPTIME);
            }
        }
        else if (pinfo->status == SOUND_RESTART)
        {
            if (soundstreamover(pinfo->handle))
            {
                pinfo->tracknum = pinfo->nexttrack;
                pinfo->nexttrack = SOUND_NOTINITED;
                pmstream = &musicheader->mstreams[musictranslatetracknum(pinfo->tracknum)];

                MEstarttrack(pinfo, pmstream);
            }
            else if ((pinfo->targetvol != SOUND_VOL_AUTOSTOP) ||
                (!soundstreamfading(pinfo->handle) &&
                (soundstreamgetvol(pinfo->handle) > SOUND_VOL_MIN)))
            {
                pinfo->targetvol = SOUND_VOL_AUTOSTOP;
                soundstreamvolume(pinfo->handle, pinfo->targetvol, SOUND_FADE_STOPTIME);
            }
        }
    }

    return (SOUND_OK);
}


#if defined(CGW) || defined(Downloadable) || defined(DLPublicBeta) || defined(OEM)

void musicEventNextTrack(void)
{
    return;
}

void musicEventPrevTrack(void)
{
    return;
}
#else

void musicEventNextTrack(void)
{
    MUSICINFO *pinfo;

    if (!singlePlayerGame && enableSpeech)
    {
        pinfo = &musicinfo[AMBIENTSTREAM];

        pinfo->nexttrack = pinfo->tracknum + 1;
        if (pinfo->nexttrack > MUS_LAST_BATTLE)
        {
            pinfo->nexttrack = MUS_FIRST_AMBIENT;
        }

#if SE_VERBOSE_LEVEL >= 2
        dbgMessagef("Starting track # %d\n", pinfo->nexttrack);
#endif
        pinfo->status = SOUND_RESTART;
        soundstreamvolume(pinfo->handle, SOUND_VOL_AUTOSTOP, MUSIC_FADE_FAST);
   }
}


void musicEventPrevTrack(void)
{
    MUSICINFO *pinfo;

    if (!singlePlayerGame && enableSpeech)
    {
        pinfo = &musicinfo[AMBIENTSTREAM];

        pinfo->nexttrack = pinfo->tracknum - 1;
        if (pinfo->nexttrack < MUS_FIRST_AMBIENT)
        {
            pinfo->nexttrack = MUS_LAST_BATTLE;
        }

#if SE_VERBOSE_LEVEL >= 2
        dbgMessagef("Starting track # %d\n", pinfo->nexttrack);
#endif
        pinfo->status = SOUND_RESTART;
        soundstreamvolume(pinfo->handle, SOUND_VOL_AUTOSTOP, MUSIC_FADE_FAST);
   }
}
#endif

sdword musicEventCurrentTrack(void)
{
    return (musicinfo[AMBIENTSTREAM].tracknum);
}

