/*
    Splitter: A Bars & Pipes Accessory for splitting a track into
	many tracks, according to a note map as used
	by the Pattern Tool for drum kits.

    Copyright (C) 1997 Richard Hagen
        This code is released into the Public Domain, and may be freely
	distributed in its original form.

	It is supplied ``as is'', and comes with no warranty.
	This program code was released because it might be useful as a
	starting point for other programmers. However, if any damage arises
	from its use, the original author will not be held liable.

	You are free to use and modify this code to your heart's content,
	provided you acknowledge me as the original author in any code
	that you might distribute which is based largely on this code.

	I acknowledge that the design of this accessory is influenced
	strongly by the example code supplied with the Rules for Tools
	package. However, I have made substantial contributions of my
	own.

	Richard Hagen
	richard@it.uq.edu.au
	August 1997

    History:
    	Version 1.1: 19 August 1997
		Added ``Clear Note Map'' button.
		Tidied up some silly code.
        Version 1.0: 12 August 1997
		Initial Release.
*/

#include <intuition/intuition.h>
#include <libraries/dos.h>
#include <libraries/gadtools.h>
#include <exec/memory.h>
#include <stdarg.h>
#include <string.h>
#include <stddef.h>
#include <stdio.h>

#include "bars.h"

#include "myheader.h"

/* -------------------------------------------------------------------- */
/* 				ICON IMAGERY				*/
/* -------------------------------------------------------------------- */

/* I did these by hand to get the feel for what goes into a bitmap image.
   I really don't want to do it again! Can anyone point me at a good tool
   for doing the design for these sorts of things? */

/* Normal icon. */
static const __chip UWORD splitter_image_data[] = {
  /* Plane 0 */
  0x0, 0x0, 0x0,
  0x0, 0x0, 0x0,
  0x0, 0x0, 0x0,
  0x0, 0x0, 0x0,
  0x0, 0x0, 0x0,
  0x0, 0x0, 0x0,
  0x0, 0x0, 0x0,
  0x0, 0x0, 0x0,
  0x0, 0x0, 0x0,
  0x0, 0x0, 0x0,
  0x0, 0x0, 0x0,
  0x0, 0x0, 0x0,
  0x7fff, 0xffff, 0xe000,
  0x7fff, 0xffff, 0xe000,
  0x38f0, 0x0c00, 0x0,
  0x38f0, 0x0c00, 0x0,
  0x0401, 0x8270, 0x0,
  0x0401, 0x8270, 0x0,
  0x0303, 0xc103, 0x0,
  0x0303, 0xc103, 0x0,
  0x7fff, 0xffff, 0xe000,
  0x7fff, 0xffff, 0xe000,
  0x0, 0x0, 0x0,
  0x0, 0x0, 0x0,
  0x0, 0x0, 0x0,
  0x0, 0x0, 0x0,
  0x0, 0x0, 0x0,
  0x0, 0x0, 0x0,
  0x0, 0x0, 0x0,
  0x0, 0x0, 0x0,
  0x0, 0x0, 0x0,
  0x0, 0x0, 0x0,
  0x0, 0x0, 0x0,
  0x0, 0x0, 0x0,

  /* Plane 1 */
  0x0, 0x0, 0x0,
  0x0, 0x0, 0x0,
  0x0, 0x0, 0x0,
  0x0, 0x0, 0x0,
  0x0, 0x0, 0x0,
  0x0, 0x0, 0x0,
  0x0, 0x0, 0x0,
  0x0, 0x0, 0x0,
  0x0, 0x0, 0x0,
  0x0, 0x0, 0x0,
  0x0, 0x0, 0x0,
  0x0, 0x0, 0x0,
  0x7fff, 0xffff, 0xe000,
  0x7fff, 0xffff, 0xe000,
  0x2108, 0x4210, 0x8000,
  0x2108, 0x4210, 0x8000,
  0x2108, 0x4210, 0x8000,
  0x2108, 0x4210, 0x8000,
  0x2108, 0x4210, 0x8000,
  0x2108, 0x4210, 0x8000,
  0x7fff, 0xffff, 0xe000,
  0x7fff, 0xffff, 0xe000,
  0x0, 0x0, 0x0,
  0x0, 0x0, 0x0,
  0x0, 0x0, 0x0,
  0x0, 0x0, 0x0,
  0x0, 0x0, 0x0,
  0x0, 0x0, 0x0,
  0x0, 0x0, 0x0,
  0x0, 0x0, 0x0,
  0x0, 0x0, 0x0,
  0x0, 0x0, 0x0,
  0x0, 0x0, 0x0,
  0x0, 0x0, 0x0,

  /* Plane #2 */
  0x0, 0x0, 0x0,
  0x0, 0x0, 0x0,
  0x0, 0x0, 0x0,
  0x0, 0x0, 0x0,
  0x0, 0x0, 0x0,
  0x0, 0x0, 0x0,
  0x0, 0x0, 0x0,
  0x0, 0x0, 0x0,
  0x0, 0x0, 0x0,
  0x0, 0x0, 0x0,
  0x0, 0x0, 0x0,
  0x0, 0x0, 0x0,
  0x0, 0x0, 0x0,
  0x0, 0x0, 0x0,
  0x0, 0x0, 0x0,
  0x0, 0x0, 0x0,
  0x0, 0x0, 0x0,
  0x0, 0x0, 0x0,
  0x0, 0x0, 0x0,
  0x0, 0x0, 0x0,
  0x0, 0x0, 0x0,
  0x0, 0x0, 0x0,
  0x0, 0x0, 0x0,
  0x0, 0x0, 0x0,
  0x0, 0x0, 0x0,
  0x0, 0x0, 0x0,
  0x0, 0x0, 0x0,
  0x0, 0x0, 0x0,
  0x0, 0x0, 0x0,
  0x0, 0x0, 0x0,
  0x0, 0x0, 0x0,
  0x0, 0x0, 0x0,
  0x0, 0x0, 0x0,
  0x0, 0x0, 0x0,
};

