// Emacs style mode select   -*- C++ -*-
//-----------------------------------------------------------------------------
//
// $Id: qmus2mid.c 633 2010-04-27 20:36:48Z wesleyjohnson $
//
// Copyright (C) 1995 by Sebastien Bacquet.
// 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: qmus2mid.c,v $
// Revision 1.6  2001/03/09 21:53:56  metzgermeister
// *** empty log message ***
//
// Revision 1.5  2001/03/03 19:45:12  ydario
// Do not compile under OS/2
//
// Revision 1.4  2000/10/08 13:30:01  bpereira
// no message
//
// Revision 1.3  2000/09/10 10:46:15  metzgermeister
// merged with SDL version
//
// Revision 1.2  2000/02/26 00:28:42  hurdler
// Mostly bug fix (see borislog.txt 23-2-2000, 24-2-2000)
//
//
// DESCRIPTION:
//      convert Doom MUS data to a MIDI music data
//      used by both DOS/WIN32
//
//-----------------------------------------------------------------------------

#ifndef __OS2__

#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include "doomdef.h"
#include "i_system.h"
#include "byteptr.h"
#include "m_swap.h"

#include "qmus2mid.h"

// MUS events
#define MUS_EV_SCOREEND     6

ULONG  TRACKBUFFERSIZE = 65536UL;  /* 64 Ko */
struct Track track[16];

static unsigned char MUS2MIDcontrol[15] =
{
        0,                  /* Program change - not a MIDI control change */
        0x00,               /* Bank select */
        0x01,               /* Modulation pot */
        0x07,               /* Volume */
        0x0A,               /* Pan pot */
        0x0B,               /* Expression pot */
        0x5B,               /* Reverb depth */
        0x5D,               /* Chorus depth */
        0x40,               /* Sustain pedal */
        0x43,               /* Soft pedal */
        0x78,               /* All sounds off */
        0x7B,               /* All notes off */
        0x7E,               /* Mono */
        0x7F,               /* Poly */
        0x79                /* Reset all controllers */
};

static byte    MUSchannel;
static byte    MIDItrack;

#define fwritemem(p,s,n,f)  memcpy(*f,p,n*s);*f+=(s*n)

#if 1
#define fwriteshort(x,f)    BE_write_16(f,x)
#define fwritelong(x,f)     BE_write_32(f,x)
// [WDJ] Proper big-endian midi read/write
static void BE_write_16(byte **p, int16_t val)
{
  *(int16_t *)*p = BE_SWAP16_FAST(val);
  *p += sizeof(int16_t);
}

static void BE_write_32(byte **p, int32_t val)
{
  *(int32_t *)*p = BE_SWAP32_FAST(val);
  *p += sizeof(int32_t);
}
#else
// Used little-endian writes on big-endian data, does endian swap twice.
#define fwriteshort(x,f)    WRITE16(*f,((x>>8) & 0xff) | (x<<8))
#define fwritelong(x,f)     WRITEU32(*f,((x>>24) & 0xff) | ((x>>8) & 0xff00) | ((x<<8) & 0xff0000) | (x<<24))
#endif

#define last(e)         ((unsigned char)(e & 0x80))
#define event_type(e)   ((unsigned char)((e & 0x7F)>>4))
#define channel(e)      ((unsigned char)(e & 0x0F))


static void FreeTracks ( void )
{
    int i;

    for (i = 0; i < 16; i++ )
        if (track[i].data )
            free( track[i].data );
}


static void TWriteByte (byte MIDItrack, char bbyte)
{
    ULONG pos;

    pos = track[MIDItrack].current;
    if (pos < TRACKBUFFERSIZE )
        track[MIDItrack].data[pos] = bbyte;
    else
        I_Error("Mus Convert Error : Track buffer full.\n");

    track[MIDItrack].current++;
}


static void TWriteVarLen (byte tracknum, ULONG value)
{
    ULONG buffer;

    buffer = value & 0x7f;
    while( (value >>= 7) )
    {
        buffer <<= 8;
        buffer |= 0x80;
        buffer += (value & 0x7f);
    }
    while( 1 )
    {
        TWriteByte( tracknum, (byte)buffer);
        if (buffer & 0x80 )
            buffer >>= 8;
        else
            break;
    }
}

