// Emacs style mode select   -*- C++ -*-
//-----------------------------------------------------------------------------
//
// $Id: I_sound.c,v 1.2 2003/08/18 18:26:06 hurdler Exp $
//
// Copyright (C) 1993-1996 by id Software, Inc.
// Portions Copyright (C) 1998-2000 by DooM Legacy Team.
//
// This program 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.
//
//
// $Log: I_sound.c,v $
// Revision 1.2  2003/08/18 18:26:06  hurdler
// stable 1.41
//
// Revision 1.5  2003/07/13 13:18:59  hurdler
// go RC1
//
// Revision 1.4  2001/03/30 17:12:52  bpereira
// no message
//
// Revision 1.3  2000/03/06 15:32:56  hurdler
// compiler warning removed
//
// Revision 1.2  2000/02/27 00:42:11  hurdler
// fix CR+LF problem
//
// Revision 1.1.1.1  2000/02/22 20:32:33  hurdler
// Initial import into CVS (v1.29 pr3)
//
//
// DESCRIPTION:
//      interface level code for sound
//
//-----------------------------------------------------------------------------

#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>

#include <math.h>

#include "../doomdef.h"
#include "../doomstat.h"
#include "../i_system.h"
#include "../i_sound.h"
#include "../z_zone.h"
#include "../m_argv.h"
#include "../m_misc.h"
#include "../w_wad.h"
#include "../s_sound.h"
#include "../console.h"

//### let's try with Allegro ###
#define  alleg_mouse_unused
#define  alleg_timer_unused
#define  alleg_keyboard_unused
#define  alleg_joystick_unused
#define  alleg_gfx_driver_unused
#define  alleg_palette_unused
#define  alleg_graphics_unused
#define  alleg_vidmem_unused
#define  alleg_flic_unused
//#define  alleg_sound_unused    we use it
#define  alleg_file_unused
#define  alleg_datafile_unused
#define  alleg_math_unused
#define  alleg_gui_unused
#include <allegro.h>
//### end of Allegro include ###

#include "../qmus2mid.h"
//#include "loadmus.h"

//allegro has 256 virtual voices
// warning should by a power of 2
#define VIRTUAL_VOICES 256
#define VOICESSHIFT 8

// Needed for calling the actual sound output.
#define SAMPLECOUNT    512

//
// this function converts raw 11khz, 8-bit data to a SAMPLE* that allegro uses
// it is need cuz allegro only loads samples from wavs and vocs
//added:11-01-98: now reads the frequency from the rawdata header.
//   dsdata points a 4 unsigned short header:
//    +0 : value 3 what does it mean?
//    +2 : sample rate, either 11025 or 22050.
//    +4 : number of samples, each sample is a single byte since it's 8bit
//    +6 : value 0
SAMPLE *raw2SAMPLE(unsigned char *dsdata, int len)
{
    SAMPLE *spl;

    spl = Z_Malloc(sizeof(SAMPLE), PU_STATIC, NULL);
    if (spl == NULL)
        I_Error("Raw2Sample : no more free mem");
    spl->bits = 8;
    spl->stereo = 0;
    spl->freq = *((unsigned short *) dsdata + 1);       //mostly 11025, but some at 22050.
    spl->len = len - 8;
    spl->priority = 255;        //priority;
    spl->loop_start = 0;
    spl->loop_end = len - 8;
    spl->param = -1;
    spl->data = (void *) (dsdata + 8);  //skip the 8bytes header

    return spl;
}

//  This function loads the sound data from the WAD lump,
//  for single sound.
//
void *I_GetSfx(sfxinfo_t * sfx)
{
    byte *dssfx;
    int size;

    if (sfx->lumpnum < 0)
        sfx->lumpnum = S_GetSfxLumpNum(sfx);

    size = W_LumpLength(sfx->lumpnum);

    dssfx = (byte *) W_CacheLumpNum(sfx->lumpnum, PU_STATIC);
    //_go32_dpmi_lock_data(dssfx,size);

    // convert raw data and header from Doom sfx to a SAMPLE for Allegro
    return (void *) raw2SAMPLE(dssfx, size);
}

void I_FreeSfx(sfxinfo_t * sfx)
{
    byte *dssfx;

    if (sfx->lumpnum < 0)
        return;

    // free sample data
    if (sfx->data)
    {
        dssfx = (byte *) ((SAMPLE *) sfx->data)->data - 8;
        Z_Free(dssfx);
        // Allegro SAMPLE structure
        Z_Free(sfx->data);
    }

    sfx->data = NULL;
    sfx->lumpnum = -1;
}

void I_SetSfxVolume(int volume)
{
    if (nosound)
        return;

    set_volume(cv_soundvolume.value * 255 / 31, -1);
}

