/*
  cccc: A Bars and Pipes tool for creating cylcically changing control changes.
  
  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
  8 September 1997
  
  History:
  	Version 1.0 (8 September 1997)
        Initial release.
*/

#include <libraries/dos.h>
#include <proto/exec.h>
#include <proto/intuition.h>
#include <proto/graphics.h>
#include <exec/memory.h>
#include <string.h>
#include <intuition/intuition.h>
#include <intuition/sghooks.h>

#include "bars.h"
#include "myheader.h"

#define CCCC_NAME "CCCC"

#define ID_CCCC MAKE_IDS(CCCC_NAME)

#define CCCC_TYPE (TOOL_INPUT)

__chip static UWORD cccc[] = { 
  /*------ plane # 0: --------*/
  0x0, 0x0,
  0x1f81, 0xf800,
  0x6006, 0x0,
  0x6006, 0x0,
  0x6006, 0x0,
  0x1f81, 0xf000,
  0x0, 0x0,
  0x0, 0x0,
  0x0, 0x0,
  0x0, 0x0,
  0x0, 0x0,
  0x0, 0x0,
  
  /*------ plane # 1: --------*/
  0x0, 0x0,
  0x1f80, 0x0,
  0x6000, 0x0,
  0x6000, 0x0,
  0x6000, 0x0,
  0x1f80, 0x0,
  0x0001, 0xf800,
  0x0006, 0x0,
  0x0006, 0x0,
  0x0006, 0x0,
  0x0001, 0xf800,
  0x0, 0x0,
  
  /*------ plane # 2: --------*/
  0x0, 0x0,
  0x0, 0x0,
  0x0, 0x0,
  0x0, 0x0, 
  0x0, 0x0,
  0x0, 0x0,
  0x1f81, 0xf800,
  0x6006, 0x0,
  0x6006, 0x0,
  0x6006, 0x0,
  0x1f81, 0xf800,
  0x0, 0x0,
};

static struct Image cccc_image = {
  0,0,
  24 , 12 , 3 ,
  &cccc[0],
  0x1f,0x00,
  NULL
};	

/* The waveforms that might be selected. */
enum Wave { TRIANGLE = 0L, SAW = 1L, SQUARE = 2L, RANDOM = 3L };

/* Possibilities for initial direction of waves. */
enum Direction { UP = 0L, DOWN = 1L };

struct CCCCTool {
  struct Tool tool;
  char controller;		/* Controller number to be varied. */
  long start;			/* When to start varying. */
  long finish;			/* When to stop varying. */
  long period;			/* Period of variation. */
  enum Wave wave;		/* Waveform to be used. */
  enum Direction direction;	/* Initial direction of waveform. */
  long granularity;		/* How often to send updates of
				   controller value. */
};

extern struct Functions *functions;

/* Some initial values. */
#define INIT_CC		 10L
#define INIT_PERIOD	 (CLOCKSPERMEASURE)
#define INIT_WAVE	 (TRIANGLE)
#define INIT_DIRECTION	 (UP)
#define INIT_GRANULARITY 2L

static void
  cccc_tool_init(struct CCCCTool *tool)
{
  tool->tool.touched = TOUCH_INIT;

  tool->controller = INIT_CC;
  tool->start = 0L;
  tool->finish = functions->stoptime;
  tool->period = INIT_PERIOD;
  tool->wave = INIT_WAVE;
  tool->direction = INIT_DIRECTION;
  tool->granularity = INIT_GRANULARITY;
}

static struct ToolMaster master;

