/**************************************************
 * SynthBuilder
 * Copyright 1993 Nick Porcaro All Rights Reserved
 **************************************************/

/* #define MIDI_DEBUG 1 */
/*
 * FakeSynthPatch.m
 * Eric Jordan, independent work, Spring, 1992
 *
 * History:
 *
 * Summer 1993  Re-wrote  Nick Porcaro
 *
 * 7/26/92, david jaffe (daj):  
 *	Changed NX_Address to NX_ADDRESS.  Commented out header file 
 *	declarations for private methods.  Fixed a bug in the writing of 
 *      idle method invocations in noteEnd method.  Added check for sineROM
 *      in data allocation code generation.  
 */

#import <stdio.h>
#import <stdlib.h>
#import <string.h>
#import <musickit/musickit.h>
#import <musickit/unitgenerators/unitgenerators.h>
#import <appkit/Application.h>
#import <appkit/Control.h>
#import <dpsclient/event.h>
#import <appkit/Matrix.h>
#import <appkit/TextField.h>
#import <appkit/Panel.h>
#import "UGDef.h"
#import "NodeView.h"
#import "FakeSynthPatch.h"
#import "FakePatchPoint.h"
#import "FakeUG.h"
#import "PatchParam.h"
#import "Clavier.h"
#import "Controller.h"
#import "NoteOnHandler.h"
#import "Utilities.h"
#define EOS '\0'

#define MAXMESSAGELENGTH 200

extern char *getAppName();

char *fix_env_string(char *str)
{
  static  char env_buffer[2048];
  int k, l, len;
  BOOL got_bracket = NO;

  /* Hack for envelope editor
   * The envelope editor wants brackets
   * we don't -- So the user
   * can have the brackets on or not
   */
  if (! str)
    {
      return(NULL);
    }

  len = strlen(str);
  env_buffer[0] = EOS;
  l = 0;
  for (k=0; k<len; k++)
    {
      if ( (str[k] == '[') ||
	  (str[k] == ']') )
	{
	  got_bracket = YES;
	}
      else
	{
	  env_buffer[l++] = str[k];
	}
    }
  
  if (got_bracket)
    {
      env_buffer[l] = EOS;
    }
  else
    {
      strcpy(env_buffer, str);
    }

  return(env_buffer);
}



#define MAXENVSTRING 100


@implementation FakeSynthPatch

static char TempString[1024];

+initialize
{
 /* Set the version. This can be used in a later version to distinguish older
  * formats when unarchiving documents. 
  */
  [FakeSynthPatch setVersion:4];
  return self;
}

- awakeFromNib
{
  inputWarningDone = NO;
  outputWarningDone = NO;
  needsAllocation = YES;
  midiRunning = NO;
  orch = nil;
  fromNode = nil;
  windowMiniaturized = NO;
  midiDidInit = NO;
  phraseInProgress = NO;
  alwaysUpdate = NO;  // Make MIDI response fast by default
  return self;
}

- setWindowMiniaturized:(BOOL) flag
{
  windowMiniaturized = flag;
  return self;
}

- (BOOL) windowMiniaturized
{
  return windowMiniaturized;
}

- init
{
  [super init];
  [self enableNoteOff:YES];
  haveMidi = NO;
  fromNode = nil;
  return self;
}

- setController:aController
{
  theController = aController;
  return self;
}

- setWindowDelegate:aDelegate
{
  [window setDelegate:aDelegate];
  return self;
}

- windowDelegate
{
  return [window delegate];
}

- theController
{
  return theController;
}

- setMemory: aMemory
{
  memory =  aMemory;
  return self;
}

- setUtilities: aUtilities
{
  utilities = aUtilities;
  return self;
}

- setMidiPortSwitch: aMidiPortSwitch
{
  midiPortSwitch = aMidiPortSwitch;
/* This didn't work  [midiPortSwitch setAction:@selector(restartMidi:)]; */
  return self;
}

- setSoundOutDeviceSwitch: aSoundOutDeviceSwitch
{
  soundOutDeviceSwitch = aSoundOutDeviceSwitch;
  return self;
}

- setSamplingRateSwitch: aSamplingRateSwitch
{
  samplingRateSwitch = aSamplingRateSwitch;
  return self;
}

- setDefaultArgValSwitch: aSwitch
{
  defaultArgValSwitch = aSwitch;
  return self;
}

- setAllocOnOpenSwitch: aSwitch
{
  allocOnOpenSwitch = aSwitch;
  return self;
}

- setAlwaysUpdateSwitch: aSwitch
{
  alwaysUpdateSwitch = aSwitch;
  [alwaysUpdateSwitch setTarget:self];
  [alwaysUpdateSwitch setAction:@selector(setAlwaysUpdate:)];
  return self;
}

- setNoteOnButton: aNoteOnButton
{
  noteOnButton = aNoteOnButton;
  [noteOnButton setTarget:self];
  [noteOnButton setAction:@selector(noteOn:)];
  return self;
}

- setNoteOffButton: aNoteOffButton
{
  noteOffButton = aNoteOffButton;
  [noteOffButton setTarget:self];
  [noteOffButton setAction:@selector(noteOff:)];
  return self;
}

- setStopButton: aStopButton
{
  stopButton = aStopButton;
  [stopButton setTarget:self];
  [stopButton setAction:@selector(deallocatePatch:)];
  return self;
}

- setResetControl: aResetControl
{
  resetControl = aResetControl;
  [resetControl setTarget:self];
  [resetControl setAction:@selector(releaseDSP:)];
  return self;
}

