
// I_SOUND.C
//
// Sound for SDL taken from Sam Lantinga's SDL Doom i_sound.c file.


#include <stdio.h>
#include <math.h>       // pow()
#include "SDL_audio.h"
#include "SDL_mutex.h"
#include "SDL_byteorder.h"
#include "SDL_version.h"
#include "h2def.h"
#include "sounds.h"
#include "i_sound.h"


int tsm_ID = -1;


/*
 *
 *                           SOUND HEADER & DATA
 *
 *
 */

// sound information

const char snd_prefixen[] = {'P', 'P', 'A', 'S', 'S', 'S', 'M', 'M', 'M', 'S'};

int snd_Channels;
int snd_DesiredMusicDevice, snd_DesiredSfxDevice;
int snd_MusicDevice,    // current music card # (index to dmxCodes)
	snd_SfxDevice,      // current sfx card # (index to dmxCodes)
	snd_MaxVolume,      // maximum volume for sound
	snd_MusicVolume;    // maximum volume for music
//int dmxCodes[NUM_SCARDS]; // the dmx code for a given card

int     snd_SBport, snd_SBirq, snd_SBdma;       // sound blaster variables
int     snd_Mport;                              // midi variables

extern boolean  snd_MusicAvail, // whether music is available
                snd_SfxAvail;   // whether sfx are available


void I_PauseSong(int handle)
{
}

void I_ResumeSong(int handle)
{
}

void I_SetMusicVolume(int volume)
{
}

void I_SetSfxVolume(int volume)
{
    snd_MaxVolume = volume; // THROW AWAY?
}

/*
 *
 *                              SONG API
 *
 */

int I_RegisterSong(void *data)
{
  return 0;
}

void I_UnRegisterSong(int handle)
{
}

int I_QrySongPlaying(int handle)
{
  return 0;
}

// Stops a song.  MUST be called before I_UnregisterSong().

void I_StopSong(int handle)
{
}

void I_PlaySong(int handle, boolean looping)
{
}

/*
 *
 *                                 SOUND FX API
 *
 */



#define NUM_CHANNELS    8
#define SAMPLERATE      11025   // Hz

static int SAMPLECOUNT = 512;

// The actual lengths of all sound effects.
//int lengths[NUMSFX];


// The channel step amount...
unsigned int channelstep[NUM_CHANNELS];
// ... and a 0.16 bit remainder of last step.
unsigned int channelstepremainder[NUM_CHANNELS];


// The channel data pointers, start and end.
unsigned char* channels[NUM_CHANNELS];
unsigned char* channelsend[NUM_CHANNELS];

// Time/gametic that the channel started playing,
//  used to determine oldest, which automatically
//  has lowest priority.
// In case number of active sounds exceeds
//  available channels.
int channelstart[NUM_CHANNELS];

// The sound in channel handles,
//  determined on registration,
//  might be used to unregister/stop/modify,
//  currently unused.
int channelhandles[NUM_CHANNELS];

// SFX id of the playing sound effect.
// Used to catch duplicates (like chainsaw).
int channelids[NUM_CHANNELS];           

// Pitch to stepping lookup, unused.
int steptable[256];

// Volume lookups.
int vol_lookup[128*256];

// Hardware left and right channel volume lookup.
int* channelleftvol_lookup[NUM_CHANNELS];
int* channelrightvol_lookup[NUM_CHANNELS];


struct Sample
{
    short a;       // always 0x2b11
    short b;       // always 3
    long length;   // sample length?
    char firstSample;
};


