/* 
 * mk2stella reads a Music Kit scorefile and writes a Stella archive file,
 * which is actually just a lisp program.  mk2stella is adapted from the
 * scorefile-to-lisp program written by David Jaffe at CCRMA. compile by:
 * 	cc -DNeXT_3 -g -c mk2stella.m -o mk2stella.o
 *      cc -o mk2stella mk2stella.o -lmusickit -lNeXT_s -lsys_s
 */

#ifndef NeXT_2
	#ifndef  NeXT_3
		#error "missing -D switches: NeXT_2 or NeXT_3"
	#endif
#endif

#ifdef NeXT_2
#import <musickit/musickit.h>
#endif

#ifdef NeXT_3
#import "/LocalDeveloper/Headers/musickit/musickit.h"
#endif

/* flags for writeParameters hackery */

#define writeParC		0
#define writeParLisp		1
#define writeParFreqName	2
#define writeParPreserveCase	4

static const char *const helpString = "Usage: mk2stella infile outfile\n";

static void writeScore(Score *aScore);
static void writeHeader(Score *aScore);
static void EnvelopesAndWaveTables(Score *aScore, List *aList);
static void writeParts(Score *aScore);
static void writeNotes(Part *aPart, unsigned modes);
static void writeParameters(Note *aNote, unsigned modes);
static char *genName(id obj, char *rootName);

static NXStream *theStream;
static FILE *fp;

void main(int ac,char *av[]) 
{
    Score *aScore = [[Score alloc] init];
    if (ac != 3) {
	fprintf(stderr,helpString);
	exit(1);
    }
    if (![aScore readScorefile:av[1]]) {
	fprintf(stderr,"mk2stella: Problem reading file.\n");
	exit(1);
    }
    fp = fopen(av[2],"w");
    theStream = NXOpenFile(fp->_file, NX_WRITEONLY);
    if (fp == NULL) {
	fprintf(stderr,"mk2stella: Problem opening output file.\n");
	exit(1);
    }
    MKWritePitchNames(YES);
    NXPrintf(theStream,";;; written by mk2stella from %s\n",av[1]);
    writeScore(aScore);
    NXClose(theStream);
    fclose(fp);
    exit(0);
}

static void indent(int n)
{
	int i;
	NXPrintf(theStream,"\n");
	for (i=0;i<n;i++) NXPrintf(theStream," ");
	  
}

static void writeScore(Score *aScore)
{
	NXPrintf(theStream,"(in-package :stella)");
	indent(0);
	NXPrintf(theStream,"(in-syntax :music-kit t)");
	indent(0);
	NXPrintf(theStream,"((lambda (score)" );

	/* print part definitions */
	writeParts(aScore);
	indent(3);
	NXPrintf(theStream,"score)");  /* end lambda */
	indent(1);
	NXPrintf(theStream,"(make-object 'merge :id '%s",
	         genName(aScore, (char *)"score-%d" ));
        /* collect envelopes etc into a header string for score */
	writeHeader(aScore);
	NXPrintf(theStream,"))\n"); /* end make-object, lambda form */

}

static void writeParts(Score *aScore)
    /* Write part declarations and "part info" */
{
	List *parts = [aScore parts];
	int i, partCount;
	if (!parts) return;
	indent(3);
	NXPrintf(theStream,"(add-objects");
	indent(5);
	NXPrintf(theStream,"(list");
	partCount = [parts count];
	for (i=0; i<partCount; i++)
	{
		Part *aPart;
		Note *info;
		char *name, *class, useNames;
		BOOL freqNames = NO;
		int par, mode;
		
		aPart = (Part *)[parts objectAt:i];
		name = (char *)MKGetObjectName(aPart);
		info = [aPart info];
		/* if user specified freqNames:YES, print freqnames */
		par=[Note parName:"freqNames"];
		if ([info isParPresent:par] == YES)
			mode=writeParLisp | writeParFreqName;
		else
			mode=writeParLisp;

		class = (char *)[info parAsString:MK_synthPatch];
		indent(7);
		NXPrintf(theStream,"((lambda (thread &aux info)");
		indent(10);
	        NXPrintf(theStream,"(setq info (partInfo \"%s\"", name);
		writeParameters(info, (writeParLisp | writeParPreserveCase));
		NXPrintf(theStream,"))"); /* close partInfo, setq */

		writeNotes(aPart, mode); 
		indent(10);
		NXPrintf(theStream,"thread)"); /* close lambda */
		indent(8);
		NXPrintf(theStream,"(make-object 'thread :id '%s))",
				name);
	}
	NXPrintf(theStream,")");	/* end list */
	indent(5);
	NXPrintf(theStream,"score)");	/* end add-objects */
}

