/*      TX.C
 
        (c) 1987 Todor Fay 
 
*/ 
 
#include "exec/types.h" 
#include "exec/exec.h" 
#include "hardware/custom.h" 
#include "hardware/cia.h" 
#include "exec/interrupts.h" 
#include "exec/memory.h" 
#include "hardware/intbits.h" 
#include "libraries/dos.h" 
#include "intuition/intuition.h"
#include "soundscape.h" 
 
extern struct CIA ciaa;
extern struct Custom custom;

/*    The state structure for this module has just the
      file name for saving a TX81Z (or DX21/27/100) voice.  
      Whenever an environment save command (SAVESTATE) 
      occurs, the file that the voice was saved in is 
      returned here.  When an environment load command 
      (LOADSTATE) happens, the file that is given here 
      will be loaded by this module.
*/

struct SynthState { 
    long length; 
    char filename[70]; 
}; 

/*    Initialise the file name as empty. */

static struct SynthState synthstate = { 70,0, }; 

/*    The port id for this port. */

static short thisport;

/*    The icon in the Patch Panel. */

UWORD tx81zdata[] = {        /* 32 x 12 */ 
0,        0,        
0,        0,        
0,        0,        
0,        0,        
0,        0,        
0,        0,        
0,        0,        
8078,     32752,        
12486,    448,        
8070,     1792,        
12486,    7168,        
8079,     32752,        
16383,    57344,        
224,      0,        
231,      28672,        
227,      57344,        
225,      49152,        
227,      57344,        
231,      28672,        
0,        32752,        
0,        448,        
0,        1792,        
0,        7168,        
0,        32752,        
};

struct Image tx81zimage = { 0,0,32,12,2,tx81zdata,3,0,0 };
 
/*    We need to be able to display messages to the user.
      For example, "Sending the voice...".  Two
      routines handle this.  Onmessage opens a window
      and displays the given strings.  Offmessage
      closes that window.  Here is the window declaration.
*/

struct NewWindow NewWindowStructure = {
        134,35,
        336,66,
        0,1,
        NULL,
        ACTIVATE,
        NULL,
        NULL,
        "TX81Z/DX21/DX27/DX100 Librarian",
        NULL,
        NULL,
        5,5,
        640,200,
        WBENCHSCREEN
};

struct Window *messagewindow = 0;

void onmessage(string1,string2) 
 
/*      Opens a window and prints the message.   
        Window is closed by offmessage(). 
*/ 
 
char string1[], string2[];
 
{ 
    static struct IntuiText text = { 2,0,JAM2,0,0,0,0, };
    messagewindow = (struct Window *) 
        OpenWindow(&NewWindowStructure);
    if (messagewindow) {
        text.IText = string1; 
        PrintIText(messagewindow->RPort,&text,20,20);
        if (string2) {
            text.IText = string2; 
            PrintIText(messagewindow->RPort,&text,20,30); 
        }
    }
} 

void offmessage() 

/*      This just closes messagewindow if it is open. */
 
{ 
    if (messagewindow) {
        CloseWindow(messagewindow); 
    }
    messagewindow = 0; 
}   
  
 
opencode(direction) 

/*      Routine to open this modules's port in the
        direction specified. Always return TRUE
        for success.
*/        
 
char direction; 
 
{ 
    return(1); 
} 
 
 
closecode(direction)

/*      Always close successfully. */
 
char direction; 
 
{ 
    return(1); 
} 


unsigned long processbyte(buffer,maxlength,midiindata) 

/*      When a data byte comes in, this routine is
        called to process it.  Buffer is a pointer
        to an array to store the data in.  Maxlength
        is the size of that array.  Midiindata is
        the data byte that just came in.
        
        The static variable sysexon keeps track of
        whether we are currently reading a system
        exclusive packet.  It is initialised to
        FALSE (0). When a System Exclusive status
        byte comes in, sysexon is set.  When the
        end of the packet is reached, sysexon is
        turned off again.

        The static variable length keeps track of 
        where we are in the array.  It is not 
        allowed to become greater than maxlength.

        Return 0 if still reading or about to
        read the packet.  Return the length when
        the packet end has been reached.
*/

