//-------------------------------------------------------------------------
/*
Copyright (C) 1997, 2005 - 3D Realms Entertainment

This file is part of Shadow Warrior version 1.2

Shadow Warrior is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.

Original Source: 1997 - Frank Maddin and Jim Norwood
Prepared for public release: 03/28/2005 - Charlie Wiederhold, 3D Realms
*/
//-------------------------------------------------------------------------
#include "compat.h"
#include "build.h"
#include "cache1d.h"

#include "keys.h"

#include "names2.h"
#include "mytypes.h"
#include "fx_man.h"
#include "music.h"
#include "al_midi.h"
#include "gamedefs.h"
#include "config.h"


#include "panel.h"
#include "game.h"
#include "sounds.h"
#include "ai.h"
#include "network.h"

#include "cache.h"
#include "text.h"
#include "rts.h"
#include "menus.h"
#include "config.h"

#ifdef _WIN32
#include "winlayer.h"
#endif

extern USERp User[MAXSPRITES];
void DumpSounds(void);

// Parentally locked sounds list
int PLocked_Sounds[] =
{
    483,328,334,335,338,478,450,454,452,453,456,457,458,455,460,462,
    461,464,466,465,467,463,342,371,254,347,350,432,488,489,490,76,339,
    499,500,506,479,480,481,482,78,600,467,548,547,544,546,545,542,541,540,
    539,536,529,525,522,521,515,516,612,611,589,625,570,569,567,565,
    558,557
};

uint8_t RedBookSong[40] =
{
    2,4,9,12,10, // Title and ShareWare levels
    5,6,8,11,12,5,10,4,6,9,7,10,8,7,9,10,11,5, // Registered levels
    11,8,7,13,5,6,  // Deathmatch levels
    13 // Fight boss
};

// Global vars used by ambient sounds to set spritenum of ambient sounds for later lookups in
// the sprite array so FAFcansee can know the sound sprite's current sector location
SWBOOL Use_SoundSpriteNum = FALSE;
int16_t SoundSpriteNum = -1;  // Always set this back to -1 for proper validity checking!

SWBOOL MusicInitialized = FALSE;
SWBOOL FxInitialized = FALSE;

void SoundCallBack(unsigned int num);
SWBOOL LoadSong(const char *track);

#define MUSIC_ID -65536

#define NUM_SAMPLES 10

const char *BitNames[2] =
{
    "8-bit", "16-bit"
};

const char *ChannelNames[2] =
{
    "Mono", "Stereo"
};

const char *VoiceNames[8] =
{
    "1", "2", "3", "4", "5", "6", "7", "8"
};

int music;
int soundfx;
int num_voices;

int NumSounds = 0;

int angle;
int distance;
int voice;

int loopflag;

typedef enum
{
    SongTypeNone,
    SongTypeMIDI,
    SongTypeWave,
} SongType_t;

char *SongPtr = NULL;
int SongLength = 0;
char *SongName = NULL;
int8_t SongTrack = 0;
SongType_t SongType = SongTypeNone;
int SongVoice = -1;
extern SWBOOL DemoMode;

//
// Includes digi.h to build the table
//

#define DIGI_TABLE
VOC_INFO voc[] =
{
#include "digi.h"
};

#undef  DIGI_TABLE

//
// Includes ambient.h to build the table of ambient sounds for game
//

#define AMBIENT_TABLE
AMB_INFO ambarray[] =
{
#include "ambient.h"
};
#undef  AMBIENT_TABLE
#define MAX_AMBIENT_SOUNDS 82

#define MAXSONGS        10              // This is the max songs per episode

SWBOOL OpenSound(VOC_INFOp vp, int *handle, int *length);
int ReadSound(int handle, VOC_INFOp vp, int length);

// 3d sound engine function prototype
VOC3D_INFOp Insert3DSound(void);

#if 0
// DEBUG
char *globsndata[DIGI_MAX], *globvpdata[DIGI_MAX];
int glength[DIGI_MAX];
#endif

/*
===================
=
= My stuff
=
===================
*/

int PlayerPainVocs[] =
{
    DIGI_PLAYERPAIN1,
    DIGI_PLAYERPAIN2,
    DIGI_PLAYERPAIN3,
    DIGI_PLAYERPAIN4,
    DIGI_PLAYERPAIN5
};

// Don't have these sounds yet
int PlayerLowHealthPainVocs[] =
{
    DIGI_HURTBAD1,
    DIGI_HURTBAD2,
    DIGI_HURTBAD3,
    DIGI_HURTBAD4,
    DIGI_HURTBAD5
};

int TauntAIVocs[] =
{
    DIGI_TAUNTAI1,
    DIGI_TAUNTAI2,
    DIGI_TAUNTAI3,
    DIGI_TAUNTAI4,
    DIGI_TAUNTAI5,
    DIGI_TAUNTAI6,
    DIGI_TAUNTAI7,
    DIGI_TAUNTAI8,
    DIGI_TAUNTAI9,
    DIGI_TAUNTAI10,
    DIGI_COWABUNGA,
    DIGI_NOCHARADE,
    DIGI_TIMETODIE,
    DIGI_EATTHIS,
    DIGI_FIRECRACKERUPASS,
    DIGI_HOLYCOW,
    DIGI_HAHA2,
    DIGI_HOLYPEICESOFCOW,
    DIGI_HOLYSHIT,
    DIGI_HOLYPEICESOFSHIT,
    DIGI_PAYINGATTENTION,
    DIGI_EVERYBODYDEAD,
    DIGI_KUNGFU,
    DIGI_HOWYOULIKEMOVE,
    DIGI_HAHA3,
    DIGI_NOMESSWITHWANG,
    DIGI_RAWREVENGE,
    DIGI_YOULOOKSTUPID,
    DIGI_TINYDICK,
    DIGI_NOTOURNAMENT,
    DIGI_WHOWANTSWANG,
    DIGI_MOVELIKEYAK,
    DIGI_ALLINREFLEXES
};

int PlayerGetItemVocs[] =
{
    DIGI_GOTITEM1,
    DIGI_HAHA1,
    DIGI_BANZAI,
    DIGI_COWABUNGA,
    DIGI_TIMETODIE
};

int PlayerYellVocs[] =
{
    DIGI_PLAYERYELL1,
    DIGI_PLAYERYELL2,
    DIGI_PLAYERYELL3
};

extern char lumplockbyte[];

#if 0
// DEBUG
void CheckSndData(char *file, int line)
{
    short i;

    //return;

    for (i = 0; i<DIGI_MAX; i++)
    {
        if (!globsndata[i] && !globvpdata[i]) continue;

        if (memcmp(globsndata[i], globvpdata[i], glength[i]) != 0)
        {
            printf("%s %d\n",file,line);
            printf("CheckSndData: Data is not the same! num = %d",i);
            exit(0);
        }
    }
}
#endif

//
// Routine called when a sound is finished playing
//

