/* Generated by Interface Builder */

#import "ChaosPerf.h"
#import "ChaosPlot.h"
#import "ScorePlot.h"
#import "StanController.h"
#import "HenonController.h"
#import "LorenzController.h"
#import "Formulas.h"
#import <appkit/appkit.h>
#import <musickit/musickit.h>
#import <musickit/synthpatches/synthpatches.h>
#import <math.h>

// Sound modes
#define DSP		0
#define MIDI	1
#define MUTE	2

// Run modes
#define STOPPED	0
#define RUNNING	1

// Tuning modes
#define TWELVE		0
#define INFINITE	1

// MK_delta_time_offset
#define OFFSET_TIME	0.25

// Chaotic systems
#define HENON		0
#define LORENZ		1
#define STANDARD	2


@implementation ChaosPerf

- soundSwitch:sender
{
	[Conductor lockPerformance];
	[self pause];
	switch (soundMode = [sender selectedTag]) {
		case DSP: nextNote = 1; break;
		case MIDI: nextNote = SOUNDSPLIT+1; break;
		case MUTE: break;
	}
	[cController setSoundMode:soundMode];
	[myConductor emptyQueue];
	if (runMode == RUNNING) [self resume];
	[Conductor unlockPerformance];
	return self;
}

- switchTuning:sender
{
	int i;
	tuningMode = [sender selectedTag];
	if (tuningMode == TWELVE) {
		for (i=5; i<=8; i++) {
			[aNoteUpdate[i] setPar:MK_pitchBend toInt:(unsigned)0x2000];
			[aNoteSender[i] sendNote:aNoteUpdate[i]];
		}
	}
	return self;
}

- startStop:sender
{
	if (cController == nil) {
		NXRunAlertPanel("Not so fast!", "You must select a system first", "OK", 0, 0); 
		return self;
	}

	[Conductor lockPerformance];

	switch (runMode) {
		case STOPPED:
			runMode = RUNNING;
			[sender setTitle:"Stop"];
			[self resume];
			break;
		case RUNNING:
			runMode = STOPPED;
			[sender setTitle:"Start"];
			[self pause];
			break;
	}

	[Conductor unlockPerformance];
	return self;
}

- clearPlot:sender
{
	[cPlot display];
	[cScorePlot display];
	return self;
}

- windowWillClose:sender
{
	plotting = 1;
	scoring = 0;
	return self;
}

// -perform gets notes to play and positions to plot by calling the
//	iterateFreq:Amp:Dur:Next:X:Y: method of the current system controller.
//	this method is required to return x and y plotting positions in the range
//	0 to 1.
- perform
{
	float freq, amp, dur, next;
	float xpos, ypos;
	int ifreq /*, bndAmt */ ;
	struct ScoreArgs sc_args;
 
	if (![cController iterateFreq:&freq Amp:&amp Dur:&dur Next:&next X:&xpos Y:&ypos]) {
		[self pause];
		runMode = STOPPED;
		[startButton setTitle:"Start"];
		return nil;
	}

	// if ((freq > 20.0) && (freq < 8000.0) && (amp > 0.0)) {

	switch (soundMode) {
		case DSP: 
			amp = MKdB(amp);
			if (++nextNote > NUMNOTES-SOUNDSPLIT) nextNote = 1;
			if (tuningMode != INFINITE) {
				ifreq = MKFreqToKeyNum((double)freq, NULL, 0.0); 
				freq = MKKeyNumToFreq(ifreq);
			}
			[aNoteDur[nextNote] setDur:dur];
			[aNoteDur[nextNote] setPar:MK_freq toDouble:(double)freq];
			[aNoteDur[nextNote] setPar:MK_amp toDouble:(double)amp];
			[aNoteSender[nextNote] sendNote:aNoteDur[nextNote]];
			break; 
		case MIDI: 
			// MIDI output cycles between 4 channels to allow for separate pitchBend messages
			if (++nextNote > NUMNOTES) nextNote -= SOUNDSPLIT;
			ifreq = (int)freq;
			if (tuningMode == INFINITE) {
				[aNoteUpdate[nextNote] setPar:MK_pitchBend 
					toInt:(unsigned)((freq - (float)ifreq) * (float)0x2000) + 0x2000];
				[aNoteSender[nextNote] sendNote:aNoteUpdate[nextNote]];
			}
			[aNoteDur[nextNote] setDur:dur];
			[aNoteDur[nextNote] setPar:MK_keyNum toInt:ifreq];
			[aNoteDur[nextNote] setPar:MK_velocity toInt:(int)amp];
			[aNoteSender[nextNote] sendNote:aNoteDur[nextNote]];
			break;
		case MUTE: break;
	}

	// }
	
	// DSP synthesis runs OFFSET_TIME behind realtime.  Therefore plotting and scoring
	// messages during DSP are delayed by the same amount to synchronize sound with image.
	// Conductor class supplies a method for delayed massage passing.

	if (plotting) {
		if (soundMode != DSP)
			[cPlot plotPoint:xpos :ypos];
		else {
			[cPlotStorage storeX:&xpos Y:&ypos];
			[Conductor lockPerformance];
			[myConductor sel:@selector(plotDelayedPoint) to:cPlot withDelay:OFFSET_TIME argCount:0];
			[Conductor unlockPerformance];
		}
	}
	if (scoring) {
		sc_args.start = [Conductor time];
		sc_args.dur = dur;
		if (soundMode != DSP) {
			if (tuningMode != INFINITE) freq = (float)ifreq;
			sc_args.key = freq;
			sc_args.vel = amp;
			[cScorePlot drawNoteKey:&sc_args];
		} else  {
			// MKFreqToKeyNum produces mystifying results for bndAmt
			// sc_args.key = (float)MKFreqToKeyNum((double)freq, &bndAmt, 1.0);
			// if (tuningMode == INFINITE) sc_args.key += ((float)bndAmt/(float)0x3fff) - 0.5;

			if (tuningMode != INFINITE) sc_args.key = (float)ifreq;
			// use A440 (= 69) as reference
			else sc_args.key = ((log10(freq/440.)/log10(2.0)) * 12.0) + 69.0;
			sc_args.vel = (float)MKAmpAttenuationToMidi((double)amp);
			[cScoreStorage addElement:&sc_args];
			[Conductor lockPerformance];
			[myConductor sel:@selector(drawDelayedNoteKey) to:cScorePlot withDelay:OFFSET_TIME argCount:0];
			[Conductor unlockPerformance];
		}
	}

	if (next <= 0.0) next = 0.001;
	nextPerform = next;
	return self;
}