/* Compute the value of the controller. */
static UBYTE
  wave_value(const enum Wave wave,		/* Wave form being output. */
	     const enum Direction direction,	/* Initial direction. */
	     const long loc,			/* Where in the cycle we are. */
	     const long period)			/* How long the cycle is. */
{
  UBYTE val = 0;
  
  switch (wave)
    {
    case TRIANGLE:
      {
	const long tmp = (loc << 8) / period;
	const long quarter = period >> 2;
	
	if (loc < quarter)
	  {
	    val = 64 + tmp;
	  }
	else if (loc < 3 * quarter)
	  {
	    val = 192 - tmp;
	  }
	else
	  {
	    val = tmp - 192;
	  }
	break;
      }
    case SAW:
      val = (loc << 7) / period;
      break;
    case SQUARE:
      if (loc < (period >> 1))
	{
	  val = 127;
	}
      else
	{
	  val = 0;
	}
      break;
    case RANDOM:
      val = (rand() >> 8) % 128;
      break;	
    }		
  return direction == UP ? val : 127 - val;
}

/* Display the value of the controller. */
static void
display_value(struct Window *window,	/* Window to display in. */
	      UBYTE value)		/* The value to display. */
{
  char string[10];

  struct RastPort *rp = window->RPort;
  sprintf(string, "%3ld", value);
  Move(rp, 229, 21);
  SetAPen(rp, 1);
  SetBPen(rp, 0);
  SetDrMd(rp, JAM2);
  Text(rp, string, 3);
}

/* Generate and queue a batch of controller events. */
static void
  queue_cccc_events(struct CCCCTool *tool,	/* Where the parameters come from. */
		    long start,			/* Start time of batch. */
		    long end)			/* End time of batch. */
{
  /* Ensure we have sane values. */
  if (!tool->tool.touched)
    {
      cccc_tool_init(tool);
    }

  /* Do we need to do any work? */
  if (end < tool->start || tool->finish < start)
    {
      return;
    }

  /* Make sure the time range for generated events
     respects the settings in the tool. */
  if (start < tool->start)
    {
      start = tool->start;
    }
  if (tool->finish < end)
    {
      end = tool->finish;
    }

  /* For each quantum, create and queue the controller event. */
  for (long time = start;
       time < end;
       time += NOTE_LENGTH(tool->granularity))
    {
      struct Event *event = (struct Event *)(*functions->allocevent)();
      if (event)
	{
	  const long position = (time - tool->start) % tool->period;
	  const UBYTE value =  wave_value(tool->wave, tool->direction,
					position, tool->period);
	  event->next = NULL;
	  event->time = time;
	  event->type = EVENT_VOICE;
	  event->status = MIDI_CCHANGE;
	  event->byte1 = tool->controller;
	  event->byte2 = value;
	  event->tool = tool->tool.next;
	  (*functions->qevent)(event);

	  /* If the tool has an open window, display the value. */
	  if (tool->tool.window)
	    {
	      display_value(tool->tool.window, value);
	    }
	} 
    }
}

/* Queue some events if necessary. */
static void
  transporttrack(struct Track *track,	/* Track where the CCCCTool is located. */
		 const long command,	/* Transport command. */
		 const long start,	/* Start time for command. */
		 const long end)	/* End time for command. */
{
  switch (command)
    {
    case TC_START:
    case TC_PLAY:
    case TC_TICK:
      queue_cccc_events((struct CCCCTool *)track->toollist, start, end);
      break;
    case TC_STOP:
    case TC_POSITION:
      break;
    }
}

/* Called whenever something happens in the transport. */
__geta4			/* Callback. */
void transportcode(long command,	/* Transport command. */
		   long start,		/* Start time for command. */
		   long end)		/* End time for command. */
{
  /* Are we in multiple input mode? */
  if (functions->multiin)
    {
      /* Find all the CCCCTools and get them to do their thing. */
      for (struct Track *track = (struct Track *) functions->tracklist;	
	   track;
	   track = track->next)
	{
	  struct Tool *tool = (struct Tool *) track->toollist;
	  if (tool && (tool->toolid == ID_CCCC))
	    {
	      transporttrack(track, command, start, end);
	    }
	}
    }
  else
    {
      /* Get the current input track, and if it has a CCCCTool, send it
	 the command. */
      struct Track *track = master.intrack;
      if (track)
	{
	  struct Tool *tool = track->toollist;
	  if (tool && (tool->toolid == ID_CCCC))
	    {
	      transporttrack(track, command, start, end);
	    }
	}
    }
}

