/*
 *    Rosegarden MIDI Sequencer
 *
 *    File:           sequence_sgi_midi.c
 *
 *    Description:    Sequencer playback functions using SGI dmedia library
 *
 *    Author:         Mostly Archer Sully, archer@elysium.esd.sgi.com
 *                    (bugs introduced by Andy Green and Chris Cannam)
 */


#undef POSIX_PLEASE
#undef _POSIX_SOURCE    /* this fancy SGI stuff will never compile as POSIX */

#include "Globals.h"
#include "Consts.h"

#include <MidiXInclude.h>
#include <MidiFile.h>
#include <MidiErrorHandler.h>
#include <MidiBHeap.h>
#include <MidiTrack.h>

#include "Sequence.h"

#include <signal.h>
#include <stdio.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/times.h>
#include <sys/signal.h>

#include <sys/prctl.h>
#include <ulocks.h>

#undef _XOPEN_SOURCE
#undef _POSIX_SOURCE
#include <dmedia/midi.h>

#include <sys/stropts.h>

#include <stdlib.h>
#include <unistd.h>
#include <sys/param.h>

#include "Tracking.h"		/* for play-tracking */

#include <Debug.h>

#define MidiPlaybackEventsMask MidiSoundEventsMask | MidiSetTempoEventMask

#define MIDI_EVENT_HEAP_SIZE 1024

long        SequenceTime;
long        TimeInc;
EventList  *FilteredTracks;
BinaryHeap  EventHeap;
static Boolean Playing = False;

struct itimerval TimerStruct;

MDport   MidiPort;

char    *MidiPortName;
Boolean  MidiPortSync        = False;
Boolean  MidiMaintainTempo   = True;

/* for process sync. */
static char *tmplate = "/tmp/.roseXXXXX";
static char *fname;
static usptr_t *arena;
static usema_t *semap;

void Midi_SeqIntCB(void);


extern int midiPortCount;

void Midi_GetDeviceInfo()
{
  midiPortCount = mdInit();
}


Boolean Midi_SeqOutInit(void)
{
    char            ErrBuff[128];
    int             OutputFlag;

BEGIN("Midi_SeqOutInit");

    OutputFlag = O_RDWR | O_NDELAY;

    if (MidiPortSync) OutputFlag |= O_SYNC;

    if (midiPortCount <= 0)
    {
        sprintf(ErrBuff, "Unable to initialize midi %s\n", MidiPortName);
        Error(NON_FATAL_REPORT_TO_MSGBOX, ErrBuff);
        RETURN_BOOL(False);
    }

    MidiPort = mdOpenOutPort(MidiPortName);

    if (MidiPort == NULL)
    {
        sprintf(ErrBuff, "Unable to open midi port %s\n", MidiPortName);
        Error(NON_FATAL_REPORT_TO_MSGBOX, ErrBuff);
        RETURN_BOOL(False);
    }

    /*    fprintf(stderr,"opened midi port %s\n",MidiPortName);*/

#if 0
    if (fcntl(mdGetFd(MidiPort), F_SETFL, FNONBLOCK) < 0)
    {
        sprintf(ErrBuff, "Unable to set NONBLOCK mode %s\n", MidiPortName);
        Error(NON_FATAL_REPORT_TO_MSGBOX, ErrBuff);
        RETURN_BOOL(False);
    }

#endif
    mdSetStampMode(MidiPort, MD_RELATIVETICKS);

    fname = strdup(tmplate);
    mktemp(fname);
    arena = usinit(fname);
    unlink(fname);
    semap = usnewsema(arena, 0);


RETURN_BOOL(True);
}


void Midi_SeqStopPlayingCB(Widget w, XtPointer a, XtPointer b)
{
BEGIN("Midi_SeqStopPlayingCB");

/* needs to use a semaphore to control playback instead of a flag */
    uspsema(semap);
    Playing = False;

    Midi_PlayTrackingClose();

    Playing = False;
    END;
}


extern void Midi_LeaveMenuMode(long);
extern void Midi_EnterMenuMode(long);

void Midi_DeviceCloseCB(Widget w, XtPointer a, XtPointer b)
{
  if (!Playing) return;

  Midi_SeqStopPlayingCB(w, a, b);
  
  Midi_LeaveMenuMode(PlaybackMode);
  Midi_EnterMenuMode(NotPlayingMode);

  Midi_PlayTrackingClose();
}