void
SoundCallBack(intptr_t num)
{
    VOC_INFOp vp;

    if ((int) num == MUSIC_ID)
    {
        return;
    }

    // RTS sounds are negative
    if ((int)num < 0)
    {
        ASSERT(-num < 11);
        lumplockbyte[-num]--;
        return;
    }

    vp = &voc[num];

    // Update counter
    //vp->playing--;
    vp->lock--;
}

//

void
ClearSoundLocks(void)
{
    unsigned i;

    for (i = 0; i < SIZ(voc); i++)
    {
        if (voc[i].lock >= 200)
            voc[i].lock = 199;
    }

    for (i = 0; i < 11; i++)
    {
        if (lumplockbyte[i] >= 200)
            lumplockbyte[i] = 199;
    }
}

void
UnInitSound(void)
{
    SoundShutdown();
    MusicShutdown();
}

void
InitFX(void)
{
    VOC_INFOp vp;

#if 0
    // DEBUG
    short i;
    for (i=0; i<DIGI_MAX; i++)
    {
        globsndata[i] = globvpdata[i] = NULL;
        glength[i] = 0;
    }
#endif

    //ExternalSoundMod();

    // Select which cards to use
    SoundStartup();

    // Get the current volume of the music and sound fx
    gs.SoundVolume = FX_GetVolume();

    for (vp = voc; vp < &voc[SIZ(voc)]; vp++)
    {
        vp->playing = 0;
    }

    // Set up our fx callback so we can display the sounds that are playing
    FX_SetCallBack(SoundCallBack);
}

void
InitMusic(void)
{
    // Select which cards to use
    MusicStartup();
    //SendGeneralMidiSysX();
}

void
ExternalSoundMod(void)
{
    FILE *fin;
    VOC_INFOp vp;
    char name[40];
    char new_name[40];
//    int pri;
    int pitch_lo, pitch_hi;
    int ret;

    fin = fopen("swextern.snd", "r");

    if (!fin)
        return;

    while (TRUE)
    {
        ret = fscanf(fin, "%s %s %d %d", name, new_name, &pitch_lo, &pitch_hi);

        if (ret == EOF)
            break;

        for (vp = voc; vp < &voc[SIZ(voc)]; vp++)
        {
#if 0
            if (!vp->name)
                continue;
#endif

            if (!Bstrcasecmp(name, vp->name))
            {
                // vp->priority = pri;
                strcpy(vp->name, new_name);
                vp->pitch_lo = pitch_lo;
                vp->pitch_hi = pitch_hi;
            }
        }
    }

    fclose(fin);
}

extern short Level;

static inline SWBOOL LoadSongUpgrade(char const * fn)
{
#if defined HAVE_FLAC || defined HAVE_VORBIS
    static char const * extensions[] =
    {
#ifdef HAVE_FLAC
        ".flac",
#endif
#ifdef HAVE_VORBIS
        ".ogg",
#endif
    };

    size_t const len = strlen(fn);
    auto testfn = (char *)Xmalloc(len+5); // 5 = strlen(".flac")
    memcpy(testfn, fn, len+1);
    char * suffix = strrchr(testfn, '.');
    if (suffix == nullptr)
        suffix = testfn + len;

    for (char const * ext : extensions)
    {
        strcpy(suffix, ext);
        SWBOOL const result = LoadSong(testfn);
        if (result)
        {
            Xfree(testfn);
            return result;
        }
    }

    Xfree(testfn);
#endif

    return LoadSong(fn);
}

SWBOOL
PlaySong(char *song_file_name, int cdaudio_track, SWBOOL loop, SWBOOL restart)
{
    if (!gs.MusicOn)
    {
        return FALSE;
    }

    if (DemoMode)
        return FALSE;

    if (!restart)
    {
        if (SongType == SongTypeWave)
        {
            if (SongTrack > 0 && SongTrack == cdaudio_track)
            {
                // ogg replacement for a CD track
                return TRUE;
            }
            else if (SongName && song_file_name && !strcmp(SongName, song_file_name))
            {
                return TRUE;
            }
        }
        else if (SongType == SongTypeMIDI)
        {
            if (SongName && song_file_name && !strcmp(SongName, song_file_name))
            {
                return TRUE;
            }
        }
    }

    StopSong();

    if (!SW_SHAREWARE)
    {
        if (cdaudio_track >= 0)
        {
            char waveformtrack[MAXWAVEFORMTRACKLENGTH];
            Bstrncpy(waveformtrack, gs.WaveformTrackName, MAXWAVEFORMTRACKLENGTH - 1);

            char *numPos = Bstrstr(waveformtrack, "??");

            if (numPos && (numPos-waveformtrack) < MAXWAVEFORMTRACKLENGTH - 2)
            {
                static const char *tracktypes[] = { ".flac", ".ogg" };
                const size_t tracknamebaselen = Bstrlen(waveformtrack);
                size_t i;

                numPos[0] = '0' + (cdaudio_track / 10) % 10;
                numPos[1] = '0' + cdaudio_track % 10;

                for (i = 0; i < ARRAY_SIZE(tracktypes); ++i)
                {
                    waveformtrack[tracknamebaselen] = '\0';
                    Bstrncat(waveformtrack, tracktypes[i], MAXWAVEFORMTRACKLENGTH - 1);

                    if (LoadSong(waveformtrack))
                    {
                        SongVoice = FX_Play(SongPtr, SongLength, loop ? FX_LOOP : FX_ONESHOT, 0, 0,
                                                gs.MusicVolume, gs.MusicVolume, gs.MusicVolume, FX_MUSIC_PRIORITY, fix16_one, MUSIC_ID);
                        if (SongVoice > FX_Ok)
                        {
                            SongType = SongTypeWave;
                            SongTrack = cdaudio_track;
                            Xfree(SongName);
                            SongName = Xstrdup(waveformtrack);
                            return TRUE;
                        }
                    }
                }

                buildprintf("Can't find CD track %i!\n", cdaudio_track);
            }
            else
            {
                buildprintf("Make sure to have \"??\" as a placeholder for the track number in your WaveformTrackName!\n");
                buildprintf("  e.g. WaveformTrackName = \"MUSIC/Track??\"\n");
            }
        }
    }

    if (!song_file_name || !LoadSongUpgrade(song_file_name))
    {
        return FALSE;
    }

    if (!memcmp(SongPtr, "MThd", 4))
    {
        MUSIC_PlaySong(SongPtr, SongLength, MUSIC_LoopSong);
        SongType = SongTypeMIDI;
        Xfree(SongName);
        SongName = Xstrdup(song_file_name);
        return TRUE;
    }
    else
    {
        SongVoice = FX_Play(SongPtr, SongLength, loop ? FX_LOOP : FX_ONESHOT, 0, 0,
                                gs.MusicVolume, gs.MusicVolume, gs.MusicVolume, FX_MUSIC_PRIORITY, fix16_one, MUSIC_ID);
        if (SongVoice > FX_Ok)
        {
            SongType = SongTypeWave;
            Xfree(SongName);
            SongName = Xstrdup(song_file_name);
            return TRUE;
        }
    }

    return FALSE;
}

void
StopFX(void)
{
    FX_StopAllSounds();
}

