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

#define DEBUG_PRINT printf

/*
 * PatchParam.m
 * Nick Porcaro Summer 1993
 */

#import <stdlib.h>
#import <sys/param.h>
#import <dsp/dsp.h>
#import <strings.h>
#import <appkit/Application.h>
#import <appkit/View.h>
#import <appkit/Panel.h>
#import <appkit/PopUpList.h>
#import <appkit/Control.h>
#import <appkit/Slider.h>
#import <appkit/Matrix.h>
#import <appkit/TextField.h>
#import <musickit/Midi.h>
#import <musickit/Conductor.h>
#import <musickit/Envelope.h>
#import "PatchParam.h"
#import "FakeSynthPatch.h"
#import "FakeUG.h"
#import "EnvDelegate.h"
#import "EnvController.h"
#import "ParamInterface.h"
/* #import "EnvelopeGraphView.h" */
#import "Utilities.h"

extern char *getAppName();

@implementation PatchParam

static char ErrorMsg[1024];

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

- awakeFromNib
{
  ugSelectorPop = [ugSelector target];
  [inputInterface setMode:CONTROLS];
  
  [ugSelectorPop setTarget:self];
  [ugSelectorPop setAutoupdate:YES];
  [ugSelectorPop changeButtonTitle:YES];
  [ugSelectorPop setAction:@selector(selectFakeUG:)];
  
  argSelectorPop = [argSelector target];
  [argSelectorPop setTarget:self];
  [argSelectorPop setAction:@selector(selectArg:)];
  [argSelectorPop setAutoupdate:YES];
  [argSelectorPop changeButtonTitle:YES];
  clickCount = 0;
  currNoteCount = 0;
  return self;
}

- init
/* This reads in the nib file for dealing with a PatchParam
 * Make sure you call setPatch after this or the mapper will not work
 * So here's an example of how you create one of these objects
 * (in this example self is a FakeUG):
 *
 *     patchParam = [[PatchParam alloc] init];
 *     [patchParam setPatch:[self thePatch]];
 *     [patchParam setPatchParamName:name];
 */
{

  [super init];
  [self addNoteReceiver:[[NoteReceiver alloc] init]];

  /* Create a new patch param inspector 
   * for the patchParam in this FakeUG.
   * We load the nib section each time so
   * we can get multiple patchParamInspector panels
   */
  [NXApp loadNibSection:"PatchParam.nib" owner:self withNames:YES];

  /* The correct values are set in the nib file fields to 
   * reflect the following:
   */
  midiController = 0;
  strcpy(midiControllerName,"Off");
  midiCtrlScaleVal = 1.0;
  minMidiCtrlScaleVal = 0.0;
  maxMidiCtrlScaleVal = 2.0;
  midiCtrlOffsetVal = 0.0;
  minMidiCtrlOffsetVal = 0.0;
  maxMidiCtrlOffsetVal = 127.0;
  noteTypeToControl = MK_noteOn;
  [offsetSlider setDoubleValue:midiCtrlOffsetVal];
  [offsetSlider setMinValue:minMidiCtrlOffsetVal];
  [offsetSlider setMaxValue:maxMidiCtrlOffsetVal];

  [scaleSlider setDoubleValue:midiCtrlScaleVal];
  [scaleSlider setMinValue:minMidiCtrlScaleVal];
  [scaleSlider setMaxValue:maxMidiCtrlScaleVal];
  mapEnvelope = nil;
  envDelegate = nil;
  theFakeUG = nil;
  clickCount = 0;
  [self syncMapEnvelope];
  paramEnabled = YES;
  useInspectorRect = NO;
  inspectorRectJustLoaded = NO;
  currNoteCount = 0;
  thinEnabled = NO;
  thinFactor = 1;  // No thinning
  return self;
}

- takeParamEnabled:sender
{
  paramEnabled = ([sender intValue] ? YES : NO);
  return self;
}

- takeThinFactor:sender
{
  thinFactor = [sender intValue];
  return self;
}

- takeThinEnable:sender
{
  thinEnabled = ([sender intValue] ? YES : NO);
  return self;
}

- free
/* Usually called by the FakeUG associated with this PatchParam */
{
  if (envDelegate)
    {
      [envDelegate free];
      envDelegate = nil;
    }

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

  return self;
}

- setPatchParamName: (char *) aName
{
  if (! [[thePatch utilities] checkName:aName])
    {
      if (thePPName)
	{
	  [thePPName setStringValue:theName];
	}
      return nil;
    }

  strcpy(theName, aName);
  return self;
}

- (char *) getName;
{
  return theName;
}

- setPatch:aPatch
{
  thePatch = aPatch;
  fakeUGs = [thePatch getFakeUGs];

  return self;
}

