/*
 *    Rosegarden MIDI Sequencer
 *
 *    File:           Sequence_LINUX.c
 *
 *    Description:    Sequencer playback functions.  Provides a generic
 *                    core to which the various hardware specific things
 *                    can be bolted, switches permitting.  Looking for
 *                    an elegant way of accomplishing this dynamically,
 *                    or indeed at all.
 *
 *    History:
 *
 *    Update  Date            Programmer      Comments
 *    ======  ====            ==========      ========
 *
 *    001     29/03/94        AJG             File Created.
 *    002     09/08/94        JPff            Error check in Midi_SeqOutInit
 *    003     17/02/95        cc              Added #ifdefs for HAVE_Z8530
 *    004     20.2.96         rwb             Changes to make sequencing
 *                                            support more portable.
 *    005     24.4.96         rwb             Completed initial Playback
 *                                            changes under Linux. 
 *    006     30.5.96         rwb             Midi Out and Synth Support.
 *    007     23.7.96         rwb             Patch Sets and Patch loading
 *                                            for FM synths started.
 *
 *                  Channel allocations inconsistent but this
 *                  works with the external devices.
 *
 *                  Initial Patches GUI is unstable still.
 *
 */


#undef POSIX_PLEASE
#undef _POSIX_SOURCE

#include "MidiSetupDlgs.h"
#include "Globals.h"
#include "Consts.h"
#include <MidiXInclude.h>
#include <MidiFile.h>
#include <MidiErrorHandler.h>
#include <MidiBHeap.h>
#include <MidiTrack.h>

#include "Tracking.h"		/* for play-tracking */
#include "Sequence.h"
#include "Menu.h"
#include <Debug.h>

/* ++++ Predeclarations ++++ */
void seqbuf_dump();
void Midi_SeqPlayNotes();
void Midi_SeqAllNotesOff();
void Midi_SeqWriteMidiEvent();
void Midi_SeqWriteSynthEvent();
void Midi_SendInitialProgramChange();

extern DeviceList Midi_NewDeviceList();
extern void Midi_DeviceCloseCB();
extern void Midi_GetDeviceInfo();
extern int Midi_OpenDevice();
extern void Midi_SeqStopPlayingCB();
extern byte InitialPatches[16];
extern Cursor PlayingCursor;
extern Widget topLevel;

char *MidiPortName;  /* we use this for the device, not the MIDI port */
char *MidiDevice;
char *SynthDevice;
int MidiDev;
int SynthDev;
int SynthEnable = 0; /* for playback only, toggles between MIDI and synth */
int SynthType;
int seqfd;

#define MidiPlaybackEventsMask MidiSoundEventsMask | MidiSetTempoEventMask

#define MIDI_EVENT_HEAP_SIZE 1024

EventList  *FilteredTracks;
BinaryHeap  EventHeap;
Boolean     Playing;
int         MidiPort;

SEQ_DEFINEBUF(1024); /* then define a buffer for the API */


/*
 * Function:     Midi_SeqPlayFile
 *
 * Description:  Once a tree.
 *
 */
int
Midi_SeqPlayFile()
{
    int       i;
    EventList Temp;

BEGIN("Midi_SeqPlayFile");

    if ( Playing )
        RETURN_INT(1);

    if ( Midi_OpenDevice(O_WRONLY, MidiPortName ) != True )
        RETURN_INT(1);

    Midi_SendInitialProgramChange();

    EventHeap = CreateBHeap(MIDI_EVENT_HEAP_SIZE, Midi_EventListTimeLessp);

    /* do a Xt malloc to the size of the event list */
    FilteredTracks = (EventList *)XtMalloc(MIDIHeaderBuffer.NumTracks *
                                                           sizeof(EventList));

    /*************************************************************/
    /* Produce filtered versions of the tracks in the MIDI file. */
    /* Filter out any non-soundable events - these can simply be */
    /* ignored during playback.                                  */
    /*************************************************************/

    for (i = 0; i < MIDIHeaderBuffer.NumTracks; ++i)
    {
        Temp = Midi_TrackFilterByEvent(MIDITracks[i], MidiPlaybackEventsMask);

        FilteredTracks[i] = Midi_TrackConvertToTwoPointRepresentation(Temp);

        Midi_TrackDelete(Temp); /* so we've got a temp list of two points */

        if (FilteredTracks[i]) BHeapInsert(EventHeap, FilteredTracks[i]);
    }

    Playing = True;   /* start the loop and away we go */

    Midi_EnterMenuMode(PlaybackMode);
    Midi_LeaveMenuMode(NotPlayingMode);
    Midi_PlayTrackingOpen();

    /* process any pending X events */
    while(XtAppPending(appContext))
    {
        XtAppProcessEvent(appContext, XtIMAll);
    }

    /* cursor change */
    XDefineCursor(display, XtWindow(topLevel), PlayingCursor);
    XSync(display, False);

    Midi_SeqPlayNotes();

    if ( Playing == True ) {
        Midi_SeqStopPlayingCB(NULL, NULL, NULL);
	Midi_PlayTrackingClose();
    }

    RETURN_INT(1);		/* so as not to enter Playing mode again */
}


