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

/*
 * ClmCodeGen.m
 * Nick Porcaro and Owen Smith Spring/Summer 1993
 *
 * This is the clm related stuff taken out of FakeSynthPatch.m
 */

#import <stdio.h>
#import <stdlib.h>
#import <string.h>
#import <math.h>
#import <appkit/Application.h>
#import <appkit/Control.h>
#import <appkit/Matrix.h>
#import <appkit/Panel.h>
#import <appkit/OpenPanel.h>
#import <appkit/SavePanel.h>
#import "FakeSynthPatch.h"
#import "FakePatchPoint.h"
#import "FakeUG.h"
#import "ClmCodeGen.h"

#define MAXMESSAGELENGTH 200

// Added by Owen
#define GetNextUG(n) [[[theUG getConnection:n] getFromNode] superview] 
#define MAXENVSTRING 100

// Added by Owen
static BOOL IsInitializeable(char *s);
static BOOL MultipleOuts(id theUG);
static void DefineBlock(FILE *f, id theUG, id definedList);
static void NextCrunch(FILE *f, id theUG, id definedList, int connection);
static void PrintCrunch(FILE *f, id theUG, id definedList);
static char *ParseEnvString(char *s, double dur);
static int GetNumberOfInputs(id theUG);

static char TempString[1024];

extern id savePanel;
extern id openPanel;
extern char *getSavePath(char *banner);
extern char *getOpenPath(char *banner);
extern char *getAppName();


@implementation ClmCodeGen

- init
{
  [super init];
  [NXApp loadNibSection:"ClmCodeGen.nib" owner:self withNames:YES];
  return self;
}

- setSP:aSP
{
  theSP = aSP;
  fakeUGs = [theSP getFakeUGs];
  fakePatchPoints = [theSP getFakePatchPoints];
  return self;
}

- displayInspector:sender
{
  [theInspector makeKeyAndOrderFront:self];
  return self;
}

- emitCode:sender
{
  char *file_name;
  
  sprintf(TempString, "%s Generate CLM code", getAppName());
  if (! (file_name = getSavePath(TempString)))
    {
      return self;
    }

  [self makeLispCode: file_name
        samplingRate: [theSamplingRate doubleValue]
      instrumentName: (char *) [theInstrName stringValue]
           startTime: [theStartTime doubleValue]
            duration: [theDuration doubleValue]
  ];

  /* Now do the right thing with the file */
  switch ([[patchDest selectedCell] tag])
    {
      case(0):
	{
	  sprintf(TempString, "open %s", file_name);
	  system(TempString);
	}
      case(1):
	{
	  NXRunAlertPanel(getAppName(), "Paste Board not supported yet", NULL, NULL, NULL);
	  break;
	}
      case(2):
	{
	  sprintf(TempString, "%s %s &",
		  [testerScript stringValue], file_name);
	  printf("Shell command: %s\n", TempString);
	  system(TempString);
	  break;
	}
    }

  return self;
}