static const struct Image splitter_image = {
  0, 0,
  48, 34, 3,
  splitter_image_data,
  0x1f, 0x00,
  NULL
};

/* Selected icon. */
static const __chip UWORD splitter_onimage_data[] =  {
  /* Plane 0 */
  0x0, 0x0, 0x0,
  0x0, 0x0, 0x0,
  0x0, 0x0, 0x0,
  0x0, 0x0, 0x0,
  0x7fff, 0xffff, 0xe000,
  0x7fff, 0xffff, 0xe000,
  0x38f0, 0x0c00, 0x0,
  0x38f0, 0x0c00, 0x0,
  0x7fff, 0xffff, 0xe000,
  0x7fff, 0xffff, 0xe000,
  0x0, 0x0, 0x0,
  0x0, 0x0, 0x0,
  0x0, 0x0, 0x0,
  0x0, 0x0, 0x0,
  0x7fff, 0xffff, 0xe000,
  0x7fff, 0xffff, 0xe000,
  0x0401, 0x8270, 0x0,
  0x0401, 0x8270, 0x0,
  0x7fff, 0xffff, 0xe000,
  0x7fff, 0xffff, 0xe000,
  0x0, 0x0, 0x0,
  0x0, 0x0, 0x0,
  0x0, 0x0, 0x0,
  0x0, 0x0, 0x0,
  0x7fff, 0xffff, 0xe000,
  0x7fff, 0xffff, 0xe000,
  0x0303, 0xc103, 0x0,
  0x0303, 0xc103, 0x0,
  0x7fff, 0xffff, 0xe000,
  0x7fff, 0xffff, 0xe000,
  0x0, 0x0, 0x0,
  0x0, 0x0, 0x0,
  0x0, 0x0, 0x0,
  0x0, 0x0, 0x0,
  
  /* Plane 1 */
  0x0, 0x0, 0x0,
  0x0, 0x0, 0x0,
  0x0, 0x0, 0x0,
  0x0, 0x0, 0x0,
  0x0, 0x0, 0x0,
  0x0, 0x0, 0x0,
  0x0, 0x0, 0x0,
  0x0, 0x0, 0x0,
  0x0, 0x0, 0x0,
  0x0, 0x0, 0x0,
  0x0, 0x0, 0x0,
  0x0, 0x0, 0x0,
  0x0, 0x0, 0x0,
  0x0, 0x0, 0x0,
  0x0, 0x0, 0x0,
  0x0, 0x0, 0x0,
  0x0, 0x0, 0x0,
  0x0, 0x0, 0x0,
  0x0, 0x0, 0x0,
  0x0, 0x0, 0x0,
  0x0, 0x0, 0x0,
  0x0, 0x0, 0x0,
  0x0, 0x0, 0x0,
  0x0, 0x0, 0x0,
  0x0, 0x0, 0x0,
  0x0, 0x0, 0x0,
  0x0, 0x0, 0x0,
  0x0, 0x0, 0x0,
  0x0, 0x0, 0x0,
  0x0, 0x0, 0x0,
  0x0, 0x0, 0x0,
  0x0, 0x0, 0x0,
  0x0, 0x0, 0x0,
  0x0, 0x0, 0x0,

  /* Plane #2 */
  0x0, 0x0, 0x0,
  0x0, 0x0, 0x0,
  0x0, 0x0, 0x0,
  0x0, 0x0, 0x0,
  0x7fff, 0xffff, 0xe000,
  0x7fff, 0xffff, 0xe000,
  0x2108, 0x4210, 0x8000,
  0x2108, 0x4210, 0x8000,
  0x7fff, 0xffff, 0xe000,
  0x7fff, 0xffff, 0xe000,
  0x0, 0x0, 0x0,
  0x0, 0x0, 0x0,
  0x0, 0x0, 0x0,
  0x0, 0x0, 0x0,
  0x7fff, 0xffff, 0xe000,
  0x7fff, 0xffff, 0xe000,
  0x2108, 0x4210, 0x8000,
  0x2108, 0x4210, 0x8000,
  0x7fff, 0xffff, 0xe000,
  0x7fff, 0xffff, 0xe000,
  0x0, 0x0, 0x0,
  0x0, 0x0, 0x0,
  0x0, 0x0, 0x0,
  0x0, 0x0, 0x0,
  0x7fff, 0xffff, 0xe000,
  0x7fff, 0xffff, 0xe000,
  0x2108, 0x4210, 0x8000,
  0x2108, 0x4210, 0x8000,
  0x7fff, 0xffff, 0xe000,
  0x7fff, 0xffff, 0xe000,
  0x0, 0x0, 0x0,
  0x0, 0x0, 0x0,
  0x0, 0x0, 0x0,
  0x0, 0x0, 0x0,
};