- setCloseAllControl: aCloseAllControl
{
  closeAllControl = aCloseAllControl;
  [closeAllControl setTarget:self];
  [closeAllControl setAction:@selector(closeAll:)];
  return self;
}

- setDisplayAllControl: aDisplayAllControl
{
  displayAllControl = aDisplayAllControl;
  [displayAllControl setTarget:self];
  [displayAllControl setAction:@selector(displayAll:)];
  return self;
}

- setPrintSelfControl: aPrintSelfControl
{
  printSelfControl = aPrintSelfControl;
  [printSelfControl setTarget:self];
  [printSelfControl setAction:@selector(printSelf:)];
  return self;
}

- setOrch:anOrch
{
  if (orch)
    {
      [self releaseDSP:self];
    }
  orch = anOrch;
  return self;
}

- memory
{
  return memory;
}

- utilities
{
  return utilities;
}

- midiPortSwitch
{
  return midiPortSwitch;
}

- soundOutDeviceSwitch
{
  return soundOutDeviceSwitch;
}

- samplingRateSwitch
{
  return samplingRateSwitch;
}

- defaultArgValSwitch
{
  return defaultArgValSwitch;
}

- allocOnOpenSwitch
{
  return allocOnOpenSwitch;
}

- alwaysUpdateSwitch
{
  return alwaysUpdateSwitch;
}

- noteOnButton
{
  return noteOnButton;
}

- noteOffButton
{
  return noteOffButton;
}

- stopButton
{
  return stopButton;
}

- resetControl
{
  return resetControl;
}

- closeAllControl
{
  return closeAllControl;
}

- closeAll:sender
{
  int i;
  
  for (i=0; i<[fakeUGs count]; i++)
    {
      [[fakeUGs objectAt:i] closeInspector];
    }

  for (i=0; i<[fakePatchPoints count]; i++)
    {
      [[fakePatchPoints objectAt:i] closeInspector];
    }

  return self;
}

- displayAllControl
{
  return displayAllControl;
}

- printSelfControl
{
  return printSelfControl;
}

- displayAll:sender
{
  int i;
  
  for (i=0; i<[fakeUGs count]; i++)
    {
      [[fakeUGs objectAt:i] displayInspector];
    }

  for (i=0; i<[fakePatchPoints count]; i++)
    {
      [[fakePatchPoints objectAt:i] displayInspector];
    }

  return self;
}

- updateSamplingRateSwitch
/* Called when a new file is read */
{
  if (samplingRate == 44100.0)
    {
      [samplingRateSwitch selectCellAt:0 :0];
    }
  else
    {
      [samplingRateSwitch selectCellAt:1 :0];
    }
  return self;
}

- updateSoundOutDeviceSwitch
/* Called when a new file is read */
{
  [soundOutDeviceSwitch selectCellAt:soundOutDevice :0];
  return self;
}

- updateMidiPortSwitch
/* Called when a new file is read */
{
  [midiPortSwitch selectCellAt:midiPort+1 :0];
  return self;
}

- (BOOL) useDefaultArgValues
{
  if ([[defaultArgValSwitch selectedCell] tag] == 0)
    { /* we decide */
      return YES;
    }
  else
    { /* let the UnitGenerator decide */
      return NO;
    }
}

- (BOOL) allocOnOpen
{
  if ([allocOnOpenSwitch intValue] == 1)
    {
      return YES;
    }
  else
    {
      return NO;
    }
}

- setAlwaysUpdate:sender
{
  if ([alwaysUpdateSwitch intValue] == 1)
    {
      alwaysUpdate = YES;
    }
  else
    {
      alwaysUpdate = NO;
    }

  return self;
}

- (BOOL) alwaysUpdate
{
  return alwaysUpdate;
}

- (double) samplingRate
{
  int val = [[samplingRateSwitch selectedCell] tag];
  switch (val)
    {
    case (0):
      {
	samplingRate = 44100.0;
	break;
      }
    case (1):
      {
	samplingRate = 22050.0;
	break;
      }
    default:
      {
	samplingRate = 44100.0;
	break;
      }
    }
  return samplingRate;
}

- (int) soundOutDevice
/* 0: NeXT internal DAC's  1: DSP serial port */
{
  soundOutDevice = [[soundOutDeviceSwitch selectedCell] tag];
  return soundOutDevice; 
}

- (int) getMidiPortFromSwitch
/* 0: Serial port A  1: Serial port B */
{
  midiPort = [[midiPortSwitch selectedCell] tag];
  return midiPort; 
}

- (int) midiPort
/* 0: Serial port A  1: Serial port B */
{
  return midiPort; 
}

- getFakeUGs
{
  return fakeUGs;
}

- addFakeUG: aDef at:(NXPoint)location
{
  NXSize *imageSize;
  id newFakeUG;
  NXRect newrect;
  id defCopy;

  /* Create the new fakeUG instance and get its graphic
   * scene together
   */
  if ([aDef isMidi]) 
    {
      if (haveMidi)
	{
	  NXRunAlertPanel(getAppName(), "Already have a MIDI object", NULL, NULL, NULL);
	  return nil;
        }
      else
        {
	  haveMidi = YES;
        }
    }

  newFakeUG = [[FakeUG alloc] init];
  defCopy = [aDef copy];
  [newFakeUG initWithDef:defCopy WithPatch:self];
  imageSize = (NXSize *)NXZoneMalloc([self zone], sizeof(*imageSize));


  if (fakeUGs==nil) 
    {
      fakeUGs = [[List alloc] init];
    }

  [fakeUGs addObject:newFakeUG];
  [newFakeUG setAllocOrder:[fakeUGs count]];
  [self addSubview:newFakeUG];
  [[newFakeUG getImage] getSize:imageSize];
  NXSetRect(&newrect, 
	    location.x,
	    location.y,
            imageSize->width, 
	    imageSize->height);
  [newFakeUG setFrame:&newrect];
  [newFakeUG setOpaque:NO];
  [newFakeUG display];
  return self;
}