/* Process notes. */
__geta4			/* Callback. */
struct Event *
  processeventcode(struct Event *event)
{
  event->tool = event->tool->next; 	/* Doesn't do anything! */
  return(event);
}

/* Gadget identifiers. */
#define CCCC_CC_GADGET		1
#define CCCC_CCDOWN_GADGET	2
#define CCCC_CCUP_GADGET	3
#define CCCC_START_GADGET	4
#define CCCC_FINISH_GADGET	5
#define CCCC_PERIOD_GADGET	6
#define CCCC_WAVE_GADGET	7
#define CCCC_DIRECTION_GADGET	8
#define CCCC_GRANULARITY_GADGET	9

/* Strings naming the various entities declared in enumerated types above. */
static const char *wave_names[] = { "Triangle", "  Saw   ", " Square ", " Random " };

static const char *direction_names[] = { " Up ", "Down" };

static const char *note_names[] = {" 1  ","1/2 ","1/4 ","1/8 ","1/16","1/32", "1/64"};

/* Granularity gadget. */
SHORT CCCCBorderVectors1[] = {
  0,0,
  81,0,
  81,12,
  0,12,
  0,0
};

struct Border CCCCBorder1 = {
  -1,-1,
  3,0,JAM1,
  5,
  CCCCBorderVectors1,
  NULL
};

struct IntuiText CCCCIText1 = {
  4,0,JAM1,
  25,1,
  NULL,
  NULL,	/* Initialised in edittoolcode(). */
  NULL
  };

struct Gadget CCCCGadget7 = {
  NULL,
  175,54,
  80,11,
  GFLG_GADGHBOX|GFLG_GADGHIMAGE,
  RELVERIFY|GADGIMMEDIATE,
  BOOLGADGET,
  (APTR)&CCCCBorder1,
  NULL,
  &CCCCIText1,
  NULL,
  NULL,
  CCCC_GRANULARITY_GADGET,
  NULL
  };

/* Direction gadget. */
SHORT CCCCBorderVectors2[] = {
  0,0,
  81,0,
  81,12,
  0,12,
  0,0
};
struct Border CCCCBorder2 = {
  -1,-1,
  3,0,JAM1,
  5,
  CCCCBorderVectors2,
  NULL
  };

struct IntuiText CCCCIText2 = {
  4,0,JAM1,
  25,1,
  NULL,
  NULL,	/* Initialised in edittoolcode(). */
  NULL
};

struct Gadget CCCCGadget6 = {
  &CCCCGadget7,
  175,41,
  80,11,
  GFLG_GADGHBOX|GFLG_GADGHIMAGE,
  RELVERIFY|GADGIMMEDIATE,
  BOOLGADGET,
  (APTR)&CCCCBorder2,
  NULL,
  &CCCCIText2,
  NULL,
  NULL,
  CCCC_DIRECTION_GADGET,
  NULL
};

/* Wave gadget. */
SHORT CCCCBorderVectors3[] = {
  0,0,
  81,0,
  81,12,
  0,12,
  0,0
};
struct Border CCCCBorder3 = {
  -1,-1,
  3,0,JAM1,
  5,
  CCCCBorderVectors3,
  NULL
};

struct IntuiText CCCCIText3 = {
  4,0,JAM1,
  10,1,
  NULL,
  NULL,	/* Initialised in edittoolcode(). */
  NULL
};

struct Gadget CCCCGadget5 = {
  &CCCCGadget6,
  175,28,
  80,11,
  GFLG_GADGHBOX|GFLG_GADGHIMAGE,
  RELVERIFY|GADGIMMEDIATE,
  BOOLGADGET,
  (APTR)&CCCCBorder3,
  NULL,
  &CCCCIText3,
  NULL,
  NULL,
  CCCC_WAVE_GADGET,
  NULL
  };

