/*
 *    Rosegarden MIDI Sequencer
 *
 *    File:           sequence_zilog.c
 *
 *    Description:    Sequencer playback functions.  ZILOG specific 
 *                    MIDI playback.
 *
 *    Author:         AJG; SGI dmedia MIDI library support by Archer Sully,
 *                    archer@elysium.esd.sgi.com, integrated into this
 *                    file by cc.
 *
 *    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/02/95        rwb             And Removed then all and stuck
 *                                            the whole lot in this file
 *
 */


#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/termio.h>
#include <sys/z8530.h>
#include <sys/stropts.h>

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

#include <Debug.h>

#define MidiPlaybackEventsMask MidiSoundEventsMask | MidiSetTempoEventMask

#define MIDI_EVENT_HEAP_SIZE 1024

long        SequenceTime;
long        TimeInc;
EventList  *FilteredTracks;
BinaryHeap  EventHeap;
Boolean     Playing;
Widget      StopButtonDlg;

struct itimerval TimerStruct;

int      MidiPort;

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

void Midi_SeqIntCB(void);



Boolean Midi_SeqOutInit(void)
{
    char            ErrBuff[128];
    struct termio   t;
    struct strioctl str;
    int             arg;
    int             OutputFlag;

BEGIN("Midi_SeqOutInit");

    OutputFlag = O_RDWR | O_NDELAY;

    if (MidiPortSync) OutputFlag |= O_SYNC;

    MidiPort = open(MidiPortName, OutputFlag, 0666);
    if (MidiPort < 0)
    {
        sprintf(ErrBuff, "Unable to open Midi Port %s\n", MidiPortName);
        Error(NON_FATAL_REPORT_TO_MSGBOX, ErrBuff);
        RETURN_BOOL(False);
    }

    t.c_iflag = IGNBRK;
    t.c_oflag = 0;
    t.c_cflag = B9600 | CS8 | CREAD | CLOCAL | HUPCL;
    t.c_lflag = 0;
    t.c_line = 1;
    t.c_cc[VINTR] = 0;
    t.c_cc[VQUIT] = 0;
    t.c_cc[VERASE] = 0;
    t.c_cc[VKILL] = 0;
    t.c_cc[VMIN] = 1;
    t.c_cc[VTIME] = 0;
    ioctl(MidiPort, TCSETAF, &t);

    if (ioctl(MidiPort, I_POP, 0) < 0)
    {
         Error(NON_FATAL_REPORT_TO_MSGBOX, "Unable to configure MIDI port");
         RETURN_BOOL(False);
    }

    str.ic_cmd = SIOC_RS422;
    str.ic_timout = 0;
    str.ic_len = 4;
    arg = RS422_ON;
    str.ic_dp = (char *)&arg;

    if (ioctl(MidiPort, I_STR, &str) < 0) 
    {
        Error(NON_FATAL_REPORT_TO_MSGBOX, "Can't ioctl RS422");
        RETURN_BOOL(False);
    }


    str.ic_cmd = SIOC_EXTCLK;
    str.ic_timout = 0;
    str.ic_len = 4;
    arg = EXTCLK_32X;
    str.ic_dp = (char *)&arg;

    if (ioctl(MidiPort, I_STR, &str) < 0) 
    {
        Error(NON_FATAL_REPORT_TO_MSGBOX, "Can't ioctl EXTCLK");
        RETURN_BOOL(False);
    }

    str.ic_cmd = SIOC_ITIMER;
    str.ic_timout = 0;
    str.ic_len = 4;
    arg = 0;
    str.ic_dp = (char *)&arg;

    if (ioctl(MidiPort, I_STR, &str) < 0) 
    {
        Error(NON_FATAL_REPORT_TO_MSGBOX, "Can't ioctl ITIMER");
        RETURN_BOOL(False);
    }

RETURN_BOOL(True);
}


void Midi_SeqOutByte(byte OutByte)
{
BEGIN("Midi_SeqOutByte");

    write(MidiPort, &OutByte, 1);

END;
}


void Midi_SeqWriteEvent(EventList OutEvent)
{
byte        StatusByte;
static byte LastStatusByte = 0;

BEGIN("Midi_SeqWriteEvent");

    StatusByte = OutEvent->Event.EventCode;

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

    if (MessageType(StatusByte) == MIDI_CTRL_CHANGE ||
        MessageType(StatusByte) == MIDI_PROG_CHANGE)
    {
                /*************************/
                /* One parameter events. */
                /*************************/

        write(MidiPort, &OutEvent->Event.EventData.ProgramChange.Program, 1);

/*       Midi_SeqOutByte(OutEvent->Event.EventData.ProgramChange.Program);*/
    }
    else
    {
                /**************************/
                /* Two parameters events. */
                /**************************/

        write(MidiPort, &OutEvent->Event.EventData.NoteOn.Note, 2);

/*
        Midi_SeqOutByte(OutEvent->Event.EventData.NoteOn.Note);
        Midi_SeqOutByte(OutEvent->Event.EventData.NoteOn.Velocity);*/
    }

END;
}