register unsigned char *buffer;
register unsigned long maxlength;
register unsigned char midiindata; 
 
{ 
    static unsigned long length;
    static char sysexon = 0; 
          /*  Is this a status byte? */
    if (midiindata & 0x80) { 
          /*  MIDI real time event? */
        if (midiindata >= CLOCK) { 
            return(0); 
        } 
          /* End of packet? */
        else if (sysexon) { 
            sysexon = 0; 
            buffer[length++] = midiindata;
            return(length);
        } 
          /* Start of packet? */
        else if (midiindata == SYSTEMX) { 
            sysexon = 1;
            length = 1;
            *buffer = SYSTEMX;
        } 
    } 
          /* Read data. */
    else if (sysexon) { 
        if (length < maxlength) { 
            buffer[length++] = midiindata; 
        }
    } 
    return(0); 
} 


unsigned long ReceiveSysEx(buffer,maxlength) 

/*      To receive a system exclusive buffer,
        disable interrupts and poll the serial
        port and mouse button.  Whenever a data
        byte comes in, pass it to the processbyte
        routine, which deals with is appropriately.
        Once the packet is in, return the length.
*/
 
register char *buffer; 
register unsigned long maxlength; 
 
{ 
    register unsigned char c; 
    unsigned long length = 0;
    Disable(); 
    for (;;) { 
        while (!(custom.serdatr & 0x4000)) {
            if ((~ciaa.ciapra) & 0xC0) {
                Enable();
                return(0);
            }
        }
        c = (unsigned char) custom.serdatr; 
        custom.intreq = INTF_RBF;
        if (length = processbyte(buffer,maxlength,c)) 
            break; 
    } 
    Enable(); 
    return(length); 
} 
 

void SendSysEx(buffer,length) 
 
/*      First, set the priority of this task to 
        higher than the SoundScape packet router
        so there is no danger of SoundScape sending
        a MIDI packet at the same time.
        
        Then, send all the bytes in the buffer,
        polling the buffer empty bit before 
        sending each byte.

        When done, bring the priority back down.
*/

register char *buffer; 
register unsigned long length;
 
{ 
    register long oldpri = SetTaskPri(FindTask(0),21); 
    register unsigned long i; 
    for (i=0; i<length; i++) { 
        while (!(custom.serdatr & 0x2000)); 
        custom.serdat = buffer[i] | 0x300; 
    } 
    SetTaskPri(FindTask(0),oldpri); 
} 
          

void dosomething(direction,filename) 

/*      This routine is called to either read
        a voice from disk and send it
        to the synth, or read a voice from
        the synth and store it to disk.
        
        Direction is set if we are to
        store a sound to disk, otherwise cleared.

        A filename may be given (the case for
        environment loads and saves.) If
        so, the variable filename will have
        that file name in it.  If there is
        no filename (filename[0] == 0), 
        put up a requester with a call to
        ReadFileName or WriteFileName.

        If saving a voice to disk, first
        send a dump request to the synth.
        Then read the system exclusive packet
        that comes back.  There is a danger
        that no synth is hooked up, or the
        wrong data is returned.  If this happens,
        the length of the data returned will not
        be 101, so don't save it to file.

        If sending a new voice, just read
        it from file and send the data.
*/
 
char direction;
register char filename[]; 
 