void
StopSong(void)
{
    if (DemoMode)
        return;

    if (SongType == SongTypeWave && SongVoice > 0)
    {
        FX_StopSound(SongVoice);
        SongVoice = 0;
    }
    else if (SongType == SongTypeMIDI)
    {
        MUSIC_StopSong();
    }
    SongType = SongTypeNone;

    DO_FREE_AND_NULL(SongName);
    SongTrack = 0;

    FreeMem(SongPtr);
    SongPtr = nullptr;
    SongLength = 0;
}

void
PauseSong(SWBOOL pauseon)
{
    if (!gs.MusicOn) return;

    if (SongType == SongTypeWave && SongVoice > 0)
    {
        FX_PauseVoice(SongVoice, pauseon);
    }
}

void
SetSongVolume(int volume)
{
    if (SongType == SongTypeWave && SongVoice > 0)
        FX_SetPan(SongVoice, volume, volume, volume);

    MUSIC_SetVolume(volume);
}

SWBOOL
SongIsPlaying(void)
{
    if (!gs.MusicOn)
        return FALSE;

    if (SongType == SongTypeWave && SongVoice >= 0)
        return FX_SoundActive(SongVoice);

    return FALSE;
}

void
StopSound(void)
{
    StopFX();
    StopSong();
}

//
// Sound Distance Calculation
//

#define MAXLEVLDIST 19000   // The higher the number, the further away you can hear sound

short
SoundDist(int x, int y, int z, int basedist)
{
    double tx, ty, tz;
    double sqrdist,retval;
    double decay,decayshift;
    extern short screenpeek;

#define DECAY_CONST 4000


    tx = fabs(Player[screenpeek].posx - x);
    ty = fabs(Player[screenpeek].posy - y);
    tz = fabs((Player[screenpeek].posz - z) >> 4);

    // Use the Pythagreon Theorem to compute the magnitude of a 3D vector
    sqrdist = fabs(tx*tx + ty*ty + tz*tz);
    retval = sqrt(sqrdist);

    if (basedist < 0) // if basedist is negative
    {
        short i;

        decayshift=2;
        decay = labs(basedist) / DECAY_CONST;

        for (i=0; i<decay; i++)
            decayshift *= 2;

        if (fabs(double(basedist)/decayshift) >= retval)
            retval = 0;
        else
            retval *= decay;
    }
    else
    {
        if (basedist > retval)
            retval = 0;
        else
            retval -= basedist;
    }

    retval = retval * 256 / MAXLEVLDIST;

    if (retval < 0) retval = 0;
    if (retval > 255) retval = 255;

    return retval;
}

//
// Angle calcuations - may need to be checked to make sure they are right
//

short
SoundAngle(int x, int y)
{
    extern short screenpeek;

    short angle, delta_angle;

    angle = getangle(x - Player[screenpeek].posx, y - Player[screenpeek].posy);

    delta_angle = GetDeltaAngle(angle, fix16_to_int(Player[screenpeek].q16ang));

    // convert a delta_angle to a real angle if negative
    if (delta_angle < 0)
        delta_angle = NORM_ANGLE((1024 + delta_angle) + 1024);

    // convert 2048 degree angle to 128 degree angle
    return delta_angle >> 4;
}

int _PlayerSound(const char *file, int line, int num, int *x, int *y, int *z, Voc3D_Flags flags, PLAYERp pp)
//PlayerSound(int num, int *x, int *y, int *z, Voc3D_Flags flags, PLAYERp pp)
{
    VOC_INFOp vp;

    if (Prediction)
        return 0;

    if (pp < Player || pp >= Player + MAX_SW_PLAYERS)
    {
        TerminateGame();
        printf("Player Sound invalid player: file %s, line %d\n",file,line);
        exit(0);
    }

    PRODUCTION_ASSERT(pp >= Player && pp < Player+MAX_SW_PLAYERS);
    PRODUCTION_ASSERT(num >= 0 && num < DIGI_MAX);

    if (TEST(pp->Flags, PF_DEAD)) return 0; // You're dead, no talking!

    // If this is a player voice and he's already yacking, forget it.
    vp = &voc[num];
    if (vp == NULL)
    {
        TerminateGame();
        printf("vp == NULL in PlayerSound, num = %d\n",num);
        exit(0);
    }

    // Not a player voice, bail.
    if (vp->priority != PRI_PLAYERVOICE && vp->priority != PRI_PLAYERDEATH)
        return 0;

    // He wasn't talking, but he will be now.
    if (!pp->PlayerTalking)
    {
        pp->PlayerTalking = TRUE;
        pp->TalkVocnum = num;   // Set the voc number
        pp->TalkVocHandle = PlaySound(num, x, y, z, flags); // Play the sound
        if (pp->TalkVocHandle < 0)
        {
            pp->PlayerTalking = FALSE;
            pp->TalkVocnum = -1;
            pp->TalkVocHandle = -1;
        }
    }

    return 0;
}

void LockSound(int num)
{
    VOC_INFOp vp = &voc[num];
    // if data is not locked
    if (vp->lock <= CACHE_UNLOCK_MAX)
    {
        vp->lock = CACHE_LOCK_START;
    }
    else
    // if data is already locked
    {
        vp->lock++;
        if (vp->lock >= CACHE_LOCK_MAX || vp->lock == 0)
        {
            DumpSounds();
            TerminateGame();
            printf("lock > MAX, num = %d",num);
            exit(0);
        }
        //ASSERT(vp->lock < CACHE_LOCK_MAX);
        //ASSERT(vp->lock != 0);
    }
}

SWBOOL CacheSound(int num, int type)
{
    VOC_INFOp vp = &voc[num];

    PRODUCTION_ASSERT(num >= 0 && num < DIGI_MAX);

    // if no data we need to cache it in
    if (!vp->data)
    {
        int handle;
        int length;

        if (!OpenSound(vp, &handle, &length))
        {
            sprintf(ds,"Could not open sound %s, num %d, priority %d\n",vp->name,num,vp->priority);
            CON_ConMessage("%s", ds);
            return FALSE;
        }

        if (vp != NULL)
        {
            //FILE *fp;

            /*
            if (type == CACHE_SOUND_PLAY)
                // start it out locked at the min
                vp->lock = CACHE_LOCK_START;
            else
            if (type == CACHE_SOUND_PRECACHE)
                // start it out unlocked at the max
            */
            vp->lock = CACHE_UNLOCK_MAX;

            g_cache.allocateBlock((intptr_t*)&vp->data, length, &vp->lock);

#if 0
            // DEBUG
            globsndata[num] = AllocMem(length);
            glength[num] = length;

            fp = fopen(vp->name, "rb");
            if (fp != NULL)
            {
                fread(globsndata[num], length, 1, fp);
                ASSERT(globsndata[num] != NULL);
                fclose(fp);
            }
#endif
            ///////

            ASSERT(vp->data);
            ReadSound(handle, vp, length);

#if 0
            //DEBUG
            globvpdata[num] = vp->data;
            CheckSndData(__FILE__, __LINE__);
#endif
        }
    }

    return TRUE;
}

////////////////////////////////////////////////////////////////////////////
// Play a sound
////////////////////////////////////////////////////////////////////////////