static const struct Image splitter_onimage = {
  1, 0,
  48, 34, 3,
  splitter_onimage_data,
  0x1f, 0x00,
  NULL
};

/* Global Bars & Pipes functions pointer. */
extern struct Functions *functions;

/* Title for error requester. */
static const char *error_title = "Splitter error";

/* Puts up a simple requester for relaying error messages. */
static void
accessory_error(struct Window *window,	/* Window to open requester in. */
		const char *s)		/* String to put in the requester. */
{
  const struct EasyStruct easy = {
    sizeof(struct EasyStruct),
    0,
    error_title,
    s,
    "OK"
  };

  (void) EasyRequest(window, &easy, NULL);
}

/* -------------------------------------------------------------------- */
/*				 Note Maps				*/
/* -------------------------------------------------------------------- */

/* Note maps are created by the Pattern tool when you edit a drum
   kit. Have a bit of a play with the drum map editing facility
   to get a feel for what data needs to be stored for a note map.
   
   The file containing the note map has the following format:

   Description		Size		Value		Comment

   File ID		4 Bytes		"NTMP"
   Length of File	4 Bytes				File length - 8
   Offset to note data	4 Bytes
   Pointer to note data	4 Bytes				Ignore this
   ?Spare byte?		1 Byte		0x00		?
   MIDI Note to ID Map	128 Bytes
   ID to MIDI Note Map	128 Bytes
   ID to position Map	128 Bytes
   ?Spare Byte?		1 Byte		0x00		?
   ?Map ID?		4 Bytes		"DRUM"		?Different maps?
   Length of map	4 Bytes
   Length of map entry	4 Bytes		0x00000018
   Map entries -
	Pointer to next	4 Bytes				0 if last entry,
							otherwise, ignore
	Note name	15 Bytes			0 in last byte
	Note ID		1 Byte
	MIDI note	1 Byte
	Note position	1 Byte
	Flags		4 Bytes				0x00000001 == selected
							?other flags?

  If anyone can help me out with the things marked ``?'', I'd really
  appreciate it! */