static void writeNotes(Part *aPart, unsigned modes)
{
	Note *info = [aPart info];
	List *notes = [aPart notes];
	char *name, *class;
	int i, noteCount;
	static char *noteTypeNames[] = {":noteDur",":noteOn",":noteOff",
					":noteUpdate",":mute"};
	Note *aNote, *nNote;
	MKNoteType nt;
	double r;

	if ((noteCount=[notes count])==0) return;
        name = (char *)MKGetObjectName(aPart);
	class = (char *)[info parAsString:MK_synthPatch];
	indent(10);
        NXPrintf(theStream,"(add-objects");
	indent(12);
	NXPrintf(theStream,"(list");
	for (i=0;i<noteCount;i++)
	{
		aNote=(Note *)[notes objectAt:i]; 
		if (i<(noteCount-1))
		{
			nNote=(Note *)[notes objectAt:i+1];
			r=[nNote timeTag] - [aNote timeTag];
		}
		else
			r=0.0;
		nt = [aNote noteType];
		indent(14);
		NXPrintf(theStream,"(make-object '%s :info info",class);
		NXPrintf(theStream," :rhythm %f",r);
		if ([aNote noteTag] != MAXINT)
		  NXPrintf(theStream," :tag %d",[aNote noteTag]);
		if (nt == MK_noteDur)
			NXPrintf(theStream," :duration %f",[aNote dur]);
		else
			NXPrintf(theStream," :type '%s",
				 noteTypeNames[nt-MK_noteDur]);
		writeParameters(aNote, modes);
		NXPrintf(theStream,")");	/* close make-object */
	}
        NXPrintf(theStream,")");		/* close list */
	indent(12);
	NXPrintf(theStream,"thread)");		/* close add-objects */

}

static void writeHeader(Score *aScore)
{
	List  *aHeader = [[List alloc] init];
	Note *info = [aScore info];
	int i, envCount;
	char *name;
	id obj;

	EnvelopesAndWaveTables(aScore,aHeader);
	envCount = [aHeader count];
	/* don't do anything if there are no header statements. */
	if ((envCount == 0) && !info)
	  return;
	indent(15);
	NXPrintf(theStream," :header \"\n");
        if (info)
	{
		NXPrintf(theStream,"info ");
		writeParameters(info,writeParC);
		NXPrintf(theStream,";\n");
	}	
	for (i=0; i<envCount; i++)
	{
		obj = [aHeader objectAt:i];
		if (!(name = (char *)MKGetObjectName(obj)))
		  name = genName(obj, (char *)"");
		if ([obj class] == [Envelope class])
		      NXPrintf(theStream,"envelope %s = [",name);
		else 
		      NXPrintf(theStream,"waveTable %s = [",name);
		[obj writeScorefileStream:theStream];
		NXPrintf(theStream,"];\n");
	}
	NXPrintf(theStream,"\""); /* close header string */
	[aHeader free];
}

