//
/* SoundEffect.m, class to play sounds
 * Originally by Terry Donahue, modified by Ali Ozer and Katzlberger Thomas (cat)
 *
 * SoundEffect is a class which conveniently groups the 3.0
 * sound stream functionality with sound data using the Sound
 * class.
 *
 *  You may freely copy, distribute and reuse the code in this example.
 *  NeXT disclaims any warranty of any kind, expressed or implied,
 *  as to its fitness for any particular use.
 */

#import "SoundEffect.h"
#import <soundkit/NXSoundOut.h>
#import <appkit/nextstd.h>
#import <appkit/Application.h>
#import <objc/List.h>

// 8/28/93 reference limiting by cat for MissileCommand

@implementation SoundEffect

static BOOL soundEnabled = YES;

+ (void)setSoundEnabled:(BOOL)flag
{
    soundEnabled = flag;
}

+ (BOOL)soundEnabled
{
    return soundEnabled;
}

#define DEFAULTMAXSOUNDSTREAMS 40

static List *soundStreams = nil;
static unsigned int soundStreamsAllocated = 0;
static unsigned int maxSoundStreams = DEFAULTMAXSOUNDSTREAMS;

	// These two methods let the client set/get the maximum number of
	// sound streams to allocate. If this number is exceeded, sound requests
	// are simply not honored until sound streams are freed up.

+ (void)setMaxSoundStreams:(unsigned int)max
{
    maxSoundStreams = max;
}

+ (unsigned int)maxSoundStreams
{
    return maxSoundStreams;
}

	// This method returns a sound stream to be used in playing a sound.
	// Sound streams allocated through this method should be given back
	// via releaseSoundStream:. Note that this is for internal use only;
	// it however might be overridden if necessary.
	// aha ! soundStreams is a list of available (unused,not playing) streams
- (NXPlayStream *)soundStream
{
    static NXSoundOut *dev = nil;				// We only have one instance of this...
    NXPlayStream *newStream = nil;
    
    if (!dev && !(dev = [[NXSoundOut alloc] init]))
	{													// We allocate from the default zone so
		NXLogError ("Couldn't create NXSoundOut");		// freeing this zone won't blast it
        return nil;
    }

    if (!soundStreams)
		soundStreams = [[List alloc] init];

    if (![soundStreams count]) 
	{	if (soundStreamsAllocated < maxSoundStreams) 
		{	newStream = [[NXPlayStream allocFromZone:[self zone]] initOnDevice:dev];
	   		soundStreamsAllocated++;
		}
    } 
	else
        newStream = [soundStreams removeLastObject];
    
    if(newStream)
	{	[newStream setDelegate:self];
		if (![newStream isActive])
	   	 	[newStream activate];
    }

    return newStream;
}

- (void)releaseSoundStream:(NXPlayStream *)soundStream
{
    [soundStreams addObject:soundStream];
}

	// This method lets you create new instances of SoundEffect. If the specified
	// sound file does not exist, the allocated instance is freed and nil is returned.


- initFromSection:(const char *)path withLimit:(unsigned int)n
{
	flags.limit = n;
	return [self initFromSection:path];
}

- initFromSection:(const char *)path
{
    [super init];

	if(flags.limit == 0) flags.limit = 255;

    if (!(sound = [[Sound alloc] initFromSection:path]))
	{	NXLogError ("Couldn't load sound from %s", path);
		[self free];
		return nil;
    }

	if([sound samplingRate]<22000.0)
		[sound convertToFormat:SND_FORMAT_LINEAR_16 samplingRate:SND_RATE_LOW channelCount:2];
	
    return self;
}

- (int)soundsPlaying
{	
	return flags.refCount;
}

	// Free frees the SoundEffect. If this sound effect is being played at the time,
	// the free is delayed and happens as soon as all pending sounds are finished.

- free
{
    if (flags.refCount) {
	flags.freeWhenDone = YES;
	return nil;
    } else {
	if (sound) [sound free];
	return [super free];
    }
}

	// These 3 methods play the sound effect.

	// This function ensures that soundstreams will be reactivated (by calling:
	// soundStream:didCompleteBuffer:) if lots of (>20) short sounds are played within
	// one function without invoking the eventloop 
- playEvent
{
	[NXApp getNextEvent:NX_NULLEVENTMASK waitFor:0.0 threshold:NX_BASETHRESHOLD];
    return [self play:1.0 pan:0.0];
}

- play
{
    return [self play:1.0 pan:0.0];
}

- play:(float)volume pan:(float)pan
{
    float left,right;
    NXPlayStream *soundStream;
    
    if((flags.refCount>=flags.limit) || (!soundEnabled)) return self;

    if (!(soundStream = [self soundStream]))
	{	NXLogError ("No sound stream to play sound %x", self);
		return self;
    }
    
    left = right = volume;
    if (pan > 0.0) left  *= 1.0 - pan;
    else if (pan < 0.0) right *= 1.0 + pan;
    [soundStream setGainLeft:left right:right];

    [soundStream playBuffer:(void *)[sound data]
		         size:(unsigned int)[sound dataSize]
		         tag:0
                 channelCount:(unsigned int)[sound channelCount]
	             samplingRate:[sound samplingRate]];

    flags.refCount++;

    return self;
}

	// Delegate methods for internal use only.

- soundStream:sender didCompleteBuffer:(int)tag
{
    flags.refCount--;
    [self releaseSoundStream:sender];
    if (flags.freeWhenDone && flags.refCount == 0)
		[self free];

    return sender;
}

- soundStreamDidAbort:sender deviceReserved:(BOOL)flag
{
    return [self soundStream:sender didCompleteBuffer:0];
}

@end