//
// This function adds a sound to the
//  list of currently active sounds,
//  which is maintained as a given number
//  (eight, usually) of internal channels.
// Returns a handle.
//
int _addSfx( int sfxid, void* data, int volume, int step, int seperation )
{
    static unsigned short handlenums = 0;
 
    //int k;
    int i;
    int rc = -1;
    
    int oldest = gametic;
    int oldestnum = 0;
    int slot;

    int rightvol;
    int leftvol;

#if 0
    // Chainsaw troubles.
    // Play these sound effects only one at a time.
    if( sfxid == sfx_sawup
     || sfxid == sfx_sawidl
     || sfxid == sfx_sawful
     || sfxid == sfx_sawhit
     || sfxid == sfx_stnmov
     || sfxid == sfx_pistol  )
    {
    // Loop all channels, check.
    for (i=0 ; i<NUM_CHANNELS ; i++)
    {
        // Active, and using the same SFX?
        if ( (channels[i])
         && (channelids[i] == sfxid) )
        {
        // Reset.
        channels[i] = 0;
        // We are sure that iff,
        //  there will only be one.
        break;
        }
    }
    }
#endif

    // Loop all channels to find oldest SFX.
    for( i=0; (i<NUM_CHANNELS) && (channels[i]); i++ )
    {
        if( channelstart[i] < oldest )
        {
            oldestnum = i;
            oldest = channelstart[i];
        }
    }

    // Tales from the cryptic.
    // If we found a channel, fine.
    // If not, we simply overwrite the first one, 0.
    // Probably only happens at startup.
    if( i == NUM_CHANNELS )
        slot = oldestnum;
    else
        slot = i;

    // Okay, in the less recent channel, we will handle the new SFX.
    // Set pointer to raw data.
    //channels[slot] = (unsigned char*) data;    //S_sfx[sfxid].data;
    channels[slot] = &((struct Sample*) data)->firstSample;

    // Set pointer to end of raw data.
    //channelsend[slot] = channels[slot] + lengths[sfxid];
    channelsend[slot] = channels[slot] + ((struct Sample*)data)->length;

#if 0
    for( k = 0; k < 20; k++ )
        printf( "%x ", ((int*)data)[ k ] );
    printf( "\n" );
#endif

    // Reset current handle number, limited to 0..100.
    if( ! handlenums )
        handlenums = 100;

    // Assign current handle number.
    // Preserved so sounds could be stopped (unused).
    channelhandles[slot] = rc = handlenums++;

    // Set stepping???
    // Kinda getting the impression this is never used.
    channelstep[slot] = step;
    // ???
    channelstepremainder[slot] = 0;
    // Should be gametic, I presume.
    channelstart[slot] = gametic;

    // Separation, that is, orientation/stereo.
    //  range is: 1 - 256
    seperation += 1;

    // Per left/right channel.
    //  x^2 seperation,
    //  adjust volume properly.
    //volume *= 8;  // puts rightvol/leftvol out of range - KR
    //volume *= 2;
    leftvol = volume - ((volume*seperation*seperation) >> 16); // (256*256);
    seperation = seperation - 257;
    rightvol = volume - ((volume*seperation*seperation) >> 16);    

    // Sanity check, clamp volume.
    if( rightvol < 0 || rightvol > 127 )
    {
        rightvol &= 127;
        printf( "rightvol out of bounds %d, id %d", rightvol, sfxid );
        //I_Error( "rightvol out of bounds %d", rightvol );
    }
    
    if( leftvol < 0 || leftvol > 127 )
    {
        leftvol &= 127;
        printf( "leftvol out of bounds %d, id %d", leftvol, sfxid );
        //I_Error( "leftvol out of bounds %d", leftvol );
    }
    
    // Get the proper lookup table piece
    //  for this volume level???
    channelleftvol_lookup[slot]  = &vol_lookup[leftvol*256];
    channelrightvol_lookup[slot] = &vol_lookup[rightvol*256];

    // Preserve sound SFX id,
    //  e.g. for avoiding duplicates of chainsaw.
    channelids[slot] = sfxid;

    // You tell me.
    return rc;
}


void _updateSound( void* unused, Uint8* stream, int len )
{
    // Mix current sound data.
    // Data, from raw sound, for right and left.
    register unsigned int sample;
    register int dl;
    register int dr;

    // Pointers in audio stream, left, right, end.
    signed short* leftout;
    signed short* rightout;
    signed short* leftend;

    // Step in stream, left and right, thus two.
    int step;

    // Mixing channel index.
    int chan;
    
    // Left and right channel
    //  are in audio stream, alternating.
    leftout = (signed short *)stream;
    rightout = ((signed short *)stream)+1;
    step = 2;

    // Determine end, for left channel only
    //  (right channel is implicit).
    leftend = leftout + SAMPLECOUNT*step;

    // Mix sounds into the mixing buffer.
    // Loop over step*SAMPLECOUNT,
    //  that is 512 values for two channels.
    while( leftout != leftend )
    {
    // Reset left/right value. 
    dl = 0;
    dr = 0;

    // Love thy L2 chache - made this a loop.
    // Now more channels could be set at compile time
    //  as well. Thus loop those  channels.
    for( chan = 0; chan < NUM_CHANNELS; chan++ )
    {
        // Check channel, if active.
        if( channels[ chan ] )
        {
            // Get the raw data from the channel. 
            sample = *channels[ chan ];

            // Add left and right part
            //  for this channel (sound)
            //  to the current data.
            // Adjust volume accordingly.
            dl += channelleftvol_lookup[ chan ][sample];
            dr += channelrightvol_lookup[ chan ][sample];

            // Increment index ???
            channelstepremainder[ chan ] += channelstep[ chan ];
            
            // MSB is next sample???
            channels[ chan ] += channelstepremainder[ chan ] >> 16;

            // Limit to LSB???
            channelstepremainder[ chan ] &= 65536-1;

            // Check whether we are done.
            if( channels[ chan ] >= channelsend[ chan ] )
            {
                channels[ chan ] = 0;
                //printf( "  channel done %d\n", chan );
            }
        }
    }
    
    // Clamp to range. Left hardware channel.
    // Has been char instead of short.
    // if (dl > 127) *leftout = 127;
    // else if (dl < -128) *leftout = -128;
    // else *leftout = dl;

    if( dl > 0x7fff )
        *leftout = 0x7fff;
    else if (dl < -0x8000)
        *leftout = -0x8000;
    else
        *leftout = dl;

    // Same for right hardware channel.
    if( dr > 0x7fff )
        *rightout = 0x7fff;
    else if (dr < -0x8000)
        *rightout = -0x8000;
    else
        *rightout = dr;

    // Increment current pointers in stream
    leftout += step;
    rightout += step;
    }
}