/* Length of note name as stored in note map file. */
#define NOTE_NAME_LEN	 15

/* Format of records stored in the note map file. */
struct NotemapFileEntry {
  char name[NOTE_NAME_LEN];
  UBYTE id;
  UBYTE note;
  UBYTE pos;
  UWORD flags;
};

/* Strings for holding note names. */
typedef char *Notemap[MIDI_NOTE_NUM];	/* Indexed by note number. */

/* My accessory needs to remember a notemap, so I've bundled the two
   things together. */
struct Splitter {
  struct Accessory accessory;
  Notemap *notemap;
};

/* My accessory. */
struct Splitter splitter;

/* Allocates, initialises and returns a new notemap.
   If something goes wrong, it returns NULL. */
static Notemap *
notemap_new(void)
{
  register unsigned int i;

  /* Attempt the allocation and check for failure. */
  Notemap *notemap =
    (Notemap *)(*functions->myalloc)(sizeof(Notemap), MEMF_CLEAR);
  if (notemap == (Notemap *) NULL)
    {
      /* Allocation failed */
      return (Notemap *) NULL;
    }
  
  /* Carefully initialise all the strings in the notemap. */
  for (i = 0; i < MIDI_NOTE_NUM; i++)
    {
      (*notemap)[i] = (char *) NULL;
    }

  return notemap;
}

/* Frees the strings in a notemap. */
static void
notemap_delete_entries(Notemap *notemap)
{
  register unsigned int i;

  /* Ensure that we've been given something to delete. */
  if (notemap != (Notemap *) NULL)
    {
      /* For each string in the notemap. */
      for (i = 0; i < MIDI_NOTE_NUM; i++)
        {
	  /* Check to see if we have to do anything. */
          if ((*notemap)[i] != (char *) NULL)
            {
	      /* Free the storage and make the pointer difficult to reuse. */
              (*functions->myfree)((*notemap)[i],
				   strlen((*notemap)[i]) + 1);
              (*notemap)[i] = (char *) NULL;
	    }
        }
    }
}

/* Frees an entire notemap structure (including the strings). */
static void
notemap_delete(Notemap *notemap)
{
  /* Ensure that we've been given something to delete. */
  if (notemap != (Notemap *) NULL)
    {
      /* Delete the entries, and free the storage for the notemap. */
      notemap_delete_entries(notemap);
      (*functions->myfree)(notemap, sizeof(Notemap));
    }
}

/* Load a notemap from the file. If an error occurs, an incomplete
   notemap isn't deleted. */