- makeLispCode: (char *) aFileName
       samplingRate: (double) aSamplingRate
      instrumentName: (char *) anInstrumentName
           startTime: (double) aStartTime
            duration: (double) aDuration
{
  int i;
  id theUG;
  char *ug_name, *ug_type;
  int method_count = 0;
  int pointCount=0, stickPoint = -1;
  int envelopes_processed = 0;
  FILE *f;
  extern char *getenv();
  char *ptr;
  char *fileName;
  double samplingRate;
  char *instrumentName;
  double startTime;
  double duration;


  // isDefined is a list of the names of all the defined multiple-output
  // synth blocks in the patch for the run-time loop.  A better way would be
  // to tack on another BOOL field to the FakeUG definition, saying whether
  // or not a particular instance of a FakeUG has been defined or not.
  id isDefined = [[List alloc] init];
  fileName = aFileName;
  samplingRate = aSamplingRate;
  instrumentName = anInstrumentName;
  startTime = aStartTime;
  duration = aDuration;


  // Open file
  f = fopen(fileName, "w");
  if (!f)
    {
      NXRunAlertPanel("Emit Code", "Can't create file.", NULL, NULL, NULL);
      return self;
    }

  // Get the parameters
  ptr = (char *) [theSP getName];
  if (ptr)
    {
      strcpy(instrumentName, ptr);
    }

  // Put out file header
  fprintf(f, ";;; -*- Syntax: Common-Lisp; Package: COMMON-MUSIC; Base: 10; Mode: Lisp -*-\n\n");
  fprintf(f, ";;; Instrument: %s; Part: %s\n;;; Created by %s for CLM\n\n", 
	  instrumentName, instrumentName, getAppName());

  // init stuff
  fprintf(f, "(in-package 'common-music)\n(in-syntax ':clm)\n\n");
  fprintf(f, "(definstrument %s (&key\n", instrumentName);     // add params someday?
  fprintf(f, "        (start-time %g)\n", startTime);
  fprintf(f, "        (duration %g)\n", duration);
  fprintf(f, "        (amplitude 0.5))\n\n");

  // initialize UGs.  beg and end are the start and end time in samples.
  fprintf(f, "  (let* ((beg (floor (* start-time sampling-rate)))\n");
  fprintf(f, "         (end (+ beg (floor (* duration sampling-rate))))\n");
  fprintf(f, "         (freq-tweak %g)\n", 8 * atan(1));	// since internal table size is 2Pi?

  // loop to initialize the actual UGs.  Loop goes through all the patchpoints and accesses the UGs through them.
  // There is definitely a better way, but...
  for (i=0; i<[fakeUGs count]; i++)
    {
        theUG = [fakeUGs objectAt:i];
	ug_type = [theUG getGenericTypeString];
	if ((ug_type != NULL) && (IsInitializeable(ug_type)))
	  {
	    ug_name = [theUG getName];
	    fprintf(f, "         (%s", ug_name);
	    
	    if (! strcmp(ug_type, "Out2sumUG")) fprintf(f, " (make-locsig))\n");
      else if (! strcmp(ug_type, "Out1aUG")) fprintf(f, " (make-locsig :degree -90))\n");
      else if (! strcmp(ug_type, "Out1bUG")) fprintf(f, " (make-locsig :degree 90))\n");
	    else if (! (strcmp(ug_type, "OscgafUG") && strcmp(ug_type, "OscgafiUG")))
	      {
		fprintf(f, " (make-oscil :frequency 0.0 :initial-phase 0.0))\n");
	      }
	    else if (! strcmp(ug_type, "Constantpp")) fprintf(f, " %g)\n", atod([theUG getConstant]));
	    else if (! strcmp(ug_type, "ConstantUG")) fprintf(f, " %g)\n", atod([theUG getArg:0]));
	    else if (! strcmp(ug_type, "DelayUG")) fprintf(f, " (make-delay %d))\n", atoi([theUG getArg:0]));

	    // We should check with Julius about these filters and the "InterpUG".
	    else if (! strcmp(ug_type, "Allpass1UG")) 
	      {
		fprintf(f, " (make-all-pass #| feedback-scaler feedforward-scaler length |#))\n");
	      }
	    else if (! strcmp(ug_type, "OnepoleUG"))
	      {
		fprintf(f, " (make-one-pole %g %g))\n", atod([theUG getArg:0]), atod([theUG getArg:1]));
	      }
	    else if (! strcmp(ug_type, "OnezeroUG"))
	      {
		fprintf(f, " (make-one-zero %g %g))\n", atod([theUG getArg:0]), atod([theUG getArg:1]));
	      }
	    else if (! strcmp(ug_type, "OscgUG"))
	      {
		fprintf(f, " (make-oscil :frequency %g :initial-phase %g))\n", atod([theUG getArg:1]), atod([theUG getArg:3]));
	      }
	    else if (! strcmp(ug_type, "oscilUG"))
	      {
		fprintf(f, " (make-oscil :frequency %g :initial-phase %g))\n", atod([theUG getArg:0]), atod([theUG getArg:1]));
	      }
	    else if (! strcmp(ug_type, "ScaleUG")) fprintf(f, " %g)\n", atod([theUG getArg:0]));
	    else if (! strcmp(ug_type, "Scl1add2UG")) fprintf(f, " %g)\n", atod([theUG getArg:0]));
	    else if (! strcmp(ug_type, "Scl2add2UG"))
	      {
		fprintf(f, "-1 %g)\n         (%s-2 %g)\n", atod([theUG getArg:0]), ug_name, atod([theUG getArg:1]));
	      }
	    else if (! strcmp(ug_type, "SnoiseUG")) fprintf(f, " (make-randh))\n");
	    else if (! strcmp(ug_type, "UnoiseUG")) fprintf(f, " (make-randi))\n");
	    else if (! strcmp(ug_type, "AsympUG"))
	      {
		fprintf(f, " (make-env :envelope '%s :start-time start-time :duration duration))\n",
			ParseEnvString([theUG getArg:4], duration));
	      }
	  }
       if ((ug_type != NULL) && (MultipleOuts(theUG) && strcmp(ug_type, "SineROM") && strcmp(ug_type, "Storage")))
	 {
	   ug_name = [theUG getName];
	   fprintf(f, "         (%s-out 0.0)\n", ug_name);
	 }
    }
  fprintf(f, "        )\n\n");

  // now for the run-time loop
  fprintf(f, "        (set-srate %g)\n", samplingRate);
  fprintf(f, "        (Run\n");
  fprintf(f, "         (loop for i from beg to end do\n          ");
    
  for (i=0; i<[fakeUGs count]; i++)
    {
        if (! [[fakeUGs objectAt:i] getConnectionName:0])
           {
             DefineBlock(f, [fakeUGs objectAt:i], isDefined);
             PrintCrunch(f, [fakeUGs objectAt:i], isDefined);
           }
    }
  fprintf(f, "\n         )\n        )\n        (end-run)\n  ))\n\n");

  // defpart
  fprintf(f, "(defpart %s (clm-part)\n", instrumentName);
  fprintf(f, "  (name &message start-time duration amplitude)\n");
  fprintf(f, "  (start-time duration amplitude))\n\n");

  // sample score file
  fprintf(f, "#|\n; Sample score file for instrument %s and part %s\n\n", instrumentName, instrumentName);
  fprintf(f, "(defscorefile (after '(load :srate %g :channels 2))\n", samplingRate);
  fprintf(f, "   (with-part %s (time 0.0 events 1)))\n", instrumentName);
  fprintf(f, "|#\n");

  fclose(f);  
  return self;
}

