/* MusicSerial.c
    Copyright (c) 1990,1991,1992,1993 by Thomas E. Janzen
    All Rights Reserved

    THIS SOFTWARE IS FURNISHED FREE OF CHARGE FOR STUDY AND USE AND MAY
    BE COPIED ONLY FOR PERSONAL USE OR COMPLETELY AS OFFERED WITH NO
    CHANGES FOR FREE DISTRIBUTION.  NO TITLE TO AND OWNERSHIP OF THE
    SOFTWARE IS HEREBY TRANSFERRED.  THOMAS E. JANZEN ASSUMES NO 
    RESPONSIBILITY FOR THE USE OR RELIABILITY OF THIS SOFTWARE.
    
    Thomas E. Janzen
    208A Olde Derby Road
    Norwood, MA  02062-1761
    (617)769-7733
**  FACILITY:
**
**    AlgoRhythms music improviser on Commodore (TM) Amiga (TM)
**    compiled with SAS/C Amiga Compiler 6.50 
**
**  ABSTRACT:
**
**    MusicSerial.c manages the serial device at the MIDI bit rate.
**    All sends to MIDI occur here.
**
**  AUTHORS: Thomas E. Janzen
**
**  CREATION DATE:    26-MAR-1990
**
**  MODIFICATION HISTORY:
**    DATE    NAME    DESCRIPTION
**  7 Dec 90 T. Janzen Used SendIO rather than DoIO - didn't up performance
**  4 Nov 91 T. Janzen incorporate MIDI running status; delete SendNoteOff
**  8 DEC 91 T. Janzen conform to SAS/C 5.10b remove extern from functs
**  4 Jan 92 TEJ  last changes for 2.0
**  14 SEP 92 TEJ Make play_note_on use NOTEOFF when dynamic is zero 
**                  (works, fixes stuck note bug)
**  15 SEP 92 TEJ Fix stuck notes by using MAXVOICE in stop_all_notes
*    2 AUG 93 TEJ stop guru's by checking before set audio call
**--
*/

#include <exec/types.h>
#include <exec/nodes.h>
#include <exec/lists.h>
#include <exec/ports.h>
#include <exec/libraries.h>
#include <exec/devices.h>
#include <devices/serial.h>
#include <exec/io.h>
#include <intuition/intuition.h>
#include <proto/dos.h>
#include <proto/graphics.h>
#include <proto/exec.h>
#include <proto/mathffp.h>
#include <proto/intuition.h>
#ifdef CLI
#include <stdio.h>
#endif
#include "Window.h"
#include "AlgoRhythms.h"
#include "MusicSerial.h"
#include "Record.h"
#include "audio.h"

#define NOTEONCMD    (0x90)    /* MIDI Note on byte         */
#define NOTEOFFCMD   (0x80)    /* MIDI Note off byte        */
#define START        (0xFA)    /* MIDI Start play byte     */
#define STOP         (0xFC)    /* MIDI Stop play byte        */
#define CONTINUE     (0xFB)    /* MIDI Continue play byte    */
#define TIMINGCLOCK  (0xF8)    /* MIDI Timing clock byte    */

static int response;

static struct IOExtSer *ior_ser;
static struct MsgPort *port;

extern struct IORequest *CreateExtIO();
extern void DeletePort(struct MsgPort *);

/* strings for error alerts */
static char quit_str[5]      = "Quit",
            ser_err_str[20]  = "Serial Device Error",
            prt_err_str[11]  = "Port Error",
            io_err_str[19]   = "Create ExtIO Error",
            dev_err_str[20]  = "Serial Device Error",
            prm_err_str[15]  = "Set Parm Error",
            wrt_err_str[26]  = "Serial.device write error";
#ifndef CLI
struct IntuiText quit_txt 
    = {2, 1, JAM1, 5, 4, &font_choice, quit_str, NULL};
#endif
static char    playbuffer[4];    /* MIDI note message buffer     */

#ifdef MEASURE
unsigned int gi_notes_measure = 0;
#endif

int set_serial(struct IOExtSer *io, unsigned long rbuf_len,
    unsigned char rlen, unsigned char wlen, unsigned long brk, 
    unsigned long baud, unsigned char sf, 
    unsigned long ta0, unsigned long ta1)