static void
notemap_load(long file,		/* Open file. */
	     Notemap *notemap)	/* Notemap to fill in. */
{
  char file_id[ID_LEN];		/* File ID. */
  ULONG offset = 0;		/* Offset to name map entries. */
  ULONG next = 0;		/* Next pointer. */

  /* Ensure that the file id is one for a note map. */
  if ((*functions->fastread)(file, file_id, ID_LEN) < ID_LEN)
    {
      /* Failed to read the correct number of bytes for the file id. */
      return;
    }
  if (strncmp(file_id, "NTMP", ID_LEN) != 0)
    {
      /* Incorrect id */
      return;
    }

  /* Skip over the file length */
  if ((*functions->fastseek)(file, sizeof(ULONG), OFFSET_CURRENT) < 0)
    {
      /* Seek failed */
      return;
    }
 
  /* Read the offset for the note map entry data. */
  if ((*functions->fastread)(file, (UBYTE *)&offset, sizeof(offset)) < sizeof(offset))
    {
      /* Failed to read the correct number of bytes for the offset. */
      return;
    }

  /* Skip to the note map entries. Don't bother checking the
     map id or record length. */
  if ((*functions->fastseek)(file, offset+12, OFFSET_CURRENT) < 0)
    {
      /* Seek failed. */
      return;
    }

  /* We've made it this far, assume that we have a valid note map file. */

  /* Read in entries as long as the ``next'' pointer is not 0. */
  do {
    /* Notemap entry to be read from file. */
    struct NotemapFileEntry entry;
    
    /* Size of storage for the note's name. */
    size_t len;

    /* Read the ``next'' pointer and note map entry. */
    if ((*functions->fastread)(file, (UBYTE *)&next, sizeof(next)) < sizeof(next))
      {
	/* Failed to read the correct number of bytes for the ``next'' pointer. */ 
        return;
      }
    if ((*functions->fastread)(file, (UBYTE *)&entry, sizeof(entry)) < sizeof(entry))
      {
	/* Failed to read the correct number of bytes for an entry. */
        return;
      }

    /* Store the note name in the note map. */

    /* Allocate some memory for the new name. */
    len = strlen(entry.name);
    (*notemap)[entry.note] = (char *)(*functions->myalloc)(len + 1, MEMF_CLEAR);
    if ((*notemap)[entry.note] == (char *)NULL)
      {
	/* Allocation failed. */
        return;
      }

    /* Copy the string into the newly allocated memory and null terminate the
       string. */
    strcpy((*notemap)[entry.note], entry.name);
    (*notemap)[entry.note][len] = '\0';

  } while (next != 0);

  return;
}

/* -------------------------------------------------------------------- */
/*			SPLITTING TRACKS				*/
/* -------------------------------------------------------------------- */

/* For splitting the tracks, I maintain two separate structures, one for
   the note events, and one for the others (i.e. control change, etc.)
   The note events are stored in an array, with each element of the array
   corresponding to a particular MIDI note. The other events are all lumped
   in together. */

/* Split a list of events into note events and other events. */
static BOOL
eventlist_split(struct Event *source,	/* Pointer to first event in list */
		                        /* to be split. */
		struct Event **notes,	/* Array of event lists, */
		                        /* one for each MIDI note. */
		struct Event **others)	/* Handle for event list for */
     					/* non-note events. */
{
  struct NoteEvent *event;

  /* For each event in the list. */
  for (event = (struct NoteEvent *) source;
       event;
       event = (struct NoteEvent *) event->next)
    {
      /* If it's a MIDI performance event.*/
      if ((event->type & 0xf) == EVENT_VOICE)
        {
	  /* Try to allocate a new event and check to see if we succeeded. */
          struct NoteEvent *new = 
	    (struct NoteEvent *)(*functions->allocevent)();
          if (new == (struct NoteEvent *) NULL)
            {
	      /* Allocation failed. */
              return FALSE;
            }

	  /* Initialise the new event. */
	  *new = *event;

	  /* Is it a note event? */
          if ((new->status & 0xff) == MIDI_NOTEON)
            {
	      new->next = notes[new->value];
	      notes[new->value] = new;
            }
          else	/* It's some other sort of MIDI event. */
            {
	      new->next = *others;
	      *others = new;
            }
        }
    }

  return TRUE;
}

/* Shameless plug inserted into track notes by the accessory. */
static const char *splitter_notes = "Created by Splitter Accessory";

/* Storage for note to name conversion. */
static char name[NOTE_NAME_LEN];