#define SOUND_UNIT  MAXLEVLDIST/255
// NOTE: If v3df_follow == 1, x,y,z are considered literal coordinates
int
PlaySound(int num, int *x, int *y, int *z, Voc3D_Flags flags)
{
    VOC_INFOp vp;
    VOC3D_INFOp v3p;
    int pitch = 0;
    short angle, sound_dist;
    int tx, ty, tz;
    uint8_t priority;
    SPRITEp sp=NULL;

    // DEBUG
    //extern SWBOOL Pachinko_Win_Cheat;


    // Don't play game sounds when in menus
    //if (UsingMenus && (*x!=0 || *y!=0 || *z!=0)) return(-1);

    // Weed out parental lock sounds if PLock is active
    if (gs.ParentalLock || Global_PLock)
    {
        unsigned i;

        for (i=0; i<sizeof(PLocked_Sounds); i++)
        {
            if (num == PLocked_Sounds[i])
                return -1;
        }
    }

    if (Prediction)
        return -1;

    if (!gs.FxOn)
        return -1;

    PRODUCTION_ASSERT(num >= 0 && num < DIGI_MAX);

    // Reset voice
    voice = -1;

    // This is used for updating looping sounds in Update3DSounds
    if (Use_SoundSpriteNum && SoundSpriteNum >= 0)
    {
        ASSERT(SoundSpriteNum >= 0 && SoundSpriteNum < MAXSPRITES);
        sp = &sprite[SoundSpriteNum];
    }

    if (gs.Ambient && TEST(flags,v3df_ambient) && !TEST(flags,v3df_nolookup))  // Look for invalid ambient numbers
    {
        if (num < 0 || num > MAX_AMBIENT_SOUNDS)
        {
            sprintf(ds,"Invalid or out of range ambient sound number %d\n",num);
            PutStringInfo(Player+screenpeek, ds);
            return -1;
        }
    }


    // Call queue management to add sound to play list.
    // 3D sound manager will update playing sound 10x per second until
    // the sound ends, at which time it is removed from both the 3D
    // sound list as well as the actual cache.
    v3p = Insert3DSound();

    // If the ambient flag is set, do a name conversion to point to actual
    // digital sound entry.
    v3p->num = num;
    v3p->priority = 0;
    v3p->FX_Ok = FALSE; // Hasn't played yet

    if (gs.Ambient && TEST(flags,v3df_ambient) && !TEST(flags,v3df_nolookup))
    {
        v3p->maxtics = STD_RANDOM_RANGE(ambarray[num].maxtics);
        flags |= ambarray[num].ambient_flags;   // Add to flags if any
        num = ambarray[num].diginame;
    }

    PRODUCTION_ASSERT(num >= 0 && num < DIGI_MAX);


    // Assign voc to voc pointer
    vp = &voc[num];
    if (UsingMenus && *x==0 && *y==0 && *z==0)  // Menus sound outdo everything
        priority = 100;
    else
        priority = vp->priority;
    v3p->vp = vp;

    // Assign voc info to 3d struct for future reference
    v3p->x = x;
    v3p->y = y;
    v3p->z = z;
    v3p->fx = *x;
    v3p->fy = *y;
    v3p->fz = *z;
    v3p->flags = flags;

    if (flags & v3df_follow)
    {
        tx = *x;
        ty = *y;
        if (!z)
            tz = 0;                     // Some sound calls don't have a z
        // value
        else
            tz = *z;
    }
    else
    {
        // Don't use pointers to coordinate values.
        tx = v3p->fx;
        ty = v3p->fy;
        tz = v3p->fz;
    }

    // Special case stuff for sounds being played in a level
    if (*x==0 && *y==0 && *z==0)
        tx = ty = tz = 0;

    if ((vp->voc_flags & vf_loop) && Use_SoundSpriteNum && SoundSpriteNum >= 0 && sp)
    {
        tx=sp->x;
        ty=sp->y;
        tz=sp->z;
        //CON_Message("Using sp to set tx=%ld,ty=%ld,tz=%ld",tx,ty,tz);
    }

    // Calculate sound angle
    if (flags & v3df_dontpan)               // If true, don't do panning
        angle = 0;
    else
        angle = SoundAngle(tx, ty);

    // Calculate sound distance
    if (tx == 0 && ty == 0 && tz == 0)
        sound_dist = 255;  // Special case for menus sounds,etc.
    else
        sound_dist = SoundDist(tx, ty, tz, vp->voc_distance);

    v3p->doplr_delta = sound_dist;      // Save of distance for doppler
    // effect

//  //DSPRINTF(ds,"sound dist = %d\n",sound_dist);
//  MONO_PRINT(ds);

    // Can the ambient sound see the player?  If not, tone it down some.
    if ((vp->voc_flags & vf_loop) && Use_SoundSpriteNum && SoundSpriteNum >= 0)
    {
        PLAYERp pp = Player+screenpeek;

        //MONO_PRINT("PlaySound:Checking sound cansee");
        if (!FAFcansee(tx, ty, tz, sp->sectnum,pp->posx, pp->posy, pp->posz, pp->cursectnum))
        {
            //MONO_PRINT("PlaySound:Reducing sound distance");
            sound_dist += ((sound_dist/2)+(sound_dist/4));  // Play more quietly
            if (sound_dist > 255) sound_dist = 255;

            // Special Cases
            if (num == DIGI_WHIPME) sound_dist = 255;
        }
    }

    // Assign ambient priorities based on distance
    if (gs.Ambient && TEST(flags, v3df_ambient))
    {
        v3p->priority = v3p->vp->priority - (sound_dist / 26);
        priority = v3p->priority;
    }

    if (!CacheSound(num, CACHE_SOUND_PLAY))
    {
        v3p->flags = v3df_kill;
        v3p->handle = -1;
        v3p->dist = 0;
        v3p->deleted = TRUE;            // Sound init failed, remove it!
        return -1;
    }

    LockSound(num);

    if (sound_dist < 5)
        angle = 0;

    // Check for pitch bending
    if (vp->pitch_lo > vp->pitch_hi)
        ASSERT(vp->pitch_lo <= vp->pitch_hi);

    if (vp->pitch_hi == vp->pitch_lo)
        pitch = vp->pitch_lo;
    else if (vp->pitch_hi != vp->pitch_lo)
        pitch = vp->pitch_lo + (STD_RANDOM_RANGE(vp->pitch_hi - vp->pitch_lo));

#if 0
    // DEBUG
    if (Pachinko_Win_Cheat)
    {
        CheckSndData(__FILE__, __LINE__);
        Pachinko_Win_Cheat = FALSE;
        CON_Message("S O U N D S   C H E C K E D");
    }
#endif

    // Request playback and play it as a looping sound if flag is set.
    if (vp->voc_flags & vf_loop)
    {
        short loopvol=0;

        if ((loopvol = 255-sound_dist) <= 0)
            loopvol = 0;

        if (sound_dist < 255 || (flags & v3df_init))
        {
            voice = FX_Play((char *)vp->data, vp->datalen, 0, 0,
                                      pitch, loopvol, loopvol, loopvol, priority, fix16_one, num); // [JM] Check the volume parameter for correctness. !CHECKME!
        }
        else
            voice = -1;

    }
    else
    //if(!flags & v3df_init)  // If not initing sound, play it
    if (tx==0 && ty==0 && tz==0)     // It's a non-inlevel sound
    {
        voice = FX_Play((char *)vp->data, vp->datalen, -1, -1, pitch, 255, 255, 255, priority, fix16_one, num); // [JM] And here !CHECKME!
    }
    else     // It's a 3d sound
    {
        if (sound_dist < 255)
        {
            voice = FX_Play3D((char *)vp->data, vp->datalen, FX_ONESHOT, pitch, angle, sound_dist, priority, fix16_one, num); // [JM] And here !CHECKME!
        }
        else
            voice = -1;
    }

    // If sound played, update our counter
    if (voice > FX_Ok)
    {
        //vp->playing++;
        v3p->FX_Ok = TRUE;
    }
    else
    {
        vp->lock--;
    }

    // Assign voc info to 3d struct for future reference
    v3p->handle = voice;                // Save the current voc handle in struct
    v3p->dist = sound_dist;
    v3p->tics = 0;                      // Reset tics
    if (flags & v3df_init)
        v3p->flags ^= v3df_init;        // Turn init off now

    return voice;
}