static void writeParameters(Note *aNote, unsigned modes)
{
	void *aState;
	int par;
	char *name, *format;

	if ((modes & writeParLisp)==writeParLisp) 
		if ((modes & writeParPreserveCase)==writeParPreserveCase)
			format=" :|%s|";
		else
			format=" :%s";
	else 
		format="%s:";
	aState = MKInitParameterIteration(aNote);
	while ((par = MKNextParameter(aNote,aState)) != MK_noPar) 
	{
		name = [Note nameOfPar:par];
		if (name[0] == (char)NULL) continue;	/* skip private pars */
		NXPrintf(theStream,format,name);

		if (((modes & writeParFreqName)==writeParFreqName) &&
		    ((strcmp([Note nameOfPar:par],"freq")==0) ||
		     (strcmp([Note nameOfPar:par],"freq0")==0)))
		{
			int keyNum = MKFreqToKeyNum([aNote parAsDouble:par],
						    NULL,1.0);
			char *pitchNames[128] = {
 "c00","cs00","d00","ds00","e00","f00","fs00","g00","gs00","a00","as00","b00",
 "c0","cs0","d0","ds0","e0","f0","fs0","g0","gs0","a0","as0","b0",
 "c1","cs1","d1","ds1","e1","f1","fs1","g1","gs1","a1","as1","b1",
 "c2","cs2","d2","ds2","e2","f2","fs2","g2","gs2","a2","as2","b2",
 "c3","cs3","d3","ds3","e3","f3","fs3","g3","gs3","a3","as3","b3",
 "c4","cs4","d4","ds4","e4","f4","fs4","g4","gs4","a4","as4","b4",
 "c5","cs5","d5","ds5","e5","f5","fs5","g5","gs5","a5","as5","b5",
 "c6","cs6","d6","ds6","e6","f6","fs6","g6","gs6","a6","as6","b6",
 "c7","cs7","d7","ds7","e7","f7","fs7","g7","gs7","a7","as7","b7",
 "c8","cs8","d8","ds8","e8","f8","fs8","g8","gs8","a8","as8","b8",
 "c9","cs9","d9","ds9","e9","f9","fs9","g9"};
			NXPrintf(theStream," '%s",pitchNames[keyNum]);

		} 
		else switch ([aNote parType:par]) 
		{
	  		case MK_envelope:
	  		case MK_waveTable:
	    			NXPrintf(theStream," \"%s\"",
				    MKGetObjectName([aNote parAsObject:par]));
	    			break;
	  		case MK_double:
	    			NXPrintf(theStream," %f",
					 [aNote parAsDouble:par]);
	    			break;
	  		case MK_int:
	    			NXPrintf(theStream," %d",[aNote parAsInt:par]);
	    			break;
	  		case MK_string:
				NXPrintf(theStream," \"%s\"",
					 [aNote parAsStringNoCopy:par]);
				break;
			default:
				break;
		}
	}
}

static char *genName(id obj, char *rootName)
{
	static char name[64];
	int i;
	if (rootName==(char *)"") rootName=(char *)"obj%d" ;
	sprintf(name, rootName,obj);
	i = 0;
	while (MKGetNamedObject(name)) 
	{
		i++;
		sprintf(name, rootName,obj+i);
	}
	MKNameObject(name,obj); /* Copies string */
	return name;
}

static void EnvelopesAndWaveTables(Score *aScore, List *allEnvsAndWaves)
    /* Dig through notes and find all envelopes and wave tables.
       Give them names, if necessary, and put them in the header. */
{
    List *parts = [aScore parts];
    Part *aPart;
    List *notes;
    Note *aNote;
    char *name;
    id obj;
    int i,partCount,j,noteCount;
    int par;
    void *aState;
    if (!parts)
      return;
    partCount = [parts count];
    for (i=0; i<partCount; i++) {
	aPart = (Part *)[parts objectAt:i];
	notes = [aPart notes];
	if (notes) {
	    noteCount = [notes count];
	    for (j=0; j<noteCount; j++) {
		aNote = [notes objectAt:j];
		aState = MKInitParameterIteration(aNote);
		while ((par = MKNextParameter(aNote,aState)) != MK_noPar) {
		    obj = [aNote parAsObject:par];
		    if (obj != nil) {
			if ([allEnvsAndWaves indexOf:obj] == NX_NOT_IN_LIST) {
			    [allEnvsAndWaves addObject:obj];
			}
		    }
		}
	    }
	    [notes free];
	}
    }
    [parts free]; /* Free list. */
}