int Midi_SeqPlayFile()
{
int       i;
XPoint    op;
EventList Temp;

BEGIN("Midi_SeqPlayFile");

    if (!Midi_SeqOutInit()) RETURN_INT(1);

    EventHeap = CreateBHeap(MIDI_EVENT_HEAP_SIZE, Midi_EventListTimeLessp);

    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);

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

    Playing = True;

    Midi_PlayTrackingOpen();

    while(XtAppPending(appContext))
    {
        XtAppProcessEvent(appContext, XtIMAll);
    }

    mdSetDivision(MidiPort, MIDIHeaderBuffer.Timing.Division);
    mdSetTempo(MidiPort, 500000);
    mdSetOrigin(MidiPort, 0);

/* sproc off this one */
    if (sproc((void (*)(void *))(Midi_SeqIntCB), PR_SALL, 0) < 0)
    {
        perror("sproc");
    }

    usvsema(semap);

    RETURN_INT(0);
}


void Midi_SeqIntCB(void)
{
    EventList NextEvent;
    long Mystamp, Oldstamp;
    MDevent evbuf[100];
    int evx = 0;
    char prbuf[800];
    int done = 1;
 
    BEGIN("Midi_SeqIntCB");

    uspsema(semap);      /* the UI proc lets us go... */

    Oldstamp = -1;
    do
    {
        NextEvent = (EventList)Value(EventHeap, Root);
        Mystamp = NextEvent->Event.DeltaTime;
        evx = 0;

        while (done && (NextEvent = (EventList)Value(EventHeap, Root)) && 
                       (NextEvent->Event.DeltaTime == Mystamp))
        {
            evbuf[evx].stamp = Mystamp;
            evbuf[evx].msglen = 0;
            /* this is the only playable event that goes in sysexmsg */

            /* tempo change code wrong... it doesn't seem to be getting
                                                              picked up */
            if (NextEvent->Event.EventData.MetaEvent.MetaEventCode ==
                                                          MIDI_SET_TEMPO)
            {
	      /*                fprintf(stderr, "tempo change\n");*/
                evbuf[evx].sysexmsg = (char *)mdMalloc(6);
                evbuf[evx].sysexmsg[0] = MD_META;
                evbuf[evx].sysexmsg[1] = 0x51;
                evbuf[evx].sysexmsg[2] = 3;
                evbuf[evx].sysexmsg[3] = 
                ((char *)(&NextEvent->Event.EventData.MetaEvent.Bytes))[0];
                  evbuf[evx].sysexmsg[4] = 
                ((char *)(&NextEvent->Event.EventData.MetaEvent.Bytes))[1];
                  evbuf[evx].sysexmsg[5] = 
                ((char *)(&NextEvent->Event.EventData.MetaEvent.Bytes))[2];
                  evbuf[evx].msglen = 6;
            }
            else
            {
                unsigned char StatusByte = evbuf[evx].msg[0] =
                                                NextEvent->Event.EventCode;
                if (MessageType(StatusByte) == MIDI_CTRL_CHANGE ||
                    MessageType(StatusByte) == MIDI_PROG_CHANGE)
                {
                    evbuf[evx].msg[1] =
                          NextEvent->Event.EventData.ProgramChange.Program;
                }
                else
                {
                    evbuf[evx].msg[1] = NextEvent->Event.EventData.NoteOn.Note;
                    evbuf[evx].msg[2] = NextEvent->Event.EventData.
                                                               NoteOn.Velocity;
                }
            }
/*          mdPrintEvent(prbuf, &evbuf[0], 1);
            puts(prbuf); */

            if (mdSend(MidiPort, evbuf, 1) <= 0)
	      {
                perror("mdSend");
         	break;
	      }

	    usvsema(semap);

	    if (evbuf[0].msglen > 0) 
	      mdFree(evbuf[0].sysexmsg);


	    ExtractMin(EventHeap);
	    if (Next(NextEvent)) 
	      {
		BHeapInsert(EventHeap, Next(NextEvent));
	      }
	    done = uscpsema(semap);
	}

	Midi_PlayTrackingJump(Mystamp);
 
	
	if (!HeapSize(EventHeap)) 
	  {
	    Playing = False;
	    break;
	  }
    } while (done);

    mdPanic(MidiPort);
    mdClosePort(MidiPort);
    if (done)
      {
	usvsema(semap);
  
	Midi_LeaveMenuMode(PlaybackMode);
	Midi_EnterMenuMode(NotPlayingMode);
	
	Midi_PlayTrackingClose();
      }
    END;
}