@end

static BOOL IsInitializeable(char *s)
{
  // If s is equal to any of these strings, NO is returned.
  return  (strcmp(s, "Add2UG") && strcmp(s, "DswitchUG") && strcmp(s, "DswitchtUG") && strcmp(s, "Mul1add2UG")
	   && strcmp(s, "Mul2UG") && strcmp(s, "SineROM") && strcmp(s, "Storage"));
}

static BOOL MultipleOuts(id theUG)
{
  // If the UG passed in has more than one output, YES is returned.
  id theOut;
  return ((theOut = [theUG getConnection:0]) ? ([[theOut getToNodes] count] > 1) : NO);
}

static void DefineBlock(FILE *f, id theUG, id definedList)
{
	int i;
  if (! MultipleOuts(theUG))
    {
      // recurse through all the inputs
      for (i=1; i <= GetNumberOfInputs(theUG); i++)
        {
          DefineBlock(f, GetNextUG(i), definedList);
        }
    }
  else
    {
      if (strcmp([theUG getGenericTypeString], "Storage") && strcmp([theUG getGenericTypeString], "SineROM"))
	{
	  if ([definedList indexOf:theUG] == NX_NOT_IN_LIST)
	    {
	      // add the UG to the list of defined synth blocks
	      [definedList addObject:theUG];
	      // recurse through all the inputs
	      for (i=1; i <= GetNumberOfInputs(theUG); i++)
		{
	          DefineBlock(f, GetNextUG(i), definedList);
	        }
	      fprintf(f, "(setf %s-out ", [theUG getName]);
	      PrintCrunch(f, theUG, definedList);
	      fprintf(f, ")\n          ");
	    }
        }
    }
}

