/*

Name:
MDRIVER.C

Description:
These routines are used to access the available soundcard drivers.

Portability:
All systems - all compilers

*/

#include "mikmod.h"

static CHAR  *ERROR_DETECTING_DEVICE  = "None of the supported sound-devices were detected.";
static CHAR  *ERROR_INVALID_PARAMETER = "Device number out of range.";

CHAR *MDERR_OUT_OF_HANDLES     = "Out of sample-handles.";
CHAR *MDERR_SAMPLE_TOO_BIG     = "Sample too big, out of memory.";

MDRIVER *firstdriver = NULL, *md_driver;

SAMPLE **md_sample = NULL;
UWORD  *md_voice   = NULL;

UWORD md_device         = 0;
UWORD md_mixfreq        = 44100;
UWORD md_mode           = 0;
UWORD md_dmabufsize     = 20000;
UBYTE md_pansep         = 128;      // 128 == 100% (full left/right)

UBYTE md_stereodelay    = 0;       // Stereo Delay
UBYTE md_reverb         = 0;       // Reverb

UBYTE md_volume         = 128;      // Global sound volume (0-128)
UBYTE md_musicvolume    = 128;      // volume of song
UBYTE md_sndfxvolume    = 128;      // volume of sound effects

UBYTE md_bpm            = 125;

// Do not modify the numchn variables yourself!  use MD_SetNumChannels()

UBYTE md_numchn         = 0;
UBYTE md_sfxchn         = 0;

BOOL  md_noloopeffect	= FALSE;	// forbid execution of ProTracker effect 0x0B

static void dummyplay(void)
{
}

void (*md_player)(void) = dummyplay;
static BOOL isplaying = 0;
static UBYTE *sfxinfo;
static int   sfxpool;


// Note: 'type' indicates whether the returned value should be for music
//       or for sound effects.

ULONG MD_SampleSpace(int type)
{
    if(type==MD_MUSIC)
       type = (md_mode & DMODE_SOFT_MUSIC) ? MD_SOFTWARE : MD_HARDWARE;
    else if(type==MD_SNDFX)
       type = (md_mode & DMODE_SOFT_SNDFX) ? MD_SOFTWARE : MD_HARDWARE;

    return md_driver->FreeSampleSpace(type);
}


ULONG MD_SampleLength(int type, SAMPLE *s)
{
    if(type==MD_MUSIC)
       type = (md_mode & DMODE_SOFT_MUSIC) ? MD_SOFTWARE : MD_HARDWARE;
    else if(type==MD_SNDFX)
       type = (md_mode & DMODE_SOFT_SNDFX) ? MD_SOFTWARE : MD_HARDWARE;

    return md_driver->RealSampleLength(type, s);
}


UWORD MD_SetDMA(int secs)

// Converts the given number of 1/10th seconds into the number of bytes of
// audio that a sample # 1/10th seconds long would require at the current md_*
// settings.

{
    ULONG result;

    result = (md_mixfreq * ((md_mode & DMODE_STEREO) ? 2 : 1) *
             ((md_mode & DMODE_16BITS) ? 2 : 1) * secs) * 10;

    if(result > 32000) result = 32000;
    return(md_dmabufsize = (result & ~3));  // round it off to an 8 byte boundry
}


void MD_InfoDriver(void)
{
    int t;
    MDRIVER *l;

    // list all registered devicedrivers:
    for(t=1,l=firstdriver; l!=NULL; l=l->next, t++)
        printf("%d. %s\n",t,l->Version);
}


void MD_RegisterDriver(MDRIVER *drv)
{
    MDRIVER *cruise = firstdriver;

    if(cruise!=NULL)
    {   while(cruise->next!=NULL)  cruise = cruise->next;
        cruise->next = drv;
    } else
        firstdriver = drv; 
}


SWORD MD_SampleLoad(SAMPLOAD *s, FILE *fp)
{
    SWORD result;

    SL_Init(s);
    result = md_driver->SampleLoad(s, fp);
    SL_Exit(s);

    return result;
}


void MD_SampleUnLoad(SWORD handle)
{
    md_driver->SampleUnLoad(handle);
}


BOOL MD_Init(void)
{
    UWORD t;

    // if md_device==0, try to find a device number

    if(md_device==0)
    {   for(t=1,md_driver=firstdriver; md_driver!=NULL; md_driver=md_driver->next, t++)
        {   if(md_driver->IsPresent()) break;
        }

        if(md_driver==NULL)
        {   _mm_error = ERROR_DETECTING_DEVICE;
            return 0;
        }
        md_device = t;
    }

    // if n>0 use that driver
    for(t=1,md_driver=firstdriver; md_driver!=NULL && t!=md_device; md_driver=md_driver->next, t++);

    if(md_driver==NULL)
    {   _mm_error = ERROR_INVALID_PARAMETER;
        return 1;
    }

    if(md_driver->Init()) return 1;
    return 0;
}