/* Returns a text string given a midi note number. */
static char *
note_name(unsigned char note)
{
  static char *note_names[] = {
    "C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "Bb", "B"
    };

  sprintf(name, "%s%1ld", note_names[note % 12], (int) note / 12);

  return name;
}

/* Allocate, initialise and return a new track.
   If something goes wrong, returns NULL. */
static struct Track *
track_new(struct Track *old,		/* Track to initialise from. */
	  char *name,			/* Name of the new track. */
	  unsigned char note,		/* Which MIDI note this corresponds to. */
	  struct Event *events)		/* Events for the new track. */
					/* In reverse order! */
{
  /* Attempt to allocate the track, and check to see if we succeeded. */
  struct Track *track =
    (struct Track *)(*functions->createtrack)(1, 0);
  if (track == (struct Track *) NULL)
    {
      return (struct Track *) NULL;
    }

  /* Initialise the track from the fields of the supplied track. */
  track->next = (struct Track *) NULL;

  /* Keep the same channel in and out assignments. */
  track->channelin = old->channelin;
  track->channelout = old->channelout;

  /* Careful! createtrack() has already given the track a name. */
  (*functions->replacestring)(&(track->clip.name),
			      name ? name : note_name(note));
  track->clip.notes =
    (struct String *)(*functions->allocstring)(splitter_notes);

  /* Highest and lowest notes for the clip are the same as the note. */
  track->clip.highnote = note;
  track->clip.lownote = note;

  /* Begin and end times. */
  track->clip.begin = 0;
  track->clip.end = events == (struct Event *) NULL ? 0 : events->time;

  /* Sort and add the events. */
  track->clip.events.first = 
    (struct Event *)(*functions->sorteventlist)(events);
  track->clip.events.point = (struct Event *) NULL;

  return track;
}

/* Split a track according to the supplied notemap. It will add as many
   tracks as needed following the selected track. */
static void
track_split(struct Track *track,	/* Track to be split. */
	    Notemap *notemap)		/* Note map to use. */
{
  int i;

  /* List of events for non-note MIDI events. */
  struct Event *others = (struct Event *) NULL;

  /* Array of lists of events, one for each MIDI note. */
  struct Event *notes[MIDI_NOTE_NUM];

  /* Initialise the event list array. */
  for (i = 0; i < MIDI_NOTE_NUM; i++)
    {
      notes[i] = (struct Event *) NULL;
    }

  /* Try to separate out the events in the track. */
  if (eventlist_split(track->clip.events.first, notes, &others))
    {
      /* List of new tracks */
      struct Track *new_tracks = NULL;

      register unsigned int i;
      
      /* Were there any non-note events? */
      if (others != (struct Event *) NULL)
	{
	  new_tracks = track_new(track, "Other Events",
				 (unsigned char) 0,
				 others);
	}
      
      /* Create tracks for each of the lists of events. */
      for (i = 0; i < MIDI_NOTE_NUM; i++)
	{
	  /* If there are notes for the track, notes[i] will be non-null. */
	  if (notes[i] != (struct Event *) NULL)
	    {
	      /* Make the new track. */
	      struct Track *new = track_new(track,
					    (*notemap)[i],
					    (unsigned char) i,
					    notes[i]);
	      
	      /* Stick it at the start of the list so far. */
	      new->next = new_tracks;
	      new_tracks = new;
	    }
	}
      
      /* Splice in the list of new tracks. */
      track->next = (struct Track *)(*functions->List_Cat)(new_tracks, track->next);
    }
}

/* -------------------------------------------------------------------- */
/*				INTERFACE				*/
/* -------------------------------------------------------------------- */

/* I built this interface by hand as well. Does anyone have a favorite
   GUI builder? */

static const struct IntuiText itext1 = {
  4,0,JAM1,
  12,1,
  NULL,
  "Load Note Map File",
  NULL
};

static const struct Gadget gadget1 = {
  NULL,
  15, 16,
  168, 9,
  (ULONG) NULL,
  RELVERIFY+GADGIMMEDIATE,
  BOOLGADGET,
  NULL,
  NULL,
  &itext1,
  (ULONG) NULL,
  NULL,
  1,
  NULL
};

