This is ChaosPerf.m in view mode; [Download] [Up]
/* 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:& 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
These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.