// MUSIC API - dummy. Some code from DOS version.
void I_SetMusicVolume(int volume)
{
    if (nomusic)
        return;

    // Now set volume on output device.
    set_volume(-1, cv_musicvolume.value * 255 / 31);
}

//
// Starting a sound means adding it
//  to the current list of active sounds
//  in the internal channels.
// As the SFX info struct contains
//  e.g. a pointer to the raw data,
//  it is ignored.
// As our sound handling does not handle
//  priority, it is ignored.
// Pitching (that is, increased speed of playback)
//  is set, but currently not used by mixing.
//
int I_StartSound(int id, int vol, int sep, int pitch, int priority)
{
    int voice;

    if (nosound)
        return 0;

    // UNUSED
    priority = 0;

    pitch = (pitch - 128) / 2 + 128;
    voice = play_sample(S_sfx[id].data, vol, sep, (pitch * 1000) / 128, 0);

    // Returns a handle
    return (id << VOICESSHIFT) + voice;
}

void I_StopSound(int handle)
{
    // You need the handle returned by StartSound.
    // Would be looping all channels,
    //  tracking down the handle,
    //  an setting the channel to zero.
    int voice = handle & (VIRTUAL_VOICES - 1);

    if (nosound)
        return;

    if (voice_check(voice) == S_sfx[handle >> VOICESSHIFT].data)
        deallocate_voice(voice);
}

int I_SoundIsPlaying(int handle)
{
    if (nosound)
        return FALSE;

    if (voice_check(handle & (VIRTUAL_VOICES - 1)) == S_sfx[handle >> VOICESSHIFT].data)
        return TRUE;
    return FALSE;
}

//
// This function loops all active (internal) sound
//  channels, retrieves a given number of samples
//  from the raw sound data, modifies it according
//  to the current (internal) channel parameters,
//  mixes the per channel samples into the global
//  mixbuffer, clamping it to the allowed range,
//  and sets up everything for transferring the
//  contents of the mixbuffer to the (two)
//  hardware channels (left and right, that is).
//
//  allegro does this now
//
void I_UpdateSound(void)
{
}

//
// This would be used to write out the mixbuffer
//  during each game loop update.
// Updates sound buffer and audio device at runtime.
// It is called during Timer interrupt with SNDINTR.
// Mixing now done synchronous, and
//  only output be done asynchronous?
//

void I_SubmitSound(void)
{
    //this should no longer be necessary cuz allegro is doing all the sound mixing now
}

// cut and past from ALLEGRO he don't share it :(
static inline int absolute_freq(int freq, SAMPLE * spl)
{
    if (freq == 1000)
        return spl->freq;
    else
        return (spl->freq * freq) / 1000;
}

void I_UpdateSoundParams(int handle, int vol, int sep, int pitch)
{
    // I fail too see that this is used.
    // Would be using the handle to identify
    //  on which channel the sound might be active,
    //  and resetting the channel parameters.
    int voice = handle & (VIRTUAL_VOICES - 1);
    int numsfx = handle >> VOICESSHIFT;

    if (nosound)
        return;

    if (voice_check(voice) == S_sfx[numsfx].data)
    {
        voice_set_volume(voice, vol);
        voice_set_pan(voice, sep);
        voice_set_frequency(voice, absolute_freq(pitch * 1000 / 128, S_sfx[numsfx].data));
    }
}

void I_ShutdownSound(void)
{
    // Wait till all pending sounds are finished.

    //added:03-01-98:
    if (!sound_started)
        return;

    //added:08-01-98: remove_sound() explicitly because we don't use
    //                Allegro's allegro_exit();
    remove_sound();
    sound_started = false;
}

void I_StartupSound()
{
    int sfxcard, midicard;
    char err[255];

    if (nosound)
        sfxcard = DIGI_NONE;
    else
        sfxcard = DIGI_AUTODETECT;

    if (nomusic)
        midicard = MIDI_NONE;
    else
        midicard = MIDI_AUTODETECT;     //DetectMusicCard();

    // Secure and configure sound device first.
    CONS_Printf("I_StartupSound: ");

    //Fab:25-04-98:note:install_sound will check for sound settings
    //    in the sound.cfg or allegro.cfg, in the current directory,
    //    or the directory pointed by 'ALLEGRO' env var.
    if (install_sound(sfxcard, midicard, NULL) != 0)
    {
        sprintf(err, "Sound init error : %s\n", allegro_error);
        CONS_Error(err);
        nosound = true;
    }
    else
        CONS_Printf(" configured audio device\n");

    //added:08-01-98:we use a similar startup/shutdown scheme as Allegro.
    I_AddExitFunc(I_ShutdownSound);
    sound_started = true;
}

//
// MUSIC API.
// Still no music done.
// Remains. Dummies.
//

MIDI *currsong;                 //im assuming only 1 song will be played at once
static int islooping = 0;
static int musicdies = -1;
int music_started = 0;
char *musicbuffer;