void PlaySoundRTS(int rts_num)
{
    char *rtsptr;
    int voice=-1;

    if (RTS_NumSounds() <= 0 || !gs.FxOn)
        return;

    rtsptr = (char *)RTS_GetSound(rts_num - 1);

    ASSERT(rtsptr);

    voice = FX_Play3D(rtsptr, RTS_SoundLength(rts_num - 1), FX_ONESHOT, 0, 0, 0, 255, fix16_one, -rts_num); // [JM] Check the volume parameter for correctness. !CHECKME!

    if (voice <= FX_Ok)
    {
        lumplockbyte[rts_num]--;
    }
}

///////////////////////////////////////////////

SWBOOL
OpenSound(VOC_INFOp vp, int *handle, int *length)
{
    *handle = kopen4load(vp->name, 0);

    if (*handle == -1)
    {
        return FALSE;
    }

    *length = kfilelength(*handle);

    return TRUE;
}


int
ReadSound(int handle, VOC_INFOp vp, int length)
{
    if (kread(handle, vp->data, length) != length)
    {
        TerminateGame();
        printf("Error reading file '%s'.\n", vp->name);
        exit(0);
    }

    vp->datalen = length;

    kclose(handle);
    return 0;
}

SWBOOL
LoadSong(const char *filename)
{
    int handle;
    int size;
    char *ptr;

    if ((handle = kopen4load(filename, 0)) == -1)
    {
        return FALSE;
    }

    size = kfilelength(handle);

    ptr = (char *) AllocMem(size);
    if (ptr == NULL)
    {
        kclose(handle);
        return FALSE;
    }

    if (kread(handle, ptr, size) != size)
    {
        FreeMem(ptr);
        kclose(handle);
        return FALSE;
    }

    kclose(handle);

    SongPtr = ptr;
    SongLength = size;

    return TRUE;
}


void
SoundStartup(void)
{
    void *initdata = 0;

    // if they chose None lets return
    if (!FXToggle)
    {
        gs.FxOn = FALSE;
        return;
    }

#ifdef MIXERTYPEWIN
    initdata = (void *) win_gethwnd();
#endif

    //gs.FxOn = TRUE;

    int status = FX_Init(NumVoices, NumChannels, MixRate, initdata);
    if (status != FX_Ok)
    {
        buildprintf("Sound error: %s\n", FX_ErrorString(status));
        return;
    }

    FxInitialized = TRUE;
    FX_SetVolume(gs.SoundVolume);

#ifdef ASS_REVERSESTEREO
    if (gs.FlipStereo)
        FX_SetReverseStereo(!FX_GetReverseStereo());
#endif

    FX_SetCallBack(SoundCallBack);
}

/*
===================
=
= SoundShutdown
=
===================
*/

void
SoundShutdown(void)
{
    // if they chose None lets return
    if (!FXToggle)
    {
        return;
    }

    if (!FxInitialized)
        return;

    int status = FX_Shutdown();
    if (status != FX_Ok)
    {
        buildprintf("Sound error: %s\n", FX_ErrorString(status));
    }
}


/*
===================
=
= MusicStartup
=
===================
*/

void MusicStartup(void)
{
    // if they chose None lets return
    if (!MusicToggle)
    {
        gs.MusicOn = FALSE;
        return;
    }

    int status;
    if ((status = MUSIC_Init(MusicDevice)) == MUSIC_Ok)
    {
        if (MusicDevice == ASS_AutoDetect)
            MusicDevice = MIDI_GetDevice();
    }
    else if ((status = MUSIC_Init(ASS_AutoDetect)) == MUSIC_Ok)
    {
        MusicDevice = MIDI_GetDevice();
    }
    else
    {
        buildprintf("Music error: %s\n", MUSIC_ErrorString(status));
        gs.MusicOn = FALSE;
        return;
    }

    MusicInitialized = TRUE;
    MUSIC_SetVolume(gs.MusicVolume);

    auto const fil = kopen4load("swtimbr.tmb", 0);

    if (fil != buildvfs_kfd_invalid)
    {
        int l = kfilelength(fil);
        auto tmb = (uint8_t *)Xmalloc(l);
        kread(fil, tmb, l);
        AL_RegisterTimbreBank(tmb);
        Xfree(tmb);
        kclose(fil);
    }
}

void COVER_SetReverb(int amt)
{
    FX_SetReverb(amt);
}

/*
===================
=
= MusicShutdown
=
===================
*/

void
MusicShutdown(void)
{
    // if they chose None lets return
    if (!MusicToggle)
        return;

    if (!MusicInitialized)
        return;

    StopSong();

    int status = MUSIC_Shutdown();
    if (status != MUSIC_Ok)
    {
        buildprintf("Music error: %s\n", MUSIC_ErrorString(status));
    }
}

///////////////////////////////////////////////////////////////////////////////

///////////////////////////////////////////////
//
//  3D sound engine
//  Sound management routines that keep a list of
//  all sounds being played in a level.
//  Doppler and Panning effects are achieved here.
//
///////////////////////////////////////////////
// Declare and initialize linked list of vocs.
VOC3D_INFOp voc3dstart = NULL;
VOC3D_INFOp voc3dend = NULL;

//////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////
// Initialize new vocs in the 3D sound queue
///////////////////////////////////////////////
VOC3D_INFOp
InitNew3DSound(VOC3D_INFOp v3p)
{
    v3p->handle = -1;                    // Initialize handle to new sound
    // value
    v3p->owner = -1;
    v3p->deleted = FALSE;                // Used for when sound gets deleted

    return v3p;
}