/*
** FUNCTIONAL DESCRIPTION:
**  Sets up serial port
**
** RETURN VALUE:
**      description: 
**        data_type: 
**
** ARGUMENTS:
**
**  io-
**         description: IO device structure
**           data_type: pointer to struct IOExtSer
**              access: read/write
**
**  rbuf_len-
**         description: read buffer length returned
**           data_type: unsigned long
**              access: read only
**
**  rlen-
**         description: length of read buffer
**           data_type: unsigned char
**              access: read only
**
**  wlen-
**         description: length of write buffer
**           data_type: unsigned char
**              access: read only
**
**  brk-
**         description: length of break time
**           data_type: unsigned long
**              access: read only
**
**  baud-
**         description: baud rate
**           data_type: unsigned long
**              access: read only
**
**  sf-
**         description: serial flags
**           data_type: unsigned char
**              access: read only
**
**  ta0-
**         description: misc flags
**           data_type: unsigned long
**              access: read only
**
**  ta1-
**         description: misc flags
**           data_type: unsigned long
**              access: read only
**
** DESIGN:
**  ROUTINE
**  : Fill out io structure for setting up serial device
**  : send the command to the serial device
**  : IF set up failed
**  : : set quit and fubar
**  : : post error
**  : ENDIF
**  : return error
**  ENDROUTINE
*/
{
#ifndef CLI
    auto struct IntuiText 
        SerErrTxt = {2, 1, JAM1, 5, 15, &font_choice, ser_err_str, NULL};
#endif
    auto int error;

    io->io_ReadLen    = rlen;
    io->io_BrkTime    = brk; /*length of break time (irrelevant)*/
    io->io_Baud       = baud;
    io->io_WriteLen   = wlen;
    io->io_StopBits   = 0x01;
    io->io_RBufLen    = rbuf_len;
    io->io_SerFlags   |= (1 << SERB_RAD_BOOGIE);
    io->IOSer.io_Command = SDCMD_SETPARAMS;
    io->io_TermArray.TermArray0 = ta0;
    io->io_TermArray.TermArray1 = ta1;
    if ((error = DoIO(io)) != 0) 
    {
        gi_quit = TRUE;
        gi_fubar = TRUE;
#ifdef CLI
        puts(ser_err_str);
#else
        response = AutoRequest(w, &SerErrTxt, &quit_txt, &quit_txt, 
                               0L, 0L, 300L, 60L);
#endif
    }
    return error;
}

void open_midi_port(void)
/*
** FUNCTIONAL DESCRIPTION:
**  Opens the serial devices for MIDI usage
**
** DESIGN:
**  ROUTINE
**  : CreatePort to serial device
**  : IF failed
**  : : set quit and fubar
**  : : post error
**  : ENDIF
**  : ior_ser = CreateExtIO()
**  : IF failed
**  : : set quit and fubar
**  : : post error
**  : ENDIF
**  : OpenDevice()
**  : IF failed
**  : : set quit and fubar
**  : : post error
**  : ENDIF
**  : IF not failed yet
**  : : set_serial()
**  : : IF failed
**  : : : set fubar and quit
**  : : : post error
**  : : ENDIF
**  : ENDIF
**  : return
**  ENDROUTINE
*/
{
#ifndef CLI
    auto struct IntuiText 
        PrtErrTxt = {2, 1, JAM1, 5, 15, &font_choice, prt_err_str, NULL},
        IOErrTxt  = {2, 1, JAM1, 5, 15, &font_choice, io_err_str,  NULL},
        DevErrTxt = {2, 1, JAM1, 5, 15, &font_choice, dev_err_str, NULL},
        PrmErrTxt = {2, 1, JAM1, 5, 15, &font_choice, prm_err_str, NULL};
#endif
    auto int error;
    auto unsigned long  rbl = 512,    /*read buffer length*/
                        brk = 750000, /* length of break in usec */
                        baud = 31250, /* MIDI baud rate 31.25k bits/sec*/
                        t0  = 0x51040303,    /*termination characters*/
                        t1  = 0x03030303;
    auto unsigned char  rwl = 0x08, /*bits per read char */
                        wwl = 0x08, /*bits per write char */
                        sf  = 0x00; /*serial flags */

    port = CreatePort(SERIALNAME, 0);
    if (NULL == port)
    {
        gi_quit = TRUE;
        gi_fubar = TRUE;
#ifdef CLI
        puts(prt_err_str);
#else
        response = AutoRequest(w, &PrtErrTxt, &quit_txt, &quit_txt, 
                                        0L, 0L, 300L, 60L);
#endif
    }
    ior_ser 
        = (struct IOExtSer *)CreateExtIO(port, sizeof(struct IOExtSer));
    if (NULL == ior_ser) 
    {
        gi_quit = TRUE;
        gi_fubar = TRUE;
#ifdef CLI
        puts(io_err_str);
#else
        response = AutoRequest(w, &IOErrTxt, 
            &quit_txt, &quit_txt, 0L, 0L, 300L, 60L);
#endif
    }
    ior_ser->io_SerFlags = SERF_7WIRE | SERF_SHARED;
    if ((error = OpenDevice(SERIALNAME, 0, ior_ser, 0)) != 0) 
    {
        gi_quit = TRUE;
        gi_fubar = TRUE;
#ifdef CLI
        puts(dev_err_str);
#else
        response = AutoRequest(w, &DevErrTxt, 
            &quit_txt, &quit_txt, 0L, 0L, 300L, 60L);
#endif
    }

    if (!gi_quit && !gi_fubar)
    {
        if ((error 
        = set_serial(ior_ser, rbl, rwl, wwl, brk, baud, sf, t0, t1)) != 0)
        {
            gi_fubar = TRUE;
            gi_quit = TRUE;
#ifdef CLI
            puts(prm_err_str);
#else
            response = AutoRequest(w, &PrmErrTxt, 
                &quit_txt, &quit_txt, 0L, 0L, 300L, 60L);
#endif
        }
    }
    return;
    /* The serial device is open,  so go ahead and play music.*/
}

