[comp.sys.amiga] Play and Record MIDI data

fredc@petsd.UUCP (01/25/87)

 Recently there has been quite a bit of interest in MIDI on the AMIGA.  
 I have a Yamaha DX-27 and have put together a few programs that will
 play and record thru the MIDI I/F.  They aren't anything spectacular,
 as a matter of fact all they do is play and record, placing the output
 in an ASCII hex file (stdout).  I hope to add more midi tools to this
 collection, maybe this posting will prompt a few more.  

 What it does:

  1) Record - Records midi data and TIMING between midi events and writes it
              to stdout.  The timing is done using TICKS (60th's of a second)
              via the DateStamp AMIGADOS call.  To do this I've introduced a
              "new" midi message number, #defined as "TIMETAG".  All other
              data recorded is pure midi data.  When a TIMETAG is found in
              the stream, a 16 bit value is assumed to follow.  I know it's
              probably not the best way to do it, but that's the way I did 
              it.  

  2) Play -   The complement of "Record".  Play will take as input the ASCII
              hex file created by "Record" and send it out the serial port
              (hopefully via a MIDI I/F).  Play will interpret the TIMETAG
              and wait between the MIDI commands so as to ATTEMPT to reproduce
              the original timings.

   Under 1.2 you must set your serial port (via pref.) to be 31250 baud.  I
   am using the 512 byte buffer size, I don't think you need more then this
   as the programs are using the lowest level of I/O.  The one problem that
   could possibly arise is due to the buffering of the serial device.  So 
   far I haven't had any problems, but I think if you were to run a few other
   tasks while using play/record, you may cause the serial device to start
   buffering it's input.  In this case I think the timing's would start to
   get out of sync with real-time, since the TIMETAG wouldn't reflect when
   the data was actually read (record seems to keep up with the serial device
   when there's nothing else going on). 

   As an aside, I usually run the programs under Matt Dillon's shell, with
   the output going to RAM:,  haven't tried it with output going directly
   to disk, but I would think it may make a difference it how accurate the
   timings are, whenever the disk flushes the serial buffer may start to
    back up, not sure.  I normally run them using a pipe like:

    % record | play

   and hit ^C to exit the record and start the playback. 

   Currently I'm working on a composite of both play and record that
   allow you to control the entire DX-27 (DX-7 .. 100 etc) from the AMIGA.

   I was having some problems getting the DX to send the presets, but I
   may have that solved.

   If anyone has any problems, I'll try to help, but I'm new at this stuff
   too.  I'd like to talk to anyone interested in writing midi tools that
   can be used together, maybe we can start some kind of a library ...

                                            Fred Cassirer


# This is a shell archive.  Remove anything before this line, then
# unpack it by saving it in a file and typing "sh file".  (Files
# unpacked will be owned by you and have default permissions.)
#
# This archive contains:
# getmidi.c play.c record.c

echo x - getmidi.c
cat > "getmidi.c" << '//E*O*F getmidi.c//'
/****************************************************************************
  
   Author:  Fred Cassirer
   Date:    January 24, 1987
   
   This program is placed in the public domain, and from what I understand
   that means anyone can do whatever they want with it.   That's fine 
   because they are fairly trivial anyway ...  If you can get someone to pay
   you for them, you must be doing something right.

 ****************************************************************************/
/*  compiler directives to fetch the necessary header files */
#include <libraries/dos.h>
#include <exec/types.h>
#include <exec/exec.h>
#include <devices/serial.h>
#include <stdio.h>
#include <ctype.h>
#include <functions.h>
#include "midi.h"

#undef NULL
#define   NULL   ((void *)0)

extern long SetSignal();

breakcheck()
{
   if (SetSignal(0L,0L) & SIGBREAKF_CTRL_C )
     return ( 1 );
   else
     return( 0 );
}

breakreset()
{
   SetSignal(0L, SIGBREAKF_CTRL_C);
}

/* this routine causes manx to use this Chk_Abort() rather than it's own */
/* otherwise it resets our ^C when doing any I/O (even when Enable_Abort */
/* is zero).  Since we want to check for our own ^C's                    */

Chk_Abort()
{
return(0);
}


/* declarations for the serial stuff */

extern struct MsgPort *CreatePort();
struct IOExtSer *Read_Request;
static unsigned char rs_in[2];

struct IOExtSer *Write_Request;
static unsigned char rs_out[2];

void initmidi()
{

Read_Request = (struct IOExtSer *)AllocMem((long)sizeof(*Read_Request),MEMF_PUBLIC|MEMF_CLEAR);
Read_Request->io_SerFlags = SERF_SHARED | SERF_RAD_BOOGIE;
Read_Request->IOSer.io_Message.mn_ReplyPort = CreatePort("Read_RS",0);

if(OpenDevice(SERIALNAME,NULL,Read_Request,NULL))
   {
   puts("Cant open Read device\n");
   DeletePort(Read_Request->IOSer.io_Message.mn_ReplyPort);
   FreeMem(Read_Request,(long)sizeof(*Read_Request));
   exit(TRUE);
   }

Read_Request->IOSer.io_Length = 1;
Read_Request->IOSer.io_Data = (APTR) &rs_in[0];
Read_Request->io_Baud = 31250;
Read_Request->io_ReadLen = 8;
Read_Request->io_WriteLen = 8;
Read_Request->io_CtlChar = 1L;
Read_Request->IOSer.io_Command = SDCMD_SETPARAMS;
DoIO(Read_Request); 
Read_Request->IOSer.io_Command = CMD_READ;

Write_Request = (struct IOExtSer *)AllocMem((long)sizeof(*Write_Request),MEMF_PUBLIC|MEMF_CLEAR);
Write_Request->io_SerFlags = SERF_SHARED | SERF_RAD_BOOGIE;
Write_Request->io_StopBits = 1;
Write_Request->IOSer.io_Message.mn_ReplyPort = CreatePort("Write_RS",0);
if(OpenDevice(SERIALNAME,NULL,Write_Request,NULL))
   {
   puts("Can't open Write device\n");
   DeletePort(Write_Request->IOSer.io_Message.mn_ReplyPort);
   FreeMem(Write_Request,(long)sizeof(*Write_Request));
   DeletePort(Read_Request->IOSer.io_Message.mn_ReplyPort);
   FreeMem(Read_Request,(long)sizeof(*Read_Request));
   exit(TRUE);
   }

Write_Request->IOSer.io_Command = CMD_WRITE;
Write_Request->IOSer.io_Length = 1;
Write_Request->IOSer.io_Data = (APTR) &rs_out[0];
Write_Request->io_SerFlags = SERF_SHARED | SERF_XDISABLED;

BeginIO(Read_Request);
breakreset();

}

void cleanup()
{ 
      
CloseDevice(Read_Request); 
DeletePort(Read_Request->IOSer.io_Message.mn_ReplyPort);
FreeMem(Read_Request,(long)sizeof(*Read_Request)); 
CloseDevice(Write_Request);
DeletePort(Write_Request->IOSer.io_Message.mn_ReplyPort);
FreeMem(Write_Request,(long)sizeof(*Write_Request));

} 

   /************************************************************/
  /* send midi data to the serial port                        */
 /************************************************************/

int putmidi(ch)
int ch;
{

 rs_out[0] = ch;
 DoIO(Write_Request);
 return(ch);

}

/******************************************************/
/*           This routine will return an unsigned     */
/*                byte of midi data.                  */
/******************************************************/

int getmidi()
{
int c;
 
    Wait( (1L << Read_Request->IOSer.io_Message.mn_ReplyPort->mp_SigBit)  
         |  (SIGBREAKF_CTRL_C) );

    if(CheckIO(Read_Request))
      {
       WaitIO(Read_Request);
       c=rs_in[0];
       BeginIO(Read_Request);
      }
     else 
      c = NON_MIDI_EVENT; 

return(c);
}

//E*O*F getmidi.c//

echo x - play.c
cat > "play.c" << '//E*O*F play.c//'
/****************************************************************************
  
   Author:  Fred Cassirer
   Date:    January 24, 1987
   
   This program is placed in the public domain, and from what I understand
   that means anyone can do whatever they want with it.   That's fine 
   because they are fairly trivial anyway ...  If you can get someone to pay
   you for them, you must be doing something right.

 ****************************************************************************/
#include "stdio.h"
#include "exec/types.h"
#include "midi.h"

#undefine GETMIDI
#define GETMIDI(t) if (scanf("%x",&t) != 1) t = NON_MIDI_EVENT;

main()
{
 FILE *fp;
 int i=0;
 int midi,timetag;

 if ( (fp=fopen("ser:31250,n,8,1","w")) == NULL) {
    printf("Could not open serial port for write!/n");
    exit(1);
 }

 GETMIDI(midi); /* First stuff is bogus timetag */
 GETMIDI(midi); /* Eat timetag .. munch .. munch crunch */
  
 GETMIDI(midi); /* First true midi stuff */

 while (midi != NON_MIDI_EVENT) {

    if ( midi == TIMETAG ) {
       GETMIDI(timetag);
       if ( timetag ) {
           Delay( (long) timetag  ); /* 60th's of second */
       }

      GETMIDI(midi);
    }
    else 
     do {
         putc(midi,fp);           
         GETMIDI(midi);
     } while ( ( midi != TIMETAG) && ( midi != NON_MIDI_EVENT) );

 }

}
//E*O*F play.c//

echo x - record.c
cat > "record.c" << '//E*O*F record.c//'
/****************************************************************************
  
   Author:  Fred Cassirer
   Date:    January 24, 1987
   
   This program is placed in the public domain, and from what I understand
   that means anyone can do whatever they want with it.   That's fine 
   because they are fairly trivial anyway ...  If you can get someone to pay
   you for them, you must be doing something right.

 ****************************************************************************/
#include "stdio.h"
#include "exec/types.h"
#include "libraries/dos.h"
#include "midi.h"

extern int getmidi();
extern void initmidi();
extern void cleanup();

/*
     Return time in ticks since last call.  For the 1st call the result is
     undefined.
*/

ULONG duration()
{ 
struct DateStamp dstmp;
ULONG curticks,retval;

static ULONG lastics;

 DateStamp(&dstmp);

 curticks = dstmp.ds_Tick + dstmp . ds_Minute * 60 * TICKS_PER_SECOND;
 retval = curticks - lastics;
 lastics = curticks;
 return(retval); /* 60th's of a second */

}

main()
{

 int i=0;
 int midi_status,midi_note,midi_velocity;
 int midi_sysdata,midi_parmno,midi_setting;

 initmidi();

 GETMIDI(midi_status);
 duration();  /* Toss the 1st duration to the wind */

 for(;;) {

  switch (midi_status & 0xff0) {

   case NON_MIDI_EVENT:
                cleanup();
                exit(FALSE);
     
   case NOTE_OFF : 
   case NOTE_ON  : 
               GETMIDI(midi_note);
               do {
                GETMIDI(midi_velocity);
                printf("%02x %04lx %02x %02x %02x ",TIMETAG
                                                   ,duration()
                                                   ,midi_status
                                                   ,midi_note
                                                   ,midi_velocity);

                i += 5;
                GETMIDI(midi_note);
                if ((i % 20) == 0) printf("\n");
               } while (!(midi_note & 0x80));

               midi_status = midi_note;
               break;

   case PITCHWHEEL:
   case PARAMETER:

                printf("%02x %04lx %02x ",TIMETAG,duration(),midi_status); 
                i += 3; if ((i%20) == 0) printf("\n");
                                         
               GETMIDI(midi_parmno);
               do {

                GETMIDI(midi_setting);
                printf("%02x %02x ",midi_parmno,midi_setting);
                i += 2;

                GETMIDI(midi_parmno);
                if ((i % 20) == 0) printf("\n");

               } while (!(midi_parmno & 0x80));

               midi_status = midi_parmno;
               break;

   case SYSTEM_EXCLUSIVE: 
              
                switch (midi_status) {

                case SYSTEM_EXCLUSIVE: /* Remember we masked off low nibble */

                      printf("%02x %04lx %02x ",TIMETAG,duration(),midi_status); 
                      i += 3; if ((i%20) == 0) printf("\n");
                                         
                      do {
                        GETMIDI(midi_sysdata);
                        printf("%02x ",midi_sysdata);
                      } while ( (midi_sysdata != EOX) &&
                                (midi_sysdata != ACTIVE_SENSING));

                default: GETMIDI(midi_status);
                         break;

                 } /* end inner switch */

                 break;

   case PROGRAM:  /* These two are followed by a single data byte */
   case CHANNEL_PRESSURE:

   case KEY_PRESSURE: /* Whereas this is followed by two.  MIDI_DATA next 
                          time around will pick up the slack. */

                printf("%02x %04lx %02x ",TIMETAG,duration(),midi_status); 
                i += 3; if ((i%20) == 0) printf("\n");
  
                GETMIDI(midi_sysdata);
                printf("%02x ",midi_sysdata);
                GETMIDI(midi_status);
                break;  
                                       
   case MIDI_DATA: /* Data bytes for any of the above */
   default:
              printf("%02x ",midi_status);

              GETMIDI(midi_status);
              break; 

   }

 }

}
  
//E*O*F record.c//

exit 0