void MD_Exit(void)
{
    MD_PlayStop();
    md_driver->Exit();
}


BOOL MD_SetNumChannels(int music, int sfx)
{
    int t, hard=0, soft=0;

    if(sfxinfo!=NULL) free(sfxinfo);
    if(md_sample!=NULL) free(md_sample);
    md_sample  = NULL;
    sfxinfo = NULL;

    if(music || sfx)
        md_sample = (SAMPLE **)_mm_calloc(music+sfx, sizeof(SAMPLE *));

    if(sfx)
        sfxinfo = (UBYTE *)_mm_calloc(sfx, sizeof(UBYTE));
    
    if(md_mode & DMODE_SOFT_SNDFX)
       soft = sfx; else hard = sfx;

    if(md_mode & DMODE_SOFT_MUSIC)
       soft += music; else hard += music;
    
    md_numchn = music;  md_sfxchn = sfx;
    if(md_driver->SetNumChannels()) return 1;

    // make sure the player doesn't start with garbage
    for(t=0; t<md_numchn; t++)
       MD_VoiceStop(t);

    sfxpool = 0;

    return 0;
}


void MD_PlayStart(void)
{
    // safety valve, prevents entering
    // playstart twice:

    if(isplaying) return;
    md_driver->PlayStart();
    isplaying = 1;
}


void MD_PlayStop(void)
{
    // safety valve, prevents calling playStop when playstart
    // hasn't been called:

    if(isplaying)
    {   isplaying = 0;
        md_driver->PlayStop();
    }
}


BOOL MD_Active(void)
{
    return isplaying;
}


void MD_SetBPM(UBYTE bpm)
{
    md_bpm = bpm;
}


void MD_RegisterPlayer(void (*player)(void))
{
    md_player = player;
}


void MD_Update(void)
{
    if(isplaying) md_driver->Update();
}


void MD_VoiceSetVolume(UBYTE voice, UWORD vol)
{
    ULONG  tmp;

    tmp = (ULONG)vol * (ULONG)md_volume * ((voice < md_numchn) ? (ULONG)md_musicvolume : (ULONG)md_sndfxvolume);
    md_driver->VoiceSetVolume(voice,tmp/16384UL);
}


void MD_VoiceSetFrequency(UBYTE voice, ULONG frq)
{
    if(md_sample[voice]!=NULL && md_sample[voice]->divfactor!=0) frq/=md_sample[voice]->divfactor;
    md_driver->VoiceSetFrequency(voice, frq);
}


void MD_VoiceSetPanning(UBYTE voice, ULONG pan)
{
    if(pan!=PAN_SURROUND)
    {   if(md_mode & DMODE_REVERSE) pan = 255-pan;
        pan = (((SWORD)(pan-128)*md_pansep) / 128)+128;
    }
    md_driver->VoiceSetPanning(voice, pan);
}


int MD_PlaySound(SAMPLE *s, ULONG start, UBYTE flags)
{
    int orig = sfxpool;    // for cases where all channels are critical

    if(md_sfxchn==0) return -1;

    // find an empty SndFX channel
    while(sfxinfo[sfxpool] & SFX_CRITICAL)
    {   sfxpool++;
        if(sfxpool > md_sfxchn) sfxpool = 0;
        if(sfxpool==orig) break;
    }

    if(sfxpool!=orig)
    {   sfxinfo[sfxpool] = flags;
        MD_VoicePlay(sfxpool+md_numchn, s, start);

        return sfxpool+md_numchn;
    } else return -1;
}


void MD_VoicePlay(UBYTE voice, SAMPLE *s, ULONG start)
{
    md_sample[voice] = s;
    md_driver->VoicePlay(voice,s->handle,start,s->length,s->loopstart,s->loopend,s->flags);
}


void MD_VoiceStop(UBYTE voice)
{
    md_driver->VoiceStop(voice);
}

 
BOOL MD_VoiceStopped(UBYTE voice)
{
    return(md_driver->VoiceStopped(voice));
}


SLONG MD_VoiceGetPosition(UBYTE voice)
{
    return(md_driver->VoiceGetPosition(voice));
}


ULONG MD_VoiceRealVolume(UBYTE voice)
{
    return(md_driver->RealVolume(voice));
}