/* Period gadget. */
struct StringExtend replace_mode_string_gadget1 = {
  NULL,
  { 1, 0 },
  { 1, 0 },
  SGM_REPLACE|SGM_FIXEDFIELD,
  NULL,
  NULL,
  };

UBYTE CCCCCCCCGadget4SIBuff[MBC_STRING_LENGTH];
struct StringInfo CCCCCCCCGadget4SInfo = {
  CCCCCCCCGadget4SIBuff,
  NULL,
  0,
  MBC_STRING_LENGTH,
  0,
  0,0,0,0,0,
  &replace_mode_string_gadget1,
  0,
  NULL
};

SHORT CCCCBorderVectors4[] = {
  0,0,
  101,0,
  101,12,
  0,12,
  0,0
};

struct Border CCCCBorder4 = {
  -1,-1,
  3,0,JAM1,
  5,
  CCCCBorderVectors4,
  NULL
};

struct IntuiText CCCCIText4 = {
  2,0,JAM1,
  -57,0,
  NULL,
  "Period:",
  NULL
};

struct Gadget CCCCGadget4 = {
  &CCCCGadget5,
  69,54,
  100,11,
  GFLG_GADGHBOX|GFLG_GADGHIMAGE|GFLG_STRINGEXTEND|GFLG_TABCYCLE,
  RELVERIFY|GADGIMMEDIATE,
  STRGADGET,
  (APTR)&CCCCBorder4,
  NULL,
  &CCCCIText4,
  NULL,
  (APTR)&CCCCCCCCGadget4SInfo,
  CCCC_PERIOD_GADGET,
  NULL
};

/* Finish gadget. */
struct StringExtend replace_mode_string_gadget2 = {
  NULL,
  { 1, 0 },
  { 1, 0 },
  SGM_REPLACE|SGM_FIXEDFIELD,
  NULL,
  NULL,
  };

UBYTE CCCCCCCCGadget3SIBuff[MBC_STRING_LENGTH];
struct StringInfo CCCCCCCCGadget3SInfo = {
  CCCCCCCCGadget3SIBuff,
  NULL,
  0,
  MBC_STRING_LENGTH,
  0,
  0,0,0,0,0,
  &replace_mode_string_gadget2,
  0,
  NULL
};

SHORT CCCCBorderVectors5[] = {
  0,0,
  101,0,
  101,12,
  0,12,
  0,0
};

struct Border CCCCBorder5 = {
  -1,-1,
  3,0,JAM1,
  5,
  CCCCBorderVectors5,
  NULL
};

struct IntuiText CCCCIText5 = {
  2,0,JAM1,
  -58,1,
  NULL,
  "Finish:",
  NULL
};

struct Gadget CCCCGadget3 = {
  &CCCCGadget4,
  69,41,
  100,11,
  GFLG_GADGHBOX|GFLG_GADGHIMAGE|GFLG_STRINGEXTEND|GFLG_TABCYCLE,
  RELVERIFY|GADGIMMEDIATE,
  STRGADGET,
  (APTR)&CCCCBorder5,
  NULL,
  &CCCCIText5,
  NULL,
  (APTR)&CCCCCCCCGadget3SInfo,
  CCCC_FINISH_GADGET,
  NULL
  };

/* Start gadget. */
struct StringExtend replace_mode_string_gadget3 = {
  NULL,
  { 1, 0 },
  { 1, 0 },
  SGM_REPLACE|SGM_FIXEDFIELD,
  NULL,
  NULL,
  };