static const struct IntuiText itext2 = {
  4,0,JAM1,
  4,1,
  NULL,
  "Split Selected Track",
  NULL
};

static const struct Gadget gadget2 = {
  &gadget1,
  15, 27,
  168, 9,
  (ULONG) NULL,
  RELVERIFY+GADGIMMEDIATE,
  BOOLGADGET,
  NULL,
  NULL,
  &itext2,
  (ULONG) NULL,
  NULL,
  2,
  NULL
};

static const struct IntuiText itext3 = {
  4,0,JAM1,
  28, 1,
  NULL,
  "Clear Notemap",
  NULL
};

static const struct Gadget gadget3 = {
  &gadget2,
  15, 38,
  168, 9,
  (ULONG) NULL,
  RELVERIFY+GADGIMMEDIATE,
  BOOLGADGET,
  NULL,
  NULL,
  &itext3,
  (ULONG) NULL,
  NULL,
  3,
  NULL
};

/* Start of gadget list. */
#define gadget_list (&gadget3)

/* Close the splitter window. */
__geta4				/* Callback function. */
splitter_close(void)
{
  /* Ensure that we have something to close. */
  if (splitter.accessory.window)
  {
    struct IntuiMessage *message;

    (*functions->EmbossOff)(splitter.accessory.window,1);
    (*functions->EmbossOff)(splitter.accessory.window,2);
    (*functions->EmbossOff)(splitter.accessory.window,3);
    (*functions->EmbossWindowOff)(splitter.accessory.window);

    /* Remember where we were in case we're reopened later. */
    splitter.accessory.left = splitter.accessory.window->LeftEdge;
    splitter.accessory.top = splitter.accessory.window->TopEdge;

    /* Make it hard to send us messages and eat up any that are pending. */
    splitter.accessory.window->UserPort = 0;
    ModifyIDCMP(splitter.accessory.window,0);
    WaitTOF();
    WaitTOF();
    WaitTOF();
    WaitTOF();
    WaitTOF();
    Forbid();
    while (message = (struct IntuiMessage *)GetMsg(functions->window->UserPort))
      {
        ReplyMsg((struct Message *)message);
      }
    (*functions->FlashyCloseWindow)(splitter.accessory.window);
    splitter.accessory.window = NULL;
    Permit();
  }

  return(1);
}

static const char *title = "Splitter v1.0";

/* Open the splitter window. */
__geta4				/* Callback function. */
splitter_open(void)
{
  /* Use v37+ style window tags to specify the new window. */
  const struct TagItem tags[] = {
    {WA_Left, splitter.accessory.left},
    {WA_Top, splitter.accessory.top},
    {WA_Width, 198},
    {WA_Height, 53},
    {WA_DetailPen, 5},
    {WA_BlockPen, 1},
    {WA_IDCMP, GADGETUP|CLOSEWINDOW},
    {WA_DragBar, FALSE},
    {WA_DepthGadget, FALSE},
    {WA_CloseGadget, FALSE},
    {WA_Activate, TRUE},
    {WA_NoCareRefresh, TRUE},
    {WA_Borderless, TRUE},
    {WA_Gadgets, (ULONG) gadget_list},
    {WA_Checkmark, (ULONG) NULL},
    {WA_Title, (ULONG) NULL},
    {WA_ScreenTitle, (ULONG) title},
    {WA_CustomScreen, (ULONG) functions->screen}
  };

  /* Attempt to open the window, and check to see if we were successful. */
  splitter.accessory.window = (struct Window *)OpenWindowTagList(NULL, &tags);
  if (splitter.accessory.window != (struct Window *) NULL)
  {
    /* Set up communication. */
    splitter.accessory.window->UserPort = functions->window->UserPort;
    ModifyIDCMP(splitter.accessory.window,functions->window->IDCMPFlags);

    (*functions->EmbossWindowOn)(splitter.accessory.window,
				 WINDOWCLOSE|WINDOWDEPTH|WINDOWDRAG,
	    			 "Splitter",(short)-1,(short)-1,0,0);
    (*functions->EmbossOn)(splitter.accessory.window,1,1);
    (*functions->EmbossOn)(splitter.accessory.window,2,1);
    (*functions->EmbossOn)(splitter.accessory.window,3,1);
  }

  return((long)splitter.accessory.window);
}