- setFrom:(NodeView *) newFromNode
{
  fromNode = newFromNode;
  return self;
}

- connect:(NodeView *)toNode
{
  int i;
  int j;
  id selectedPatchPoint;
  char msgString[MAXMESSAGELENGTH];
  char headerString[MAXMESSAGELENGTH];
  static int ppCount = 0;

  if ([toNode isAnOutput])
    {
      id temp = toNode;
      toNode = fromNode;
      fromNode = temp;
    }

  if (toNode==fromNode)
    {
      selectedPatchPoint=[[toNode superview] getConnection:[toNode getTag]];
      if (selectedPatchPoint!=nil)
	{
	  [self setSelected:selectedPatchPoint];
	}
      else
	{
	  if ([[fromNode superview] isData])
	    {
	      sprintf(msgString, "This node designates who will read the");
	      sprintf(msgString, "%s memory associated with this SynthData.",
		      msgString);
	    }
	  else
	    {
	      sprintf(msgString,
		      "This node is associated with the method \"%s\".",
		      [[fromNode superview] 
		     getConnectionName:[fromNode getTag]]);
	    }
	  
	  sprintf(headerString,
		  "%s Connection Info", [[fromNode superview] getTypeString]);
	  NXRunAlertPanel(headerString, msgString, NULL, NULL, NULL);
	}
      return self;
    }


  for (i=0; i<[fakePatchPoints count]; i++)
    for (j=0; j<[[[fakePatchPoints objectAt:i] getToNodes] count]; j++)
      if ([[[fakePatchPoints objectAt:i] getToNodes] objectAt:j]==toNode)
	return self;
  {
    FakePatchPoint *newFakePatchPoint = nil;
     NXRect *newrect = (NXRect *) NXZoneMalloc([self zone], sizeof (*newrect));
     if (fakePatchPoints==nil) fakePatchPoints = [[List alloc] init];
     for (i=0; i<[fakePatchPoints count]; i++)
       if ([[fakePatchPoints objectAt:i] getFromNode]==fromNode)
	 newFakePatchPoint = [fakePatchPoints objectAt:i];
     /* Make sure both inputs to a dswitch are from the same memory. */
     if (([[toNode superview] isDSwitch]) &&
	  ([[toNode superview] getConnection:(3-[toNode getTag])]) &&
	  ([[[toNode superview] 
	   getConnection:(3-[toNode getTag])] getXMemory]!=
	   (newFakePatchPoint ? 
	    [newFakePatchPoint getXMemory] :
	    [[memory selectedCell] tag])))
       return self;
     if (!newFakePatchPoint)
       {
	 newFakePatchPoint = [[FakePatchPoint alloc] init];
         sprintf(TempString, "%sOut%d", [[fromNode superview] getName], ppCount++);
         [newFakePatchPoint setSP: self];
         [newFakePatchPoint setName: TempString];
	 [self addSubview:newFakePatchPoint :NX_BELOW relativeTo:nil];
	 NXSetRect(newrect, bounds.origin.x, bounds.origin.y,
		   bounds.size.width, bounds.size.height);
	 [newFakePatchPoint setFrame:newrect];
	 [fakePatchPoints addObject:newFakePatchPoint];
	 [newFakePatchPoint setAllocOrder:[fakePatchPoints count]];
       }
     /* sine rom's and Biquad outputs must use y memory. */
     if ( [[fromNode superview] isSineROM] ||
          (strcmp([[fromNode superview] getGenericTypeString], "BiquadUG") == 0))
       {
	 [newFakePatchPoint connect:fromNode to:toNode xmemory:NO];
       }
     else
       {
	 [newFakePatchPoint connect:fromNode to:toNode
	  xmemory:[[memory selectedCell] tag]];
       }

     [newFakePatchPoint display];
     [[fromNode superview]
    setConnection:[fromNode getTag]
    toFakePatchPoint:newFakePatchPoint];
     [[toNode superview]
    setConnection:[toNode getTag]
    toFakePatchPoint:newFakePatchPoint];
     return self;
   }
}

- makeSelected:anObject
{
  selected = anObject;
  return self;
}

- setSelected:sender
{
  [window makeFirstResponder:self];

  if ( (selected!=nil) &&  ([selected respondsTo:@selector(eraseSelf)]))
    {
      id old=selected;
      selected=sender;
      [old lockFocus];
      [old eraseSelf];
      [old unlockFocus];
      [old display];
    }
  else
    {
      selected = sender;
    }

  if ((selected!=self) && (selected!=nil))
    {
      [sender displayIfDoubleClick];
    }
  else
    {
      [selected display];
    }

  return self;
}

- displayIfDoubleClick
{
  return self;
}

- getSelected
{
  return selected;
}

- setNameOfSelectedObject:sender
{
  char *newName, *s;
  int len;

  s = (char *) [sender stringValue];
  if (! s || ( (len = strlen(s)) < 0))
    {
      return self;
    }

  len++;
  newName = (char *) NXZoneCalloc([self zone], len, sizeof(char));
  strcpy(newName, [sender stringValue]);
  [selected changeName:newName];
  return self;
}