UBYTE CCCCCCCCGadget2SIBuff[MBC_STRING_LENGTH];
struct StringInfo CCCCCCCCGadget2SInfo = {
  CCCCCCCCGadget2SIBuff,
  NULL,
  0,
  MBC_STRING_LENGTH,
  0,
  0,0,0,0,0,
  &replace_mode_string_gadget3,
  0,
  NULL
};

SHORT CCCCBorderVectors6[] = {
  0,0,
  101,0,
  101,12,
  0,12,
  0,0
};

struct Border CCCCBorder6 = {
  -1,-1,
  3,0,JAM1,
  5,
  CCCCBorderVectors6,
  NULL
};

struct IntuiText CCCCIText6 = {
  2,0,JAM1,
  -58,1,
  NULL,
  "Start:",
  NULL
};

struct Gadget CCCCGadget2 = {
  &CCCCGadget3,
  69,28,
  100,11,
  GFLG_GADGHBOX|GFLG_GADGHIMAGE|GFLG_STRINGEXTEND|GFLG_TABCYCLE,
  RELVERIFY|GADGIMMEDIATE,
  STRGADGET,
  (APTR)&CCCCBorder6,
  NULL,
  &CCCCIText6,
  NULL,
  (APTR)&CCCCCCCCGadget2SInfo,
  CCCC_START_GADGET,
  NULL
};

/* CC# gadget. */
struct PropInfo CCCCCCCCGadget1SInfo = {
  AUTOKNOB+FREEHORIZ,
  -16384, -1,
  16384, -1,
};

struct Image CCCCImage1 = {
  0,0,
  102,6,
  0,
  NULL,
  0x0000,0x0000,
  NULL
};

struct IntuiText CCCCIText7 = {
  2,0,JAM1,
  -58,1,
  NULL,
  "CC #:",
  NULL
};

struct Gadget CCCCGadget1 = {
  &CCCCGadget2,
  69,14,
  100,10,
  GFLG_GADGHBOX|GFLG_GADGHIMAGE,
  RELVERIFY|GADGIMMEDIATE,
  PROPGADGET,
  (APTR)&CCCCImage1,
  NULL,
  &CCCCIText7,
  NULL,
  (APTR)&CCCCCCCCGadget1SInfo,
  CCCC_CC_GADGET,
  NULL
};

#define CCCCGadgetList1 CCCCGadget1

struct NewWindow CCCCNewWindowStructure1 = {
  75,85,
  265,70,
  0,6,
  GADGETDOWN+GADGETUP+CLOSEWINDOW,
  WINDOWDRAG+WINDOWDEPTH+WINDOWCLOSE+ACTIVATE+NOCAREREFRESH,
  &CCCCGadget1,
  NULL,
  "CCCC",
  NULL,
  NULL,
  5,5,
  -1,-1,
  CUSTOMSCREEN
};

/* Sets the text of the gadget to text. */
void
  setgadgettext(struct Window *window,	/* Window where the gadget lives. */
		const short id,		/* Gadget id. */
		const char *text)	/* String to stick in gadget text. */
{
  struct Gadget *gadget = (struct Gadget *) (*functions->GetGadget)(window,id);
  if (gadget)
    {
      gadget->GadgetText->IText = text;
      (functions->DrawEmbossed)(window,id);
    }
}

/* Prints out the value associated with the CC# prop gadget. */
__geta4		/* Callback. */
long controller_routine(struct Window *window,	/* Window where the gadget lives. */
			struct Gadget *gadget,	/* The gadget itself. Unused. */
			unsigned long value)	/* The value to display. */
{
  char text[10];
  struct RastPort *rp = window->RPort;
  Move(rp, 175, 21);
  SetAPen(rp, 1);
  SetBPen(rp, 0);
  SetDrMd(rp, JAM2);
  sprintf(text, "%3ld", value);
  Text(rp, text, 3);
  return value;
}

/* Converts a time value to a string and sets a string gadget with this
   information. */