/* load_midi_mem:
 *  Loads a standard MIDI file from memory, returning a pointer to
 *  a MIDI structure, *  or NULL on error.
 *  It is the load_midi from Allegro modified to load it from memory
 */
MIDI *load_midi_mem(char *mempointer, int *e)
{
    int c;
    long data = 0;
    char *fp;
    MIDI *midi;
    int num_tracks = 0;

    fp = mempointer;
    if (!fp)
        return NULL;

    midi = malloc(sizeof(MIDI));        /* get some memory */
    if (!midi)
        return NULL;

    for (c = 0; c < MIDI_TRACKS; c++)
    {
        midi->track[c].data = NULL;
        midi->track[c].len = 0;
    }

    fp += 4 + 4;        // header size + 'chunk' size

    swab(fp, &data, 2); // convert to intel-endian
    fp += 2;    /* MIDI file type */
    if ((data != 0) && (data != 1))     // only type 0 and 1 are suported
        return NULL;

    swab(fp, &num_tracks, 2);   /* number of tracks */
    fp += 2;
    if ((num_tracks < 1) || (num_tracks > MIDI_TRACKS))
        return NULL;

    swab(fp, &data, 2); /* beat divisions */
    fp += 2;
    midi->divisions = ABS(data);

    for (c = 0; c < num_tracks; c++)
    {   /* read each track */
        if (memcmp(fp, "MTrk", 4))
            return NULL;
        fp += 4;

//      swab(fp,&data,4);       don't work !!!!??
        ((char *) &data)[0] = fp[3];
        ((char *) &data)[1] = fp[2];
        ((char *) &data)[2] = fp[1];
        ((char *) &data)[3] = fp[0];
        fp += 4;

        midi->track[c].len = data;

        midi->track[c].data = fp;
        fp += data;
    }

    lock_midi(midi);
    return midi;
}

#define MIDBUFFERSIZE   128*1024L

void I_InitMusic(void)
{
    if (nomusic)
        return;

    // initialisation of midicard by I_StartupSound
    musicbuffer = (char *) Z_Malloc(MIDBUFFERSIZE, PU_STATIC, NULL);

    _go32_dpmi_lock_data(musicbuffer, MIDBUFFERSIZE);
    I_AddExitFunc(I_ShutdownMusic);
    music_started = true;
}

void I_ShutdownMusic(void)
{
    if (!music_started)
        return;

    I_StopSong(1);

    music_started = false;
}

void I_PlaySong(int handle, int looping)
{
    if (nomusic)
        return;

    islooping = looping;
    musicdies = gametic + TICRATE * 30;
    play_midi(currsong, looping);
}

void I_PauseSong(int handle)
{
    if (nomusic)
        return;

    midi_pause();
}

void I_ResumeSong(int handle)
{
    if (nomusic)
        return;

    midi_resume();
}

void I_StopSong(int handle)
{
    if (nomusic)
        return;

    islooping = 0;
    musicdies = 0;
    stop_midi();
}

// Is the song playing?
int I_QrySongPlaying(int handle)
{
    if (nomusic)
        return 0;

    //return islooping || musicdies > gametic;
    return (midi_pos == -1);
}

void I_UnRegisterSong(int handle)
{
    if (nomusic)
        return;

//    destroy_midi(currsong);
}

int I_RegisterSong(void *data, int len)
{
    int e;
    ULONG midlenght;
    if (nomusic)
        return 1;

    if (memcmp(data, "MUS", 3) == 0)
    {
        // convert mus to mid with a wonderfull function
        // thanks to S.Bacquet for the source of qmus2mid
        // convert mus to mid and load it in memory
        if ((e = qmus2mid((char *) data, musicbuffer, 89, 64, 0, len, MIDBUFFERSIZE, &midlenght)) != 0)
        {
            CONS_Printf("Cannot convert mus to mid, converterror :%d\n", e);
            return 0;
        }
        currsong = load_midi_mem(musicbuffer, &e);
    }
    else
        // supprot mid file in WAD !!!
    if (memcmp(data, "MThd", 4) == 0)
    {
        currsong = load_midi_mem(data, &e);
    }
    else
    {
        CONS_Printf("Music Lump is not MID or MUS lump\n");
        return 0;
    }

    if (currsong == NULL)
    {
        CONS_Printf("Not a valid mid file : %d\n", e);
        return 0;
    }

    return 1;
}

//Hurdler: TODO
void I_StartFMODSong()
{
    CONS_Printf("I_StartFMODSong: Not yet supported under DOS.\n");
}

void I_StopFMODSong()
{
    CONS_Printf("I_StopFMODSong: Not yet supported under DOS.\n");
}
void I_SetFMODVolume(int volume)
{
    CONS_Printf("I_SetFMODVolume: Not yet supported under DOS.\n");
}