/*
 * Function:     Midi_SeqPlayNotes
 *
 * Description:  Play the events.  Sibling to Midi_SeqIntCB.
 *
 */
void
Midi_SeqPlayNotes()
{
    EventList         NextEvent;
    unsigned int      TempoValue;
    unsigned int      LastTime = 0;
    float             Deltatime = 0.0;
    float             PlayTime = 0.0;
    float             TimeInc;

    BEGIN("Midi_SeqPlayNotes");

    ioctl(seqfd, SNDCTL_SEQ_RESET, 0);

    SEQ_START_TIMER();

    while ( ( NextEvent = (EventList)Value(EventHeap, Root) ) &&
                                ( Playing == True ) )
    {
        if ( ( NextEvent->Event.DeltaTime ) > LastTime )
        {
            Deltatime = ( ( NextEvent->Event.DeltaTime ) - LastTime ) * TimeInc;
            PlayTime += Deltatime;

            SEQ_WAIT_TIME( (int) PlayTime );

            SEQ_DUMPBUF();  /* synchronisation dump */

	    if (Deltatime > 0) {
	      Midi_PlayTrackingJump(LastTime);
	    }

            LastTime = NextEvent->Event.DeltaTime;
        }

        if (NextEvent->Event.EventCode == MIDI_FILE_META_EVENT)
        {
            if (NextEvent->Event.EventData.MetaEvent.MetaEventCode ==
                                                          MIDI_SET_TEMPO)
            {

              TempoValue = (NextEvent->Event.EventData.MetaEvent.Bytes
                 << 16) | (*(&NextEvent->Event.EventData.MetaEvent.Bytes
                 + 1) << 8) | *(&NextEvent->Event.EventData.MetaEvent.Bytes
                 + 2);

              TimeInc = TempoValue / ( 10000.0 * MIDIHeaderBuffer.
                                                        Timing.Division );
            }
        }
        else
        {
            /* write the event out to the desired device */
            if ( SynthEnable )
                Midi_SeqWriteSynthEvent(NextEvent);
            else
                Midi_SeqWriteMidiEvent(NextEvent);
        }

        /* shuffle around and get the next event */
        ExtractMin(EventHeap);
        if (Next(NextEvent))
        {
            BHeapInsert(EventHeap, Next(NextEvent));
        }

        if (!HeapSize(EventHeap))
        {
            Midi_SeqStopPlayingCB(NULL, NULL, NULL);
	    Midi_PlayTrackingClose();
        }
        while (XtAppPending(appContext)) XtAppProcessEvent(appContext, XtIMAll);
    }
END;
}



/*
 * Function:     seqbuf_dump (USS Lite)
 *
 * Description:  Write the queued events out of the selected port.
 *
 */
void
seqbuf_dump()
{
    BEGIN("seqbuf_dump");

    if ( _seqbufptr )
        if ( write ( seqfd, _seqbuf, _seqbufptr ) == -1 )
        {
            /* write has failed */
            perror ("write /dev/sequencer");
        }
    _seqbufptr = 0;

    /* synchonisation call - we wait for the alloted period of time */
    ioctl(seqfd, SNDCTL_SEQ_SYNC);

END;
}        



/*
 * Function:     Midi_SeqWriteMidiEvent
 *
 * Description:  Write the events onto the output buffer according to type
 *
 */