static int WriteMIDheader( USHORT ntrks, USHORT division, byte **file )
{
    fwritemem( MIDIMAGIC , 10, 1,file );
    fwriteshort( ntrks, file);
    fwriteshort( division, file );
    return 0;
}



static void WriteTrack (int tracknum, byte **file)
{
    USHORT size;
    size_t quot, rem;

    /* Do we risk overflow here ? */
    size = (USHORT)track[tracknum].current + 4;
    fwritemem( "MTrk", 4, 1, file );
    if (!tracknum )
        size += 33;

    fwritelong( size, file );
    if (!tracknum)
    {
        memset(*file,'\0',33);
        *file+=33;
    }
    quot = (size_t) (track[tracknum].current / 4096);
    rem = (size_t) (track[tracknum].current - quot*4096);
    fwritemem (track[tracknum].data, 4096, quot, file );
    fwritemem (((byte *) track[tracknum].data)+4096*quot, rem, 1, file );
    fwritemem (TRACKMAGIC2, 4, 1, file );
}


static void WriteFirstTrack (byte **file)
{
    USHORT size;

    size = 43;
    fwritemem( "MTrk", 4, 1, file );
    fwritelong( size, file );
    fwritemem( TRACKMAGIC3 , 4, 1, file );
    memset(*file,'\0',22);
    *file+=22;
    fwritemem( TRACKMAGIC4, 6, 1, file );
    fwritemem( TRACKMAGIC5, 7, 1, file );
    fwritemem( TRACKMAGIC6, 4, 1, file );
}

static ULONG ReadTime( byte **file )
{
    ULONG time = 0;
    int   bbyte;

    do
    {
        bbyte = *(*file)++;
        if (bbyte != EOF )
            time = (time << 7) + (bbyte & 0x7F);
    } while( (bbyte != EOF) && (bbyte & 0x80) );

    return time;
}

// return first MIDI channel available, except percussion channel 9
static char FirstChannelAvailable (char MUS2MIDchannel[])
{
    int i;
    signed char max = -1;

    // note: skip channel 15 which is percussions
    for (i = 0; i < 15; i++ )
        if (MUS2MIDchannel[i] > max )
            max = MUS2MIDchannel[i];

    // MIDI channel 9 is used for percussions
    return (max == 8 ? 10 : max+1);
}


static void MidiEvent(byte NewEvent)
{
    if ((NewEvent != track[MIDItrack].LastEvent) /*|| nocomp*/ )
    {
        TWriteByte( MIDItrack, NewEvent );
        track[MIDItrack].LastEvent = NewEvent;
    }
}