// Gets lump nums of the named sound.  Returns pointer which will be
// passed to I_StartSound() when you want to start an SFX.  Must be
// sure to pass this to UngetSoundEffect() so that they can be
// freed!


int I_GetSfxLumpNum(sfxinfo_t *sound)
{
  return W_GetNumForName(sound->lumpname);
}

int I_StartSound (int id, void *data, int vol, int sep, int pitch, int priority)
{
    //return SFX_PlayPatch(data, pitch, sep, vol, 0, 0);

    //printf( "I_StartSound: %d %p %d %d %d\n", id, data, vol, sep, pitch );

    SDL_LockAudio();
    id = _addSfx( id, data, vol, steptable[pitch], sep );
    SDL_UnlockAudio();

    return id;
}

void I_StopSound(int handle)
{
    //SFX_StopPatch(handle);
}

int I_SoundIsPlaying(int handle)
{
    //return SFX_Playing(handle);
    return 0;
}

void I_UpdateSoundParams(int handle, int vol, int sep, int pitch)
{
    //SFX_SetOrigin(handle, pitch, sep, vol);
}

/*
 *
 *                                                      SOUND STARTUP STUFF
 *
 *
 */

// inits all sound stuff

void I_StartupSound (void)
{
    SDL_AudioSpec wanted;
    int rc, i;

    if (debugmode)
        ST_Message("I_StartupSound: Hope you hear a pop.\n");

  
    // Open the audio device

    wanted.freq = SAMPLERATE;
    if( SDL_BYTEORDER == SDL_BIG_ENDIAN )
    {
        wanted.format = AUDIO_S16MSB;
    }
    else
    {
        wanted.format = AUDIO_S16LSB;
    }
    wanted.channels = 2;
    wanted.samples = SAMPLECOUNT;
    wanted.callback = _updateSound;

    if( SDL_OpenAudio( &wanted, NULL ) < 0 )
    {
        fprintf( stderr, "couldn't open audio with desired format\n" );
        return;
    }
    SAMPLECOUNT = wanted.samples;

    fprintf( stderr, " configured audio device with %d samples/slice\n",
             SAMPLECOUNT );


#if 0
    // Initialize external data (all sounds) at start, keep static.
    fprintf( stderr, "I_InitSound: ");

    for( i=1 ; i<NUMSFX ; i++ )
    {
        // Alias? Example is the chaingun sound linked to pistol.
        if( ! S_sfx[i].link )
        {
            // Load data from WAD file.
            S_sfx[i].data = getsfx( S_sfx[i].name, &lengths[i] );
        }   
        else
        {
            // Previously loaded already?
            S_sfx[i].data = S_sfx[i].link->data;
            lengths[i] = lengths[(S_sfx[i].link - S_sfx)/sizeof(sfxinfo_t)];
        }
    }

    fprintf( stderr, " pre-cached all sound data\n" );
#endif

    SDL_PauseAudio( 0 );
}


// shuts down all sound stuff

void I_ShutdownSound (void)
{
    SDL_CloseAudio();
}


void I_SetChannels(int channels)
{
    //WAV_PlayMode(channels, SND_SAMPLERATE);

    // NOTE: Currently hardcoded to NUM_CHANNELS (8) channels.

    int i, j;
    int* steptablemid = steptable + 128;
        
    // This table provides step widths for pitch parameters.
    for( i=-128 ; i<128 ; i++ )
    {
        steptablemid[i] = (int) (pow( 2.0, (i/64.0) ) * 65536.0);
    }


    // Generates volume lookup tables which also turn the unsigned samples
    // into signed samples.
    for( i=0 ; i<128 ; i++ )
    {
        for( j=0 ; j<256 ; j++ )
        {
            vol_lookup[i*256+j] = (i*(j-128)*256)/127;
//fprintf(stderr, "vol_lookup[%d*256+%d] = %d\n", i, j, vol_lookup[i*256+j]);
        }
    }
}