static void NextCrunch(FILE *f, id theUG, id definedList, int connection)
{
  id nextUG;
  
  nextUG = GetNextUG(connection);
  if (MultipleOuts(nextUG))
    {
      if ([definedList indexOf:nextUG] == NX_NOT_IN_LIST)
        {
          fprintf(f, "\n   ERROR:  No definition for synthblock %s-out\n", [nextUG getName]);
        }
      else fprintf(f, "%s-out", [nextUG getName]);
    }
  else PrintCrunch(f, nextUG, definedList);
}


static void PrintCrunch(FILE *f, id theUG, id definedList)
{
  char *type = [theUG getGenericTypeString];
  
  if (! strcmp(type, "Add2UG"))
    {
      fprintf(f, "(+ ");
      NextCrunch(f, theUG, definedList, 1);
      fprintf(f, " ");
      NextCrunch(f, theUG, definedList, 2);
      fprintf(f, ")");
    }
  else if (! strcmp(type, "AsympUG"))
    {
      fprintf(f, "(env %s)", [theUG getName]);
    }
  else if (! strcmp(type, "Allpass1UG"))
    {
      fprintf(f, "(all-pass %s ", [theUG getName]);
      NextCrunch(f, theUG, definedList, 1);
      fprintf(f, ")");
    }
  else if (! (strcmp(type, "ConstantUG") && strcmp(type, "Constantpp")))
    {
      fprintf(f, "%s", [theUG getName]);
    }
  else if (! strcmp(type, "DelayUG"))
    {
      fprintf(f, "(delay %s ", [theUG getName]);
      NextCrunch(f, theUG, definedList, 1);
      fprintf(f, ")");
    }
  else if (! strcmp(type, "Mul2UG"))
    {
      fprintf(f, "(* ");
      NextCrunch(f, theUG, definedList, 1);
      fprintf(f, " ");
      NextCrunch(f, theUG, definedList, 2);
      fprintf(f, ")");
    }
  else if (! strcmp(type, "OnepoleUG"))
    {
      fprintf(f, "(one-pole %s ", [theUG getName]);
      NextCrunch(f, theUG, definedList, 1);
      fprintf(f, ")");
    }
  else if (! strcmp(type, "OnezeroUG"))
    {
      fprintf(f, "(one-zero %s ", [theUG getName]);
      NextCrunch(f, theUG, definedList, 1);
      fprintf(f, ")");
    }
  else if (! strcmp(type, "OscgUG"))
    {
      fprintf(f, "(oscil %s)", [theUG getName]);
    }
  else if (! (strcmp(type, "OscgafUG") && strcmp(type, "OscgafiUG")))
    {
      fprintf(f, "(* ");
      NextCrunch(f, theUG, definedList, 1);                                      // amp input
      fprintf(f, " (oscil %s (* freq-tweak ", [theUG getName]);
      NextCrunch(f, theUG, definedList, 2);                                      // phase inc. input
      fprintf(f, ")))");
    }
  else if (! (strcmp(type, "Out1aUG") && strcmp(type, "Out1bUG") && strcmp(type, "Out2sumUG")))
    {
      fprintf(f, "(locsig %s i (* amplitude ", [theUG getName]);
      NextCrunch(f, theUG, definedList, 1);
      fprintf(f, "))");
    }
  else if (! strcmp(type, "Scl1add2UG"))
    {
      fprintf(f, "(+ (* %s ", [theUG getName]);
      NextCrunch(f, theUG, definedList, 1);
      fprintf(f, ") ");
      NextCrunch(f, theUG, definedList, 2);
      fprintf(f, ")");
    }
  else if (! strcmp(type, "Scl2add2UG"))
    {
      fprintf(f, "(+ (* %s-1 ", [theUG getName]);
      NextCrunch(f, theUG, definedList, 1);
      fprintf(f, ") (* %s-2 ", [theUG getName]);
      NextCrunch(f, theUG, definedList, 2);
      fprintf(f, "))");
    }
  else if (! strcmp(type, "SnoiseUG"))
    {
      fprintf(f, "(randh %s)", [theUG getName]);
    }
  else if (! strcmp(type, "UnoiseUG"))
    {
      fprintf(f, "(randi %s)", [theUG getName]);
    }
  else if (! strcmp(type, "oscilUG"))
    {
      fprintf(f, "(oscil %s (* freq-tweak ", [theUG getName]);
      NextCrunch(f, theUG, definedList, 1);
      fprintf(f, "))");
    }
  else fprintf(f, "\n I don't know how to handle this UG yet:  %s\n", type);
}