- cut:sender
{
  id temp;

  temp = selected;

  [window orderFront:self];
  if ((selected!=self) && (selected!=nil))
    {
      [self setSelected:self];
      [temp closeInspector];
      [self deallocatePatch:self];
      [temp removeSelf];
      [temp free];
      [self display];
    }

  return self;
}

- delete:sender
{
  return [self cut:sender];
}

- remFakeUGFromList:fakeUG
{
  [fakeUGs removeObject:fakeUG];
  return self;
}

- remFakePatchPointFromList:fakePatchPoint
{
  [fakePatchPoints removeObject:fakePatchPoint];
  return self;
}

- removeSelf
{

  [self deallocatePatch:self];
  while ([fakeUGs count])
    {
      id temp = [fakeUGs objectAt:0];
      [temp removeSelf];
      [temp free];
      temp = nil;
    }

  selected = nil;
  return self;
}

- (char *)getName
{
  if (name==NULL)
    name = "MySynthPatch";
  return name;
}

- changeName:(char *)newName
{
  name = newName;
  return self;
}

- (char *)getTypeString
{
  return "synth patch";
}

- (BOOL)getXMemory
{
  return [[memory selectedCell] tag];
}

- (BOOL)isAFakeUG
{
  return NO;
}

- setUGParameterFromFakeUG:aFakeUG argNum:(int)j

/*************

This method was added by daj with nick and ods watching,
to support real time update of unit generator
parameters.  The value of the jth noteParam (gotten from
the interface) is real ug for aFakeUG.  This happens while
the orchestra (dsp) is running -- the unit generator
object takes care of sending the parameter value to the
dsp code for the unit generator, hopefully without having to
interrupt the DSP in a noticable way.
**************/

{
  if ([aFakeUG isPatchParameter] ||
      [aFakeUG isMidi] ||
      [aFakeUG isClavier])
    {
      return self;
    }
  
  if ([aFakeUG isData])
    {
      if (strcmp([aFakeUG getGenericTypeString], "Constantpp") == 0)
	{
	  /* This must be a double argument to a constant pp */
	  [aFakeUG setConstantPP:atod([aFakeUG getArg:j])];
	}
    }
  
  if (![aFakeUG isData] || ([aFakeUG getArgType:j]==PARTIALS))
    {
      if ( ([aFakeUG getArgType:j] == INT) && ([aFakeUG getArg:j]!= NULL))
	{
	  int arg;
	  id curUG = [aFakeUG getUG];
	  /* For a full explanation of why we need the 
	   * following typedef, see the documentation
	   * on the Object class's -methodFor: method
	   */
	  typedef id (*methodType) (id, SEL, int);
	  
	  if (! curUG)
	    {
	      return self;
	    }
	  
	  sscanf([aFakeUG getArg:j], "%d", &arg);
	  (*((methodType) ([curUG methodFor:
			    sel_getUid([aFakeUG
				      getArgName:j])])))
	    (curUG, sel_getUid([aFakeUG getArgName:j]),
	     arg);
	}
      else if ([aFakeUG getArgType:j]==ENV)
	{
	  id arg;
	  arg = [aFakeUG getArgEnvelope:j];

#ifdef MIDI_DEBUG
	  printf("setUGParameterFromFakeUG:  phraseInProgress: %s\n", (phraseInProgress ? "YES" : "NO"));
#endif MIDI_DEBUG

	  MKUpdateAsymp([aFakeUG getUG] , arg, 0.0, 1.0,
			MK_NODVAL, MK_NODVAL, MK_NODVAL,
			((phraseInProgress)? MK_phraseRearticulate:MK_phraseOn));
	  phraseInProgress = YES;
	}
      else if ( ([aFakeUG getArgType:j]==BOOLEAN) && ([aFakeUG getArg:j]!= NULL))
	{
	  BOOL arg;
	  int ival;
	  id curUG = [aFakeUG getUG];
	  typedef id (*methodType) (id, SEL, BOOL);
	  if (! curUG)
	    {
	      return self;
	    }
	  sscanf([aFakeUG getArg:j], "%d", &ival);
	  arg = (BOOL) ival;
	  (*((methodType) ([curUG methodFor:
			    sel_getUid([aFakeUG
				      getArgName:j])])))
	    (curUG, sel_getUid([aFakeUG 
			      getArgName:j]),
	     arg);
	}
      else if ( ([aFakeUG getArgType:j]==PARTIALS) && ([aFakeUG getArg:j]!= NULL))
	{
	  int k, l, pointCount=0;
	  double *freqs, *amps;
	  id arg=[[Partials alloc] init];
	  id curpp = [[aFakeUG getConnection:0]
		      getpp];
	  char *scoreString = [aFakeUG getArg:j];
	  if (! curpp)
	    {
	      return self;
	    }

	  for (k=0; scoreString[k]!='\0'; k++)
	    {
	      if (scoreString[k]=='{')
		pointCount++;
	    }
	  freqs=(double *) NXZoneCalloc([self zone], pointCount, sizeof(double));
	  amps=(double *) NXZoneCalloc([self zone], pointCount, sizeof(double));
	  l=0;
	  for (k=0; k<pointCount; k++)
	    {
	      freqs[k] = amps[k] = 0.0;
	      while (scoreString[l]!='{')
		l++;
	      sscanf(scoreString+l, "{%lf,%lf}",
		     &freqs[k], &amps[k]);
	      l++;
	    }
	  [arg setPartialCount:pointCount freqRatios:freqs 
	       ampRatios:amps phases:NULL orDefaultPhase:0.0];
	  [curpp setData:[arg dataDSPLength: [aFakeUG getLength]]];
	}
      else if ([aFakeUG getArg:j] != NULL)
	{
	  double arg;
	  id curUG = [aFakeUG getUG];
	  typedef id (*methodType) (id, SEL, double);
	  if (! curUG)
	    {
	      return self;
	    }
	  sscanf([aFakeUG getArg:j], "%lf", &arg);
	  
	  /* This monster of a line is
	   * saying:  get a pointer to the function
	   * for the jth method of the current unit generator
	   * (curUG) and then call it with the three arguments
	   */
	  (*((methodType) ([curUG methodFor:
			    sel_getUid([aFakeUG 
				      getArgName:j])])))
	    (curUG, sel_getUid([aFakeUG
			      getArgName:j]),
	     arg);
	}
      else
	{
	  ; /* Do nothing */
	}
    }


  return self;
}