int qmus2mid (byte  *mus, 
              byte *mid,     // buffers in memory
              USHORT division, 
              int BufferSize, 
              int nocomp,
              int length, 
              int midbuffersize,
              unsigned long* midilength)    //faB: returns midi file length in here
{
    byte*    file_mus;
    byte*    file_mid;     // pointer in memory

    static   MUSheader* MUSh;
    byte     MIDIchannel;
    byte     et;
    USHORT   TrackCnt=0;
    int      i, event, data;
    ULONG    DeltaTime;
    ULONG    TotalTime=0;

    byte     MIDIchan2track[16];
    char     MUS2MIDchannel[16];

    char ouch = 0;

    file_mus = mus;
    file_mid = mid;

    //r = ReadMUSheader (&MUSh, file_mus);
    MUSh = (MUSheader *)mus;
    if (strncmp (MUSh->ID, MUSMAGIC, 4))
        return NOTMUSFILE;

    //fseek( file_mus, MUSh.scoreStart, SEEK_SET );
    file_mus = mus + MUSh->scoreStart;

    if (MUSh->channels > 15)      /* <=> MUSchannels+drums > 16 */
        return TOOMCHAN;

    for (i=0; i<16; i++)
    {
        MUS2MIDchannel[i] = -1;
        track[i].current = 0;
        track[i].vel = 64;
        track[i].DeltaTime = 0;
        track[i].LastEvent = 0;
        track[i].data = NULL;
    }

    if (BufferSize)
        TRACKBUFFERSIZE = ((ULONG) BufferSize) << 10;

    event = *(file_mus++);
    et    = event_type (event);
    MUSchannel = channel (event);

    while ( (et != MUS_EV_SCOREEND) &&
            file_mus-mus < length   &&     /*!feof( file_mus )*/
            (event != EOF) )
    {
        if (MUS2MIDchannel[MUSchannel] == -1)
        {
            MIDIchannel = MUS2MIDchannel[MUSchannel] =
                // if percussion use channel 9
                (MUSchannel == 15 ? 9 : FirstChannelAvailable (MUS2MIDchannel) );
            MIDItrack = MIDIchan2track[MIDIchannel] = (byte)TrackCnt++;
            if (!(track[MIDItrack].data = (char *) malloc(TRACKBUFFERSIZE) ))
            {
                FreeTracks();
                return MEMALLOC;
            }
        }
        else
        {
            MIDIchannel = MUS2MIDchannel[MUSchannel];
            MIDItrack   = MIDIchan2track [MIDIchannel];
        }
        TWriteVarLen( MIDItrack, track[MIDItrack].DeltaTime );
        track[MIDItrack].DeltaTime = 0;
        switch (et)
        {
        case 0 :                /* release note */
            MidiEvent((byte)0x90 | MIDIchannel);
            
            data = *(file_mus++);
            TWriteByte( MIDItrack, (byte)(data & 0x7F));
            TWriteByte( MIDItrack, 0 );
            break;
            
        case 1 :
            MidiEvent((byte)0x90 | MIDIchannel);
            
            data = *(file_mus++);
            TWriteByte( MIDItrack, (byte)(data & 0x7F));
            if (data & 0x80)
                track[MIDItrack].vel = (*file_mus++) & 0x7F;
            TWriteByte( MIDItrack, track[MIDItrack].vel );
            break;
            
        case 2 :
            MidiEvent((byte)0xE0 | MIDIchannel);
            
            data = *(file_mus++);
            TWriteByte( MIDItrack, (data & 1) << 6 );
            TWriteByte( MIDItrack, data >> 1 );
            break;
        case 3 :
            MidiEvent((byte)0xB0 | MIDIchannel);
            
            data = *(file_mus++);
            TWriteByte( MIDItrack, MUS2MIDcontrol[data] );
            if (data == 12 )
                TWriteByte( MIDItrack, MUSh->channels+1 );
            else
                TWriteByte( MIDItrack, 0 );
            break;
        case 4 :
            data = *(file_mus++);
            if (data )
            {
                MidiEvent((byte)0xB0 | MIDIchannel);
                
                TWriteByte( MIDItrack, MUS2MIDcontrol[data] );
            }
            else  /* program change */
                MidiEvent((byte)0xC0 | MIDIchannel);
            
            data = *(file_mus++);
            TWriteByte( MIDItrack, data & 0x7F );
            break;
        case 5 :
        case 7 :
            FreeTracks();
            return MUSFILECOR;
        default : break;
        }
        if (last( event ) )
        {
            DeltaTime = ReadTime( &file_mus );
            TotalTime += DeltaTime;
            for (i = 0; i < (int) TrackCnt; i++ )
                track[i].DeltaTime += DeltaTime;
        }
        event = *(file_mus++);
        if (event != EOF )
        {
            et = event_type( event );
            MUSchannel = channel( event );
        }
        else
            ouch = 1;
    }

    if (!division)
        division = 89;

#ifndef SDL
    for (i = 0; i < 16; i++)
    {
        if (track[i].current && track[i].data)
        {
            TWriteByte(i, (byte)0x00); // midi end of track code
            TWriteByte(i, (byte)0xFF);
            TWriteByte(i, (byte)0x2F);
            TWriteByte(i, (byte)0x00);

        }
    }
#endif

    WriteMIDheader( TrackCnt+1, division, &file_mid );
    WriteFirstTrack( &file_mid );
    for (i = 0; i < (int) TrackCnt; i++ )
        WriteTrack( i, &file_mid );

    if (file_mid>mid+midbuffersize)
        return MIDTOLARGE;

    FreeTracks();

    //faB: return lenght of Midi data
    *midilength = (file_mid - mid);

    return 0;
}

#endif // __OS2__