/* Gather and process user input from the splitter window. */
__geta4				/* Callback function. */
splitter_edit(struct IntuiMessage *message)	/* Message to process. */
{
  struct Window *window = message->IDCMPWindow;
  struct Gadget *gadget;
  long class,code;

  class = message->Class;
  code = message->Code;
  gadget = (struct Gadget *) message->IAddress;
  ReplyMsg((struct Message *)message);
  class = (*functions->SystemGadgets)(window,class,gadget,code);

  if (class == CLOSEWINDOW)
    {
      (void) splitter_close();
      return 0;
    }
  else if (class == GADGETUP)
    {
      switch (gadget->GadgetID)
        {
	case 1:	/* Load Note Map File */
	  {
	    char filename[255];
	    
	    /* Get the filename */
	    (*functions->FileName)(filename,"Load Note Map File:","drummap",
				   functions->screen,
				   FILES_OPEN|FILES_DELETE|FILES_TYPE,0,0);
	    if (filename[0])
	      {
		/* Attempt to open the file and check to see if we
		   succeeded. */
		long file = (*functions->fastopen)(filename,MODE_OLDFILE);
		if (file)
		  {
		    if ((*functions->areyousure)("replace the note map?"))
		      {
			(*functions->openwait)();
                        notemap_delete_entries(splitter.notemap);
                        notemap_load(file, splitter.notemap);
			(*functions->closewait)();
		      }
		    (*functions->fastclose)(file);
		  }
		else
		  {
		    accessory_error(splitter.accessory.window,
				    "Couldn't open file for reading");
		  }
	      }
	  }
          break;
	case 2:	/* Split Selected Track */
	  {
	    if (functions->groupid != 0)	/* Group selected? */
	      {
		accessory_error(splitter.accessory.window,
				"Can't split a group");
	      }  
	    else if (functions->selectedtrack)	/* Track selected? */
	      {
		(*functions->openwait)();
		track_split(functions->selectedtrack, splitter.notemap);
		(*functions->display)(0x1000000);
		(*functions->closewait)();
	      }
	    else /* No track selected. */
	      {
		accessory_error(splitter.accessory.window,
				"You must select a track for splitting");
	      }
	  }
          break;
	case 3:
	  {
	    if ((*functions->areyousure)("clear the note map?"))
	      {
		(*functions->openwait)();
		notemap_delete_entries(splitter.notemap);
		(*functions->closewait)();
	      }
	  }
	}
    }
  return 1;
}

/* Free the notemap. */
__geta4				/* Callback function. */
splitter_remove(void)
{
  notemap_delete(splitter.notemap);
}

struct Accessory *
inittoolmaster(void)
{
  memset((char *)&splitter,0,sizeof(struct Splitter));
  splitter.accessory.left = INITIAL_LEFT;
  splitter.accessory.top = INITIAL_TOP;
  splitter.accessory.id = MAKE_IDS("SPLT");
  splitter.accessory.next = 0;
  splitter.accessory.image = &splitter_image;
  splitter.accessory.onimage = &splitter_onimage;
  splitter.accessory.open = splitter_open;
  splitter.accessory.close = splitter_close;
  splitter.accessory.edit = (no_prototype) splitter_edit;
  splitter.accessory.remove = splitter_remove;
  strcpy(splitter.accessory.name, "Splitter");
  splitter.notemap = notemap_new();
  if (splitter.notemap == (struct Notemap *) NULL)
    {
      return (struct Accessory *) NULL;
    }
  
  return(&splitter.accessory);
}