static void
settimestring(struct Window *window,	/* The window where the gadget lives. */
	      short gadget,		/* The id of the gadget to be set. */
	      struct Clip *clip,	/* The clip the time refers to. */
	      long value,		/* The new value. */
	      char *string)		/* Some storage for the conversion. */
{
  (*functions->timetostring)(clip, value, string);
  (*functions->SetStringInfo)(window, gadget, string);
}

/* Converts a length value to a string and sets a string gadget with this
   information. */
static void
setlengthstring(struct Window *window,	/* The window where the gadget lives. */
		short gadget,		/* The id of the gadget to be set. */
		struct Clip *clip,	/* The clip the length refers to. */
		long start,		/* The start time for the calculation. */
		long value,		/* The length. */
		char *string)		/* Some storage for the conversion. */
{
  (*functions->lengthtostring)(clip,  start, value, string);
  (*functions->SetStringInfo)(window, gadget, string);
}

/* Update a gadget with a wave name. */
static void
setwave(struct Window *window,		/* The window where the gadget lives. */
	short gadget,			/* The id of the gadget to be updated. */
	enum Wave value)		/* The wave value. */
{
  setgadgettext(window, gadget, wave_names[value]);
}

/* Update a gadget with a direction name. */
static void
setdirection(struct Window *window,	/* The window where the gadget lives. */
	     short gadget,		/* The id of the gadget to be updated. */
	     enum Direction direction)	/* The direction value. */
{
  setgadgettext(window, gadget, direction_names[direction]);
}

/* Update a gadget with a note name. */
static void
setgranularity(struct Window *window,	/* The window where the gadget lives. */
	       short gadget,		/* The id of the gadget to be updated. */
	       long value)		/* The index of the note name. */
{
  setgadgettext(window, gadget, note_names[value]);
}

/* Grab a time value from a string gadget. */
static long
updatetimestring(struct Window *window,	/* The window where the gadget lives. */
		 short id,		/* The id of the gadget to be updated. */
		 struct Clip *clip,	/* The clip the time refers to. */
		 long oldvalue)		/* The old value this string gadget
					   represented. */
{
  struct StringInfo *stringinfo = 
    (struct StringInfo *)(*functions->GetStringInfo)(window, id);

  return (*functions->stringtotime)(clip, stringinfo->Buffer, oldvalue);
}

/* Grab a length value from a string gadget. */
static long
  updatelengthstring(struct Window *window,	/* The window where the gadget lives. */
		     short id,			/* The id of the gadget to be updated. */
		     struct Clip *clip,		/* The clip the length refers to. */
		     long start,		/* The start time for the calculation. */
		     long oldvalue)		/* The old value this string
						   gadget represented. */
{
  struct StringInfo *stringinfo = 
    (struct StringInfo *)(*functions->GetStringInfo)(window, id);
  
  (*functions->stringtolength)(clip, start, stringinfo->Buffer, oldvalue);
}