- getOrch
{
  return orch;
}

- claimDSP:sender
/* This method prepares for usage of the
 * DSP -- The Orchestra is allocated and prepared for
 * subsequent allocation of UnitGenerators and PatchPoints.
 */
{
  /* If we already have it don't fool around */
  if (orch)
    {
      return self;
    }

  /* Deallocate and turn off the orchestra */
  [self releaseDSP:sender];

  /* 
   * Allocate the orchestra 
   */
  orch = [[Orchestra alloc] init];

  printf("claimDSP: orch: 0x%x\n", (unsigned int) orch);
  if (! orch)
    {
      NXRunAlertPanel(getAppName(), "Could not claim DSP.  Does another application have it?",
		      NULL, NULL, NULL);
      return nil;
    }

  [Conductor setFinishWhenEmpty:NO];
  [UnitGenerator enableErrorChecking:YES];
  [orch setTimed:NO];
  MKSetDeltaT(0.006);
  [orch setFastResponse:YES];
  [orch setSamplingRate:[self samplingRate]];
  serialPortSoundIn = NO;
  /* 
   * Open the orchestra and run it 
   * Run midi if necessary
   * Tell the controller to hit the switches
   */
  [orch open];

  return self;
}

- releaseDSP:sender
/* This shuts down the orchestra, deallocates
 * any UnitGenerators and PatchPoints and frees the
 * orchestra, but it leaves MIDI running
 */
{
  /* Do we even need to release it ? */
  if (! orch)
    {
      needsAllocation = YES;
      return nil;
    }

  [self deallocatePatch:self];
  [orch close];
  [orch free];
  orch = nil;
  return self;
}

- allocatePatch
/* This allocates the unit generators
 * and patch points
 */
{
  int i, j;
  id aFakeUG;
  int numFakeUGs;
  
  if (! needsAllocation && orch)
    {
      return self;
    }
  else
    {
      needsAllocation = YES;
    }

  /* claim the DSP if necessary */
  [Conductor lockPerformance];
  [self claimDSP:self];
  [self checkDSPSerialPorts];
  [self initMidi];  

  printf("allocatePatch: orch: 0x%x\n", (unsigned int) orch);
  for (i=0; i<[fakePatchPoints count]; i++)
    {
      if (! [[fakePatchPoints objectAt:i] allocatePPMK])
	{
	  NXRunAlertPanel(getAppName(), "Error:  Could not allocate: %s",
			  NULL, NULL, NULL, 
			  [[fakePatchPoints objectAt:i] getName]);
	  return [self deallocatePatch:self];
	}
      [[fakePatchPoints objectAt:i] validate];
    }

  numFakeUGs = [fakeUGs count];
  for (i=0; i<numFakeUGs; i++)
    {
      aFakeUG = [fakeUGs objectAt:i];
      if ([aFakeUG isComplete])
	{
	  for (j=0; j<[aFakeUG getArgCount]; j++)
	    {
	      [aFakeUG syncEnvelope:j];
	    }

	  if (! [aFakeUG allocateUGMK])
	    {
	      NXRunAlertPanel(getAppName(), "Error:  Could not allocate: %s",
			      NULL, NULL, NULL, 
			      [aFakeUG getName]);
	      return [self deallocatePatch:self];
	    }
	  [[aFakeUG getUG] finish];
	}
    }
  
  /* Make connections */
  for (i=0; i<[fakePatchPoints count]; i++)
      {
      [[fakePatchPoints objectAt:i] makeConnectionsMK];
      }

  /* Special case for sine rom */
  for (i=0; i<[fakePatchPoints count]; i++)
    if ([[[[fakePatchPoints objectAt:i] getFromNode] superview] isData])
      for (j=0; j<[[[fakePatchPoints objectAt:i] getToNodes] count]; j++)
	if ([[[[[fakePatchPoints objectAt:i] 
		getToNodes] objectAt:j] superview] isComplete]
	    && [[[[[fakePatchPoints objectAt:i] 
		   getToNodes] objectAt:j] superview] isOscg]
	    && [[[[fakePatchPoints objectAt:i] getFromNode] superview]
		isSineROM])
	  [[[[[[fakePatchPoints objectAt:i] getToNodes] 
	    objectAt:j] superview] getUG]
	   setTableToSineROM];

  needsAllocation = NO;

  [self syncUGArgs];
  [Conductor unlockPerformance];
  return self;
}

