/* 
  playscorefile2.

  David A. Jaffe.
  
  See README for a description of this program.
*/

/* playscorefile2 is an example of a Music Kit performance that "spools" a 
   scorefile to the DSP. Since no real-time interaction is involved, all 
   timing is done on the DSP; thus, the Orchestra is set to timed mode and the
   Conductor is set to unclocked mode. In unclocked mode the Conductor's
   +startPerformance method initiates a tight loop that sends Notes as
   fast as possible until all Notes have been sent, then returns.  */

#import <musickit/musickit.h>
#import <musickit/synthpatches/synthpatches.h>

static int handleObjcError(char *className) 
{    return 0; }

main(ac, av)
  int ac;
  char * av[];
{
    int i;
    id aSFPerformer,anOrch;

    [UnitGenerator enableErrorChecking:YES]; /* Added for debugging purposes */

    for (i=1; i<ac; i++) {  
	/* Read command line arguments. Usage: [-t <num>  <  <file>] */
	if (av[i][1] == 't') 
	    MKSetTrace(atoi(av[++i]));
    }	
    
    if (isatty(0)) {
	fprintf(stderr,"usage: playscorefile2 [-t] < inputfile\n");
	exit(1);
    }

    {	/* Create a Score object and read a scorefile into it. Then create a 
	   ScorePerformer to perform the Score and configure the performance 
	   from the 'info' field of the scorefile . */

	id scoreInfo;                                    /* Used for 'info' */
	NXStream *stdinStream = NXOpenFile(stdin->_file,NX_READONLY);
	aSFPerformer = [[ScorefilePerformer alloc] init];

	[aSFPerformer setStream:stdinStream];
	[aSFPerformer activate]; 

	scoreInfo = [aSFPerformer info];
	if (scoreInfo) { /* Configure performance as specified in info. */ 

	    /* "headroom" determines how close to the limit of the DSP we want
	       to run. If headroom is 0 or negative, there is a higher risk
	       of falling out of real time (interruptions will be heard).
	       If headroom is positive (e.g. .25), it is unlikely you will
	       fall out of real time. */
	    if ([scoreInfo isParPresent:MK_headroom])
		[Orchestra setHeadroom:[scoreInfo parAsDouble:MK_headroom]];
	    
	    /* Set sampling rate. The Sound hardware only functions at 
	       44100 or 22050. */
	    if ([scoreInfo isParPresent:MK_samplingRate]) {
		double samplingRate = [scoreInfo parAsDouble:MK_samplingRate];
		if ((samplingRate == 44100.0) || (samplingRate == 22050.0)) 
		  [Orchestra setSamplingRate:samplingRate];
		else fprintf(stderr,
			     "Sampling rate must be 44100 or 22050.\n");
	    }
	    /* Get tempo and set the tempo of the default Conductor. */
	    if ([scoreInfo isParPresent:MK_tempo]) {
		double tempo = [scoreInfo parAsDouble:MK_tempo];
		[[Conductor defaultConductor] setTempo:tempo];
	    }
    	}
    }

    /* Open the Orchestra. */
    anOrch = [Orchestra new];
    if (![anOrch open]) {
	fprintf(stderr,"Can't open DSP.\n");
	exit(1);
    }

    { /* Create a SynthInstrument for each part in the scorefile and set them
	 up as specified in the corresponding 'info'. Each part in the 
	 scorefile has a corresponding NoteSender in the ScorefilePerformer */

	int partCount,synthPatchCount,voices;
	char *className;
	id noteSenders,synthPatchClass,partInfo,anIns,aNoteSender;

	noteSenders = [aSFPerformer noteSenders];
	partCount = [noteSenders count];
	for (i = 0; i < partCount; i++) {
	    aNoteSender = [noteSenders objectAt:i];
	    partInfo = [aSFPerformer infoForNoteSender:aNoteSender];
	    if (!partInfo) {              /* Omit Parts with no info. */ 
		fprintf(stderr,"%s info missing.\n",
			MKGetObjectName(aNoteSender));
		continue;
	    }		

	    /* Look in the partInfo for a SynthPatch name. If none, omit
	       this part. */
	    if (![partInfo isParPresent:MK_synthPatch]) {
		fprintf(stderr,"%s info missing synthPatch.\n",
			MKGetObjectName(aNoteSender));
		continue;
	    }

	    /* Now set the SynthPatch of this part as specified in the info */
	    className = [partInfo parAsStringNoCopy:MK_synthPatch];
	    /* We find the class. */ 
	    objc_setClassHandler(handleObjcError); /* Supress error printing */
	    synthPatchClass = (strlen(className) ? 
			       objc_getClass(className) : nil);
	    if (!synthPatchClass) {         /* Class not loaded in program? */ 
		fprintf(stderr,"Class %s not loaded into program.\n",
			className);
		continue;
	    }

	    /* Create a new SynthInstrument to manage the notes from this part
	       and connect the noteSender to the SynthInsturment's 
	       noteReceiver. */
	    anIns = [[SynthInstrument alloc] init];
	    [aNoteSender connect:[anIns noteReceiver]];

	    /* Set the new SynthInstrument to use the specified SynthPatch */
	    [anIns setSynthPatchClass:synthPatchClass];

	    /* Look for the synthPatchCount for this part. */
	    if (![partInfo isParPresent:MK_synthPatchCount])
	      continue;         /* Do allocation of voices from a common pool
				   on the fly during performance. */

	    /* Otherwise, use a number of voices specified by the part info.*/
	    voices = [partInfo parAsInt:MK_synthPatchCount];
	    synthPatchCount = 
	      [anIns setSynthPatchCount:voices patchTemplate:
	       [synthPatchClass patchTemplateFor:partInfo]];
	    /* A given SynthPatch can have several versions or 
	       "PatchTemplates". For example, there may be one version
	       that supports vibrato and another that does not.
	       The SynthPatch class provides a method to
	       determine the correct version for a given Note. In this case,
	       we pass the SynthPatch class the Part info and allow it
	       to customize based on the information contained therein. */

	    if (synthPatchCount < voices) 
		fprintf(stderr,
			"Could only allocate %d instead of %d %ss for %s\n",
			synthPatchCount,voices,[synthPatchClass name],
			MKGetObjectName(aNoteSender));
	}
    }

    /* Prepare Conductor */
    MKSetDeltaT(1.0);              /* Run at least one second ahead of DSP.
				      For very dense scores, this number may
				      have to be increased. */
    [Conductor setClocked:NO];     /* Conductor feeds DSP as fast as it can. */
    [Conductor setThreadPriority:1.0];  /* Boost priority of performance. */ 
    [anOrch run];                  /* Start the DSP. */
    fprintf(stderr,"playing...\n");
    [Conductor startPerformance];  /* Start sending Notes, loops till done.*/

     /* Here's where the music plays. Conductor's startPerformance method
       does not return until the performance is over.  Note, however, that
       if the Conductor is in a different mode, startPerformance returns 
       immediately (if it is in clocked mode or if you have specified that the 
       performance is to occur in a separate thread).  See the Conductor 
       documentation for details. 
       */

    /* Now clean up. */
    [anOrch close];                /* Releases DSP. */
    fprintf(stderr,"...done\n");
    exit(0);
}