///////////////////////////////////////////////
// Inserts new vocs in the 3D sound queue
///////////////////////////////////////////////
VOC3D_INFOp
Insert3DSound(void)
{
    VOC3D_INFOp vp, old;

    // Allocate memory for new sound
    // You can allocate new sounds as long as memory holds out.
    // If you run out of memory for sounds, you got problems anyway.
    vp = (VOC3D_INFOp) AllocMem(sizeof(VOC3D_INFO));
    ASSERT(vp != NULL);
    memset(vp,0xCC,sizeof(VOC3D_INFO)); // Zero out the memory

    if (!voc3dend)                      // First item in list
    {
        vp->next = vp->prev = NULL;
        voc3dend = vp;
        voc3dstart = vp;
        InitNew3DSound(vp);
        return vp;
    }

    old = voc3dend;                     // Put it on the end
    old->next = vp;
    vp->next = NULL;
    vp->prev = old;
    voc3dend = vp;

    InitNew3DSound(vp);
    return vp;

}

/////////////////////////////////////////////////////
// Deletes vocs in the 3D sound queue with no owners
/////////////////////////////////////////////////////
void
DeleteNoSoundOwner(short spritenum)
{
    VOC3D_INFOp vp, dp;

    vp = voc3dstart;

    while (vp)
    {
        dp = NULL;
        if (vp->owner == spritenum && vp->owner >= 0 && (vp->vp->voc_flags & vf_loop))
        {
            //DSPRINTF(ds,"Deleting owner %d\n",vp->owner);
            //MONO_PRINT(ds);

            // Make sure to stop active
            // sounds
            if (FX_SoundValidAndActive(vp->handle))
            {
                FX_StopSound(vp->handle);
                vp->handle = 0;
            }

#if 0
            // Clean up the sound active counter for cacheing locks
            if (vp->FX_Ok) // Only decrement if sound ever played
                vp->vp->playing--; // Decrement instance of sound playing
            if (vp->vp->playing == 0 && vp->vp->lock > CACHE_UNLOCK_MAX)
                vp->vp->lock = CACHE_UNLOCK_MAX;
#endif


            dp = vp;                    // Point to sound to be deleted

            if (vp->prev)
            {
                vp->prev->next = vp->next;
            }
            else
            {
                voc3dstart = vp->next;  // New first item
                if (voc3dstart)
                    voc3dstart->prev = NULL;
            }

            if (vp->next)
            {
                vp->next->prev = vp->prev;      // Middle element
            }
            else
            {
                voc3dend = vp->prev;    // Delete last element
            }
        }

        vp = vp->next;

        if (dp != NULL)
            FreeMem(dp);                // Return memory to heap
    }
}

// This is called from KillSprite to kill a follow sound with no valid sprite owner
// Stops and active sound with the follow bit set, even play once sounds.
void DeleteNoFollowSoundOwner(short spritenum)
{
    VOC3D_INFOp vp, dp;
    SPRITEp sp = &sprite[spritenum];

    vp = voc3dstart;

    while (vp)
    {
        dp = NULL;
        // If the follow flag is set, compare the x and y addresses.
        if ((vp->flags & v3df_follow) && vp->x == &sp->x && vp->y == &sp->y)
        {
            if (FX_SoundValidAndActive(vp->handle))
            {
                FX_StopSound(vp->handle);
                vp->handle = 0;
            }

#if 0
            if (vp->FX_Ok) // Only decrement if sound ever played
                vp->vp->playing--; // Decrement instance of sound playing
            if (vp->vp->playing == 0 && vp->vp->lock > CACHE_UNLOCK_MAX)
                vp->vp->lock = CACHE_UNLOCK_MAX;
#endif

            dp = vp;                    // Point to sound to be deleted

            if (vp->prev)
            {
                vp->prev->next = vp->next;
            }
            else
            {
                voc3dstart = vp->next;  // New first item
                if (voc3dstart)
                    voc3dstart->prev = NULL;
            }

            if (vp->next)
            {
                vp->next->prev = vp->prev;      // Middle element
            }
            else
            {
                voc3dend = vp->prev;    // Delete last element
            }
        }

        vp = vp->next;

        if (dp != NULL)
            FreeMem(dp);                // Return memory to heap
    }
}

///////////////////////////////////////////////
// Deletes vocs in the 3D sound queue
///////////////////////////////////////////////
void
Delete3DSounds(void)
{
    VOC3D_INFOp vp, dp;
    PLAYERp pp;


    vp = voc3dstart;

    while (vp)
    {
        dp = NULL;
        if (vp->deleted)
        {
#if 0
            if (vp->FX_Ok) // Only decrement if sound ever played
                vp->vp->playing--; // Decrement instance of sound playing
            if (vp->vp->playing == 0 && vp->vp->lock > CACHE_UNLOCK_MAX)
                vp->vp->lock = CACHE_UNLOCK_MAX;
#endif

            //DSPRINTF(ds,"Delete3DSounds: deleting owner %d digi %d\n",vp->owner,vp->num);
            //MONO_PRINT(ds);
            // Reset Player talking flag if a voice was deleted
            //if(vp->num > DIGI_FIRSTPLAYERVOICE && vp->num < DIGI_LASTPLAYERVOICE)
            if (!vp->vp)
            {
                printf("Delete3DSounds(): NULL vp->vp\n");
            }
            else    // JBF: added null check
            if (vp->vp->priority == PRI_PLAYERVOICE || vp->vp->priority == PRI_PLAYERDEATH)
            {
                int16_t pnum;

                TRAVERSE_CONNECT(pnum)
                {
                    pp = &Player[pnum];

                    if (vp->num == pp->TalkVocnum)
                    {
                        pp->PlayerTalking = FALSE;
                        pp->TalkVocnum = -1;
                        pp->TalkVocHandle = -1;
                        //DSPRINTF(ds,"DELETED PLAYER VOICE VOC! NUM=%d\n",vp->num);
                        //MONO_PRINT(ds);
                    }
                }
            }

            dp = vp;                    // Point to sound to be deleted
            if (vp->prev)
            {
                vp->prev->next = vp->next;
            }
            else
            {
                voc3dstart = vp->next;  // New first item
                if (voc3dstart)
                    voc3dstart->prev = NULL;
            }

            if (vp->next)
            {
                vp->next->prev = vp->prev;      // Middle element
            }
            else
            {
                voc3dend = vp->prev;    // Delete last element
            }
        }

        vp = vp->next;

        if (dp != NULL)
        {
            FreeMem(dp);                // Return memory to heap
        }
    }
}

////////////////////////////////////////////////////////////////////////////
// Play a sound
////////////////////////////////////////////////////////////////////////////

int
RandomizeAmbientSpecials(int handle)
{
#define MAXRNDAMB 12
    int ambrand[] =
    {
        56,57,58,59,60,61,62,63,64,65,66,67
    };
    short i;

    // If ambient sound is found in the array, randomly pick a new sound
    for (i=0; i<MAXRNDAMB; i++)
    {
        if (handle == ambrand[i])
            return ambrand[STD_RANDOM_RANGE(MAXRNDAMB-1)];
    }

    return handle;   // Give back the sound, no new one was found
}