int write_ser(struct IOExtSer *ser_io, char *data, int length)
/*
** FUNCTIONAL DESCRIPTION:
**  Write data to serial port.
**
** RETURN VALUE:
**      description: TRUE if failed, FALSE if succeeded
**        data_type: int
**
** ARGUMENTS:
**
**  ser_io-
**         description: serial device structure
**           data_type: pointer to IOExtSer
**              access: read only
**
**  data-
**         description: data to send to serial device
**           data_type: pointer to char
**              access: read only
**
**  length-
**         description: length in bytes of serial data
**           data_type: int
**              access: read only
**
** DESIGN:
**  ROUTINE
**  : WaitIO() in case some send is not done yet
**  : copy send data and length into ser_io
**  : DoIO(ser_io) (send data)
**  : IF failed
**  : : set quit and fubar
**  : : post error
**  : ENDIF
**  : return error
**  ENDROUTINE
*/
{
    auto int error;
#ifndef CLI
    auto struct IntuiText WrtErrTxt = {2, 1, JAM1, 5, 15, &font_choice, 
                                      wrt_err_str, NULL};
#endif
    WaitIO(ser_io);
    ser_io->IOSer.io_Data = (APTR)data;
    ser_io->IOSer.io_Length = length;
    ser_io->IOSer.io_Command = CMD_WRITE;
    error = DoIO(ser_io);
    if (error)
    {
        gi_quit = TRUE;    /* flag to get out of program */
        gi_fubar = TRUE;    /* flag to dump out of program with error*/
#ifdef CLI
        puts(wrt_err_str);
#else
        response = AutoRequest(w, &WrtErrTxt, 
            &quit_txt, &quit_txt, 0L, 0L, 300L, 60L);
#endif
    }
    return error;
}

void play_note_on(NOTE_EVENT_TYPE *play_event) 
/*
** FUNCTIONAL DESCRIPTION:
**  Sends note on and note off MIDI commands; tracks running status
**
** ARGUMENTS:
**
**  play_event-
**         description: Information about a voice and its status
**           data_type: pointer to NOTE_EVENT_TYPE
**              access: read only
**
** DESIGN:
**  ROUTINE
**  : IF channel is negative
**  : : clear running status
**  : : return
**  : ENDIF
**  : copy channel and NOTEONCMD to playbuffer[0]
**  : copy dynamic to playbuffer[2]
**  : IF this is a note-on
**  : : playbuffer[1] = new pitch from scale
**  : ELSE
**  : : playbuffer[1] = old pitch
**  : ENDIF
**  : IF playbuffer[0] == Running_Status
**  : : write_ser &playbuffer[1], length of 2
**  : ELSE running status has changed
**  : : write_ser &playbuffer[0], length of 3
**  : : set new running status
**  : ENDIF
**  ENDROUTINE
*/
{
        static unsigned char Running_Status = 0X00;
        
        if ((play_event->nv_i_channel) < 0)
        {
            Running_Status = 0;
            return;
        }
        playbuffer[0] = (unsigned char)
            (((play_event->nv_i_channel) & 0XF) | NOTEONCMD);
        playbuffer[2] = (unsigned char)(play_event->nv_i_dynamic);

        playbuffer[1] = (unsigned char)(play_event->nv_i_cur_pitch);
#ifdef MEASURE
        if (play_event->nv_i_dynamic != 0)
        {
            gi_notes_measure++;
        }
#endif
        if (playbuffer[0] == Running_Status)
        {
            write_ser(ior_ser, &playbuffer[1], 2); /* send it out MIDI */
        }
        else
        {
            write_ser(ior_ser, playbuffer, 3);    /* send it out MIDI */
            Running_Status = playbuffer[0];
        }
        play_event->nv_i_was_audio = 0;
        return;
}