void Midi_SeqStopPlayingCB(Widget w, XtPointer a, XtPointer b)
{
BEGIN("Midi_SeqStopPlayingCB");
    Playing = False;
END;
}


void Midi_SeqAllNotesOff(void)
{
int i;
byte CmdBuffer[3];

BEGIN("Midi_SeqAllNotesOff");

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

    for(i = 0; i < 16; ++i)
    {
        CmdBuffer[0] = CreateMessageByte(MIDI_CTRL_CHANGE, i);
        write(MidiPort, CmdBuffer, 3);
    }

END;
}


int Midi_SeqPlayFile()
{
int       i;
Widget    StopButton;
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]);
    }

    SequenceTime = 0;
    TimeInc = 24000 / MIDIHeaderBuffer.Timing.Division;

    StopButtonDlg = XtCreatePopupShell("Stop Button Dialogue",
                                transientShellWidgetClass, topLevel, NULL, 0);

    StopButton = YCreateSurroundedWidget("STOP", commandWidgetClass,
                                StopButtonDlg, SurroundShade, NoShade);

    XtAddCallback(StopButton, XtNcallback, Midi_SeqStopPlayingCB, NULL);

    op = YPlacePopupAndWarp(StopButtonDlg, XtGrabNonexclusive,
                                                 StopButton, StopButton);

    YAssertDialogueActions(StopButtonDlg, StopButton, StopButton, NULL);

    Playing = True;

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


/*  TimerStruct.it_interval.tv_sec  = 0;
    TimerStruct.it_interval.tv_usec = 10000;
    TimerStruct.it_value.tv_sec  = 1;
    TimerStruct.it_value.tv_usec = 0;
    setitimer(ITIMER_REAL, &TimerStruct, NULL); */

    Midi_SeqIntCB();

    RETURN_INT(0);
}



void Midi_SeqIntCB(void)
{
    EventList NextEvent, NewEvent;
    long      TempoValue;
    clock_t   Time, LastTime;
    struct tms ThrowAway;

BEGIN("Midi_SeqIntCB");

/*    signal(SIGALRM, Midi_SeqIntCB);*/

    if (!Playing)
    {
/*              TimerStruct.it_value.tv_sec = 0;
                TimerStruct.it_value.tv_usec = 0;
                signal(SIGALRM, SIG_IGN);
                setitimer(ITIMER_REAL, &TimerStruct, NULL);*/

                XtDestroyWidget(StopButtonDlg);
                END;
    }


/*  TimerStruct.it_interval.tv_sec  = 0;
    TimerStruct.it_interval.tv_usec = 10000;
    TimerStruct.it_value.tv_sec  = 0;
    TimerStruct.it_value.tv_usec = 10000;
    setitimer(ITIMER_REAL, &TimerStruct, NULL);*/

    Time = times(&ThrowAway);

    while(Playing)
    {
        LastTime = Time;
        Time = times(&ThrowAway);

        if (Time > LastTime)
        {
            if (MidiMaintainTempo)
            {
                SequenceTime += (Time - LastTime) * TimeInc;
            }
            else SequenceTime += TimeInc;
        }

        while ((NextEvent = (EventList)Value(EventHeap, Root)) &&
                       NextEvent->Event.DeltaTime <= SequenceTime) 
        {
            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 = 10000 * MIDIHeaderBuffer.Timing.Division /
                                                              TempoValue;
                }
            }
            else Midi_SeqWriteEvent(NextEvent);

            ExtractMin(EventHeap);
            if (Next(NextEvent)) 
            {
                BHeapInsert(EventHeap, Next(NextEvent));
            }

            if (!HeapSize(EventHeap)) 
            {
                Playing = False;
                break;
            }
        }

        if (XtAppPending(appContext)) XtAppProcessEvent(appContext, XtIMAll);

    }

/*  TimerStruct.it_value.tv_sec = 0;
    TimerStruct.it_value.tv_usec = 0;
    signal(SIGALRM, SIG_IGN);
    setitimer(ITIMER_REAL, &TimerStruct, NULL); */

    Midi_SeqAllNotesOff();
    close(MidiPort);
    XtDestroyWidget(StopButtonDlg);
    END;
END;
}