void Midi_SeqWriteMidiEvent(EventList OutEvent)
{
    byte        StatusByte;
    static byte LastStatusByte = 0;

    BEGIN("Midi_SeqWriteMidiEvent");

#ifdef DEBUG
    fprintf(stdout,"Writing out Event Data %x\n",OutEvent->
                                      Event.EventData.NoteOn.Note);
#endif
    
    StatusByte = OutEvent->Event.EventCode;

    if (StatusByte != LastStatusByte)
    {
        LastStatusByte = StatusByte;
        SEQ_MIDIOUT(MidiDev,StatusByte);
    }

    if (MessageType(StatusByte) == MIDI_CTRL_CHANGE ||
        MessageType(StatusByte) == MIDI_PROG_CHANGE)
    {
        SEQ_MIDIOUT(MidiDev,(byte)OutEvent->Event.EventData.
                                                   ProgramChange.Program);
    }
    else
    {
        /* out with an event */
        SEQ_MIDIOUT(MidiDev,(byte)OutEvent->Event.EventData.NoteOn.Note);
        SEQ_MIDIOUT(MidiDev,(byte)OutEvent->Event.EventData.NoteOn.Velocity);
    }

END;
}


/*
 * Function:     Midi_SeqWriteSynthEvent
 *
 * Description:  Kajagoogoo
 *
 */
void
Midi_SeqWriteSynthEvent(EventList OutEvent)
{
    byte        StatusByte;
    static byte LastStatusByte = 0;
    byte Note = (byte)OutEvent->Event.EventData.NoteOn.Note;
    byte Vely = (byte)OutEvent->Event.EventData.NoteOn.Velocity;
    static byte Channel = 0;

    BEGIN("Midi_SeqWriteMidiEvent");

    StatusByte = OutEvent->Event.EventCode;

    if (StatusByte != LastStatusByte)
    {
        LastStatusByte = StatusByte;
    }

    /* channel for output */
    if ( (StatusByte&0xf) != Channel )
    {
        Channel = ((StatusByte & 0xf));
    }

    switch(MessageType(StatusByte & 0xf0))
    {
        case MIDI_KEY_PRESSURE:
            SEQ_KEY_PRESSURE(SynthDev, Channel, Note, Vely);
            break;

        case MIDI_NOTEON:
            SEQ_START_NOTE(SynthDev, Channel, Note, Vely);
            break;

        case MIDI_NOTEOFF:
            SEQ_STOP_NOTE(SynthDev, Channel, Note, Vely);
            break;

        case MIDI_CTL_CHANGE:
             SEQ_CONTROL(SynthDev, Channel, Note, Vely);
             break;

        case MIDI_CHN_PRESSURE:
             SEQ_CHN_PRESSURE(SynthDev, Channel, Note);
             break;

        case MIDI_PITCH_BEND:
             SEQ_BENDER(SynthDev, Channel, Note);

        case MIDI_PGM_CHANGE:
             SEQ_SET_PATCH(SynthDev, Channel, Note);
             break;
 
        default:
            break;
    }

    SEQ_DUMPBUF();

END;
}

/*
 * Function:     Midi_SeqAllNotesOff
 *
 * Description:  Clear down all channels on the specified device.
 *
 */
void Midi_SeqAllNotesOff(void)
{
    int i;
    byte CmdBuffer[3];

    BEGIN("Midi_SeqAllNotesOff");

    CmdBuffer[1] = 123;
    CmdBuffer[2] = 0;

    for(i = 0; i < 16; ++i)
    {
        if ( SynthEnable )
        {
            /* clear down the synth */
            
            /* reel off the NOTES OFF queue when it becomes available */
        }
        else
        {
            /* note that CreateMessageByte is another well hidden macro */
            CmdBuffer[0] = CreateMessageByte(MIDI_CTRL_CHANGE, i);
            SEQ_MIDIOUT(MidiDev,(byte)CmdBuffer[0]);
            SEQ_MIDIOUT(MidiDev,(byte)CmdBuffer[1]);
            SEQ_MIDIOUT(MidiDev,(byte)CmdBuffer[2]);
        }
    }

    SEQ_DUMPBUF();
    END;
}

/*
 * Warnings:
 *
 * If the MIDI Device isn't in multi timbral mode then the
 * last loaded program change is going to be used for all
 * the channels.
 *
 */
void Midi_SendInitialProgramChange()
{
    int i;

    /* set up some patches */
    for ( i = 0; i < 16; i++ )
    {
        if ( SynthEnable )
        {
            SEQ_SET_PATCH(SynthDev, i, InitialPatches[i]);
        }
        else
        {
            SEQ_MIDIOUT(MidiDev ,(byte)(MIDI_PGM_CHANGE+(byte)i));
            SEQ_MIDIOUT(MidiDev ,(byte)InitialPatches[i]);
        }
    }

    SEQ_DUMPBUF();
}