void send_function(const int Function)
/*
** FUNCTIONAL DESCRIPTION:
**  Sends special MIDI functions down serial line
**
** ARGUMENTS:
**
**  Function-
**         description: the MIDI function to send out MIDI
**           data_type: int
**              access: read only
**
** DESIGN:
**  ROUTINE
**  : clear playbuffer[1]
**  : CASE Function
**  : : STARTFUNCT
**  : : : set playbuffer[0] to START
**  : : STOPFUNCT
**  : : : set playbuffer[0] to STOP
**  : : CLOCKFUNCT
**  : : : set playbuffer[0] to TIMINGCLOCK
**  : : CONTFUNCT
**  : : : set playbuffer[0] to CONTINUE
**  : ENDCASE
**  : write_ser playbuffer[0] length of 1
**  ENDROUTINE
*/
{ 
    playbuffer[1] = 0;
    switch (Function) 
    {
        case STARTFUNCT:
            playbuffer[0] = (unsigned char)START;
            break;
        case STOPFUNCT:
            playbuffer[0] = (unsigned char)STOP;
            break;
        case CLOCKFUNCT:
            playbuffer[0] = (unsigned char)TIMINGCLOCK;
            break;
        case CONTFUNCT:
            playbuffer[0] = (unsigned char)CONTINUE;
            break;
        default:
            break;
    }
    write_ser(ior_ser, playbuffer, 1);    /* send it out MIDI */
    return;
}

void stop_all_notes(NOTE_EVENT_TYPE *notes_to_stop)
/*
** FUNCTIONAL DESCRIPTION:
**  Sends note-offs for all MIDI notes in all voices
**
** ARGUMENTS:
**
**  notes_to_stop-
**         description: 
**           data_type: pointer to NOTE_EVENT_TYPE
**              access: read/write only
**
** DESIGN:
**  ROUTINE
**  : FOR vox_index = 0 to MAXVOICE
**  : : IF notes_to_stop[vox_index] is playing
**  : : : send a note off for this voice
**  : : : IF recording
**  : : : : record a note off for this voice
**  : : : ENDIF
**  : : : Clear playing bit for this voice
**  : : : IF fubar
**  : : : : break out of FOR
**  : : : ENDIF
**  : : ENDIF
**  : ENDFOR
**  : send_function(STOPFUNCT)
**  ENDROUTINE
*/
{
    auto int vox_index;

    for (vox_index = 0; vox_index < MAXVOICE; vox_index++)
    {
        if (notes_to_stop[vox_index].nv_i_playing)
        {
            Delay(1);
            notes_to_stop[vox_index].nv_i_dynamic = 0;
            if (notes_to_stop[vox_index].nv_i_was_audio) 
            {
                play_audio_note(&(notes_to_stop[vox_index]));
            }
            else
            {
                play_note_on(&(notes_to_stop[vox_index]));
            }
#ifndef CLI
            if (recording)
            {
                record_note_event(&(notes_to_stop[vox_index]));
            }
#endif
            notes_to_stop[vox_index].nv_i_playing = FALSE;
            if (gi_fubar) 
            {
                break;
            }
        }
    }
    send_function(STOPFUNCT);
    return;
}

void stop_midi(void)
/*
** FUNCTIONAL DESCRIPTION:
**  Disconnects from serial device
**
** DESIGN:
**  ROUTINE
**  : IF port
**  : : DeletePort
**  : ENDIF
**  : IF ior_ser
**  : : CloseDevice
**  : ENDIF
**  ENDROUTINE
*/
{
    if (port) 
    {
        DeletePort(port);
    }
    if (ior_ser) 
    {
        CloseDevice(ior_ser);
    }
    return;
}