- syncUGArgs
{
  int i, j;
  id aFakeUG;
  int count;

  /* Set parameters from the "FakeUG" to the real UG 
   * We do this at allocate time to get the initial values down to
   * the DSP.  When a PatchParameter is activated, or when
   * the user sends a message.  This code was formerly in the noteOn method
   * but I put it here so the midi response would be faster.
   */
  count = [fakeUGs count];
  for (i=0; i<count; i++)
    {
      aFakeUG = [fakeUGs objectAt:i];
      if (aFakeUG)
	{
	  if ([aFakeUG isComplete])
	    {
	      for (j=0; j<[aFakeUG getArgCount]; j++) 
		{  
		  /* This will set the latest parameter value, including
		   * an envelope that changed
		   */
		  [aFakeUG sendArg:j];
		}
	    }
	}
    }

  return self;
}

- deallocatePatch:sender
/* Deallocate all Unit generators and patch points  */
{
  int i;

  if (! orch)
    {
      needsAllocation = YES;
      return nil;
    }

  [Conductor finishPerformance];
  [orch stop];

  for (i=0; i<[fakePatchPoints count]; i++)
    {
      [[fakePatchPoints objectAt:i] deallocPPMK];
    }

  for (i=0; i<[fakeUGs count]; i++)
    {
      if ([[fakeUGs objectAt:i] isComplete])
	{
	  [[fakeUGs objectAt:i] deallocUGMK];
	}
    }

  if (serialPortDevice)
    {
      serialPortDevice = [serialPortDevice free];
      serialPortDevice = nil;
    }

  needsAllocation = YES;
  inputWarningDone = NO;
  outputWarningDone = NO;
  return self;
}

- noteOn:sender
/* sends a run message to all the ugs
 * This is either called by the NoteOnHandler,
 * or by the NoteOn button
 */
{
  int i;

  /* Allocate the patch if necessary */
  [self allocatePatch];

  /* I think this is what kills it */
//  [self syncUGArgs];

  /* Start the orchestra if necessary */
  if ([orch deviceStatus] != MK_devRunning)
    {
      [orch run];
      if (haveMidi && !midiRunning)
	{
#ifdef MIDI_DEBUG
	  printf("noteOn: Running MIDI\n");
#endif MIDI_DEBUG

	  [midi run];
	  midiRunning = YES;
	}
      [Conductor startPerformance];
      [Orchestra flushTimedMessages];
    }

  for (i=0; i<[fakeUGs count]; i++)
    {
      id aFakeUG = [fakeUGs objectAt:i];
      if ([aFakeUG isComplete])
	{
 	  [aFakeUG runMK];
	}
    }

  [self enableNoteOff:YES];

  return self;
}

- noteOff:sender
/* Stop the sound but leave all the orchestra/Conductor
 * apparatus intact.  This is most useful when you 
 * want to tell an envelope to stop sticking and ramp to
 * it's final value.
 * This method is called either from the NoteOnHandler,
 * or the NoteOff button.
 */
{
  int i;
  id aUG;
  id aFakeUG;
  double t;
  MKMsgStruct *msgStruct;

  /* See if we can send a noteOff
   * There are certain cases where
   * there were memory exceptions caused
   * if a noteOff was enabled in.
   * Search for noteOffEnabled in the code
   * to see where.
   */

  /* If noteOff disabled, it will send a noteOn */
  [self noteOffEnabled];
  t = 0;
  for (i=0; i<[fakeUGs count]; i++)
    {
      aFakeUG = [fakeUGs objectAt:i];
      if ([aFakeUG isSoundMaker])
	{
	  aUG = [[fakeUGs objectAt:i] getUG];
	  /* #warning temporary hack */
	  /* What we really want to do is something like what SynthPatch.m does */
	  if ([aUG isKindOf:[AsympUG class]]) 
	    {
	      [aUG useInitialValue:NO];
	    }
	  
	  t = MAX(t,[aUG finish]);
	  msgStruct = MKNewMsgRequest(t + [[Conductor defaultConductor] time],
				      @selector(noteEnd),self,0);
	  MKScheduleMsgRequest(msgStruct,[Conductor defaultConductor]);
	}
    }

  return self;
}

- noteEnd
/*
This should clear the flag phraseInProgress.  For patches without envelopes,
you also might want to do something to make the sound stop--e.g. set the sound  
output scaling to 0.
*/
{
  phraseInProgress = NO;
  return self;
}

- checkDSPSerialPorts
{
  int count, i;
  char *typeString;

  /* 
   * Check for dsp serial input/output
   */
  count = [fakeUGs count];
  serialPortSoundOut = NO;
  for (i=0; i<count && !serialPortSoundIn; i++) 
    {
      typeString = [[fakeUGs objectAt:i] getGenericTypeString];
      if ( (strcmp(typeString,"In1aUG") == 0) || (strcmp(typeString,"In1bUG") == 0)) 
	{
          serialPortSoundIn = YES;
	}
    }

  if (serialPortSoundIn) 
    {
      if (!inputWarningDone)
	{
	  if (NXRunAlertPanel(getAppName(),
			    "This patch requires a DSP serial port device." 
			      " Make sure one is plugged into the DSP port.",
			      "OK","Cancel",NULL) != NX_ALERTDEFAULT)
	    {
	      return [self deallocatePatch:self];
	    }
	}

      inputWarningDone = YES;
      [orch setSerialSoundIn:YES];

      /* See DSPSerialPortDevice.h (musickit) for other options. 
       * MRDigitalEars, SSAD64x, ArielProPort are the possibilities
       */
      [orch setSerialPortDevice:serialPortDevice = [[SSAD64x alloc] init]];
    }
  else 
    {
      [orch setSerialSoundIn:NO];
    }

  serialPortSoundOut = NO;
  if ([self soundOutDevice] == 0)
    {  /* NeXT internal DAC's */
      serialPortSoundOut = NO;
    }
  else
    { /* DSP serial port */
      serialPortSoundOut = YES;
    }

  serialPortSoundOut = NO;
  if (serialPortSoundOut) 
    {
      if (!outputWarningDone) 
	{
	  if (NXRunAlertPanel(getAppName(),
			      "This patch requires a DSP serial port device." 
			      " Make sure one is plugged into the DSP port.",
			      "OK","Cancel",NULL) != NX_ALERTDEFAULT)
	    {
	      return [self deallocatePatch:self];
	    }
	}
      
      outputWarningDone = YES;
      [orch setSerialSoundOut:YES];
      /* See DSPSerialPortDevice.h (musickit) for other options. 
       * MRDigitalEars, SSAD64x, ArielProPort are the possibilities
       */
      [orch setSerialPortDevice:serialPortDevice = [[SSAD64x alloc] init]];
    } 
  else 
    {
      [orch setSerialSoundOut:NO];
    }
 
  return self;
}