- setViewFakeUG:aFakeUG
{
  theViewFakeUG = aFakeUG;
  return self;
}

- thePatch
{
  return thePatch;
}

- setInspectorTitles
{
  if (inspector)
    {
      [inspector setTitle:""];
      [inspector setTitle:theName];
      [thePPName setStringValue:theName];
    }

  return self;
}

- takeNameFrom:sender
{
  if (![sender stringValue])
    {
      NXRunAlertPanel(getAppName(), "Invalid name", NULL, NULL, NULL);
      return self;
    }
      
  [self setPatchParamName: (char *) [sender stringValue]];
  [self setInspectorTitles];
  return self;
}

- takeMidiCtrlFrom:sender
{
  char *sys_str, *user_str;
  int num;

  if ([sender isKindOfClassNamed: "TextField"])
    {
      /* This came from the user. so be paranoid */
      user_str = (char *) [sender stringValue];
      sys_str = [paramInterface validControlName: user_str Number: &num];
      if (sys_str)
	{
	  midiController = num;
	  [sender setStringValue:sys_str];  /* Make the case correct */
	  strcpy(midiControllerName, sys_str);
	}
      else
	{
	  NXRunAlertPanel(getAppName(), "Invalid controller", NULL, NULL, NULL);
	}
    }
  else
    {
      /* a "trusted" source */
      midiController = [sender intValue];
      strcpy(midiControllerName, (char *)[midiCtrlField stringValue]);
    }


  midiControllerAsMKParm = [paramInterface MidiParamAsMK:midiController];
  return self;
}

- takeScaleFrom:sender
{
  midiCtrlScaleVal = [sender doubleValue];
  [sender setDoubleValue:midiCtrlScaleVal];
  [scaleSlider setDoubleValue:midiCtrlScaleVal];
  [midiCtrlScale setDoubleValue:midiCtrlScaleVal];
  if (midiCtrlScaleVal > maxMidiCtrlScaleVal)
    {
      maxMidiCtrlScaleVal = midiCtrlScaleVal;
      [maxMidiCtrlScale setDoubleValue:maxMidiCtrlScaleVal];
      [scaleSlider setMaxValue:maxMidiCtrlScaleVal];
    }
  else if (midiCtrlScaleVal < minMidiCtrlScaleVal)
    {
      minMidiCtrlScaleVal = midiCtrlScaleVal;
      [minMidiCtrlScale setDoubleValue:minMidiCtrlScaleVal];
      [scaleSlider setMinValue:minMidiCtrlScaleVal];
    }

  return self;
}

- takeMinScaleFrom:sender
{
  minMidiCtrlScaleVal = [sender doubleValue];
  if (midiCtrlScaleVal < minMidiCtrlScaleVal)
    {
      midiCtrlScaleVal = minMidiCtrlScaleVal;
      [midiCtrlScale setDoubleValue:midiCtrlScaleVal];
      [scaleSlider setDoubleValue:midiCtrlScaleVal];
    }

  [scaleSlider setMinValue:minMidiCtrlScaleVal];
  [sender setDoubleValue:minMidiCtrlScaleVal];
  return self;
}

- takeMaxScaleFrom:sender
{
  maxMidiCtrlScaleVal = [sender doubleValue];
  if (midiCtrlScaleVal > maxMidiCtrlScaleVal)
    {
      midiCtrlScaleVal = maxMidiCtrlScaleVal;
      [midiCtrlScale setDoubleValue:midiCtrlScaleVal];
      [scaleSlider setDoubleValue:midiCtrlScaleVal];
    }

  [scaleSlider setMaxValue:maxMidiCtrlScaleVal];
  [sender setDoubleValue:maxMidiCtrlScaleVal];
  return self;
}

- takeOffsetFrom:sender
{
  midiCtrlOffsetVal = [sender doubleValue];
  [sender setDoubleValue:midiCtrlOffsetVal];
  [offsetSlider setDoubleValue:midiCtrlOffsetVal];
  [midiCtrlOffset setDoubleValue:midiCtrlOffsetVal];
  if (midiCtrlOffsetVal > maxMidiCtrlOffsetVal)
    {
      maxMidiCtrlOffsetVal = midiCtrlOffsetVal;
      [maxMidiCtrlOffset setDoubleValue:maxMidiCtrlOffsetVal];
      [offsetSlider setMaxValue:maxMidiCtrlOffsetVal];
    }
  else if (midiCtrlOffsetVal < minMidiCtrlOffsetVal)
    {
      minMidiCtrlOffsetVal = midiCtrlOffsetVal;
      [minMidiCtrlOffset setDoubleValue:minMidiCtrlOffsetVal];
      [offsetSlider setMinValue:minMidiCtrlOffsetVal];
    }

  return self;
}

- takeMinOffsetFrom:sender
{
  minMidiCtrlOffsetVal = [sender doubleValue];
  if (midiCtrlOffsetVal < minMidiCtrlOffsetVal)
    {
      midiCtrlOffsetVal = minMidiCtrlOffsetVal;
      [midiCtrlOffset setDoubleValue:midiCtrlOffsetVal];
      [offsetSlider setDoubleValue:midiCtrlOffsetVal];
    }

  [offsetSlider setMinValue:minMidiCtrlOffsetVal];
  [sender setDoubleValue:minMidiCtrlOffsetVal];
  return self;
}

- takeMaxOffsetFrom:sender
{
  maxMidiCtrlOffsetVal = [sender doubleValue];
  if (midiCtrlOffsetVal > maxMidiCtrlOffsetVal)
    {
      midiCtrlOffsetVal = maxMidiCtrlOffsetVal;
      [midiCtrlOffset setDoubleValue:midiCtrlOffsetVal];
      [offsetSlider setDoubleValue:midiCtrlOffsetVal];
    }
  [offsetSlider setMaxValue:maxMidiCtrlOffsetVal];
  [sender setDoubleValue:maxMidiCtrlOffsetVal];
  return self;
}

- takeMapEnableFrom:sender
{
  if ([sender intValue])
    {
      mapEnabled = YES;
    }
  else
    {
      mapEnabled = NO;
    }

  return self;
}

- displayInspector
{
  NXRect virginRect;

  if ([thePatch hasControllableSoundMakers])
    {
      if (! inspector)
	{
	  [NXApp loadNibSection:"PatchParam.nib" owner:self withNames:YES];
	  [inspector getFrame:&virginRect];
	}

      if (inspectorRectJustLoaded)
	{
	  if (useInspectorRect)
	    {
	      [inspector placeWindowAndDisplay:&inspectorRect];
	      useInspectorRect = NO;
	    }
	  inspectorRectJustLoaded = NO;
	}
      else
	{
	  inspectorRect = virginRect;
	}
      
      [self updateUgSelector];
      [self updateArgSelector];

      if (midiController)
	{
	  [midiCtrlField setStringValue:midiControllerName];
	}

      [midiCtrlField setStringValue:midiControllerName];
      [midiCtrlScale setDoubleValue:midiCtrlScaleVal];
      [minMidiCtrlScale setDoubleValue:minMidiCtrlScaleVal];
      [maxMidiCtrlScale setDoubleValue:maxMidiCtrlScaleVal];
      [midiCtrlOffset setDoubleValue:midiCtrlOffsetVal];  
      [minMidiCtrlOffset setDoubleValue:minMidiCtrlOffsetVal];
      [maxMidiCtrlOffset setDoubleValue:maxMidiCtrlOffsetVal];
      
      [offsetSlider setDoubleValue:midiCtrlOffsetVal];
      [offsetSlider setMaxValue:maxMidiCtrlOffsetVal];
      [offsetSlider setMinValue:minMidiCtrlOffsetVal];
      
      [scaleSlider setDoubleValue:midiCtrlScaleVal];
      [scaleSlider setMaxValue:maxMidiCtrlScaleVal];
      [scaleSlider setMinValue:minMidiCtrlScaleVal];
      
      if (mapEnabled)
	{
	  [enableSwitch setIntValue:1];
	}
      else
	{
	  [enableSwitch setIntValue:0];
	}

      if (paramEnabled)
	{
	  [paramEnabledSwitch setIntValue:1];
	}
      else
	{
	  [paramEnabledSwitch setIntValue:0];
	}

      [thinField setIntValue:thinFactor];
      if (thinEnabled)
	{
	  [thinSwitch setIntValue:1];
	}
      else
	{
	  [thinSwitch setIntValue:0];
	}

      [self setInspectorTitles];

      /* This line adds the PatchParam panel to the WindowsMenu 
       * This happens automatically for FakePatchPoints and FakeUGs
       * (maybe because they are separate Views?).
       * When the user closes the PatchParam inspector, this
       * item is automatically removed from the WindowsMenu
       * (presumably by NXApp)
       */
      [NXApp addWindowsItem: inspector title:[self getName] filename:NO];
      /* doHighlight toggle prevents the instance
       * from being highlighted twice
       */
      doHighlight = NO;
      [theViewFakeUG setDoHighlight:NO];
      [inspector makeKeyAndOrderFront:self];
      inspectorDisplayed = YES;
      doHighlight = YES;
      [theViewFakeUG setDoHighlight:YES];
    }
  else
    {
      NXRunAlertPanel(getAppName(), "This patch has no controllable unit generators", 
		      NULL, NULL, NULL);
    }

  return self;
}

- selectFakeUG:sender

/* This sets the theFakeUG
 * based on the selection from the ugSelectorPop
 */
{
  char *name;

  name = (char *) [ugSelectorPop selectedItem];
  [self setFakeUG:name];
  [self perform:@selector(updateUgSelector) with:nil afterDelay:0 cancelPrevious:NO];
  [self perform:@selector(updateArgSelector) with:nil afterDelay:0 cancelPrevious:NO];
  return self;
}

- selectArg:sender

/* This sets the theArg
 * based on the selection from the argSelectorPop
 */
{
  char *name;

  name = (char *) [argSelectorPop selectedItem];
  [self setArg:name];
  return self;
}

- takeNoteType:sender
{

  switch ([[sender selectedCell] tag])
    {
    case (0):
      {
	noteTypeToControl = MK_noteOn;
	break;
      }
    case (1):
      {
	noteTypeToControl = MK_noteOff;
	break;
      }
    case (2):
      {
	noteTypeToControl = MK_noteUpdate;
	break;
      }
    default:
      {
	noteTypeToControl = MK_noteOn;
	break;
      }
    }
  
  return self;
}

- setFakeUG:(char *) name

/* This sets theFakeUG
 */
{
  theFakeUG = [self getFakeUG:name];
  return self;
}

- getFakeUG:(char *) name

/* This returns the id named FakeUG
 * in the List fakeUGs
 */
{
  int count, i;
  char *old_name;

  /* Yeah, I could do this with a Hash, but there
   * are typically less than 20 FakeUGs in a patch
   * so a cheezy linear search doesn't hurt us.
   */
  count = [fakeUGs count];
  for (i=0; i<count; i++)
    {
      old_name = [ [fakeUGs objectAt:i] getName];
      if (old_name)
	{
	  if (strcmp(old_name, name) == 0)
	    {
#ifdef DEBUG_PARAM
	      DEBUG_PRINT("found %s in list of existing fakeUGs\n", old_name);
#endif DEBUG_PARAM

	      return [fakeUGs objectAt:i];
	      break;
	    }
	}
    }

  return nil;
}

- updateUgSelector

/*
 * Update the list of available FakeUGs that
 * can be controlled by the this parameter
 */
{
  int count, i, availUG = -1;
  char *name;
  id aFakeUG;

  count = [ugSelectorPop count];
  for (i = count-1; i >= 0;  i--)  
    {
      [[ugSelectorPop removeItemAt:i] free];
    }

  fakeUGs = [thePatch getFakeUGs];
  count = [fakeUGs count];
  for (i=0; i<count; i++)
    {
      aFakeUG = [fakeUGs objectAt:i];
      if ([aFakeUG isSoundMaker] && [aFakeUG getArgCount])
	{
	  name = [[fakeUGs objectAt:i] getName];
	  availUG = i;
	  [ugSelectorPop addItem:name];
	}
    }
  
  if (! theFakeUG && (availUG >= 0))
    {
      /* If we got this far, we are assured object 0 exists and
       * is legit
       */
      theFakeUG = [fakeUGs objectAt:availUG];
    }

  if (theFakeUG)
    { 
      [ugSelector setTitle:[theFakeUG getName]];
    }
  else
    {
      /* Should never ever happen */
      theFakeUG = nil;
      [ugSelector setTitle:""];
    }


  return self;
}

- inspectorClosed
/* Called by the inspector's delegate */
{
  inspectorDisplayed = NO;
  return self;
}

- closeInspector
{
  [inspector performClose:self];
  if (envDelegate)
    {
      [envDelegate closeInspector];
    }

  return self;
}

- highlightSelf
{
  if ( (![thePatch windowMiniaturized]) && doHighlight)
    {
      [theViewFakeUG highlightSelf];
    }

  return self;
}

- setArg:(char *) name

/* This sets theArg
 */
{

  /* The theArg cannot be set unless the 
   * theFakeUG is set
   */
  if (! theFakeUG)
    {
      NXRunAlertPanel(getAppName(), "Select UG to control first", NULL, NULL, NULL);
      return self;
    }

  theArg = [self getIndexForArg:name FromFakeUG:theFakeUG];
  if (theArg < 0)
    {  /* snafu */
      sprintf(ErrorMsg, "Unexpected theArg file: %s line: %d\n", __FILE__, __LINE__);
      NXRunAlertPanel(getAppName(), ErrorMsg, NULL, NULL, NULL);
      return self;
    }
 
  return self;
}

- (int) getIndexForArg:(char *) name FromFakeUG:aFakeUG

/* This returns the index of the arg named "name"
 * in aFakeUG
 */
{
  int i;
  char *aName;
  
  for (i=0; i<[aFakeUG getArgCount]; i++)
    {
      aName = [aFakeUG getArgName:i];
      if (strcmp(aName, name) == 0)
	{
#ifdef DEBUG_PARAM
	  DEBUG_PRINT("Found arg: %s (index %d)\n",
		      aName, i);
#endif DEBUG_PARAM

	  return i;
	  break;
	}
    }

  return -1;
}

- updateArgSelector
/* Update the list of args for theFakeUG
 */
{
  int count, i;

  count = [argSelectorPop count];
  for (i = count-1; i >= 0;  i--)  
    {
      [[argSelectorPop removeItemAt:i] free];
    }

  if (! theFakeUG)
    {
      return self;
    }

  for (i=0; i<[theFakeUG getArgCount]; i++)
    {
      [argSelectorPop addItem:[theFakeUG getArgName:i]];
    }
  
  [argSelector setTitle:[theFakeUG getArgName:theArg]];

  return self;
}

- createDefaultMapEnvelope
{
  static double x[150], y[150], s[150];
  int i, npoints;

  npoints = 0;
  for (i=0; i<127; i++)
    {
      if ((i % 5) == 0)
	{
	  x[npoints] = (double) i;
	  y[npoints] = (double) i;
	  s[npoints] = 1.0;
	  npoints++;
	}
    }
  
  x[0] = 0;
  y[0] = 0;
  s[0] = 1.0;
  x[npoints-1] = 127;
  y[npoints-1] = 127;
  s[npoints-1] = 1.0;
  mapEnvelope = [[[Envelope alloc] init]
	       setPointCount: npoints
	       xArray: x
	       orSamplingPeriod: 1.0
	       yArray: y
	       smoothingArray: s
	       orDefaultSmoothing: 1.0
		 ];
  return self;
}

- syncMapEnvelope
{
  if (! envDelegate)
    {
      envDelegate = [[EnvDelegate alloc] init];
    }

  [envDelegate setPatchParameter:self];
  [envDelegate setFakeUG:nil ArgNum:-1];
  return self;
}

- clearMapEnvelope
{
  mapEnvelope = nil;
  return self;
}

- setMapEnvelope:anEnv
{
  mapEnvelope = anEnv;
  return self;
}

- displayMapEnvelope:sender
{
  id envController;
  int val;

  [self syncMapEnvelope];
  if (mapEnvelope)
    {
      envController = [envDelegate envController];
      [envController setNewEnvelope:mapEnvelope];
      [envController reScaleLimits:self];
      [envController makeGraphType:0];
    }
  else
    {
      val = NXRunAlertPanel(getAppName(),
			    "No lookup table defined.  Create default lookup table?",
			    "YES",   /* default */
			    "NO",    /* alt */
			    "Cancel"); /* other */
      switch (val)
	{
	  case(NX_ALERTDEFAULT):
	    {
	      [self createDefaultMapEnvelope];
	      envController = [envDelegate envController];
	      [envController setNewEnvelope:mapEnvelope];
	      [envController reScaleLimits:self];
	      [envController makeGraphType:0];
	      [envController reScaleLimits:self];
	      break;
	    }
	  case(NX_ALERTALTERNATE):
	    {
	      break;
	    }
	default:
	  {
	    return self;
	  }
	}
    }

  sprintf(ErrorMsg, "%s - lookup table", theName);
  [envDelegate displayWindow:ErrorMsg];
  return self;
}
  
- setParamValue:(double) ctrl_val
/* Send the float value to our controlled arg(s) */
{
  static char value[512];

/*  May have caused delays  [Conductor lockPerformance]; */
  sprintf(value, "%f", ctrl_val);

#ifdef DEBUG_PARAM
  DEBUG_PRINT("setParamValue %s sending %s to fakeUG: %s arg %s (index=%d)\n",
	      theName, value, [theFakeUG getName], 
	      [theFakeUG getArgName:theArg], theArg);
#endif DEBUG_PARAM


  [theFakeUG setArgWithIndex: theArg To:value];
  if ([thePatch allowPPInspectorUpdate] || [thePatch alwaysUpdate])
    {
      [theFakeUG displayArg: theArg];
      [thePatch setAllowPPInspectorUpdate:NO];
    }

/* May have caused delays  [Conductor unlockPerformance]; */
  return self;
}

- (double) applyMap:(double) val
{
  double newVal;
  
  if ( (mapEnvelope) && (mapEnabled) )
    {
      newVal = [mapEnvelope lookupYForX:val];
    }
  else if ( ! (mapEnvelope) && (mapEnabled) )
    {
      newVal = 0.0;
    }
  else
    {
      newVal = val;
    }

  return newVal;
}

// #define DEBUG_PARAM 0
- realizeNote:aNote fromNoteReceiver:aNR
{
  double controlVal;
  double fval;
  double pbSensitivity;
  double bend;
  double pitchBend;
  double frequency;
  static double lastFreq = 0.0;
  static double pitchbendSensitivity = 2.0;

  /* Do we even send it ?*/
  if ( !(([aNote noteType] == noteTypeToControl) && 
	 paramEnabled && 
	 [theFakeUG isArgEnabled:theArg]))
    {
      return self;
    }

#ifdef DEBUG_PARAM
  DEBUG_PRINT("- **** PatchParam/realizeNote Looking for midiController: %s (%d)\n",
	      [paramInterface getControlName:midiController], midiController);
#endif DEBUG_PARAM

  /* Make sure the Note isn't being "thinned=out" */
  if (thinEnabled)
    {
      currNoteCount++;
      if ((currNoteCount % thinFactor) != 0)
	{
	  return self;
	}
      else
	{
	  currNoteCount = 0;
	}
    }

//  printf ("**** Processing note:    %d\n", [aNote noteType]);

  /* Deal with some common MusicKit parameters */
  if (midiControllerAsMKParm == MK_freq)
    {
      /* Convert the keyNum to freq */
      if (MKIsNoteParPresent(aNote, MK_keyNum))
	{
	  frequency = MKKeyNumToFreq([aNote keyNum]);
	  [aNote setPar:MK_freq toDouble:frequency];
	}
    }

  if (isControlPresent(aNote, midiController))
    {
      if (MKIsNoteParPresent(aNote, MK_pitchBend) && 
	       (midiControllerAsMKParm == MK_pitchBend))
	{
	  if (lastFreq > 0)
	    {
#ifdef DEBUG_PARAM
	      DEBUG_PRINT("- !bend! **** PatchParam/realizeNote Have midiController: %s (%d)\n",
			  [paramInterface getControlName:midiController], midiController);
#endif DEBUG_PARAM


	      if (MKIsNoteParPresent(aNote, MK_pitchBendSensitivity))
		{
		  pitchbendSensitivity = MKGetNoteParAsDouble(aNote, 
							      MK_pitchBendSensitivity);
		}
	      else
		{
		  pitchbendSensitivity = 2.0;  // Pick something "reasonable"
		}

	      pbSensitivity = (pitchbendSensitivity / 12.0) / 8192.0;
	      bend = MKGetNoteParAsDouble(aNote, MK_pitchBend);
	      pitchBend = pow(2.0, (bend - 8192.0) * pbSensitivity);
#ifdef DEBUG_PARAM
	      DEBUG_PRINT("pitchbendSensitivity: %f pbSensitivity: %f bend: %f pitchBend: %f\n",
			  pitchbendSensitivity, pbSensitivity, bend, pitchBend);
#endif DEBUG_PARAM

	      fval = lastFreq * pitchBend;
#ifdef DEBUG_PARAM
	      DEBUG_PRINT("PatchParam realizeNote: Have pitchBend %f\n", fval);
#endif DEBUG_PARAM
	      fval = [self applyMap:fval];
	      fval = (fval * midiCtrlScaleVal) + midiCtrlOffsetVal;
	      [self setParamValue:fval];
	    }
	}
      else
	{
#ifdef DEBUG_PARAM
	  DEBUG_PRINT("- **** PatchParam/realizeNote Have midiController: %s (%d)\n",
		      [paramInterface getControlName:midiController], midiController);
#endif DEBUG_PARAM


	  controlVal = getControlValAsDouble(aNote, midiController);
	  controlVal = [self applyMap:controlVal];
	  controlVal = (controlVal * midiCtrlScaleVal) + midiCtrlOffsetVal;
#ifdef DEBUG_PARAM
	  DEBUG_PRINT("PatchParam realizeNote: Have controller %d val: %f\n",
		      midiController, controlVal);
#endif DEBUG_PARAM

	  [self setParamValue:controlVal];
	}
    }

  /* Hack for pitch bend */
  if (MKIsNoteParPresent(aNote, MK_freq)  || MKIsNoteParPresent(aNote, MK_keyNum))
    {
      lastFreq = [self applyMap:[aNote freq]];
    }

  return self;
  
}

/* Stuff that came from Mike's mapper **********************
> 
> - free
> {
>   [[envelopeView window] close];
>   [[envelopeView window] free];
>   return [super free];
> }
> 
> - editEnvelope:sender
> {
>   if (! mapEnvelope)
>     {
>       double x[2], y[2];
>       mapEnvelope = [[Envelope allocFromZone:[self zone]] init];
>       x[0] = y[0] = 0.0;
>       x[1] = y[1] = 127.0;
>       [mapEnvelope setPointCount:2 xArray:x yArray:y];
>     }
> 
>   [envelopeView setEnvelope:mapEnvelope];
>   [[envelopeView window] setTitle:[self getName]];
>   [[envelopeView window] orderFront:sender];
>   return self;
> }
> 
> - windowDidBecomeKey:sender
> {
>   if ([[envelopeView window] isVisible]) 
>     [[envelopeView window] orderWindow:NX_BELOW 
>    relativeTo:[inspector windowNum]];
>   return self;
> }
> 
>
**************************************************/
- write:(NXTypedStream *) stream
{
  char *aName;
  [super write:stream];

  aName = midiControllerName;
  NXWriteTypes(stream, "*", &aName);
  aName = theName;
  NXWriteTypes(stream, "*", &aName);
  NXWriteTypes(stream, "d", &midiCtrlScaleVal);
  NXWriteTypes(stream, "d", &minMidiCtrlScaleVal);
  NXWriteTypes(stream, "d", &maxMidiCtrlScaleVal);
  NXWriteTypes(stream, "d", &midiCtrlOffsetVal);
  NXWriteTypes(stream, "d", &minMidiCtrlOffsetVal);
  NXWriteTypes(stream, "d", &maxMidiCtrlOffsetVal);
  NXWriteTypes(stream, "i", &noteTypeToControl);
  NXWriteTypes(stream, "@", &theFakeUG);
  NXWriteTypes(stream, "i", &theArg);
  NXWriteTypes(stream, "i", &midiController);
  NXWriteTypes(stream, "i", &midiControllerAsMKParm);
  [self syncMapEnvelope];
  NXWriteTypes(stream, "@", &mapEnvelope);
  NXWriteTypes(stream, "c", &mapEnabled);
  NXWriteTypes(stream, "c", &paramEnabled);
  NXWriteTypes(stream, "c", &thinEnabled);
  NXWriteTypes(stream, "i", &thinFactor);
  if (inspector)
    {
      useInspectorRect = YES;
      [inspector getFrame:&inspectorRect];
    }
  else
    {
      useInspectorRect = NO;
      inspectorRect.origin.x = -1;
      inspectorRect.origin.y = -1;
      inspectorRect.size.width = -1;
      inspectorRect.size.height = -1;
    }

  NXWriteTypes(stream, "d", &inspectorRect.origin.x);
  NXWriteTypes(stream, "d", &inspectorRect.origin.y);
  NXWriteTypes(stream, "d", &inspectorRect.size.width);
  NXWriteTypes(stream, "d", &inspectorRect.size.height);
  NXWriteTypes(stream, "c", &useInspectorRect);
  NXWriteTypes(stream, "c", &inspectorDisplayed);

  return self;
}