void
DoTimedSound(VOC3D_INFOp p)
{
    p->tics += synctics;

    if (p->tics >= p->maxtics)
    {
        if (!FX_SoundValidAndActive(p->handle))
        {
            // Check for special case ambient sounds
            p->num = RandomizeAmbientSpecials(p->num);

            // Sound was bumped from active sounds list, try to play again.
            // Don't bother if voices are already maxed out.
            if (FX_SoundsPlaying() < NumVoices)
            {
                if (p->flags & v3df_follow)
                {
                    PlaySound(p->num, p->x, p->y, p->z, p->flags);
                    p->deleted = TRUE;  // Mark old sound for deletion
                }
                else
                {
                    PlaySound(p->num, &p->fx, &p->fy, &p->fz, p->flags);
                    p->deleted = TRUE;  // Mark old sound for deletion
                }
            }
        }

        p->tics = 0;
        //while (p->tics >= p->maxtics)  // Really stupid thing to do!
        //    {
        //    p->tics -= p->maxtics;
        //    }
    }
}

void
StopAmbientSound(void)
{
    VOC3D_INFOp p;
    extern SWBOOL InMenuLevel;

    if (InMenuLevel) return;

    p = voc3dstart;

    while (p)
    {
        // kill ambient sounds if Ambient is off
        if (TEST(p->flags,v3df_ambient))
            SET(p->flags, v3df_kill);

        if (p->flags & v3df_kill)
        {
            if (FX_SoundValidAndActive(p->handle))
            {
                FX_StopSound(p->handle); // Make sure to stop active sounds
                p->handle = 0;
            }

            p->deleted = TRUE;
        }

        p = p->next;
    }

    Delete3DSounds();
}

void
StartAmbientSound(void)
{
    short i,nexti;
    extern SWBOOL InMenuLevel;

    if (InMenuLevel) return; // Don't restart ambience if no level is active! Will crash game.

    TRAVERSE_SPRITE_STAT(headspritestat[STAT_AMBIENT], i, nexti)
    {
        SPRITEp sp = &sprite[i];

        PlaySound(sp->lotag, &sp->x, &sp->y, &sp->z, v3df_ambient | v3df_init
                  | v3df_doppler | v3df_follow);
        Set3DSoundOwner(i);  // Ambient sounds need this to get sectnum for later processing
    }
}

///////////////////////////////////////////////
// Main function to update 3D sound array
///////////////////////////////////////////////
typedef struct
{
    VOC3D_INFOp p;
    short dist;
    uint8_t priority;
} TVOC_INFO, *TVOC_INFOp;

void
DoUpdateSounds3D(void)
{
    VOC3D_INFOp p;
    SWBOOL looping;
    int pitch = 0, pitchmax;
    short dist, angle;

    TVOC_INFO TmpVocArray[32];
    int i;
    static SWBOOL MoveSkip8 = 0;

    if (UsingMenus) return;

    // This function is already only call 10x per sec, this widdles it down even more!
    MoveSkip8 = (MoveSkip8 + 1) & 15;

    //CON_Message("Sounds Playing = %d",FX_SoundsPlaying());

    // Zero out the temporary array
    //memset(&TmpVocArray[0],0,sizeof(TmpVocArray));
    for (i=0; i<32; i++)
    {
        TmpVocArray[i].p = NULL;
        TmpVocArray[i].dist = 0;
        TmpVocArray[i].priority = 0;
    }

    p = voc3dstart;

    while (p)
    {
        ASSERT(p->num >= 0 && p->num < DIGI_MAX);

        looping = p->vp->voc_flags & vf_loop;

//      //DSPRINTF(ds,"sound %d FX_SoundActive = %d\n,",p->num,FX_SoundActive(p->handle));
//      MONO_PRINT(ds);

        // If sprite owner is dead, kill this sound as long as it isn't ambient
        if (looping && p->owner == -1 && !TEST(p->flags,v3df_ambient))
        {
            SET(p->flags, v3df_kill);
        }

        // Is the sound slated for death? Kill it, otherwise play it.
        if (p->flags & v3df_kill)
        {
            if (FX_SoundValidAndActive(p->handle))
            {
                FX_StopSound(p->handle); // Make sure to stop active sounds
                p->handle = 0;
            }

            //DSPRINTF(ds,"%d had v3df_kill.\n",p->num);
            //MONO_PRINT(ds);
            p->deleted = TRUE;
        }
        else
        {
            if (!FX_SoundValidAndActive(p->handle) && !looping)
            {
                if (p->flags & v3df_intermit)
                {
                    DoTimedSound(p);
                }
                else
                //if(p->owner == -1 && !TEST(p->flags,v3df_ambient))
                {
                    //DSPRINTF(ds,"%d is now inactive.\n",p->num);
                    //MONO_PRINT(ds);
                    p->deleted = TRUE;
                }
            }
            else if (FX_SoundValidAndActive(p->handle))
            {
                if (p->flags & v3df_follow)
                {
                    dist = SoundDist(*p->x, *p->y, *p->z, p->vp->voc_distance);
                    angle = SoundAngle(*p->x, *p->y);
                }
                else
                {
                    if (p->fx == 0 && p->fy == 0 && p->fz == 0)
                        dist = 0;
                    else
                        dist = SoundDist(p->fx, p->fy, p->fz, p->vp->voc_distance);
                    angle = SoundAngle(p->fx, p->fy);
                }

                // Can the ambient sound see the player?  If not, tone it down some.
                if ((p->vp->voc_flags & vf_loop) && p->owner != -1)
                {
                    PLAYERp pp = Player+screenpeek;
                    SPRITEp sp = &sprite[p->owner];

                    //MONO_PRINT("Checking sound cansee");
                    if (!FAFcansee(sp->x, sp->y, sp->z, sp->sectnum,pp->posx, pp->posy, pp->posz, pp->cursectnum))
                    {
                        //MONO_PRINT("Reducing sound distance");
                        dist += ((dist/2)+(dist/4));  // Play more quietly
                        if (dist > 255) dist = 255;

                        // Special cases
                        if (p->num == 76 && TEST(p->flags,v3df_ambient))
                        {
                            dist = 255; // Cut off whipping sound, it's secret
                        }

                    }
                }

                if (dist >= 255 && p->vp->voc_distance == DIST_NORMAL)
                {
                    FX_StopSound(p->handle);    // Make sure to stop active
                    p->handle = 0;
                    // sounds
                }
                else
                {
                    // Handle Panning Left and Right
                    if (!(p->flags & v3df_dontpan))
                        FX_Pan3D(p->handle, angle, dist);
                    else
                        FX_Pan3D(p->handle, 0, dist);

                    // Handle Doppler Effects
#define DOPPLERMAX  400
                    if (!(p->flags & v3df_doppler) && FX_SoundActive(p->handle))
                    {
                        pitch -= (dist - p->doplr_delta);

                        if (p->vp->pitch_lo != 0 && p->vp->pitch_hi != 0)
                        {
                            if (abs(p->vp->pitch_lo) > abs(p->vp->pitch_hi))
                                pitchmax = abs(p->vp->pitch_lo);
                            else
                                pitchmax = abs(p->vp->pitch_hi);

                        }
                        else
                            pitchmax = DOPPLERMAX;

                        if (pitch > pitchmax)
                            pitch = pitchmax;
                        if (pitch < -pitchmax)
                            pitch = -pitchmax;

                        p->doplr_delta = dist;  // Save new distance to
                        // struct
                        FX_SetPitch(p->handle, pitch);
                    }
                }
            }
            else if (!FX_SoundValidAndActive(p->handle) && looping)
            {
                if (p->flags & v3df_follow)
                {
                    dist = SoundDist(*p->x, *p->y, *p->z, p->vp->voc_distance);
                    angle = SoundAngle(*p->x, *p->y);
                }
                else
                {
                    dist = SoundDist(p->fx, p->fy, p->fz, p->vp->voc_distance);
                    angle = SoundAngle(p->fx, p->fy);
                }

                // Sound was bumped from active sounds list, try to play
                // again.
                // Don't bother if voices are already maxed out.
                // Sort looping vocs in order of priority and distance
                //if (FX_SoundsPlaying() < NumVoices && dist <= 255)
                if (dist <= 255)
                {
                    for (i=0; i<min((int)SIZ(TmpVocArray), NumVoices); i++)
                    {
                        if (p->priority >= TmpVocArray[i].priority)
                        {
                            if (!TmpVocArray[i].p || dist < TmpVocArray[i].dist)
                            {
                                ASSERT(p->num >= 0 && p->num < DIGI_MAX);
                                TmpVocArray[i].p = p;
                                TmpVocArray[i].dist = dist;
                                TmpVocArray[i].priority = p->priority;
                                break;
                            }
                        }
                    }
                }
            }                       // !FX_SoundActive
        }                           // if(p->flags & v3df_kill)

        p = p->next;
    }                               // while(p)

//  //DSPRINTF(ds,"Num vocs in list: %d, Sounds playing: %d\n",numelems,FX_SoundsPlaying());
//  MONO_PRINT(ds);

    // Process all the looping sounds that said they wanted to get back in
    // Only update these sounds 5x per second!  Woo hoo!, aren't we optimized now?
    //if(MoveSkip8==0)
    //    {
    for (i=0; i<min((int)SIZ(TmpVocArray), NumVoices); i++)
    {
//        int handle;

        p = TmpVocArray[i].p;

        //if (FX_SoundsPlaying() >= NumVoices || !p) break;
        if (!p) break;

        ASSERT(p->num >= 0 && p->num < DIGI_MAX);

        if (p->flags & v3df_follow)
        {
            if (p->owner == -1)
            {
                int enumber;
                enumber = p->num;
                TerminateGame();
                printf("Owner == -1 on looping sound with follow flag set!\n");
                printf("p->num = %d\n",enumber);
                exit(0);
            }

            Use_SoundSpriteNum = TRUE;
            SoundSpriteNum = p->owner;

            /*handle = */PlaySound(p->num, p->x, p->y, p->z, p->flags);
            //if(handle >= 0 || TEST(p->flags,v3df_ambient)) // After a valid PlaySound, it's ok to use voc3dend
            voc3dend->owner = p->owner; // Transfer the owner
            p->deleted = TRUE;

            Use_SoundSpriteNum = FALSE;
            SoundSpriteNum = -1;

            //MONO_PRINT("TmpVocArray playing a follow sound");
        }
        else
        {
            if (p->owner == -1)
            {
                int enumber;
                enumber = p->num;
                TerminateGame();
                printf("Owner == -1 on looping sound, no follow flag.\n");
                printf("p->num = %d\n",enumber);
                exit(0);
            }

            Use_SoundSpriteNum = TRUE;
            SoundSpriteNum = p->owner;

            /*handle = */PlaySound(p->num, &p->fx, &p->fy, &p->fz, p->flags);
            //if(handle >= 0 || TEST(p->flags,v3df_ambient))
            voc3dend->owner = p->owner; // Transfer the owner
            p->deleted = TRUE;

            Use_SoundSpriteNum = FALSE;
            SoundSpriteNum = -1;

            //MONO_PRINT("TmpVocArray playing a non-follow sound");
        }
    }
    //    } // MoveSkip8

    // Clean out any deleted sounds now
    Delete3DSounds();
}

