2002-07-30 21:19:05 +00:00
|
|
|
/** <title>NSSound</title>
|
|
|
|
|
|
|
|
<abstract>Load, manipulate and play sounds</abstract>
|
|
|
|
|
2009-08-23 03:37:03 +00:00
|
|
|
Copyright (C) 2002, 2009 Free Software Foundation, Inc.
|
2002-07-30 21:19:05 +00:00
|
|
|
|
2009-08-23 03:37:03 +00:00
|
|
|
Author: Enrico Sersale <enrico@imago.ro>,
|
|
|
|
Stefan Bidigaray <stefanbidi@gmail.com>
|
|
|
|
Date: Jul 2002, Jul 2009
|
2002-07-30 21:19:05 +00:00
|
|
|
|
|
|
|
This file is part of the GNUstep GUI Library.
|
|
|
|
|
|
|
|
This library is free software; you can redistribute it and/or
|
2007-10-29 21:16:17 +00:00
|
|
|
modify it under the terms of the GNU Lesser General Public
|
2002-07-30 21:19:05 +00:00
|
|
|
License as published by the Free Software Foundation; either
|
2008-06-10 04:01:49 +00:00
|
|
|
version 2 of the License, or (at your option) any later version.
|
2007-10-29 21:16:17 +00:00
|
|
|
|
2002-07-30 21:19:05 +00:00
|
|
|
This library is distributed in the hope that it will be useful,
|
|
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
2007-10-29 21:16:17 +00:00
|
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
|
|
Lesser General Public License for more details.
|
|
|
|
|
|
|
|
You should have received a copy of the GNU Lesser General Public
|
2002-07-30 21:19:05 +00:00
|
|
|
License along with this library; see the file COPYING.LIB.
|
2007-10-29 21:16:17 +00:00
|
|
|
If not, see <http://www.gnu.org/licenses/> or write to the
|
|
|
|
Free Software Foundation, 51 Franklin Street, Fifth Floor,
|
|
|
|
Boston, MA 02110-1301, USA.
|
2002-07-30 21:19:05 +00:00
|
|
|
*/
|
|
|
|
|
2011-03-04 11:33:22 +00:00
|
|
|
#import <Foundation/Foundation.h>
|
|
|
|
#import "AppKit/NSPasteboard.h"
|
|
|
|
#import "AppKit/NSSound.h"
|
2002-07-30 21:19:05 +00:00
|
|
|
|
2011-03-04 11:33:22 +00:00
|
|
|
#import "GNUstepGUI/GSSoundSource.h"
|
|
|
|
#import "GNUstepGUI/GSSoundSink.h"
|
2002-07-30 21:19:05 +00:00
|
|
|
|
2009-08-23 03:37:03 +00:00
|
|
|
// Private NSConditionLock conditions used for streaming
|
|
|
|
enum
|
|
|
|
{
|
|
|
|
SOUND_SHOULD_PLAY = 1,
|
|
|
|
SOUND_SHOULD_PAUSE
|
|
|
|
};
|
2002-07-30 21:19:05 +00:00
|
|
|
|
2009-08-23 03:37:03 +00:00
|
|
|
#define BUFFER_SIZE 4096
|
2002-07-30 21:19:05 +00:00
|
|
|
|
|
|
|
/* Class variables and functions for class methods */
|
|
|
|
static NSMutableDictionary *nameDict = nil;
|
|
|
|
static NSDictionary *nsmapping = nil;
|
2009-08-23 03:37:03 +00:00
|
|
|
static NSArray *sourcePlugIns = nil;
|
|
|
|
static NSArray *sinkPlugIns = nil;
|
|
|
|
|
|
|
|
static inline void _loadNSSoundPlugIns (void)
|
|
|
|
{
|
|
|
|
NSString *path;
|
|
|
|
NSArray *paths;
|
|
|
|
NSBundle *bundle;
|
|
|
|
NSEnumerator *enumerator;
|
|
|
|
NSMutableArray *all,
|
|
|
|
*_sourcePlugIns,
|
|
|
|
*_sinkPlugIns;
|
|
|
|
Class plugInClass;
|
|
|
|
|
|
|
|
/* Gather up the paths */
|
|
|
|
paths = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory,
|
|
|
|
NSAllDomainsMask, YES);
|
|
|
|
|
|
|
|
enumerator = [paths objectEnumerator];
|
|
|
|
all = [NSMutableArray array];
|
|
|
|
while ((path = [enumerator nextObject]) != nil)
|
|
|
|
{
|
|
|
|
bundle = [NSBundle bundleWithPath: path];
|
|
|
|
paths = [bundle pathsForResourcesOfType: @"nssound"
|
|
|
|
inDirectory: @"Bundles"];
|
|
|
|
[all addObjectsFromArray: paths];
|
|
|
|
}
|
|
|
|
|
|
|
|
enumerator = [all objectEnumerator];
|
|
|
|
_sourcePlugIns = [NSMutableArray array];
|
|
|
|
_sinkPlugIns = [NSMutableArray array];
|
|
|
|
while ((path = [enumerator nextObject]) != nil)
|
|
|
|
{
|
|
|
|
NSBundle *nssoundBundle = [NSBundle bundleWithPath: path];
|
|
|
|
plugInClass = [nssoundBundle principalClass];
|
|
|
|
if ([plugInClass conformsToProtocol: @protocol(GSSoundSource)])
|
|
|
|
{
|
|
|
|
[_sourcePlugIns addObject:plugInClass];
|
|
|
|
}
|
|
|
|
else if ([plugInClass conformsToProtocol: @protocol(GSSoundSink)])
|
|
|
|
{
|
|
|
|
[_sinkPlugIns addObject:plugInClass];
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
NSLog (@"Bundle %@ does not conform to GSSoundSource or GSSoundSink",
|
|
|
|
path);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
sourcePlugIns = [[NSArray alloc] initWithArray: _sourcePlugIns];
|
|
|
|
sinkPlugIns = [[NSArray alloc] initWithArray: _sinkPlugIns];
|
|
|
|
}
|
|
|
|
|
2002-07-30 21:19:05 +00:00
|
|
|
|
|
|
|
|
2002-09-25 22:56:08 +00:00
|
|
|
@implementation NSBundle (NSSoundAdditions)
|
|
|
|
|
2002-10-13 10:33:26 +00:00
|
|
|
- (NSString *) pathForSoundResource: (NSString *)name
|
2002-09-25 22:56:08 +00:00
|
|
|
{
|
|
|
|
NSString *ext = [name pathExtension];
|
|
|
|
NSString *path = nil;
|
|
|
|
|
|
|
|
if ((ext == nil) || [ext isEqualToString:@""])
|
|
|
|
{
|
|
|
|
NSArray *types = [NSSound soundUnfilteredFileTypes];
|
|
|
|
unsigned c = [types count];
|
|
|
|
unsigned i;
|
|
|
|
|
|
|
|
for (i = 0; path == nil && i < c; i++)
|
2009-08-23 03:37:03 +00:00
|
|
|
{
|
|
|
|
ext = [types objectAtIndex: i];
|
|
|
|
path = [self pathForResource: name ofType: ext];
|
|
|
|
}
|
2002-09-25 22:56:08 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
name = [name stringByDeletingPathExtension];
|
|
|
|
path = [self pathForResource: name ofType: ext];
|
|
|
|
}
|
|
|
|
return path;
|
|
|
|
}
|
|
|
|
|
|
|
|
@end
|
|
|
|
|
2002-07-30 21:19:05 +00:00
|
|
|
@interface NSSound (PrivateMethods)
|
|
|
|
|
2009-08-23 03:37:03 +00:00
|
|
|
- (void)_stream;
|
|
|
|
- (void)_finished: (NSNumber *)finishedPlaying;
|
2002-07-30 21:19:05 +00:00
|
|
|
|
|
|
|
@end
|
|
|
|
|
|
|
|
@implementation NSSound (PrivateMethods)
|
|
|
|
|
2009-08-23 03:37:03 +00:00
|
|
|
- (void)_stream
|
2002-07-30 21:19:05 +00:00
|
|
|
{
|
2009-08-23 03:37:03 +00:00
|
|
|
NSUInteger bytesRead;
|
|
|
|
BOOL success = NO;
|
|
|
|
void *buffer;
|
|
|
|
|
|
|
|
// Exit with success = NO if device could not be open.
|
|
|
|
if ([_sink open])
|
2002-07-30 21:19:05 +00:00
|
|
|
{
|
2009-08-23 03:37:03 +00:00
|
|
|
// Allocate space for buffer and start writing.
|
|
|
|
buffer = NSZoneMalloc(NSDefaultMallocZone(), BUFFER_SIZE);
|
|
|
|
do
|
|
|
|
{
|
|
|
|
do
|
|
|
|
{
|
|
|
|
// If not SOUND_SHOULD_PLAY block thread
|
|
|
|
[_readLock lockWhenCondition: SOUND_SHOULD_PLAY];
|
|
|
|
if (_shouldStop)
|
|
|
|
{
|
|
|
|
[_readLock unlock];
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
bytesRead = [_source readBytes: buffer
|
|
|
|
length: BUFFER_SIZE];
|
|
|
|
[_readLock unlock];
|
|
|
|
[_playbackLock lock];
|
|
|
|
success = [_sink playBytes: buffer length: bytesRead];
|
|
|
|
[_playbackLock unlock];
|
|
|
|
} while ((!_shouldStop) && (bytesRead > 0) && success);
|
|
|
|
|
|
|
|
[_source setCurrentTime: 0.0];
|
|
|
|
} while (_shouldLoop == YES && _shouldStop == NO);
|
2002-07-30 21:19:05 +00:00
|
|
|
|
2009-08-23 03:37:03 +00:00
|
|
|
[_sink close];
|
|
|
|
NSZoneFree (NSDefaultMallocZone(), buffer);
|
2002-07-30 21:19:05 +00:00
|
|
|
}
|
2009-08-23 03:37:03 +00:00
|
|
|
|
|
|
|
RETAIN(self);
|
|
|
|
[self performSelectorOnMainThread: @selector(_finished:)
|
|
|
|
withObject: [NSNumber numberWithBool: success]
|
|
|
|
waitUntilDone: YES];
|
|
|
|
RELEASE(self);
|
2002-07-30 21:19:05 +00:00
|
|
|
}
|
|
|
|
|
2009-08-23 03:37:03 +00:00
|
|
|
- (void)_finished: (NSNumber *)finishedPlaying
|
2002-07-30 21:19:05 +00:00
|
|
|
{
|
2009-08-23 03:37:03 +00:00
|
|
|
DESTROY(_readLock);
|
|
|
|
DESTROY(_playbackLock);
|
|
|
|
|
|
|
|
/* FIXME: should I call -sound:didFinishPlaying: when -stop was sent? */
|
|
|
|
if ([_delegate respondsToSelector: @selector(sound:didFinishPlaying:)])
|
2002-07-30 21:19:05 +00:00
|
|
|
{
|
2009-08-23 03:37:03 +00:00
|
|
|
[_delegate sound: self didFinishPlaying: [finishedPlaying boolValue]];
|
2002-07-30 21:19:05 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@end
|
|
|
|
|
|
|
|
@implementation NSSound
|
|
|
|
|
2002-10-13 10:33:26 +00:00
|
|
|
+ (void) initialize
|
2002-07-30 21:19:05 +00:00
|
|
|
{
|
2009-08-23 03:37:03 +00:00
|
|
|
if (self == [NSSound class])
|
2002-07-30 21:19:05 +00:00
|
|
|
{
|
2003-04-28 02:33:10 +00:00
|
|
|
NSString *path = [NSBundle pathForLibraryResource: @"nsmapping"
|
2009-08-23 03:37:03 +00:00
|
|
|
ofType: @"strings"
|
|
|
|
inDirectory: @"Sounds"];
|
|
|
|
[self setVersion: 2];
|
2002-07-30 21:19:05 +00:00
|
|
|
|
|
|
|
nameDict = [[NSMutableDictionary alloc] initWithCapacity: 10];
|
|
|
|
|
2009-08-23 03:37:03 +00:00
|
|
|
if (path)
|
|
|
|
{
|
|
|
|
nsmapping = RETAIN([[NSString stringWithContentsOfFile: path]
|
|
|
|
propertyListFromStringsFileFormat]);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* FIXME: Not sure if this is the best way... */
|
|
|
|
_loadNSSoundPlugIns ();
|
2002-07-30 21:19:05 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2002-10-13 10:33:26 +00:00
|
|
|
- (void) dealloc
|
2002-07-30 21:19:05 +00:00
|
|
|
{
|
2009-08-23 03:37:03 +00:00
|
|
|
// Make sure sound is stopped before deallocating.
|
|
|
|
[self stop];
|
|
|
|
|
|
|
|
RELEASE (_data);
|
|
|
|
if (self == [nameDict objectForKey: _name])
|
|
|
|
{
|
2002-11-10 17:11:49 +00:00
|
|
|
[nameDict removeObjectForKey: _name];
|
2002-07-30 21:19:05 +00:00
|
|
|
}
|
2009-08-23 03:37:03 +00:00
|
|
|
RELEASE (_name);
|
|
|
|
RELEASE (_playbackDeviceIdentifier);
|
|
|
|
RELEASE (_channelMapping);
|
|
|
|
RELEASE (_source);
|
|
|
|
RELEASE (_sink);
|
|
|
|
|
2002-07-30 21:19:05 +00:00
|
|
|
[super dealloc];
|
|
|
|
}
|
|
|
|
|
|
|
|
//
|
|
|
|
// Creating an NSSound
|
|
|
|
//
|
2002-10-13 10:33:26 +00:00
|
|
|
- (id) initWithContentsOfFile: (NSString *)path byReference:(BOOL)byRef
|
2002-07-30 21:19:05 +00:00
|
|
|
{
|
2009-08-23 03:37:03 +00:00
|
|
|
NSData *fileData;
|
|
|
|
|
|
|
|
// Problem here: should every NSSound instance have a _name set?
|
|
|
|
// The Apple docs are a bit confusing here. For now, the only way
|
|
|
|
// _name will be set is if -setName: is called, or if the sound already
|
|
|
|
// exists in on of the Sounds/ directories.
|
|
|
|
_onlyReference = byRef;
|
|
|
|
|
|
|
|
|
|
|
|
fileData = [NSData dataWithContentsOfMappedFile: path];
|
|
|
|
if (!fileData)
|
|
|
|
{
|
|
|
|
NSLog (@"Could not get sound data from: %@", path);
|
|
|
|
DESTROY(self);
|
|
|
|
return nil;
|
2002-07-30 21:19:05 +00:00
|
|
|
}
|
2009-08-23 03:37:03 +00:00
|
|
|
|
|
|
|
return [self initWithData: fileData];
|
2002-07-30 21:19:05 +00:00
|
|
|
}
|
|
|
|
|
2002-10-13 10:33:26 +00:00
|
|
|
- (id) initWithContentsOfURL: (NSURL *)url byReference:(BOOL)byRef
|
2002-07-30 21:19:05 +00:00
|
|
|
{
|
2002-11-10 17:11:49 +00:00
|
|
|
_onlyReference = byRef;
|
2002-07-30 21:19:05 +00:00
|
|
|
return [self initWithData: [NSData dataWithContentsOfURL: url]];
|
|
|
|
}
|
|
|
|
|
2009-08-25 07:46:37 +00:00
|
|
|
- (id) initWithData: (NSData *)data
|
2002-07-30 21:19:05 +00:00
|
|
|
{
|
2009-08-23 03:37:03 +00:00
|
|
|
NSEnumerator *enumerator;
|
|
|
|
Class sourceClass,
|
|
|
|
sinkClass;
|
|
|
|
|
|
|
|
_data = data;
|
|
|
|
RETAIN(_data);
|
|
|
|
|
|
|
|
// Search for an GSSoundSource bundle that can play this data.
|
|
|
|
enumerator = [sourcePlugIns objectEnumerator];
|
|
|
|
while ((sourceClass = [enumerator nextObject]) != nil)
|
|
|
|
{
|
|
|
|
if ([sourceClass canInitWithData: _data])
|
|
|
|
{
|
|
|
|
_source = [[sourceClass alloc] initWithData: _data];
|
|
|
|
if (_source == nil)
|
|
|
|
{
|
|
|
|
NSLog (@"Could not read sound data!");
|
|
|
|
DESTROY(self);
|
|
|
|
return nil;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
enumerator = [sinkPlugIns objectEnumerator];
|
|
|
|
/* FIXME: Grab the first available sink/device for now. In the future
|
|
|
|
look for what is set in the GSSoundDeviceBundle default first. */
|
|
|
|
while ((sinkClass = [enumerator nextObject]) != nil)
|
|
|
|
{
|
|
|
|
if ([sinkClass canInitWithPlaybackDevice: nil])
|
|
|
|
{
|
|
|
|
_sink = [[sinkClass alloc] initWithEncoding: [_source encoding]
|
|
|
|
channels: [_source channelCount]
|
|
|
|
sampleRate: [_source sampleRate]
|
|
|
|
byteOrder: [_source byteOrder]];
|
|
|
|
if (_sink == nil)
|
|
|
|
{
|
|
|
|
NSLog (@"Could not open sound sink!");
|
|
|
|
DESTROY(self);
|
|
|
|
return nil;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* FIXME: There has to be a better way to do this check??? */
|
|
|
|
if (sourceClass == nil || sinkClass == nil)
|
|
|
|
{
|
|
|
|
NSLog (@"Could not find suitable sound plug-in");
|
|
|
|
DESTROY(self);
|
|
|
|
return nil;
|
|
|
|
}
|
|
|
|
|
|
|
|
return self;
|
2002-07-30 21:19:05 +00:00
|
|
|
}
|
|
|
|
|
2002-10-13 10:33:26 +00:00
|
|
|
- (id) initWithPasteboard: (NSPasteboard *)pasteboard
|
2002-07-30 21:19:05 +00:00
|
|
|
{
|
2011-07-14 07:17:24 +00:00
|
|
|
if ([object_getClass(self) canInitWithPasteboard: pasteboard] == YES)
|
2009-08-23 03:37:03 +00:00
|
|
|
{
|
|
|
|
/* FIXME: Should this be @"NSGeneralPboardType" or @"NSSoundPboardType"?
|
|
|
|
Apple also defines "NSString *NSSoundPboardType". */
|
2002-07-30 21:19:05 +00:00
|
|
|
NSData *d = [pasteboard dataForType: @"NSGeneralPboardType"];
|
|
|
|
return [self initWithData: d];
|
|
|
|
}
|
|
|
|
return nil;
|
|
|
|
}
|
|
|
|
|
|
|
|
//
|
2009-08-23 03:37:03 +00:00
|
|
|
// Playing and Information
|
2002-07-30 21:19:05 +00:00
|
|
|
//
|
2002-10-13 10:33:26 +00:00
|
|
|
- (BOOL) pause
|
2002-07-30 21:19:05 +00:00
|
|
|
{
|
2009-08-23 03:37:03 +00:00
|
|
|
// Do nothing if sound is already paused.
|
|
|
|
if ([_readLock condition] == SOUND_SHOULD_PAUSE)
|
2002-07-30 21:19:05 +00:00
|
|
|
{
|
2009-08-23 03:37:03 +00:00
|
|
|
return NO;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ([_readLock tryLock] == NO)
|
|
|
|
{
|
|
|
|
return NO;
|
|
|
|
}
|
|
|
|
[_readLock unlockWithCondition: SOUND_SHOULD_PAUSE];
|
|
|
|
return YES;
|
2002-07-30 21:19:05 +00:00
|
|
|
}
|
|
|
|
|
2002-10-13 10:33:26 +00:00
|
|
|
- (BOOL) play
|
2002-07-30 21:19:05 +00:00
|
|
|
{
|
2009-08-23 03:37:03 +00:00
|
|
|
// If the locks exists this instance is already playing
|
|
|
|
if (_readLock != nil && _playbackLock != nil)
|
|
|
|
{
|
|
|
|
return NO;
|
|
|
|
}
|
|
|
|
|
|
|
|
_readLock = [[NSConditionLock alloc] initWithCondition: SOUND_SHOULD_PAUSE];
|
|
|
|
_playbackLock = [[NSLock alloc] init];
|
|
|
|
|
|
|
|
if ([_readLock tryLock] != YES)
|
|
|
|
{
|
|
|
|
return NO;
|
|
|
|
}
|
|
|
|
_shouldStop = NO;
|
|
|
|
[NSThread detachNewThreadSelector: @selector(_stream)
|
|
|
|
toTarget: self
|
|
|
|
withObject: nil];
|
|
|
|
[_readLock unlockWithCondition: SOUND_SHOULD_PLAY];
|
|
|
|
|
|
|
|
return YES;
|
2002-07-30 21:19:05 +00:00
|
|
|
}
|
|
|
|
|
2002-10-13 10:33:26 +00:00
|
|
|
- (BOOL) resume
|
2002-07-30 21:19:05 +00:00
|
|
|
{
|
2009-08-23 03:37:03 +00:00
|
|
|
// Do nothing if sound is already playing.
|
|
|
|
if ([_readLock condition] == SOUND_SHOULD_PLAY)
|
2002-07-30 21:19:05 +00:00
|
|
|
{
|
2009-08-23 03:37:03 +00:00
|
|
|
return NO;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ([_readLock tryLock] == NO)
|
|
|
|
{
|
|
|
|
return NO;
|
|
|
|
}
|
|
|
|
[_readLock unlockWithCondition: SOUND_SHOULD_PLAY];
|
|
|
|
return YES;
|
2002-07-30 21:19:05 +00:00
|
|
|
}
|
|
|
|
|
2002-10-13 10:33:26 +00:00
|
|
|
- (BOOL) stop
|
2002-07-30 21:19:05 +00:00
|
|
|
{
|
2009-08-23 03:37:03 +00:00
|
|
|
if (_readLock == nil)
|
2002-07-30 21:19:05 +00:00
|
|
|
{
|
2009-08-23 03:37:03 +00:00
|
|
|
return NO;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ([_readLock tryLock] != YES)
|
|
|
|
{
|
|
|
|
return NO;
|
|
|
|
}
|
|
|
|
_shouldStop = YES;
|
|
|
|
// Set to SOUND_SHOULD_PLAY so that thread isn't blocked.
|
|
|
|
[_readLock unlockWithCondition: SOUND_SHOULD_PLAY];
|
|
|
|
|
|
|
|
return YES;
|
2002-07-30 21:19:05 +00:00
|
|
|
}
|
|
|
|
|
2002-10-13 10:33:26 +00:00
|
|
|
- (BOOL) isPlaying
|
2002-07-30 21:19:05 +00:00
|
|
|
{
|
2009-08-23 03:37:03 +00:00
|
|
|
if (_readLock == nil)
|
2002-07-30 21:19:05 +00:00
|
|
|
{
|
2009-08-23 03:37:03 +00:00
|
|
|
return NO;
|
|
|
|
}
|
|
|
|
if ([_readLock condition] == SOUND_SHOULD_PLAY)
|
|
|
|
{
|
|
|
|
return YES;
|
|
|
|
}
|
2002-07-30 21:19:05 +00:00
|
|
|
return NO;
|
|
|
|
}
|
|
|
|
|
2009-08-23 03:37:03 +00:00
|
|
|
- (float) volume
|
|
|
|
{
|
|
|
|
return [_sink volume];
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void) setVolume: (float) volume
|
|
|
|
{
|
|
|
|
[_playbackLock lock];
|
|
|
|
[_sink setVolume: volume];
|
|
|
|
[_playbackLock unlock];
|
|
|
|
}
|
|
|
|
|
2009-08-25 07:46:37 +00:00
|
|
|
- (NSTimeInterval) currentTime
|
2009-08-23 03:37:03 +00:00
|
|
|
{
|
|
|
|
return [_source currentTime];
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void) setCurrentTime: (NSTimeInterval) currentTime
|
|
|
|
{
|
|
|
|
[_readLock lock];
|
|
|
|
[_source setCurrentTime: currentTime];
|
|
|
|
[_readLock unlock];
|
|
|
|
}
|
|
|
|
|
2009-08-25 07:46:37 +00:00
|
|
|
- (BOOL) loops
|
2009-08-23 03:37:03 +00:00
|
|
|
{
|
|
|
|
return _shouldLoop;
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void) setLoops: (BOOL) loops
|
|
|
|
{
|
|
|
|
_shouldLoop = loops;
|
|
|
|
}
|
|
|
|
|
|
|
|
- (NSTimeInterval) duration
|
|
|
|
{
|
|
|
|
return [_source duration];
|
|
|
|
}
|
|
|
|
|
2002-07-30 21:19:05 +00:00
|
|
|
//
|
|
|
|
// Working with pasteboards
|
|
|
|
//
|
2002-10-13 10:33:26 +00:00
|
|
|
+ (BOOL) canInitWithPasteboard: (NSPasteboard *)pasteboard
|
2002-07-30 21:19:05 +00:00
|
|
|
{
|
|
|
|
NSArray *pbTypes = [pasteboard types];
|
|
|
|
NSArray *myTypes = [NSSound soundUnfilteredPasteboardTypes];
|
|
|
|
|
|
|
|
return ([pbTypes firstObjectCommonWithArray: myTypes] != nil);
|
|
|
|
}
|
|
|
|
|
2002-10-13 10:33:26 +00:00
|
|
|
+ (NSArray *) soundUnfilteredPasteboardTypes
|
2002-07-30 21:19:05 +00:00
|
|
|
{
|
|
|
|
return [NSArray arrayWithObjects: @"NSGeneralPboardType", nil];
|
|
|
|
}
|
|
|
|
|
2002-10-13 10:33:26 +00:00
|
|
|
- (void) writeToPasteboard: (NSPasteboard *)pasteboard
|
2002-07-30 21:19:05 +00:00
|
|
|
{
|
|
|
|
NSData *d = [NSArchiver archivedDataWithRootObject: self];
|
|
|
|
|
|
|
|
if (d != nil) {
|
|
|
|
[pasteboard declareTypes: [NSSound soundUnfilteredPasteboardTypes]
|
|
|
|
owner: nil];
|
|
|
|
[pasteboard setData: d forType: @"NSGeneralPboardType"];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//
|
|
|
|
// Working with delegates
|
|
|
|
//
|
2002-10-13 10:33:26 +00:00
|
|
|
- (id) delegate
|
2002-07-30 21:19:05 +00:00
|
|
|
{
|
2002-11-10 17:11:49 +00:00
|
|
|
return _delegate;
|
2002-07-30 21:19:05 +00:00
|
|
|
}
|
|
|
|
|
2002-10-13 10:33:26 +00:00
|
|
|
- (void) setDelegate: (id)aDelegate
|
2002-07-30 21:19:05 +00:00
|
|
|
{
|
2002-11-10 17:11:49 +00:00
|
|
|
_delegate = aDelegate;
|
2002-07-30 21:19:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
//
|
|
|
|
// Naming Sounds
|
|
|
|
//
|
2002-10-13 10:33:26 +00:00
|
|
|
+ (id) soundNamed: (NSString*)name
|
2002-07-30 21:19:05 +00:00
|
|
|
{
|
2002-10-13 10:33:26 +00:00
|
|
|
NSString *realName = [nsmapping objectForKey: name];
|
2002-07-30 21:19:05 +00:00
|
|
|
NSSound *sound;
|
|
|
|
|
2009-08-23 03:37:03 +00:00
|
|
|
if (realName)
|
2002-07-30 21:19:05 +00:00
|
|
|
{
|
2002-10-13 10:33:26 +00:00
|
|
|
name = realName;
|
2002-07-30 21:19:05 +00:00
|
|
|
}
|
|
|
|
|
2002-10-13 10:33:26 +00:00
|
|
|
sound = (NSSound *)[nameDict objectForKey: name];
|
2002-07-30 21:19:05 +00:00
|
|
|
|
2009-08-23 03:37:03 +00:00
|
|
|
if (sound == nil)
|
2002-07-30 21:19:05 +00:00
|
|
|
{
|
2002-10-13 10:33:26 +00:00
|
|
|
NSString *extension;
|
|
|
|
NSString *path = nil;
|
|
|
|
NSBundle *main_bundle;
|
2002-07-30 21:19:05 +00:00
|
|
|
NSArray *array;
|
2002-10-13 10:33:26 +00:00
|
|
|
NSString *the_name = name;
|
2002-07-30 21:19:05 +00:00
|
|
|
|
|
|
|
// FIXME: This should use [NSBundle pathForSoundResource], but this will
|
|
|
|
// only allow soundUnfilteredFileTypes.
|
|
|
|
/* If there is no sound with that name, search in the main bundle */
|
|
|
|
|
|
|
|
main_bundle = [NSBundle mainBundle];
|
2002-10-13 10:33:26 +00:00
|
|
|
extension = [name pathExtension];
|
2002-07-30 21:19:05 +00:00
|
|
|
|
2009-08-23 03:37:03 +00:00
|
|
|
if (extension != nil && [extension length] == 0)
|
2002-07-30 21:19:05 +00:00
|
|
|
{
|
|
|
|
extension = nil;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Check if extension is one of the sound types */
|
|
|
|
array = [NSSound soundUnfilteredFileTypes];
|
|
|
|
|
2009-08-23 03:37:03 +00:00
|
|
|
if ([array indexOfObject: extension] != NSNotFound)
|
2002-07-30 21:19:05 +00:00
|
|
|
{
|
|
|
|
/* Extension is one of the sound types
|
|
|
|
So remove from the name */
|
2002-10-13 10:33:26 +00:00
|
|
|
the_name = [name stringByDeletingPathExtension];
|
2002-07-30 21:19:05 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
/* Otherwise extension is not an sound type
|
|
|
|
So leave it alone */
|
2002-10-13 10:33:26 +00:00
|
|
|
the_name = name;
|
2002-07-30 21:19:05 +00:00
|
|
|
extension = nil;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* First search locally */
|
2009-08-23 03:37:03 +00:00
|
|
|
if (extension)
|
2002-07-30 21:19:05 +00:00
|
|
|
{
|
|
|
|
path = [main_bundle pathForResource: the_name ofType: extension];
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
id o, e;
|
|
|
|
|
|
|
|
e = [array objectEnumerator];
|
2009-08-23 03:37:03 +00:00
|
|
|
while ((o = [e nextObject]))
|
2002-07-30 21:19:05 +00:00
|
|
|
{
|
|
|
|
path = [main_bundle pathForResource: the_name ofType: o];
|
2009-08-23 03:37:03 +00:00
|
|
|
if (path != nil && [path length] != 0)
|
2002-07-30 21:19:05 +00:00
|
|
|
{
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* If not found then search in system */
|
2009-08-23 03:37:03 +00:00
|
|
|
if (!path)
|
2002-07-30 21:19:05 +00:00
|
|
|
{
|
2009-08-23 03:37:03 +00:00
|
|
|
if (extension)
|
2002-07-30 21:19:05 +00:00
|
|
|
{
|
2003-04-28 02:33:10 +00:00
|
|
|
path = [NSBundle pathForLibraryResource: the_name
|
|
|
|
ofType: extension
|
|
|
|
inDirectory: @"Sounds"];
|
2002-07-30 21:19:05 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
id o, e;
|
|
|
|
|
|
|
|
e = [array objectEnumerator];
|
|
|
|
while ((o = [e nextObject])) {
|
2003-04-28 02:33:10 +00:00
|
|
|
path = [NSBundle pathForLibraryResource: the_name
|
|
|
|
ofType: o
|
|
|
|
inDirectory: @"Sounds"];
|
2009-08-23 03:37:03 +00:00
|
|
|
if (path != nil && [path length] != 0)
|
2002-07-30 21:19:05 +00:00
|
|
|
{
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2009-08-23 03:37:03 +00:00
|
|
|
if ([path length] != 0)
|
2002-07-30 21:19:05 +00:00
|
|
|
{
|
|
|
|
sound = [[self allocWithZone: NSDefaultMallocZone()]
|
|
|
|
initWithContentsOfFile: path byReference: NO];
|
|
|
|
|
2009-08-23 03:37:03 +00:00
|
|
|
if (sound != nil)
|
2002-07-30 21:19:05 +00:00
|
|
|
{
|
2002-10-13 10:33:26 +00:00
|
|
|
[sound setName: name];
|
2002-07-30 21:19:05 +00:00
|
|
|
RELEASE(sound);
|
2002-11-10 17:11:49 +00:00
|
|
|
sound->_onlyReference = YES;
|
2002-07-30 21:19:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return sound;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return sound;
|
|
|
|
}
|
|
|
|
|
2002-10-13 10:33:26 +00:00
|
|
|
+ (NSArray *) soundUnfilteredFileTypes
|
2002-07-30 21:19:05 +00:00
|
|
|
{
|
2009-08-23 03:37:03 +00:00
|
|
|
Class sourceClass;
|
|
|
|
NSMutableArray *array;
|
|
|
|
NSEnumerator *enumerator;
|
|
|
|
|
|
|
|
array = [NSMutableArray arrayWithCapacity: 10];
|
|
|
|
enumerator = [sourcePlugIns objectEnumerator];
|
|
|
|
while ((sourceClass = [enumerator nextObject]) != nil)
|
|
|
|
{
|
|
|
|
[array addObjectsFromArray: [sourceClass soundUnfilteredFileTypes]];
|
|
|
|
}
|
|
|
|
|
|
|
|
return array;
|
|
|
|
}
|
|
|
|
|
|
|
|
+ (NSArray *) soundUnfilteredTypes
|
|
|
|
{
|
|
|
|
Class sourceClass;
|
|
|
|
NSMutableArray *array;
|
|
|
|
NSEnumerator *enumerator;
|
|
|
|
|
|
|
|
array = [NSMutableArray arrayWithCapacity: 10];
|
|
|
|
enumerator = [sourcePlugIns objectEnumerator];
|
|
|
|
while ((sourceClass = [enumerator nextObject]) != nil)
|
|
|
|
{
|
|
|
|
[array addObjectsFromArray: [sourceClass soundUnfilteredTypes]];
|
|
|
|
}
|
|
|
|
|
|
|
|
return array;
|
2002-07-30 21:19:05 +00:00
|
|
|
}
|
|
|
|
|
2002-10-13 10:33:26 +00:00
|
|
|
- (NSString *) name
|
2002-07-30 21:19:05 +00:00
|
|
|
{
|
2002-11-10 17:11:49 +00:00
|
|
|
return _name;
|
2002-07-30 21:19:05 +00:00
|
|
|
}
|
|
|
|
|
2002-10-13 10:33:26 +00:00
|
|
|
- (BOOL) setName: (NSString *)aName
|
2002-07-30 21:19:05 +00:00
|
|
|
{
|
2009-08-23 03:37:03 +00:00
|
|
|
if (!aName || [nameDict objectForKey: aName])
|
2002-07-30 21:19:05 +00:00
|
|
|
{
|
|
|
|
return NO;
|
|
|
|
}
|
|
|
|
|
2009-08-23 03:37:03 +00:00
|
|
|
if ((_name != nil) && self == [nameDict objectForKey: _name])
|
2002-07-30 21:19:05 +00:00
|
|
|
{
|
2002-11-10 17:11:49 +00:00
|
|
|
[nameDict removeObjectForKey: _name];
|
2002-07-30 21:19:05 +00:00
|
|
|
}
|
|
|
|
|
2002-11-10 17:11:49 +00:00
|
|
|
ASSIGN(_name, aName);
|
2002-07-30 21:19:05 +00:00
|
|
|
|
2002-11-10 17:11:49 +00:00
|
|
|
[nameDict setObject: self forKey: _name];
|
2002-07-30 21:19:05 +00:00
|
|
|
|
|
|
|
return YES;
|
|
|
|
}
|
|
|
|
|
2009-08-23 03:37:03 +00:00
|
|
|
- (NSString *) playbackDeviceIdentifier
|
|
|
|
{
|
|
|
|
return [_sink playbackDeviceIdentifier];
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void) setPlaybackDeviceIdentifier: (NSString *)playbackDeviceIdentifier
|
|
|
|
{
|
|
|
|
if ([[_sink class] canInitWithPlaybackDevice: playbackDeviceIdentifier])
|
|
|
|
{
|
|
|
|
[_playbackLock lock];
|
|
|
|
[_sink setPlaybackDeviceIdentifier: playbackDeviceIdentifier];
|
|
|
|
[_playbackLock unlock];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
- (NSArray *) channelMapping
|
|
|
|
{
|
|
|
|
return [_sink channelMapping];
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void) setChannelMapping: (NSArray *)channelMapping
|
|
|
|
{
|
|
|
|
[_playbackLock lock];
|
|
|
|
[_sink setChannelMapping: channelMapping];
|
|
|
|
[_playbackLock unlock];
|
|
|
|
}
|
|
|
|
|
2002-07-30 21:19:05 +00:00
|
|
|
//
|
|
|
|
// NSCoding
|
|
|
|
//
|
2002-10-13 10:33:26 +00:00
|
|
|
- (void) encodeWithCoder: (NSCoder *)coder
|
2002-07-30 21:19:05 +00:00
|
|
|
{
|
2006-10-15 08:34:47 +00:00
|
|
|
if ([coder allowsKeyedCoding])
|
2002-07-30 21:19:05 +00:00
|
|
|
{
|
2006-06-04 16:31:30 +00:00
|
|
|
// TODO_NIB: Determine keys for NSSound.
|
2002-07-30 21:19:05 +00:00
|
|
|
}
|
2006-06-04 16:31:30 +00:00
|
|
|
else
|
2002-07-30 21:19:05 +00:00
|
|
|
{
|
2006-06-04 16:31:30 +00:00
|
|
|
[coder encodeValueOfObjCType: @encode(BOOL) at: &_onlyReference];
|
|
|
|
[coder encodeObject: _name];
|
|
|
|
|
2009-08-23 03:37:03 +00:00
|
|
|
if (_onlyReference == YES)
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
2006-06-04 16:31:30 +00:00
|
|
|
[coder encodeConditionalObject: _delegate];
|
|
|
|
[coder encodeObject: _data];
|
2009-08-23 03:37:03 +00:00
|
|
|
[coder encodeObject: _playbackDeviceIdentifier];
|
|
|
|
[coder encodeObject: _channelMapping];
|
2002-07-30 21:19:05 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2002-10-13 10:33:26 +00:00
|
|
|
- (id) initWithCoder: (NSCoder*)decoder
|
2002-07-30 21:19:05 +00:00
|
|
|
{
|
2006-10-15 08:34:47 +00:00
|
|
|
if ([decoder allowsKeyedCoding])
|
2002-07-30 21:19:05 +00:00
|
|
|
{
|
2006-06-04 16:31:30 +00:00
|
|
|
// TODO_NIB: Determine keys for NSSound.
|
|
|
|
}
|
|
|
|
else
|
2002-11-10 17:11:49 +00:00
|
|
|
{
|
2006-06-04 16:31:30 +00:00
|
|
|
[decoder decodeValueOfObjCType: @encode(BOOL) at: &_onlyReference];
|
2002-11-10 17:11:49 +00:00
|
|
|
|
2009-08-23 03:37:03 +00:00
|
|
|
if (_onlyReference == YES)
|
|
|
|
{
|
|
|
|
NSString *theName = [decoder decodeObject];
|
|
|
|
RELEASE (self);
|
|
|
|
self = RETAIN ([NSSound soundNamed: theName]);
|
|
|
|
[self setName: theName];
|
|
|
|
}
|
2006-06-04 16:31:30 +00:00
|
|
|
else
|
2009-08-23 03:37:03 +00:00
|
|
|
{
|
|
|
|
_name = RETAIN ([decoder decodeObject]);
|
|
|
|
[self setDelegate: [decoder decodeObject]];
|
|
|
|
_data = RETAIN([decoder decodeObject]);
|
|
|
|
_playbackDeviceIdentifier = RETAIN([decoder decodeObject]);
|
|
|
|
_channelMapping = RETAIN([decoder decodeObject]);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* FIXME: Need to prepare the object for playback before going further. */
|
2002-11-10 17:11:49 +00:00
|
|
|
}
|
2002-07-30 21:19:05 +00:00
|
|
|
return self;
|
|
|
|
}
|
|
|
|
|
|
|
|
//
|
|
|
|
// NSCopying
|
|
|
|
//
|
2002-10-13 10:33:26 +00:00
|
|
|
- (id) copyWithZone: (NSZone *)zone
|
2002-07-30 21:19:05 +00:00
|
|
|
{
|
|
|
|
NSSound *newSound = (NSSound *)NSCopyObject(self, 0, zone);
|
2009-08-23 03:37:03 +00:00
|
|
|
|
|
|
|
/* FIXME: Is all this correct? And is this all that needs to be copied? */
|
|
|
|
newSound->_name = [_name copyWithZone: zone];
|
|
|
|
newSound->_data = [_data copyWithZone: zone];
|
|
|
|
newSound->_playbackDeviceIdentifier = [_playbackDeviceIdentifier
|
|
|
|
copyWithZone: zone];
|
|
|
|
newSound->_channelMapping = [_channelMapping copyWithZone: zone];
|
2002-07-30 21:19:05 +00:00
|
|
|
|
2009-08-23 03:37:03 +00:00
|
|
|
/* FIXME: Need to prepare the object for playback before going further. */
|
2002-07-30 21:19:05 +00:00
|
|
|
return newSound;
|
|
|
|
}
|
|
|
|
|
|
|
|
@end
|