{ 
    register long buffer; 
    register long file; 
    static unsigned char dumprequest[] = { 0xF0,0x43,0x20,0x03,0xF7};
    long length; 
    buffer = AllocMem(200,MEMF_PUBLIC); 
    if (buffer) { 
        if (direction) { 
            if (!filename[0]) 
                WriteFileName(filename,"TX81Z/DX Voice","tx81zvoice"); 
            if (filename[0]) { 
                onmessage("Getting a Voice... ",
                    "Click mouse to abort");
                SendSysEx(dumprequest,5);
                length = ReceiveSysEx(buffer,200);
                offmessage(); 
                if (length == 101) {
                    file = Open(filename,MODE_NEWFILE); 
                    if (file) { 
                        Write(file,&length,4); 
                        Write(file,buffer,length); 
                        Close(file); 
                    }
                } 
            } 
        } 
        else { 
            if (!filename[0]) 
                ReadFileName(filename,"TX81Z/DX Voice","tx81zvoice");
            if (filename[0]) { 
                file = Open(filename,MODE_OLDFILE); 
                if (file) { 
                    Read(file,&length,4);
                    if (length == 101) { 
                        Read(file,buffer,length); 
                        Close(file); 
                        onmessage("Sending the Voice... ",0);
                        SendSysEx(buffer,length); 
                        offmessage(); 
                    }
                } 
            } 
        } 
        FreeMem(buffer,200); 
    } 
} 

  
void editcode(direction,command,buffer) 
 
/*      This, the edit routine, is called under
        five circumstances:

        USEREDIT:
        The user clicked on either the left or right
        icon in the patch panel.  Which icon is
        determined by the variable direction.  Clear
        the SynthState filename field so there is no
        current filename and make a FunctionCall to 
        dosomething(direction,synthstate.filename)
        which will either load a sound or save
        one.

        SETSTATE:
        This occurs when another module wishes to
        change the data in this module's state
        structure.  Simply copy the passed buffer
        into synthstate.

        GETSTATE:
        Another module wishes to read the contents
        of this module's state structure.  Simply
        copy synthstate into the passed buffer.

        LOADSTATE:
        Another module asks this module to load
        files.  This ususally occurs in the
        context of an environment load.  Once again,
        a buffer is passed and the data from it
        is copied into synthstate.  This should have the
        name of the file to read and send to the
        TX81Z.

        SAVESTATE:
        Another module asks this module to save
        files.  This usually occurs in the
        context of an environment save. This
        time, we get the sound from the TX81Z and
        save it to disk, then return the file name,
        so that can be stored in the environment 
        file.
*/ 
 
char direction, command; 
struct SynthState *buffer; 
 
{ 
    enteraztec();
    switch (command) { 
        case USEREDIT : 
            synthstate.filename[0] = 0; 
            FunctionCall(dosomething,direction,synthstate.filename); 
            break; 
        case SETSTATE : 
            movmem(buffer,&synthstate,sizeof(synthstate)); 
            break; 
        case GETSTATE : 
            movmem(&synthstate,buffer,sizeof(synthstate)); 
            break; 
        case LOADSTATE : 
            movmem(buffer,&synthstate,sizeof(synthstate)); 
            FunctionCall(dosomething,0,synthstate.filename); 
            movmem(&synthstate,buffer,sizeof(synthstate)); 
            break; 
        case SAVESTATE : 
            FunctionCall(dosomething,1,synthstate.filename); 
            movmem(&synthstate,buffer,sizeof(synthstate)); 
            break; 
    } 
    buffer->length = 70; 
    leaveaztec();
} 
  
long SoundScapeBase, IntuitionBase, GfxBase;
 
/*      Here's the main program.  As always with a 
        SoundScape module, open the SoundScape library
        to get a handle on the routines, then close
        it so it can be eventually closed by the 
        program that opened it initially.
        Create a midi port in the patch panel and
        wait for it to be closed, then leave.
*/

void main() { 
    IntuitionBase = OpenLibrary("intuition.library",0); 
    GfxBase = OpenLibrary("graphics.library",0); 
    SoundScapeBase = OpenLibrary("soundscape.library",0); 
    if (SoundScapeBase) { 
        CloseLibrary(SoundScapeBase); 
        thisport = AddMidiPort(opencode,closecode,editcode,0, 
            &tx81zimage,&tx81zimage,-1,"tx81z"); 
        SetTaskPri(FindTask(0),-20); 
        while (MidiPort(thisport)) Delay(100); 
    } 
    CloseLibrary(IntuitionBase); 
    CloseLibrary(GfxBase); 
}