- enableNoteOff:(BOOL) flag
{
  noteOffEnabled = flag;
  return self;
}

- (BOOL) noteOffEnabled
{
  /* Send a NoteOn to make it cool
   * for the noteOff to happen. 
   * This is an idiosyncracy from when an envelope
   * is deleted or added.
   */
  if (! noteOffEnabled)
    {
      [self noteOn:self];
    }

  return noteOffEnabled;
}

- setAllowPPInspectorUpdate:(BOOL) flag
{
  allowPPInspectorUpdate = flag;
  return self;
}

- (BOOL) allowPPInspectorUpdate
{
  return allowPPInspectorUpdate;
}

- eraseSelf
{
  return self;
}

- (BOOL)acceptsFirstMouse
{
  return YES;
}

- mouseDown:(NXEvent *)theEvent
{
  [self setSelected:self];
  return self;
}

- getFakePatchPoints;
{
  return fakePatchPoints;
}

- setSerialIn:(BOOL)state;
{
  serialPortSoundIn = state;
  return self;
}

- setSerialOut:(BOOL)state;
{
  serialPortSoundOut = state;
  return self;
}

- initMidi

/* Create if necessary and initialize the midi object
 * There is only one object per serial port, so the folowing returns the
 * same object if the port has not changed via preferences. 
 */
{
  if (! midiDidInit)
    {
      if (! [self startMidi])
	{
	  return nil;
	}

      /* This should be unified so 
       * that we have only one noteOnHandler in the App
       */
      [self initMidiNoteOnHandler];
    }

  return self;
}

- initMidiNoteOnHandler
{
  midiNoteOnHandler = [[NoteOnHandler alloc] init];
  [midiNoteOnHandler initConnection:self];
  [[midi noteSender] connect:[midiNoteOnHandler noteReceiver]];
  return self;
}

- startMidi
{
  static char midi_port_name[20];
  int portNum;
  int i;
  BOOL midiHappening = NO;

  /* Only start if the patch has midi in it */
  for (i=0; i<[fakeUGs count]; i++)
    {
      if ([[fakeUGs objectAt:i] isMidi])
	{
	  midiHappening = YES;
	}
    }

  if (! midiHappening)
    {
      return nil;
    }

  portNum = [self getMidiPortFromSwitch];
  if (portNum < 0)
    {
      return nil;
    }

  sprintf(midi_port_name, "midi%d", portNum);

  if (midi)
    {
      [self freeMidi];
    }

  midi = [Midi newOnDevice:midi_port_name];
  /*  [midi setOutputTimed:[preferences midiTimedOutput]];  */
  [midi acceptSys:MK_sysStart];
  [midi acceptSys:MK_sysContinue];
  [midi acceptSys:MK_sysStop];
  if (! [midi run])
    {
      NXRunAlertPanel(getAppName(), 
		      "Could not start MIDI on port %s.  Check connections", 
		      NULL, NULL, NULL,
		      (portNum ? "B" : "A"));
      return nil;
    }

  midiRunning = YES;
  midiDidInit = YES;
  haveMidi = YES;
  return self;
}
      
- restartMidi:sender
{
  id nr;
  int portNum;

  portNum = [self getMidiPortFromSwitch];
  if (portNum < 0)
    {
      [self freeMidi];
      return nil;
    }

  if (midiNoteOnHandler && midi)
    {
      nr = [midiNoteOnHandler noteReceiver];
      [[midi noteSender] disconnect:nr];
      [midiNoteOnHandler free];
    }

  if ([self startMidi])
    {
      [self initMidiNoteOnHandler];
    }

  [self updateElements];

  return self;
}

- setHaveMidi:(BOOL) flag
{
  haveMidi = flag;
  return self;
}

- (BOOL) haveMidi
{
  return haveMidi;
}

- freeMidi
{
  [midi stop];
  [midi close];
  midi = nil;
  [self setHaveMidi:NO];
  midiRunning  = NO;
  midiDidInit = NO;
  return self;
}

- midiNoteOnHandler
{
   return midiNoteOnHandler;
}

- midi
{
  return midi;
}

- conductor
{
  return [Conductor defaultConductor];
}

- (BOOL) hasSoundMakers
/* Check if there are any real ugs in the patch */
{
  int i;
  for (i=0; i<[fakeUGs count]; i++)
    {
      if ([[fakeUGs objectAt:i] isSoundMaker])
	{
	  return YES;
	}
    }
  
  return NO;
}

- (BOOL) hasControllableSoundMakers
/* Check if there are any real ugs in the patch 
 * that have methods
 */
{
  int i;
  id aFakeUG;

  for (i=0; i<[fakeUGs count]; i++)
    {
      aFakeUG = [fakeUGs objectAt:i];
      if ([aFakeUG isSoundMaker] && [aFakeUG getArgCount])
	{
	  return YES;
	}
    }
  
  return NO;
}