//////////////////////////////////////////////////
// Terminate the sounds list
//////////////////////////////////////////////////
void
Terminate3DSounds(void)
{
    VOC3D_INFOp vp;

    vp = voc3dstart;

    while (vp)
    {
        if (vp->handle > 0)
            FX_StopSound(vp->handle);       // Make sure to stop active sounds
        vp->handle = 0;
        vp->deleted = TRUE;
        vp = vp->next;
    }

    Delete3DSounds();                   // Now delete all remaining sounds
    ClearSoundLocks();
}

void
DumpSounds(void)
{
    VOC3D_INFOp vp;

    vp = voc3dstart;

    while (vp)
    {
        if (TEST(vp->flags,v3df_ambient))
            sprintf(ds,"vp->num=%d, vp->owner=%d, vp->vp->lock=%d",ambarray[vp->num].diginame,vp->owner,vp->vp->lock);
        else
            sprintf(ds,"vp->num=%d, vp->owner=%d, vp->vp->lock=%d",vp->num,vp->owner,vp->vp->lock);
        DebugWriteString(ds);
        if (vp->owner >= 0)
        {
            SPRITEp sp = &sprite[vp->owner];
            sprintf(ds,"sp->picnum=%d, sp->hitag=%d, sp->lotag=%d, sp->owner=%d\n",TrackerCast(sp->picnum), TrackerCast(sp->hitag), TrackerCast(sp->lotag), TrackerCast(sp->owner));
            DebugWriteString(ds);
        }
        vp = vp->next;
    }
}


//////////////////////////////////////////////////
// Set owner to check when to kill looping sounds
// Must be called immediately after PlaySound call
// since this only assigns value to last sound
// on voc list
//////////////////////////////////////////////////
void
Set3DSoundOwner(short spritenum)
{
    VOC3D_INFOp p;

//  ASSERT(p->handle != -1); // Check for bogus sounds

    p = voc3dend;
    if (!p) return;

    // Queue up sounds with ambient flag even if they didn't play right away!
    if (p->handle != -1 || TEST(p->flags,v3df_ambient))
    {
        p->owner = spritenum;
    }
    else
    {
        p->deleted = TRUE;
        p->flags = v3df_kill;
    }
}

//////////////////////////////////////////////////
// Play a sound using special sprite setup
//////////////////////////////////////////////////
void
PlaySpriteSound(short spritenum, int attrib_ndx, Voc3D_Flags flags)
{
    SPRITEp sp = &sprite[spritenum];
    USERp u = User[spritenum];

    ASSERT(u);

//  //DSPRINTF(ds,"index = %d, digi num = %d\n",attrib_ndx,u->Attrib->Sounds[attrib_ndx]);
//  MONO_PRINT(ds);
    PlaySound(u->Attrib->Sounds[attrib_ndx], &sp->x, &sp->y, &sp->z, flags);
}

// vim:ts=4:sw=4:expandtab:
