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

// #define DEBUG_PARAM

/* Clavier is a Performer-like object that displays three graphic piano octaves
 * with keys that can be clicked on, pitchbend and modwheel sliders,
 * and a sustain button.
 *
 * See page 212 of NexXTSTEP Programming for example on how to resize
 * the inspector automatically so the Clavier shows.
 */

#define DEBUG_PRINT printf
#import <appkit/appkit.h>
#import <appkit/graphics.h>
#import <musickit/Conductor.h>
#import <musickit/Orchestra.h>
#import <musickit/Performer.h>
#import <musickit/Note.h>
#import <musickit/NoteSender.h>
#import <musickit/NoteFilter.h>
#import "Clavier.h"
#import "PianoOctave.h"
#import "MySlider.h"
#import <musickit/TuningSystem.h>
#import <mididriver/midi_spec.h>
#import "NoteOnHandler.h"
#import "FakeSynthPatch.h"
#import "FakeUG.h"

extern char *getAppName();

@implementation Clavier

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

- init
{
    clickCount = 0;
    pitchbendSensitivity = 2.0;
    [super init];
    return self;
}

- initConnectionWithPatch:aSP WithFakeUG: aFakeUG

/* This is called after the nib file is loaded
 * We connect to the NoteOnHandler here and allocate
 * our note sender
 */
{
    int i;
    theSP = aSP;
    theFakeUG = aFakeUG;  // Done so the highlightSelf method will work.

    if (! inspector)
      {
	[NXApp loadNibSection:"Clavier.nib" owner:self withNames:YES];	
	[inspector getFrame:&inspectorRect];
      }

    noteOnHandler = [[NoteOnHandler alloc] init];
    [noteOnHandler initConnection:theSP];
    noteSender = [[NoteSender alloc] init];
    [noteSender connect:[noteOnHandler noteReceiver]];
    octave = 4;
    [pianoOctave1 setTag:0];	/* Indicates octaves from base octave */
    [pianoOctave2 setTag:1];
    [pianoOctave3 setTag:2];
    [modWheelSlider sendActionOn: (NX_MOUSEDOWNMASK | NX_MOUSEDRAGGEDMASK)];
    [pitchBendSlider setMinValue:0];
    [pitchBendSlider setMaxValue:16383.0];
    [pitchBendSlider setReturnValue:8192.0]; /* The centering value */
    [pitchBendSlider setDoubleValue:8192.0];
    [pitchBendSlider sendActionOn:
     (NX_MOUSEDOWNMASK | NX_MOUSEDRAGGEDMASK | NX_MOUSEUPMASK)];

    for (i=0; i<128; i++)
	noteTags[i] = MKNoteTag();
    return self;
}

- displayInspector
{
  NXRect virginRect;

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

  if (inspectorRectJustLoaded)
    {
      if (useInspectorRect)
	{
	  [inspector placeWindowAndDisplay:&inspectorRect];
	  useInspectorRect = NO;
	}
      inspectorRectJustLoaded = NO;
    }
  else
    {
      inspectorRect = virginRect;
    }
  
  [theNameField setStringValue:theName];
  [inspector setTitle:""];
  [inspector setTitle:theName];
  [pitchBendSens setDoubleValue:pitchbendSensitivity];

  /* This line adds the Clavier 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;
  [theFakeUG setDoHighlight:NO];
  [inspector makeKeyAndOrderFront:self];
  doHighlight = YES;
  [theFakeUG setDoHighlight:YES];
  inspectorDisplayed = YES;
  return self;
}

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

  return self;
}

- displayInspectorIfWasActive
{
  if (inspectorDisplayed)
    {
      [self displayInspector];
      if (NX_WIDTH(&inspectorRect) == 718)
	{
	  [toggleSwitch setIntValue:1];
	}
      else
	{
	  [toggleSwitch setIntValue:0];
	}
    }

  return self;
}

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

- closeInspector
{
  [inspector performClose:self];
  return self;
}

- highlightSelf
{
  if ( (![theSP windowMiniaturized]) && doHighlight)
    {
      [theFakeUG highlightSelf];
    }

  return self;
}

- toggleInspector:sender
/* When the inspector is small, the width = 90
 * When it is big the width is 718
 * This routine changes the width depending
 * of the window.  Set the initial state
 * of the switch in the InterfaceBuilder accordingly
 */
{
  [inspector getFrame:&inspectorRect];
  if (NX_WIDTH(&inspectorRect) == 718)
    {
      inspectorRect.size.width = 90;
    }
  else
    {
      inspectorRect.size.width = 718;
    }

  /* doHighlight toggle prevents the instance
   * from being highlighted twice
   */
  doHighlight = NO;
  [inspector placeWindowAndDisplay:&inspectorRect];
  [inspector makeKeyAndOrderFront:self];
  doHighlight = YES;

  return self;
}

- inspector
{
    return inspector;
}

- noteSender
{
  return noteSender;
}

- connectNoteSenderTo:aNoteReceiver
{
  [noteSender connect:aNoteReceiver];
  return self;
}

- noteOnHandler
{
  return noteOnHandler;
}

- takeNameFrom:sender
{
  [self setClavierName:(char *) [sender stringValue]];
  return self;
}

- setClavierName: (char *) aName
{
  if (! aName)
    {
      NXRunAlertPanel(getAppName(), "Invalid name", NULL, NULL, NULL);
      return self;
    }

  strcpy(theName, aName);
  [theNameField setStringValue:theName];
  [inspector setTitle:""];
  [inspector setTitle:theName];

  return self;

}

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

- takeOctaveFrom:sender
    /* Set the base octave of the three octaves available. */
{
    static char str[] = "C0";

    octave = MAX(MIN(octave+[[sender selectedCell] tag],7),1);
    sprintf (str, "C%d", octave-1);
    [octaveDisplayer setStringValue:str];

    return self;
}

- takeKeyValueFrom:sender
    /* This gets sent by a PianoOctave control when a key is clicked on. */
{
    int key_num;
    static id note = nil;
    int key = [sender intValue];
    double frequency;
    int note_type = MK_noteOff;

    [theSP allocatePatch];
    if ([sender state:key])
      {
	note_type = MK_noteOn;
      }
    else
      {
	note_type = MK_noteOff;
      }

    [Conductor lockPerformance];
    if (!note) {
	note = [[Note alloc] init];
	[note setPar:MK_velocity toInt:64];
    }
    [note setNoteType:note_type];

    /* The tag indicates how many octaves up from the base octave */
    key_num = key+=12*(octave+[sender tag]);
    [note setPar:MK_keyNum toInt:key_num];
    frequency = MKKeyNumToFreq(key_num);
    [note setPar:MK_freq toDouble:frequency];
#ifdef DEBUG_PARAM
    DEBUG_PRINT("clavier:takeKeyValueFrom: key_num: %d frequency: %f\n", 
		key_num, frequency);
#endif DEBUG_PARAM


    [note setNoteTag:noteTags[key]];
#ifdef DEBUG_PARAM
    DEBUG_PRINT("clavier:takeKeyValueFrom: Sending Note\n");
#endif DEBUG_PARAM

    [theSP setAllowPPInspectorUpdate:YES];
    [noteSender sendNote:note];
    [Conductor unlockPerformance];

    return self;
}

- takeModWheelFrom:sender
    /* This is sent by the modwheel slider. */
{
    static id note = nil;
    int value = [sender intValue];

    [Conductor lockPerformance];
    if (!note) {
	[(note=[[Note alloc] init]) setNoteType:MK_noteUpdate];
	[note setPar:MK_controlChange toInt:MIDI_MODWHEEL];
    }
    [note setPar:MK_controlVal toInt:value];
#ifdef DEBUG_PARAM
    DEBUG_PRINT("clavier:takeModWheelFrom: Sending Note\n");
#endif DEBUG_PARAM

    [theSP setAllowPPInspectorUpdate:YES];
    [noteSender sendNote:note];
    [Conductor unlockPerformance];

    return self;
}

- takePitchBendFrom:sender
    /* This is sent by the pitchbend slider. */
{
    static id note = nil;
    int value = [sender intValue];

    [Conductor lockPerformance];
    if (!note)
      [(note=[[Note alloc] init]) setNoteType:MK_noteUpdate];
    [note setPar:MK_pitchBend toInt:value];
    pitchbendSensitivity = [pitchBendSens doubleValue];
    [note setPar:MK_pitchBendSensitivity toDouble:[pitchBendSens doubleValue]];
#ifdef DEBUG_PARAM
    DEBUG_PRINT("clavier:takePitchBendFrom: Sending Note\n");
#endif DEBUG_PARAM

    [theSP setAllowPPInspectorUpdate:YES];
    [noteSender sendNote:note];
    [Conductor unlockPerformance];

    return self;
}

- takeSostenutoFrom:sender
    /* This is sent by the sustain button. */
{
    static id note = nil;

    [Conductor lockPerformance];
    if (!note) {
	[(note=[[Note alloc] init]) setNoteType:MK_noteUpdate];
	[note setPar:MK_controlChange toInt:MIDI_DAMPER];
    }
    [note setPar:MK_controlVal toInt:([sender state])?127:0];
#ifdef DEBUG_PARAM
    DEBUG_PRINT("clavier:takeSostenutoFrom: Sending Note\n");
#endif DEBUG_PARAM

    [theSP setAllowPPInspectorUpdate:YES];
    [noteSender sendNote:note];
    [Conductor unlockPerformance];

    return self;
}

- write:(NXTypedStream *) stream
{
  char *aName;
  [super write:stream];
  aName = theName;
  NXWriteTypes(stream, "*", &aName);
  NXWriteTypes(stream, "d", &pitchbendSensitivity);

  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];

  useInspectorRect = NO;
  inspectorRectJustLoaded = NO;
  version = NXTypedStreamClassVersion(stream, "Clavier");

  NXReadTypes(stream, "*", &aName);
  switch (version)
    {
      case(3):
	{
	  NXReadTypes(stream, "d", &pitchbendSensitivity);
	  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(2):
	{
	  NXReadTypes(stream, "d", &pitchbendSensitivity);
	  break;
	}
      default:
        {
	  /* Versions before 2 did not have pitchbendSensitivity */
	  pitchbendSensitivity = 2.0;
	}
    }

  if (aName)
    {
      strcpy(theName, aName);
    }
  else
    {
      strcpy(theName, "");
    }

  return self;
}

@end