- updateElements
{
  int i;
  id anObject;

  [self updateSoundOutDeviceSwitch];
  [self updateSamplingRateSwitch];
  [self updateMidiPortSwitch];
  for (i=0; i<[fakePatchPoints count]; i++)
    {
      anObject = [fakePatchPoints objectAt:i];
      [anObject setSP:self];
      [anObject validate];
    }

  for (i=0; i<[fakeUGs count]; i++)
    {
      anObject = [fakeUGs objectAt:i];
      [anObject setSP:self];
      [anObject validate];
      [anObject checkConnections];
    }

  /* Bring up the inspectors that were up before */
  for (i=0; i<[fakePatchPoints count]; i++)
    {
      anObject = [fakePatchPoints objectAt:i];
      [anObject displayInspectorIfWasActive];
    }

  for (i=0; i<[fakeUGs count]; i++)
    {
      anObject = [fakeUGs objectAt:i];
      [anObject displayInspectorIfWasActive];
    }

  return self;
}

- printSelf:sender
{
  int i;
  int fakeUGcount, fakePPcount;

  fakeUGcount = [fakeUGs count];
  fakePPcount = [fakePatchPoints count];

  printf("- (FakeSynthPatch %s)  There are %d FakeUGs and %d FakePatchPoints\n",
	 [self getName], fakeUGcount, fakePPcount);

  for (i=0; i<fakeUGcount; i++)
    {
      [[fakeUGs objectAt:i] printSelf];
    }

  printf("- (FakeSynthPatch %s)  Allocation order of the FakePatchPoints\n",
	 [self getName]);
  for (i=0; i<fakePPcount; i++)
    {
      printf("%d %s\n",	
	     [[fakePatchPoints objectAt:i] getAllocOrder],
	     [[fakePatchPoints objectAt:i] getName]);
    }
  
  printf("- (FakeSynthPatch %s)  Allocation order of the FakeUGs\n",
	 [self getName]);
  for (i=0; i<fakeUGcount; i++)
    {
      printf("%d %s\n",	
	     [[fakeUGs objectAt:i] getAllocOrder],
	     [[fakeUGs objectAt:i] getName]);
    }
  
  return self;
}

static int allocOrderCompare(const void *ele1, const void *ele2)
{
  return [*(id *)ele1 getAllocOrder] - [*(id *)ele2 getAllocOrder];
}

- sortFakePatchPointsByAllocOrder
{
  [utilities sortList:fakePatchPoints sortFunction:allocOrderCompare];
  return self;
}

- sortFakeUGsByAllocOrder
{
  [utilities sortList:fakeUGs sortFunction:allocOrderCompare];
  return self;
}

- write:(NXTypedStream *) stream
{
  [super write:stream];

  /* Get the latest values from the switches */
  [self getMidiPortFromSwitch];
  [self soundOutDevice];
  [self samplingRate];
  NXWriteTypes(stream, "@", &fakeUGs);
  NXWriteTypes(stream, "@", &fakePatchPoints);
  NXWriteTypes(stream, "*", &name);
  NXWriteTypes(stream, "i", &midiPort);
  NXWriteTypes(stream, "i", &soundOutDevice);
  NXWriteTypes(stream, "d", &samplingRate);
  return self;
}

- read:(NXTypedStream *) stream
{
  int version;
  id  anObj;
  BOOL aBool;

  [self deallocatePatch:self];
  [super read:stream];
  version = NXTypedStreamClassVersion(stream, "FakeSynthPatch");
  switch (version)
    {
      case(4):
	{
	  NXReadTypes(stream, "@", &fakeUGs);
	  NXReadTypes(stream, "@", &fakePatchPoints);
	  NXReadTypes(stream, "*", &name);
	  NXReadTypes(stream, "i", &midiPort);
	  NXReadTypes(stream, "i", &soundOutDevice);
	  NXReadTypes(stream, "d", &samplingRate);
	  fromNode = nil;
	  break;
	}
      case(3):
	{
	  NXReadTypes(stream, "@", &fakeUGs);
	  NXReadTypes(stream, "@", &fakePatchPoints);
	  NXReadTypes(stream, "@", &fromNode);
	  NXReadTypes(stream, "*", &name);
	  NXReadTypes(stream, "i", &midiPort);
	  NXReadTypes(stream, "i", &soundOutDevice);
	  NXReadTypes(stream, "d", &samplingRate);
	  break;
	}
      case(2):
	{
	  NXReadTypes(stream, "@", &fakeUGs);
	  NXReadTypes(stream, "@", &fakePatchPoints);
	  NXReadTypes(stream, "@", &fromNode);
	  NXReadTypes(stream, "*", &name);
	  NXReadTypes(stream, "@", &anObj);
	  NXReadTypes(stream, "d", &samplingRate);
	  NXReadTypes(stream, "c", &aBool);
	  NXReadTypes(stream, "c", &aBool);
	  NXReadTypes(stream, "@", &anObj);
	  midiPort = 0;
	  soundOutDevice = 0;
	  break;
	}
      default:
        {
	  /* Old versions didn't have versioning */
	  NXReadTypes(stream, "@@@@*",
		      &fakeUGs, &fakePatchPoints, &fromNode,
		      &selected, &name);
	  samplingRate = 44100.0;
	  midiPort = 0;
	  soundOutDevice = 0;
	  break;
        }
    }

  return self;
}

@end