/* Interface driver called by Bars and Pipes. */
__geta4		/* Callback. */
void edittoolcode(struct CCCCTool *tool)
{
  char *string[MBC_STRING_LENGTH];

  struct IntuiMessage *message;
  struct Window *window;
  long class, code;
  struct Gadget *gadget;
  struct NewWindow *newwindow;
  long refresh = 1;

  struct Clip *clip = tool->tool.track == NULL ? NULL : &(tool->tool.track->clip);

  if (!tool->tool.touched)
    {
      cccc_tool_init(tool);
    }
  
  CCCCNewWindowStructure1.Screen = functions->screen;
  
  if (tool->tool.touched & TOUCH_EDIT)
    {
      CCCCNewWindowStructure1.LeftEdge = tool->tool.left;
      CCCCNewWindowStructure1.TopEdge = tool->tool.top;
    }
  
  newwindow = (struct NewWindow *)
    (*functions->DupeNewWindow)(&CCCCNewWindowStructure1);
  if (!newwindow)
    {
      return;
    }
  newwindow->Title = 0;
  newwindow->Flags |= BORDERLESS;
  newwindow->Flags &= ~0xF;
  newwindow->BlockPen = 0;
  newwindow->DetailPen = 0;

  window = (struct Window *) (*functions->FlashyOpenWindow)(newwindow);
  if (window == NULL)
    {
      return;
    }
  
  tool->tool.window = window;

  /* Make the various gadgets look pretty. */
  (*functions->EmbossWindowOn)(window,WINDOWCLOSE|WINDOWDEPTH|WINDOWDRAG,
			       CCCC_NAME, (short)-1, (short)-1, 0, 0);

  for (long i = CCCC_WAVE_GADGET;
       i <= CCCC_GRANULARITY_GADGET;
       i++)
    {
      (*functions->EmbossOn)(window, i, 9);
    }

  (*functions->FatEmbossedPropOn)(window,
				  CCCC_CC_GADGET, CCCC_CCDOWN_GADGET,
				  CCCC_CCUP_GADGET,
				  (no_prototype) controller_routine,
				  128, 1);

  (*functions->ModifyEmbossedProp)(window, CCCC_CC_GADGET,
				   tool->controller, 0, 0, 0, 0, 0);
  
  (*functions->DrawEmbossedProp)(window, CCCC_CC_GADGET);

  /* Initialise the contents of the various gadgets. */
  settimestring(window, CCCC_START_GADGET,
		clip, tool->start, string);
  settimestring(window, CCCC_FINISH_GADGET,
		clip, tool->finish, string);
  setlengthstring(window, CCCC_PERIOD_GADGET,
		  clip, tool->start, tool->period, string);
  
  setwave(window, CCCC_WAVE_GADGET, tool->wave);
  
  setdirection(window, CCCC_DIRECTION_GADGET, tool->direction);
  
  setgranularity(window, CCCC_GRANULARITY_GADGET, tool->granularity);

  /* Process messages until a CLOSEWINDOW. */
  for (;;)
    {
      short stringid = 0;	/* Set to number of last string gadget worked on. */

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

      ReplyMsg((struct Message *)message);
      
      if (class == CLOSEWINDOW)
	{
	  break;	/* ... out of the for(;;) loop. */
	}
      else if (class == GADGETDOWN)
	{
	  const short id = gadget->GadgetID;
	  switch (id)
	    {
	    case CCCC_CC_GADGET:
	      tool->controller =
		(*functions->DragEmbossedProp)(window, CCCC_CC_GADGET);
	      (*functions->DrawEmbossedProp)(window, CCCC_CC_GADGET);
	      break;
	    case CCCC_CCDOWN_GADGET:
	      tool->controller =
		(*functions->ShiftEmbossedProp)(window, CCCC_CC_GADGET, -1, 0);
	      (*functions->DrawEmbossedProp)(window, CCCC_CC_GADGET);
	      break;
	    case CCCC_CCUP_GADGET:
	      tool->controller =
		(*functions->ShiftEmbossedProp)(window, CCCC_CC_GADGET, 1, 0);
	      (*functions->DrawEmbossedProp)(window, CCCC_CC_GADGET);
	      break;
	    case CCCC_START_GADGET:
	      settimestring(window, CCCC_START_GADGET,
			    clip, tool->start, string);
	      break;
	    case CCCC_FINISH_GADGET:
	      settimestring(window, CCCC_FINISH_GADGET,
			      clip, tool->finish, string);
	      break;
	    case CCCC_PERIOD_GADGET:
	      setlengthstring(window, CCCC_PERIOD_GADGET,
			      clip, tool->start, tool->period, string);
	      break;
	    case CCCC_WAVE_GADGET:
	      if (tool->wave == RANDOM)
		{
		  tool->wave = TRIANGLE;
		}
	      else
		{
		  tool->wave++;
		}
	      setwave(window, CCCC_WAVE_GADGET, tool->wave);
	      break;
	    case CCCC_DIRECTION_GADGET:
	      if (tool->direction == DOWN)
		{
		  tool->direction = UP;
		}
	      else
		{
		  tool->direction++;
		}
	      setdirection(window, CCCC_DIRECTION_GADGET, tool->direction);
	      break;
	    case CCCC_GRANULARITY_GADGET:
	      {
		const int value = (*functions->popupnote)(window, tool->granularity);
		if (value != -1)
		  {
		    /* The tranposrt only sends out commands on beats, so it's
		       pointless having a granularity of less then 2 (== 1/4). */
		    if (value < 2)
		      {
			tool->granularity = 2;
		      }
		    else
		      {
			tool->granularity = value;
		      }
		  }
		setgranularity(window, CCCC_GRANULARITY_GADGET, tool->granularity);
	      }
	      break;
	    }
	}
      else if (class == GADGETUP)
	{
	  const short id = gadget->GadgetID;
	  switch (id)
	    {
	    case CCCC_CC_GADGET:
	    case CCCC_CCDOWN_GADGET:
	    case CCCC_CCUP_GADGET:
	      break;
	    case CCCC_START_GADGET:
	      /* Make sure start time is before finish time. */
	      tool->start = updatetimestring(window, id, clip, tool->start);
	      if (tool->start > tool->finish)
		{
		  tool->start = tool->finish;
		}
	      settimestring(window, CCCC_START_GADGET, clip,
			    tool->start, string);
	      break;
	    case CCCC_FINISH_GADGET:
	      /* Make sure finish time is after start time. */
	      tool->finish = updatetimestring(window, id, clip, tool->finish);
	      if (tool->finish < tool->start)
		{
		  tool->finish = tool->start;
		}
	      settimestring(window, CCCC_FINISH_GADGET, clip,
			    tool->finish, string);
	      break;
	    case CCCC_PERIOD_GADGET:
	      tool->period = updatelengthstring(window, id, clip, tool->start,
				                tool->period);
	      setlengthstring(window, CCCC_PERIOD_GADGET, clip,
                              tool->start, tool->period, string);
	      break;
	    case CCCC_WAVE_GADGET:
	    case CCCC_DIRECTION_GADGET:
	    case CCCC_GRANULARITY_GADGET:
	      break;
	    }
	}
    }
  tool->tool.window = 0;
  tool->tool.left = window->LeftEdge;
  tool->tool.top = window->TopEdge;
  tool->tool.width = window->Width;
  tool->tool.height = window->Height;
  tool->tool.touched = TOUCH_INIT | TOUCH_EDIT;

  /* Turn off the prettiness. */
  (*functions->FatEmbossedPropOff)(window,
				  CCCC_CC_GADGET, CCCC_CCDOWN_GADGET,
				  CCCC_CCUP_GADGET);

  for (long i = CCCC_WAVE_GADGET;
       i <= CCCC_GRANULARITY_GADGET;
       i++)
    {
      (*functions->EmbossOff)(window, i);
    }

  (*functions->EmbossWindowOff)(window);
  (*functions->FlashyCloseWindow)(window);
  (*functions->DeleteNewWindow)(newwindow);
}

/* Make sure we remove the transport routine. */
__geta4		/* Callback. */
void removetool()
{
  (*functions->removetransport)(transportcode);
}

struct ToolMaster *
  inittoolmaster()
{
  memset((char *)&master,0,sizeof(struct ToolMaster));
  master.toolid = ID_CCCC;
  master.image = &cccc_image;
  strcpy(master.name, CCCC_NAME);
  master.edittool = (void_prototype) edittoolcode;
  master.processevent = (event_prototype) processeventcode;
  master.tooltype = CCCC_TYPE;
  master.removetool = removetool;
  master.toolsize = sizeof(struct CCCCTool);
  (*functions->installtransport)(transportcode);
  return(&master);
}