- read:(NXTypedStream *) stream
{
  int version;
  char *aName;

  [super read:stream];

  thinEnabled = NO;
  thinFactor = 1;
  paramEnabled = YES;
  mapEnabled = NO;
  version = NXTypedStreamClassVersion(stream, "PatchParam");
  switch (version)
    {
      case(5):
	{
	  NXReadTypes(stream, "*", &aName);
	  strcpy(midiControllerName, aName);
	  NXReadTypes(stream, "*", &aName);
	  strcpy(theName, aName);
	  NXReadTypes(stream, "d", &midiCtrlScaleVal);
	  NXReadTypes(stream, "d", &minMidiCtrlScaleVal);
	  NXReadTypes(stream, "d", &maxMidiCtrlScaleVal);
	  NXReadTypes(stream, "d", &midiCtrlOffsetVal);
	  NXReadTypes(stream, "d", &minMidiCtrlOffsetVal);
	  NXReadTypes(stream, "d", &maxMidiCtrlOffsetVal);
	  NXReadTypes(stream, "i", &noteTypeToControl);
	  NXReadTypes(stream, "@", &theFakeUG);
	  NXReadTypes(stream, "i", &theArg);
	  NXReadTypes(stream, "i", &midiController);
	  NXReadTypes(stream, "i", &midiControllerAsMKParm);
	  NXReadTypes(stream, "@", &mapEnvelope);
	  NXReadTypes(stream, "c", &mapEnabled);
	  NXReadTypes(stream, "c", &paramEnabled);
	  NXReadTypes(stream, "c", &thinEnabled);
	  NXReadTypes(stream, "i", &thinFactor);
	  NXReadTypes(stream, "d", &inspectorRect.origin.x);
	  NXReadTypes(stream, "d", &inspectorRect.origin.y);
	  NXReadTypes(stream, "d", &inspectorRect.size.width);
	  NXReadTypes(stream, "d", &inspectorRect.size.height);
	  NXReadTypes(stream, "c", &useInspectorRect);
	  NXReadTypes(stream, "c", &inspectorDisplayed);
	  inspectorRectJustLoaded = YES;
	  break;
	}
      case(4):
	{
	  NXReadTypes(stream, "*", &aName);
	  strcpy(midiControllerName, aName);
	  NXReadTypes(stream, "*", &aName);
	  strcpy(theName, aName);
	  NXReadTypes(stream, "d", &midiCtrlScaleVal);
	  NXReadTypes(stream, "d", &minMidiCtrlScaleVal);
	  NXReadTypes(stream, "d", &maxMidiCtrlScaleVal);
	  NXReadTypes(stream, "d", &midiCtrlOffsetVal);
	  NXReadTypes(stream, "d", &minMidiCtrlOffsetVal);
	  NXReadTypes(stream, "d", &maxMidiCtrlOffsetVal);
	  NXReadTypes(stream, "i", &noteTypeToControl);
	  NXReadTypes(stream, "@", &theFakeUG);
	  NXReadTypes(stream, "i", &theArg);
	  NXReadTypes(stream, "i", &midiController);
	  NXReadTypes(stream, "i", &midiControllerAsMKParm);
	  NXReadTypes(stream, "@", &mapEnvelope);
	  NXReadTypes(stream, "c", &mapEnabled);
	  NXReadTypes(stream, "c", &paramEnabled);
	  NXReadTypes(stream, "d", &inspectorRect.origin.x);
	  NXReadTypes(stream, "d", &inspectorRect.origin.y);
	  NXReadTypes(stream, "d", &inspectorRect.size.width);
	  NXReadTypes(stream, "d", &inspectorRect.size.height);
	  NXReadTypes(stream, "c", &useInspectorRect);
	  NXReadTypes(stream, "c", &inspectorDisplayed);
	  inspectorRectJustLoaded = YES;
	  break;
	}
      case(3):
	{
	  NXReadTypes(stream, "*", &aName);
	  strcpy(midiControllerName, aName);
	  NXReadTypes(stream, "*", &aName);
	  strcpy(theName, aName);
	  NXReadTypes(stream, "d", &midiCtrlScaleVal);
	  NXReadTypes(stream, "d", &minMidiCtrlScaleVal);
	  NXReadTypes(stream, "d", &maxMidiCtrlScaleVal);
	  NXReadTypes(stream, "d", &midiCtrlOffsetVal);
	  NXReadTypes(stream, "d", &minMidiCtrlOffsetVal);
	  NXReadTypes(stream, "d", &maxMidiCtrlOffsetVal);
	  NXReadTypes(stream, "i", &noteTypeToControl);
	  NXReadTypes(stream, "@", &theFakeUG);
	  NXReadTypes(stream, "i", &theArg);
	  NXReadTypes(stream, "i", &midiController);
	  NXReadTypes(stream, "i", &midiControllerAsMKParm);
	  NXReadTypes(stream, "@", &mapEnvelope);
	  NXReadTypes(stream, "c", &mapEnabled);
	  NXReadTypes(stream, "c", &paramEnabled);
	  break;
	}
      case(2):
	{
	  NXReadTypes(stream, "*", &aName);
	  strcpy(midiControllerName, aName);
	  NXReadTypes(stream, "*", &aName);
	  strcpy(theName, aName);
	  NXReadTypes(stream, "d", &midiCtrlScaleVal);
	  NXReadTypes(stream, "d", &minMidiCtrlScaleVal);
	  NXReadTypes(stream, "d", &maxMidiCtrlScaleVal);
	  NXReadTypes(stream, "d", &midiCtrlOffsetVal);
	  NXReadTypes(stream, "d", &minMidiCtrlOffsetVal);
	  NXReadTypes(stream, "d", &maxMidiCtrlOffsetVal);
	  NXReadTypes(stream, "i", &noteTypeToControl);
	  NXReadTypes(stream, "@", &theFakeUG);
	  NXReadTypes(stream, "i", &theArg);
	  NXReadTypes(stream, "i", &midiController);
	  NXReadTypes(stream, "i", &midiControllerAsMKParm);
	  NXReadTypes(stream, "@", &mapEnvelope);
	  NXReadTypes(stream, "c", &mapEnabled);
	  paramEnabled = YES;
	  break;
	}
    default:
      {
	sprintf(ErrorMsg, 
		"Unexpected document version: %d  reading a PatchParam: File: %s Line: %d\n",
		version, __FILE__, __LINE__);
	NXRunAlertPanel(getAppName(), ErrorMsg, NULL, NULL, NULL);
      }	
    }
  
  /* Make sure the map envelope is synced */
  if (envDelegate)	
    {
      [envDelegate free];
      envDelegate = nil;
    }

  [self syncMapEnvelope];
  return self;
}

- displayIfDoubleClick
{
  clickCount++;
  if (clickCount >= 2)
    {
      clickCount = 0;
      [self displayInspector];
    }

  return self;
}

- displayInspectorIfWasActive
{
  if (inspectorDisplayed)
    {
      [self displayInspector];
    }

  return self;
}


@end