static char *ParseEnvString(char *s, double dur)
{
  int i, j, bkptnum;
  BOOL isSticker = NO;
  char *parsed, *numpair, *final;
  double *xPts, *yPts;
  int stickIndex=0, parseLength;
  double endTime, susTime, newx;

  for (i=0, bkptnum=0; s[i] != '\0'; i++)
    {
      if (s[i] == '|') isSticker = YES;
      if (s[i] == '(') bkptnum++;
    }

  xPts = (double*) malloc(bkptnum*sizeof(double));
  yPts = (double*) malloc(bkptnum*sizeof(double));
  parsed = (char *) malloc(MAXENVSTRING*sizeof(char));
  numpair = (char *) malloc(30*sizeof(char));

  for (i=0, j=0; s[i] != '\0'; i++)
    {
      if (s[i] == '(')
	{
	  sscanf(s+i, "(%lf, %lf)", &xPts[j], &yPts[j]);
	  j++;
	}
      if (s[i] == '|') stickIndex = j;
    }

  endTime = xPts[j-1];

  if (! (isSticker && (dur > endTime)))
    {
      sprintf(parsed, "(");
      for (i=0; i < bkptnum; i++)
	{
	  sprintf(numpair, "%g %g ", xPts[i], yPts[i]);
	  strcat(parsed, numpair);
	}
      strcat(parsed, ")");
    }
  else
    {
      susTime = dur - endTime;
      sprintf(parsed, "(");
      for (i=0; i < stickIndex; i++)
	{
	  sprintf(numpair, "%g %g ", xPts[i], yPts[i]);
	  strcat(parsed, numpair);
	}
      i--;
      newx = xPts[i] + susTime;
      sprintf(numpair, "%g %g ", newx, yPts[i]);
      strcat(parsed, numpair);
      i++;
      while (i < bkptnum)
	{
	  newx = xPts[i] + susTime;
	  sprintf(numpair, "%g %g ", newx, yPts[i]);
	  strcat(parsed, numpair);
	  i++;
	}
      strcat(parsed, ")");
    }
  parseLength = strlen(parsed);
  final = (char *) malloc(parseLength*sizeof(char));
  strcpy(final, parsed);
  free(parsed);
  free(numpair);
  return parsed;
}

static int GetNumberOfInputs(id theUG)
{
  int i;
  // all inputs start at connection index 1.
  // if there is no name for the connection,
  // then the connection does not exist.
  for (i=1; [theUG getConnectionName:i]; i++);
  return (i-1);
}
