
#import <gamekit/gamekit.h>
#import <daymisckit/daymisckit.h>
#import <soundkit/soundkit.h>
#import <sound/sound.h>
#import <sys/param.h>

@implementation GKSoundStream

- init
{ // This method should be avoided.
	return [self initStreams:1];
}

- initStreams:(int)nstr
{
	int i;
	id stream, timeLeft;
	if (!_initialized) [super init];
	else [self freeObjects];
	streamList = [[List alloc] initCount:nstr];
	timeLeftList = [[List alloc] initCount:nstr];
	device = [[NXSoundOut alloc] init]; // set up sound out device
	for (i=0; i<nstr; i++) {
	// In 3.1 need to use -initOnDevice:withParameters: to set this up
	// right and remove the obsolete play method used down below. *****
		stream = [[NXPlayStream alloc] initOnDevice:device];
		[stream activate]; // set up our output stream
		[streamList addObject:stream];
		timeLeft = [[DAYTime alloc] initWithCurrentTime];
		[timeLeftList addObject:timeLeft];
	}
	currentTime = [[DAYTime alloc] init]; // alloc here so only alloc'd once
	_initialized = YES; tag = 0;
	return self;
}

- playSoundStruct:(SNDSoundStruct *)sound 
{	// We assume proper format sounds.  Whatever object calls this one
	// should assure that any SNDSoundStruct passed in is for a sound
	// which is:  (1) non-fragmented, (2) 16-bit linear, (3) 22.05 kHz,
	// and (4) mono.  If you don't stick to this, weird things might
	// happen!!! (I don't check for any of this.  In the future, I
	// probably should do so, and adjust for it. *****
	int i, best = 0; // num is stream num we choose
	id stream, time = [timeLeftList objectAt:best];
	long timeOnStream;
	// return if no data to play
	if (!sound) return self;
	[currentTime initWithCurrentTime];
	// find the stream which will be available soonest.
#ifdef NOISYDEBUG
	fprintf(stderr, "GKSoundStream:  Now is: %s (%d)\n",
		[currentTime stringValue], [currentTime microsecond]);
	fprintf(stderr, "GKSoundStream:  Stream #%d: %s (%d)\n",
		0, [time stringValue], [time microsecond]);
#endif
	for (i=1; i<[streamList count]; i++) {
		id tempTime = [timeLeftList objectAt:i];
#ifdef NOISYDEBUG
	fprintf(stderr, "GKSoundStream:  Stream #%d: %s (%d)\n",
		i, [tempTime stringValue], [tempTime microsecond]);
#endif
		if ([time isAfter:tempTime]) {
			time = tempTime;
			best = i;
		}
	}
	stream = [streamList objectAt:best];
#ifdef NOISYDEBUG
	fprintf(stderr, "GKSoundStream:  Best time is #%d.\n", best);
#endif
	// enqueue the sound for playback
	[stream playBuffer:(char *)sound+sound->dataLocation
			size:sound->dataSize tag:tag++ channelCount:sound->channelCount
			samplingRate:sound->samplingRate];
	// estimate how long sound is to be on stream:
	// (based on 16it 22050 Hz mono sounds.)
	timeOnStream = (sound->dataSize / 2) * 45.3514739229;
	// set up when stream will be finished.
	if ([currentTime isAfter:time]) [time initWithCurrentTime]; // gap in play
	// (above 'if' fails if stuff is on the stream right now.  Needed because
	// if there was a gap in stream playback, then we start playing NOW, but
	// it the stream is busy, then we start when it finishes.)  Note that
	// realistically, I should add in a fudge factor if the if is true since
	// the stream doesn't start to play _immediately_.  This seems to be
	// close enough for my purposes, though.  Eventually, I may play with
	// adding in a fudge factor to try and take this into account.  *****
	// Note:  the stream delegate method - soundStream:didStartBuffer:
	// might be adequate here.
	[time addMicroseconds:timeOnStream];
#ifdef NOISYDEBUG
	fprintf(stderr, "GKSoundStream:  On stream for %ld usec.\n", timeOnStream);
	fprintf(stderr, "GKSoundStream:  Estimate of time when done: %s (%d)\n\n",
		[time stringValue], [time microsecond]);
#endif
	return time;
}

- freeObjects
{
	[timeLeftList freeObjects];
	[timeLeftList free];
	[streamList freeObjects];
	[streamList free];
	[device free];
	[currentTime free];
	return self;
}

- free
{
	[self freeObjects];
	return nil;
}

- streamList { return streamList; }
- timeLeftList { return timeLeftList; }

@end