- showFormula:sender
{
	[formulas setUp:sender];
	return self;
}
	
- showScore:sender
{
	[cScorePlot setUp];
	scoring = 1;
	plotting = 0;
	return self;
}

- setSystem:sender
{
	if (runMode == RUNNING) {
		runMode = STOPPED;
		[startButton setTitle:"Start"];
		[self pause];
	}
	[myConductor emptyQueue];
	[cPlot display];

	if (cController) [cController closeUp];

	switch ([sender selectedTag]) {
		case HENON:
			cController = [[HenonController alloc] init]; 
			[cPlot labelPlot:"Henon Map" Titlefont:[Font newFont:"Helvetica-Oblique" size:18]
				Xmax:"1.5"  Xlabel:"X"  Xmin:"-1.5"
				Ymax:"0.5"  Ylabel:"Y"  Ymin:"-0.5"
				axisFont:[Font newFont:"Helvetica" size:18]];
			break;
		case LORENZ:
			cController = [[LorenzController alloc] initWithPlot:cPlot withConductor:myConductor]; 
			// [cPlot labelPlot:"Lorenz System" Titlefont:[Font newFont:"Helvetica-Oblique" size:18]
				// Xmax:"30"  Xlabel:"X"  Xmin:"-30"
				// Ymax:"60"  Ylabel:"Y"  Ymin:"0"
				// axisFont:[Font newFont:"Helvetica" size:18]];
			break;
		case STANDARD:
			cController = [[StanController alloc] init]; 
			[cPlot labelPlot:"Standard Map" Titlefont:[Font newFont:"Helvetica-Oblique" size:18]
				Xmax:"2p"  Xlabel:"q"  Xmin:"0"
				Ymax:"2p"  Ylabel:"I"  Ymin:"0"
				axisFont:[Font newFont:"Symbol" size:18]];
			break;
	}
	[cController setUp];
	return self;
}
	
- appDidInit:sender
{
	int i;

	plotting = 1;
	scoring = 0;
	soundMode = DSP;
	runMode = STOPPED;
	tuningMode = TWELVE;
	cController = nil;
	nextNote = 1;

	[cPlot setUp];
	[cPlotStorage initCount:0 elementSize:sizeof(float) description:"f"];
	[cScoreStorage initCount:0 elementSize:sizeof(struct ScoreArgs) description:@encode(struct ScoreArgs)];

	formulas = [Formulas alloc];

	// Set up DSP instruments.  Default condition connects noteSenders to the DSP instrument.
	theOrch = [Orchestra new];
	[theOrch setSamplingRate:44100.0];
	[theOrch setFastResponse:YES];
	[theOrch open];
	dspIns = [[SynthInstrument alloc] init];
	[dspIns setSynthPatchClass:[Pluck class]];

	midiObj = [Midi new];
	[midiObj setOutputTimed:NO];

	/** aNoteSender[0] is not used.
 	 ** aNoteSender[1-4] are used for DSP.
	 ** aNoteSender[5-8] are used for MIDI.
	 **/
	for (i=0; i<=NUMNOTES; i++) {
		aNoteSender[i] = [[NoteSender alloc] init];
		[self addNoteSender:aNoteSender[i]];
		aNoteUpdate[i] = [[Note alloc] init];
		[aNoteUpdate[i] setNoteType:MK_noteUpdate];
		[aNoteUpdate[i] setNoteTag:MKNoteTag()];
		aNoteDur[i] = [[Note alloc] init];
		[aNoteDur[i] setNoteType:MK_noteDur];
		[aNoteDur[i] setNoteTag:MKNoteTag()];
		if (i <= SOUNDSPLIT) {
			aNoteReceiver[i] = [[NoteReceiver alloc] init]; 
			[dspIns addNoteReceiver:aNoteReceiver[i]];
			[aNoteSender[i] connect:aNoteReceiver[i]];
			[aNoteDur[i] setPar:MK_ampRel toDouble:0.25];
			[aNoteDur[i] setPar:MK_sustain toDouble:0.5];
			[aNoteDur[i] setPar:MK_pickNoise toDouble:0.01];
		} else {
			[aNoteSender[i] connect:[midiObj channelNoteReceiver:i-SOUNDSPLIT]];
		}
	}

	MKSetDeltaT(OFFSET_TIME);
	[Conductor setFinishWhenEmpty:NO];
	[Conductor setClocked:YES];
	myConductor = [[Conductor alloc] init];
	[self activate];
	[self pause];

	[Conductor startPerformance];
	[theOrch run];	
	[midiObj run];

	return self;
}

- terminate:sender
{
	[Conductor finishPerformance];
	[theOrch close];
	[midiObj close];
	[NXApp terminate:self];
	return self;
}

@end
