Index: Source/NSSound.m =================================================================== --- Source/NSSound.m (revision 28389) +++ Source/NSSound.m (working copy) @@ -2,10 +2,11 @@ Load, manipulate and play sounds - Copyright (C) 2002 Free Software Foundation, Inc. + Copyright (C) 2002, 2009 Free Software Foundation, Inc. - Author: Enrico Sersale - Date: Jul 2002 + Author: Enrico Sersale , + Stefan Bidigaray + Date: Jul 2002, Jul 2009 This file is part of the GNUstep GUI Library. @@ -26,26 +27,81 @@ Boston, MA 02110-1301, USA. */ -#include "config.h" #include -#include #include "AppKit/NSPasteboard.h" #include "AppKit/NSSound.h" -#ifdef HAVE_AUDIOFILE_H -#include -#endif +#include "GNUstepGUI/GSSoundSource.h" +#include "GNUstepGUI/GSSoundSink.h" -#define BUFFER_SIZE_IN_FRAMES 4096 +// Private NSConditionLock conditions used for streaming +enum +{ + SOUND_SHOULD_PLAY = 1, + SOUND_SHOULD_PAUSE +}; -#define DEFAULT_CHANNELS 2 +#define BUFFER_SIZE 4096 /* Class variables and functions for class methods */ static NSMutableDictionary *nameDict = nil; static NSDictionary *nsmapping = nil; +static NSArray *sourcePlugIns = nil; +static NSArray *sinkPlugIns = nil; -#define GSNDNAME @"GNUstepGSSoundServer" +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 arrayWithCapacity: 10]; + while ((path = [enumerator nextObject]) != nil) + { + bundle = [NSBundle bundleWithPath: path]; + paths = [bundle pathsForResourcesOfType: @"nssound" + inDirectory: @"Bundles"]; + [all addObjectsFromArray: paths]; + } + + enumerator = [all objectEnumerator]; + _sourcePlugIns = [NSMutableArray arrayWithCapacity: 5]; + _sinkPlugIns = [NSMutableArray arrayWithCapacity: 5]; + 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]; +} + + + @implementation NSBundle (NSSoundAdditions) - (NSString *) pathForSoundResource: (NSString *)name @@ -60,10 +116,10 @@ unsigned i; for (i = 0; path == nil && i < c; i++) - { - ext = [types objectAtIndex: i]; - path = [self pathForResource: name ofType: ext]; - } + { + ext = [types objectAtIndex: i]; + path = [self pathForResource: name ofType: ext]; + } } else { @@ -75,300 +131,124 @@ @end address@hidden GSSoundSvr - -- (BOOL) playSound: (id)aSound; -- (BOOL) stopSoundWithIdentifier: (NSString *)identifier; -- (BOOL) pauseSoundWithIdentifier: (NSString *)identifier; -- (BOOL) resumeSoundWithIdentifier: (NSString *)identifier; -- (BOOL) isPlayingSoundWithIdentifier: (NSString *)identifier; - address@hidden - @interface NSSound (PrivateMethods) -+ (id) gsnd; -+ (void) localServer: (id)s; -+ (id) lostServer: (NSNotification*)notification; +- (void)_stream; +- (void)_finished: (NSNumber *)finishedPlaying; -- (BOOL) getDataFromFileAtPath: (NSString *)path; -- (void) setIdentifier: (NSString *)identifier; -- (NSString *) identifier; -- (float) samplingRate; -- (float) frameSize; -- (long) frameCount; -- (NSData *) data; - @end @implementation NSSound (PrivateMethods) -#ifdef HAVE_AUDIOFILE_H -static id the_server = nil; - -+ (id) gsnd +- (void)_stream { - if (the_server == nil) + NSUInteger bytesRead; + BOOL success = NO; + void *buffer; + + id source = (id)_private[0]; + id sink = (id)_private[1]; + NSConditionLock *readLock = (NSConditionLock*)_private[2]; + NSLock *playbackLock = (NSLock *)_private[3]; + + // Exit with success = NO if device could not be open. + if ([sink open]) { - NSString *host; - NSString *description; + // 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 writeBytes: buffer length: bytesRead]; + [playbackLock unlock]; + } while ((!_shouldStop) && (bytesRead > 0) && success); + + [source setCurrentTime: 0.0]; + } while (_shouldLoop == YES && _shouldStop == NO); - host = [[NSUserDefaults standardUserDefaults] stringForKey: @"NSHost"]; - if (host == nil) - { - host = @""; - } - else - { - NSHost *h = [NSHost hostWithName: host]; - if (h == nil) - { - NSLog(@"Unknown NSHost (%@) ignored", host); - host = @""; - } - else if ([h isEqual: [NSHost currentHost]] == YES) - { - host = @""; - } - else - { - host = [h name]; - } - } - - if ([host length] == 0) - { - description = @"local host"; - } - else - { - description = host; - } - - the_server = (id)[NSConnection - rootProxyForConnectionWithRegisteredName: GSNDNAME host: host]; - - if (the_server == nil && [host length] > 0) - { - NSString *service = [GSNDNAME stringByAppendingFormat: @"-%@", host]; - - the_server = (id)[NSConnection - rootProxyForConnectionWithRegisteredName: service host: @"*"]; - } - - if (RETAIN ((id)the_server) != nil) - { - NSConnection* conn = [(id)the_server connectionForProxy]; - - [[NSNotificationCenter defaultCenter] - addObserver: self - selector: @selector(lostServer:) - name: NSConnectionDidDieNotification - object: conn]; - } - else - { - static BOOL recursion = NO; - static NSString *cmd = nil; - static NSArray *args = nil; - - if (cmd == nil && recursion == NO) - { - cmd = [NSTask launchPathForTool: @"gnustep_sndd"]; - } - - if (recursion == YES || cmd == nil) - { - NSLog(@"Unable to contact sound server - " - @"please ensure that gnustep_sndd is running for address@hidden", - description); - return nil; - } - else - { - NSLog(@"\nI couldn't contact the sound server for %@ -\n" - @"so I'm attempting to to start one - which will take a few seconds.\n" - @"Trying to launch gnustep_sndd from %@ or a machine/operating-system subdirectory.\n" - @"It is recommended that you start the sound server (gnustep_sndd) when\n" - @"your windowing system is started up.\n", description, - [cmd stringByDeletingLastPathComponent]); - - if ([host length] > 0) - { - args = [[NSArray alloc] initWithObjects: @"-NSHost", host, nil]; - } - - [NSTask launchedTaskWithLaunchPath: cmd arguments: args]; - - [NSTimer scheduledTimerWithTimeInterval: 5.0 - invocation: nil repeats: NO]; - - [[NSRunLoop currentRunLoop] runUntilDate: - [NSDate dateWithTimeIntervalSinceNow: 5.0]]; - - recursion = YES; - [self gsnd]; - recursion = NO; - } - } + NSZoneFree (NSDefaultMallocZone(), buffer); } - - return the_server; + + RETAIN(self); + [self performSelectorOnMainThread: @selector(_finished:) + withObject: [NSNumber numberWithBool: success] + waitUntilDone: YES]; + + [sink close]; } -+ (void) localServer: (id)s +- (void)_finished: (NSNumber *)finishedPlaying { - the_server = s; -} - -+ (id) lostServer: (NSNotification*)notification -{ - id obj = the_server; - - the_server = nil; - [[NSNotificationCenter defaultCenter] - removeObserver: self - name: NSConnectionDidDieNotification - object: [notification object]]; - RELEASE (obj); - return self; -} - -- (BOOL) getDataFromFileAtPath: (NSString *)path -{ - NSMutableData *d; - AFfilehandle file; - AFframecount framesRead; - void *buffer; - -#define CHECK_AF_ERR(x) \ -if ((x) == -1) { \ -afCloseFile(file); \ -return NO; \ -} - - if ((file = afOpenFile([path fileSystemRepresentation], "r", NULL)) - == AF_NULL_FILEHANDLE) + NSConditionLock *readLock = (NSConditionLock *)_private[2]; + NSLock *playbackLock = (NSLock *)_private[3]; + DESTROY(readLock); + DESTROY(playbackLock); + _private[2] = (void *)readLock; + _private[3] = (void *)playbackLock; + + /* FIXME: should I call -sound:didFinishPlaying: when -stop was sent? */ + if (_delegate) { - return NO; + [_delegate sound: self didFinishPlaying: [finishedPlaying boolValue]]; } - _dataFormat = AF_SAMPFMT_TWOSCOMP; - CHECK_AF_ERR (afSetVirtualSampleFormat(file, AF_DEFAULT_TRACK, _dataFormat, 16)); - _channelCount = DEFAULT_CHANNELS; - CHECK_AF_ERR (afSetVirtualChannels(file, AF_DEFAULT_TRACK, _channelCount)); - CHECK_AF_ERR (_samplingRate = afGetRate(file, AF_DEFAULT_TRACK)); - CHECK_AF_ERR (_frameCount = afGetFrameCount(file, AF_DEFAULT_TRACK)); - CHECK_AF_ERR (_frameSize = afGetVirtualFrameSize(file, AF_DEFAULT_TRACK, 1)); - CHECK_AF_ERR (_dataLocation = afGetDataOffset(file, AF_DEFAULT_TRACK)); - - buffer = NSZoneMalloc(NSDefaultMallocZone(), BUFFER_SIZE_IN_FRAMES * _frameSize); - d = [[NSMutableData alloc] initWithCapacity: 1]; - - CHECK_AF_ERR (framesRead = afReadFrames(file, AF_DEFAULT_TRACK, buffer, - BUFFER_SIZE_IN_FRAMES)); - while (framesRead > 0) - { - [d appendBytes: (const void *)buffer - length: framesRead * _frameSize]; - CHECK_AF_ERR (framesRead = afReadFrames(file, AF_DEFAULT_TRACK, buffer, - BUFFER_SIZE_IN_FRAMES)); - } - - _data = d; - _dataSize = [_data length]; - NSZoneFree(NSDefaultMallocZone(), buffer); - afCloseFile(file); - - return YES; + RELEASE(self); } -#else -/* No sound software */ - -+ (id) gsnd -{ - return nil; -} - -+ (void) localServer: (id)s -{ -} - -+ (id) lostServer: (NSNotification*)notification -{ - return self; -} - -- (BOOL) getDataFromFileAtPath: (NSString *)path -{ - NSLog(@"NSSound: No sound software installed, cannot get sound"); - return NO; -} -#endif - -- (void) setIdentifier: (NSString *)identifier -{ - ASSIGN (_uniqueIdentifier, identifier); -} - -- (NSString *) identifier -{ - return _uniqueIdentifier; -} - -- (float) samplingRate -{ - return _samplingRate; -} - -- (float) frameSize -{ - return _frameSize; -} - -- (long) frameCount -{ - return _frameCount; -} - -- (NSData *) data -{ - return _data; -} - @end @implementation NSSound + (void) initialize { - if (self == [NSSound class]) + if (self == [NSSound class]) { NSString *path = [NSBundle pathForLibraryResource: @"nsmapping" - ofType: @"strings" - inDirectory: @"Sounds"]; - [self setVersion: 1]; + ofType: @"strings" + inDirectory: @"Sounds"]; + [self setVersion: 2]; nameDict = [[NSMutableDictionary alloc] initWithCapacity: 10]; - if (path) - { - nsmapping = RETAIN([[NSString stringWithContentsOfFile: path] - propertyListFromStringsFileFormat]); - } + if (path) + { + nsmapping = RETAIN([[NSString stringWithContentsOfFile: path] + propertyListFromStringsFileFormat]); + } + + /* FIXME: Not sure if this is the best way... */ + _loadNSSoundPlugIns (); } } - (void) dealloc { - TEST_RELEASE (_data); - if ((_name != nil) && self == [nameDict objectForKey: _name]) - { + // Make sure sound is stopped before deallocating. + [self stop]; + + RELEASE (_data); + if (self == [nameDict objectForKey: _name]) + { [nameDict removeObjectForKey: _name]; } - TEST_RELEASE (_name); - TEST_RELEASE (_uniqueIdentifier); + RELEASE (_name); + RELEASE (_playbackDeviceIdentifier); + RELEASE (_channelMapping); + RELEASE ((id)_private[0]); + RELEASE ((id)_private[1]); + [super dealloc]; } @@ -377,21 +257,24 @@ // - (id) initWithContentsOfFile: (NSString *)path byReference:(BOOL)byRef { - self = [super init]; - - if (self) - { - _onlyReference = byRef; - ASSIGN (_name, [path lastPathComponent]); - _uniqueIdentifier = nil; - if ([self getDataFromFileAtPath: path] == NO) - { - NSLog(@"Could not get sound data from %@", path); - DESTROY (self); - } + 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 Sound/ directories. + _onlyReference = byRef; + + + fileData = [NSData dataWithContentsOfMappedFile: path]; + if (!fileData) + { + NSLog (@"Could not get sound data from: %@", path); + DESTROY(self); + return nil; } - - return self; + + return [self initWithData: fileData]; } - (id) initWithContentsOfURL: (NSURL *)url byReference:(BOOL)byRef @@ -400,16 +283,76 @@ return [self initWithData: [NSData dataWithContentsOfURL: url]]; } -- (id) initWithData: (NSData *)data +- (id) initWithData: (NSData *)data; { - [self notImplemented: _cmd]; - return nil; + id source; + id sink; + 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; + } + _private[0] = (void *)source; + + 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; + } + _private[1] = (void *)sink; + + 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; } - (id) initWithPasteboard: (NSPasteboard *)pasteboard { - if ([NSSound canInitWithPasteboard: pasteboard] == YES) + if ([NSSound canInitWithPasteboard: pasteboard] == YES) { + /* FIXME: Should this be @"NSGeneralPboardType" or @"NSSoundPboardType"? + Apple also defines "NSString *NSSoundPboardType". */ NSData *d = [pasteboard dataForType: @"NSGeneralPboardType"]; return [self initWithData: d]; } @@ -417,49 +360,153 @@ } // -// Playing +// Playing and Information // - (BOOL) pause { - if (_uniqueIdentifier) + NSConditionLock *readLock = (NSConditionLock*)_private[2]; + + // Do nothing if sound is already paused. + if ([readLock condition] == SOUND_SHOULD_PAUSE) { - return [[NSSound gsnd] pauseSoundWithIdentifier: _uniqueIdentifier]; - } - return NO; + return NO; + } + + if ([readLock tryLock] == NO) + { + return NO; + } + [readLock unlockWithCondition: SOUND_SHOULD_PAUSE]; + return YES; } - (BOOL) play { - return [[NSSound gsnd] playSound: self]; + NSConditionLock *readLock = (NSConditionLock *)_private[2]; + NSLock *playbackLock = (NSLock *)_private[3]; + + // 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]; + _private[2] = (void *)readLock; + _private[3] = (void *)playbackLock; + if ([readLock tryLock] != YES) + { + return NO; + } + _shouldStop = NO; + [NSThread detachNewThreadSelector: @selector(_stream) + toTarget: self + withObject: nil]; + [readLock unlockWithCondition: SOUND_SHOULD_PLAY]; + + return YES; } - (BOOL) resume { - if (_uniqueIdentifier) + NSConditionLock *readLock = (NSConditionLock*)_private[2]; + + // Do nothing if sound is already playing. + if ([readLock condition] == SOUND_SHOULD_PLAY) { - return [[NSSound gsnd] resumeSoundWithIdentifier: _uniqueIdentifier]; - } - return NO; + return NO; + } + + if ([readLock tryLock] == NO) + { + return NO; + } + [readLock unlockWithCondition: SOUND_SHOULD_PLAY]; + return YES; } - (BOOL) stop { - if (_uniqueIdentifier) + NSConditionLock *readLock = (NSConditionLock*)_private[2]; + if (readLock == nil) { - return [[NSSound gsnd] stopSoundWithIdentifier: _uniqueIdentifier]; - } - return NO; + 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; } - (BOOL) isPlaying { - if (_uniqueIdentifier) + NSConditionLock *readLock = (NSConditionLock*)_private[2]; + if (readLock == nil) { - return [[NSSound gsnd] isPlayingSoundWithIdentifier: _uniqueIdentifier]; - } + return NO; + } + if ([readLock condition] == SOUND_SHOULD_PLAY) + { + return YES; + } return NO; } +- (float) volume +{ + id sink = (id)_private[1]; + return [sink volume]; +} + +- (void) setVolume: (float) volume +{ + id sink = (id)_private[1]; + NSLock *playbackLock = (NSLock *)_private[3]; + + [playbackLock lock]; + [sink setVolume: volume]; + [playbackLock unlock]; +} + +- (NSTimeInterval) currentTime; +{ + id source = (id)_private[0]; + return [source currentTime]; +} + +- (void) setCurrentTime: (NSTimeInterval) currentTime +{ + id source = (id)_private[0]; + NSConditionLock *readLock = (NSConditionLock *)_private[2]; + + [readLock lock]; + [source setCurrentTime: currentTime]; + [readLock unlock]; +} + +- (BOOL) loops; +{ + return _shouldLoop; +} + +- (void) setLoops: (BOOL) loops +{ + _shouldLoop = loops; +} + +- (NSTimeInterval) duration +{ + id source = (id)_private[0]; + return [source duration]; +} + // // Working with pasteboards // @@ -508,14 +555,14 @@ NSString *realName = [nsmapping objectForKey: name]; NSSound *sound; - if (realName) + if (realName) { name = realName; } sound = (NSSound *)[nameDict objectForKey: name]; - if (sound == nil) + if (sound == nil) { NSString *extension; NSString *path = nil; @@ -530,7 +577,7 @@ main_bundle = [NSBundle mainBundle]; extension = [name pathExtension]; - if (extension != nil && [extension length] == 0) + if (extension != nil && [extension length] == 0) { extension = nil; } @@ -538,7 +585,7 @@ /* Check if extension is one of the sound types */ array = [NSSound soundUnfilteredFileTypes]; - if ([array indexOfObject: extension] != NSNotFound) + if ([array indexOfObject: extension] != NSNotFound) { /* Extension is one of the sound types So remove from the name */ @@ -553,7 +600,7 @@ } /* First search locally */ - if (extension) + if (extension) { path = [main_bundle pathForResource: the_name ofType: extension]; } @@ -562,10 +609,10 @@ id o, e; e = [array objectEnumerator]; - while ((o = [e nextObject])) + while ((o = [e nextObject])) { path = [main_bundle pathForResource: the_name ofType: o]; - if (path != nil && [path length] != 0) + if (path != nil && [path length] != 0) { break; } @@ -573,9 +620,9 @@ } /* If not found then search in system */ - if (!path) + if (!path) { - if (extension) + if (extension) { path = [NSBundle pathForLibraryResource: the_name ofType: extension @@ -590,7 +637,7 @@ path = [NSBundle pathForLibraryResource: the_name ofType: o inDirectory: @"Sounds"]; - if (path != nil && [path length] != 0) + if (path != nil && [path length] != 0) { break; } @@ -598,12 +645,12 @@ } } - if ([path length] != 0) + if ([path length] != 0) { sound = [[self allocWithZone: NSDefaultMallocZone()] initWithContentsOfFile: path byReference: NO]; - if (sound != nil) + if (sound != nil) { [sound setName: name]; RELEASE(sound); @@ -619,9 +666,36 @@ + (NSArray *) soundUnfilteredFileTypes { - return [NSArray arrayWithObjects: @"aiff", @"waw", @"snd", @"au", nil]; + 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; +} + - (NSString *) name { return _name; @@ -631,15 +705,14 @@ { BOOL retained = NO; - if (!aName || [nameDict objectForKey: aName]) + if (!aName || [nameDict objectForKey: aName]) { return NO; } - if ((_name != nil) && self == [nameDict objectForKey: _name]) + if ((_name != nil) && self == [nameDict objectForKey: _name]) { - /* We retain self in case removing from the dictionary releases - us */ + /* We retain self in case removing from the dictionary releases us */ RETAIN (self); retained = YES; [nameDict removeObjectForKey: _name]; @@ -648,7 +721,7 @@ ASSIGN(_name, aName); [nameDict setObject: self forKey: _name]; - if (retained) + if (retained) { RELEASE (self); } @@ -656,6 +729,55 @@ return YES; } +- (NSString *) playbackDeviceIdentifier +{ + id sink = (id)_private[1]; + NSLock *playbackLock = (NSLock *)_private[3]; + NSString *playbackDevId; + + [playbackLock lock]; + playbackDevId = [sink playbackDeviceIdentifier]; + [playbackLock unlock]; + + return playbackDevId; +} + +- (void) setPlaybackDeviceIdentifier: (NSString *)playbackDeviceIdentifier +{ + id sink = (id)_private[1]; + NSLock *playbackLock = (NSLock *)_private[3]; + + if ([[sink class] canInitWithPlaybackDevice: playbackDeviceIdentifier]) + { + [playbackLock lock]; + [sink setPlaybackDeviceIdentifier: playbackDeviceIdentifier]; + [playbackLock unlock]; + } +} + +- (NSArray *) channelMapping +{ + id sink = (id)_private[1]; + NSLock *playbackLock = (NSLock *)_private[3]; + NSArray *mapping; + + [playbackLock lock]; + mapping = [sink channelMapping]; + [playbackLock unlock]; + + return mapping; +} + +- (void) setChannelMapping: (NSArray *)channelMapping +{ + id sink = (id)_private[1]; + NSLock *playbackLock = (NSLock *)_private[3]; + + [playbackLock lock]; + [sink setChannelMapping: channelMapping]; + [playbackLock unlock]; +} + // // NSCoding // @@ -670,26 +792,14 @@ [coder encodeValueOfObjCType: @encode(BOOL) at: &_onlyReference]; [coder encodeObject: _name]; - if (_onlyReference == YES) - { - return; - } - - if (_uniqueIdentifier != nil) - { - [coder encodeObject: _uniqueIdentifier]; - } - + if (_onlyReference == YES) + { + return; + } [coder encodeConditionalObject: _delegate]; - [coder encodeValueOfObjCType: @encode(long) at: &_dataLocation]; - [coder encodeValueOfObjCType: @encode(long) at: &_dataSize]; - [coder encodeValueOfObjCType: @encode(int) at: &_dataFormat]; - [coder encodeValueOfObjCType: @encode(float) at: &_samplingRate]; - [coder encodeValueOfObjCType: @encode(float) at: &_frameSize]; - [coder encodeValueOfObjCType: @encode(long) at: &_frameCount]; - [coder encodeValueOfObjCType: @encode(int) at: &_channelCount]; - [coder encodeObject: _data]; + [coder encodeObject: _playbackDeviceIdentifier]; + [coder encodeObject: _channelMapping]; } } @@ -703,30 +813,23 @@ { [decoder decodeValueOfObjCType: @encode(BOOL) at: &_onlyReference]; - if (_onlyReference == YES) - { - NSString *theName = [decoder decodeObject]; - - RELEASE (self); - self = RETAIN ([NSSound soundNamed: theName]); - [self setName: theName]; - } + if (_onlyReference == YES) + { + NSString *theName = [decoder decodeObject]; + RELEASE (self); + self = RETAIN ([NSSound soundNamed: theName]); + [self setName: theName]; + } else - { - _name = TEST_RETAIN ([decoder decodeObject]); - _uniqueIdentifier = TEST_RETAIN ([decoder decodeObject]); - [self setDelegate: [decoder decodeObject]]; - - [decoder decodeValueOfObjCType: @encode(long) at: &_dataLocation]; - [decoder decodeValueOfObjCType: @encode(long) at: &_dataSize]; - [decoder decodeValueOfObjCType: @encode(int) at: &_dataFormat]; - [decoder decodeValueOfObjCType: @encode(float) at: &_samplingRate]; - [decoder decodeValueOfObjCType: @encode(float) at: &_frameSize]; - [decoder decodeValueOfObjCType: @encode(long) at: &_frameCount]; - [decoder decodeValueOfObjCType: @encode(int) at: &_channelCount]; - - _data = RETAIN([decoder decodeObject]); - } + { + _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. */ } return self; } @@ -742,11 +845,15 @@ - (id) copyWithZone: (NSZone *)zone { NSSound *newSound = (NSSound *)NSCopyObject(self, 0, zone); + + /* 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]; - newSound->_data = [_data copyWithZone: zone]; - newSound->_name = [_name copyWithZone: zone]; - newSound->_uniqueIdentifier = [_uniqueIdentifier copyWithZone: zone]; - + /* FIXME: Need to prepare the object for playback before going further. */ return newSound; } Index: Source/GNUmakefile =================================================================== --- Source/GNUmakefile (revision 28389) +++ Source/GNUmakefile (working copy) @@ -432,7 +432,9 @@ GSEPSPrintOperation.h \ GSPDFPrintOperation.h \ GSModelLoaderFactory.h \ -GSInstantiator.h +GSInstantiator.h \ +GSSoundSink.h \ +GSSoundSource.h libgnustep-gui_HEADER_FILES = ${GUI_HEADERS} Index: configure.ac =================================================================== --- configure.ac (revision 28389) +++ configure.ac (working copy) @@ -354,32 +354,9 @@ #-------------------------------------------------------------------- # NSSound #-------------------------------------------------------------------- -AC_ARG_ENABLE(gsnd, - [ --disable-gsnd Disable gsnd server],, - enable_gsnd=yes) -BUILD_GSND= -# This is only for gsnd, so don't add it to LIBS -save_LIBS="$LIBS" -AC_CHECK_LIB(portaudio, Pa_OpenDefaultStream, have_portaudio=yes, have_portaudio=no) -LIBS="$save_LIBS" -AC_CHECK_LIB(audiofile, afGetVirtualFrameSize) -AC_CHECK_HEADERS(portaudio.h audiofile.h) -AC_MSG_CHECKING(for portaudio version >= 19) -have_portaudio19=no -if test $ac_cv_header_portaudio_h = yes; then - AC_EGREP_HEADER(PaStreamCallback, portaudio.h, - have_portaudio19=yes, have_portaudio19=no) -fi -AC_MSG_RESULT($have_portaudio19) - -if test $have_portaudio19 = yes -a $have_portaudio = yes -a $enable_gsnd = yes; then - BUILD_GSND=gsnd -fi -AC_SUBST(BUILD_GSND) - #-------------------------------------------------------------------- # Find CUPS #-------------------------------------------------------------------- Index: Headers/AppKit/NSSound.h =================================================================== --- Headers/AppKit/NSSound.h (revision 28389) +++ Headers/AppKit/NSSound.h (working copy) @@ -3,10 +3,11 @@ Load, manipulate and play sounds - Copyright (C) 2002 Free Software Foundation, Inc. + Copyright (C) 2002, 2009 Free Software Foundation, Inc. - Written by: Enrico Sersale - Date: Jul 2002 + Written by: Enrico Sersale , + Stefan Bidigaray + Date: Jul 2002, Jun 2009 This file is part of the GNUstep GUI Library. @@ -32,6 +33,7 @@ #include #include +#include @class NSArray; @class NSData; @@ -39,39 +41,96 @@ @class NSPasteboard; @class NSString; @class NSURL; address@hidden NSThread; +/** Function used to retrieve all available playback devices. + *

This function is the only way to retrieve possible playback + * device identifiers understood by + * [NSSound -setPlaybackDeviceIdentifier:].

+ */ +NSArray *PlaybackDeviceIdentifiers (void); + @interface NSSound : NSObject { NSString *_name; - NSString *_uniqueIdentifier; - BOOL _onlyReference; - id _delegate; - - NSData *_data; - float _samplingRate; - float _frameSize; - long _dataLocation; - long _dataSize; - long _frameCount; - int _channelCount; - int _dataFormat; + NSData *_data; + NSString *_playbackDeviceIdentifier; // Currently unused + NSArray *_channelMapping; // Currently unused + BOOL _onlyReference; + id _delegate; + + // Private info: + // 0 - input bundle (id) + // 1 - output bundle (id) + // 2 - read lock (NSConditionLock *) + // 3 - playback lock (NSLock *) + void *_private[4]; + BOOL _shouldStop; + BOOL _shouldLoop; } // // Creating an NSSound // +/** + * Initalizes the receiver object with the contents of file located at path. + * If byRef is set to YES only the name of the NSSound is encoded with + * -encodeWithCoder:; if set to NO the data is encoded. + *

See Also:

+ * + * -initWithContentsOfURL:byReference: + * -initWithData: + * + */ - (id)initWithContentsOfFile:(NSString *)path byReference:(BOOL)byRef; +/** + * Initializes the receiver object with the contents of the data located in + * url. If byRef is set to YES only the name of the NSSound is encoded with + * -encodeWithCoder:; if set to NO the data is encoded. + *

See Also:

+ * + * -initWithContentsOfFile:byReference: + * -initWithData: + * + */ - (id)initWithContentsOfURL:(NSURL *)url byReference:(BOOL)byRef; +/** + * Initializes the receiver object with the contents of data with a + * valid magic number. + *

See Also:

+ * + * -initWithContentsOfFile:byReference: + * -initWithContentsOfURL:byReference: + * + */ - (id)initWithData:(NSData *)data; - (id)initWithPasteboard:(NSPasteboard *)pasteboard; // // Playing // +/** Pauses audio playback. + *

Returns NO if receiver could not be paused or is already paused, + * and YES if receiver was successfully paused.

+ */ - (BOOL)pause; -- (BOOL)play; +/** Start audio playback. Playback is done asynchronously. + *

Returns NO if receiver is already playing or if an error occurred, and + * YES if receiver was started successfully.

+ */ +- (BOOL)play; +/** Resume audio playback after a -pause. + *

Returns NO if receiver is already playing or if an error occurred, and + * YES if receiver was successfully restarted/resumed.

+ */ - (BOOL)resume; +/** Stop audio playback. + *

Return YES if receiver was successfully stopped.

+ *

This method will close the playback device.

+ */ - (BOOL)stop; +/* Returns YES if receiver is playing and NO otherwise. + */ - (BOOL)isPlaying; // @@ -84,17 +143,69 @@ // // Working with delegates // +/** Returns the receiver's delegate + */ - (id)delegate; +/** Sets the receiver's delegate + */ - (void)setDelegate:(id)aDelegate; // -// Naming Sounds +// Sound Information // + (id)soundNamed:(NSString *)name; +/** Provides an array of file types that NSSound can understand. The returning + * array may be directly passed to [NSOpenPanel -runModalForTypes:]. + *

Built with libsndfile:

+ * wav, aiff, aifc, aif, au, snd, mat, mat4, mat5, paf, sf, voc, w64, + * xi, caf, sd2, flac, ogg, oga + *

Built without libsndfile:

+ * wav, aiff, aifc, aif, au, snd + */ + (NSArray *)soundUnfilteredFileTypes; +/** Returns the name of the receiver. Use -setName: to set the name. + */ - (NSString *)name; +/** Sets the receiver's name. Method -name will return aName. + */ - (BOOL)setName:(NSString *)aName; +#if OS_API_VERSION(MAC_OS_X_VERSION_10_5, GS_API_LATEST) ++ (NSArray *)soundUnfilteredTypes; +/** Returns the length, in seconds, of the receiver. + */ +- (NSTimeInterval)duration; +/** Returns the volume of the receiver. + * Volume will be between 0.0 and 1.0. + */ +- (float)volume; +/** Sets the volume of the receiver. + * Volume must be between 0.0 and 1.0. + */ +- (void)setVolume: (float)volume; +/** Returns the current position of the audio playback. + */ +- (NSTimeInterval)currentTime; +/** Sets the current time of the audio playback. + */ +- (void)setCurrentTime: (NSTimeInterval)currentTime; +/** Returns the current loop property of the receiver. + * YES indicates this NSSound will restart once it reaches the end, + * otherwise NO. + */ +- (BOOL)loops; +/** Sets the loop property of the receiver. + * YES indicates this NSSound will restart once it reaches the end, + * otherwise NO. + */ +- (void)setLoops: (BOOL)loops; + +- (NSString *)playbackDeviceIdentifier; +- (void)setPlaybackDeviceIdentifier: (NSString *)playbackDeviceIdentifier; +- (NSArray *)channelMapping; +- (void)setChannelMapping: (NSArray *)channelMapping; +#endif + @end // @@ -102,6 +213,9 @@ // @interface NSObject (NSSoundDelegate) +/** Method called when sound has finished playing. Currently this method + * is not called. + */ - (void)sound:(NSSound *)sound didFinishPlaying:(BOOL)aBool; @end Index: Headers/Additions/GNUstepGUI/GSSoundSource.h =================================================================== --- Headers/Additions/GNUstepGUI/GSSoundSource.h (revision 0) +++ Headers/Additions/GNUstepGUI/GSSoundSource.h (revision 0) @@ -0,0 +1,96 @@ +/* + GSSoundSource.h + + Load and read sound data. + + Copyright (C) 2009 Free Software Foundation, Inc. + + Written by: Stefan Bidigaray + Date: Jun 2009 + + This file is part of the GNUstep GUI Library. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + 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 + License along with this library; see the file COPYING.LIB. + If not, see or write to the + Free Software Foundation, 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#ifndef _GNUstep_H_GSSoundSource +#define _GNUstep_H_GSSoundSource + address@hidden NSArray; + +enum +{ + GSSoundFormatUnknown = 0x0000, + GSSoundFormatPCMS8 = 0x0001, + GSSoundFormatPCM16 = 0x0002, + GSSoundFormatPCM24 = 0x0003, + GSSoundFormatPCM32 = 0x0004, + GSSoundFormatPCMU8 = 0x0005, + GSSoundFormatFloat32 = 0x0006, + GSSoundFormatFloat64 = 0x0007, + + GSSoundFormatULaw = 0x0010, + GSSoundFormatALaw = 0x0011 +}; + address@hidden GSSoundSource + +/** Returns an array of the file types supported by the class. + */ ++ (NSArray *)soundUnfilteredFileTypes; +/** Returns an array of UTIs identifying the file types the class understands. + */ ++ (NSArray *)soundUnfilteredTypes; +/** Returns YES if the class can understand data and NO otherwise. + */ ++ (BOOL)canInitWithData: (NSData *)data; + +/** + * Initilizes the reciever for output. + */ +- (id)initWithData: (NSData *)data; +/** Reads data provided in -initWithData:. Parameter bytes must be big enough + * to hold length bytes. + */ +- (NSUInteger)readBytes: (void *)bytes length: (NSUInteger)length; + +/** Returns the duration, in seconds. Equivalent to [NSSound-duration]. + */ +- (NSTimeInterval)duration; +/** Called by [NSSound-setCurrentTime:]. + */ +- (void)setCurrentTime: (NSTimeInterval)currentTime; +/** Called by [NSSound-currentTime]. + */ +- (NSTimeInterval)currentTime; + +/** Returns encoding of the audio data. + */ +- (int)encoding; +/** Returns the number of channels. + */ +- (NSUInteger)channelCount; +/** Returns the receiver's sample rate (ie 44100, 8000, etc). + */ +- (NSUInteger)sampleRate; +/** Returns the byte order of the audio data. + */ +- (NSByteOrder)byteOrder; + address@hidden + +#endif // _GNUstep_H_GSSoundSource Index: Headers/Additions/GNUstepGUI/GSSoundSink.h =================================================================== --- Headers/Additions/GNUstepGUI/GSSoundSink.h (revision 0) +++ Headers/Additions/GNUstepGUI/GSSoundSink.h (revision 0) @@ -0,0 +1,83 @@ +/* + GSSoundSink.h + + Sink audio data. + + Copyright (C) 2009 Free Software Foundation, Inc. + + Written by: Stefan Bidigaray + Date: Jun 2009 + + This file is part of the GNUstep GUI Library. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + 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 + License along with this library; see the file COPYING.LIB. + If not, see or write to the + Free Software Foundation, 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#ifndef _GNUstep_H_GSSoundSink +#define _GNUstep_H_GSSoundSink + address@hidden GSSoundSink + +/** Returns YES if class has the ability of playing audio data + * through a playback device playbackDevice, NO otherwise. + */ ++ (BOOL)canInitWithPlaybackDevice: (NSString *)playbackDevice; + +/** + * Initializes the receiver for output using the defined parameters. + *

WARNING: This method does not open the device, see -open.

+ */ +- (id)initWithEncoding: (int)encoding + channels: (NSUInteger)channelCount + sampleRate: (NSUInteger)sampleRate + byteOrder: (NSByteOrder)byteOrder; +/** Opens the device for output, called by [NSSound-play]. + */ +- (BOOL)open; +/** Closes the device, called by [NSSound-stop]. + */ +- (void)close; +/** Writes/Plays the data in bytes to the device. Data must be in + * the same format as specified in + * -initWithEncoding:channels:sampleRate:byteOrder:. + */ +- (BOOL)writeBytes: (void *)bytes length: (NSUInteger)length; + +/** Called by [NSSound-setVolume:], and corresponds to it. Parameter volume + * is between the values 0.0 and 1.0. + */ +- (void)setVolume: (float)volume; +/** Called by [NSSound-volume]. + */ +- (float)volume; +/** Called by [NSSound-setPlaybackDeviceIdentifier:]. + */ +- (void)setPlaybackDeviceIdentifier: (NSString *)playbackDeviceIdentifier; +/** Called by [NSSound-playbackDeviceIdentifier]. + */ +- (NSString *)playbackDeviceIdentifier; +/** Called by [NSSound-setChannelMapping:]. + */ +- (void)setChannelMapping: (NSArray *)channelMapping; +/** Called by [NSSound-channelMapping]. + */ +- (NSArray *)channelMapping; + address@hidden + +#endif // _GNUstep_H_GSSoundSink + Index: Headers/Additions/GNUstepGUI/config.h.in =================================================================== --- Headers/Additions/GNUstepGUI/config.h.in (revision 28389) +++ Headers/Additions/GNUstepGUI/config.h.in (working copy) @@ -6,9 +6,6 @@ /* Define to 1 if you have the header file. */ #undef HAVE_ASPELL_H -/* Define to 1 if you have the header file. */ -#undef HAVE_AUDIOFILE_H - /* Define to 1 if you have the header file. */ #undef HAVE_CUPS_CUPS_H @@ -24,9 +21,6 @@ /* Define to 1 if you have the `aspell' library (-laspell). */ #undef HAVE_LIBASPELL -/* Define to 1 if you have the `audiofile' library (-laudiofile). */ -#undef HAVE_LIBAUDIOFILE - /* Define to 1 if you have the `gif' library (-lgif). */ #undef HAVE_LIBGIF @@ -60,9 +54,6 @@ /* Define to 1 if you have the header file. */ #undef HAVE_MNTENT_H -/* Define to 1 if you have the header file. */ -#undef HAVE_PORTAUDIO_H - /* Define to 1 if you have the `rint' function. */ #undef HAVE_RINT Index: Sound/SndfileSource.m =================================================================== --- Sound/SndfileSource.m (revision 0) +++ Sound/SndfileSource.m (revision 0) @@ -0,0 +1,237 @@ +/* + SndfileSource.m + + Load and read sound data using libsndfile. + + Copyright (C) 2009 Free Software Foundation, Inc. + + Written by: Stefan Bidigaray + Date: Jun 2009 + + This file is part of the GNUstep GUI Library. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + 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 + License along with this library; see the file COPYING.LIB. + If not, see or write to the + Free Software Foundation, 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include +#include +#include + address@hidden SndfileSource : NSObject +{ + NSData *_data; + SNDFILE *_snd; + SF_INFO _info; + + NSUInteger _curPos; + NSTimeInterval _dur; + int _encoding; +} + +- (NSData *)data; +- (NSUInteger)currentPosition; +- (void)setCurrentPosition: (NSUInteger)curPos; address@hidden + +/**********************************/ +/* Sndfile virtual I/O functions. */ +/**********************************/ +static inline sf_count_t dataLength (void *user_data) +{ + SndfileSource *snd = (SndfileSource *)user_data; + + return (sf_count_t)[[snd data] length]; +} + +static inline sf_count_t dataSeek (sf_count_t offset, int whence, + void *user_data) +{ + SndfileSource *snd = (SndfileSource *)user_data; + + switch (whence) + { + case SEEK_SET: + break; + case SEEK_END: + offset = (sf_count_t)[[snd data] length] + offset; + break; + case SEEK_CUR: + offset = (sf_count_t)[snd currentPosition] + offset; + break; + default: + return 0; + } + [snd setCurrentPosition: (NSUInteger)offset]; + return (sf_count_t)[snd currentPosition]; +} + +static inline sf_count_t dataRead (void *ptr, sf_count_t count, + void *user_data) +{ + NSUInteger newPos; + NSRange range; + SndfileSource *snd = (SndfileSource *)user_data; + + // Can't read more data that we have available... + if (([snd currentPosition] + (NSUInteger)count) > [[snd data] length]) + { + count = (sf_count_t)([[snd data] length] - [snd currentPosition]); + } + + newPos = [snd currentPosition] + (NSUInteger)count; + range = NSMakeRange ([snd currentPosition], (NSUInteger)count); + [[snd data] getBytes: ptr range: range]; + [snd setCurrentPosition: newPos]; + + return count; +} + +static inline sf_count_t dataWrite (const void *ptr, sf_count_t count, + void *user_data) +{ + /* FIXME: No write support... do we even need it? */ + return 0; +} + +static inline sf_count_t dataTell (void *user_data) +{ + SndfileSource *snd = (SndfileSource *)user_data; + return (sf_count_t)[snd currentPosition]; +} + +// The libsndfile virtual I/O function structure +static SF_VIRTUAL_IO dataIO = { (sf_vio_get_filelen)dataLength, + (sf_vio_seek)dataSeek, + (sf_vio_read)dataRead, + (sf_vio_write)dataWrite, + (sf_vio_tell)dataTell }; +/**********************************/ + address@hidden SndfileSource + ++ (NSArray *)soundUnfilteredFileTypes +{ + return [NSArray arrayWithObjects: @"wav", @"au", @"snd", @"aif", @"aiff", + @"aifc", @"paf", @"sf", @"voc", @"w64", @"mat", @"mat4", @"mat5", + @"pcf", @"xi", @"caf", @"sd2", @"iff", @"flac", @"ogg", @"oga", + nil]; +} ++ (NSArray *)soundUnfilteredTypes +{ + /* FIXME: I'm not sure what the UTI for all the types above are. */ + return [NSArray arrayWithObjects: @"com.microsoft.waveform-audio", + @"public.ulaw-audio", @"public.aiff-audio", @"public.aifc-audio", + @"com.apple.coreaudio-format", @"com.digidesign.sd2-audio", + /* FIXME: are these right? */ + @"org.xiph.flac-audio", @"org.xiph.vorbis-audio", nil]; +} ++ (BOOL)canInitWithData: (NSData *)data +{ + return YES; +} + +- (void)dealloc +{ + TEST_RELEASE (_data); + sf_close (_snd); + + [super dealloc]; +} + +- (id)initWithData: (NSData *)data +{ + [self init]; + + _data = data; + RETAIN(_data); + + _info.format = 0; + _snd = sf_open_virtual (&dataIO, SFM_READ, &_info, self); + if (_snd == NULL) + { + DESTROY(self); + return nil; + } + + // Setup immutable values... + /* FIXME: support multiple types */ + _encoding = GSSoundFormatPCM16; + _dur = (double)_info.frames / (double)_info.samplerate; + + return self; +} + +- (NSUInteger)readBytes: (void *)bytes length: (NSUInteger)length +{ + return (NSUInteger) (sf_read_short (_snd, bytes, (length>>1))<<1); +} + +- (NSTimeInterval)duration +{ + return _dur; +} + +- (void)setCurrentTime: (NSTimeInterval)currentTime +{ + sf_count_t frames = (sf_count_t)((double)_info.samplerate * currentTime); + sf_seek (_snd, frames, SEEK_SET); +} +- (NSTimeInterval)currentTime +{ + sf_count_t frames; + frames = sf_seek (_snd, 0, SEEK_CUR); + return (NSTimeInterval)((double)frames / (double)_info.samplerate); +} + +- (int)encoding +{ + return _encoding; +} + +- (NSUInteger)channelCount +{ + return (NSUInteger)_info.channels; +} + +- (NSUInteger)sampleRate; +{ + return (NSUInteger)_info.samplerate; +} + +- (NSByteOrder)byteOrder +{ + // Equivalent to sending native byte order... + // Sndfile always reads as native format. + return NS_UnknownByteOrder; +} + +- (NSData *)data +{ + return _data; +} + +- (NSUInteger)currentPosition +{ + return _curPos; +} + +- (void)setCurrentPosition: (NSUInteger)curPos +{ + _curPos = curPos; +} + address@hidden Index: Sound/AudioOutputSink.m =================================================================== --- Sound/AudioOutputSink.m (revision 0) +++ Sound/AudioOutputSink.m (revision 0) @@ -0,0 +1,163 @@ +/* + AudioOutputSink.m + + Sink audio data to libao. + + Copyright (C) 2009 Free Software Foundation, Inc. + + Written by: Stefan Bidigaray + Date: Jun 2009 + + This file is part of the GNUstep GUI Library. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + 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 + License along with this library; see the file COPYING.LIB. + If not, see or write to the + Free Software Foundation, 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include +#include +#include +#include + address@hidden AudioOutputSink : NSObject +{ + ao_device *_dev; + int _driver; + ao_sample_format _format; +} address@hidden + address@hidden AudioOutputSink + ++ (BOOL)canInitWithPlaybackDevice: (NSString *)playbackDevice +{ + // This is currently the only sink in NSSound, just say + // YES to everything. + /* FIXME: What is OS X's identifier for the main sound? */ + return (playbackDevice == nil ? YES : NO); +} + +- (void)dealloc +{ + ao_shutdown(); + + [super dealloc]; +} + +- (id)initWithEncoding: (int)encoding + channels: (NSUInteger)channelCount + sampleRate: (NSUInteger)sampleRate + byteOrder: (NSByteOrder)byteOrder +{ + self = [super init]; + + ao_initialize(); + _driver = ao_default_driver_id(); + + _format.channels = (int)channelCount; + _format.rate = (int)sampleRate; + + switch (encoding) + { + case GSSoundFormatPCMS8: + _format.bits = 8; + break; + + case GSSoundFormatPCM16: + _format.bits = 16; + break; + + case GSSoundFormatPCM24: + _format.bits = 24; + break; + + case GSSoundFormatPCM32: + _format.bits = 32; + break; + + case GSSoundFormatFloat32: // Float and double not supported by libao. + case GSSoundFormatFloat64: + default: + DESTROY(self); + return nil; + } + + if (byteOrder == NS_LittleEndian) + { + _format.byte_format = AO_FMT_LITTLE; + } + else if (byteOrder == NS_BigEndian) + { + _format.byte_format = AO_FMT_BIG; + } + else + { + _format.byte_format = AO_FMT_NATIVE; + } + + return self; +} + +- (BOOL)open +{ + _dev = ao_open_live(_driver, &_format, NULL); + return ((_dev == NULL) ? NO : YES); +} + +- (void)close +{ + ao_close(_dev); +} + +- (BOOL)writeBytes: (void *)bytes length: (NSUInteger)length +{ + int ret = ao_play(_dev, bytes, (uint_32)length); + return (ret == 0 ? NO : YES); +} + +/* Functionality not supported by libao */ +- (void)setVolume: (float)volume +{ + return; +} + +- (float)volume +{ + return 1.0; +} + +- (void)setPlaybackDeviceIdentifier: (NSString *)playbackDeviceIdentifier +{ + return; +} + +- (NSString *)playbackDeviceIdentifier +{ + return nil; +} + +- (void)setChannelMapping: (NSArray *)channelMapping +{ + return; +} + +- (NSArray *)channelMapping +{ + return nil; +} + address@hidden + Index: Sound/GNUmakefile =================================================================== --- Sound/GNUmakefile (revision 0) +++ Sound/GNUmakefile (revision 0) @@ -0,0 +1,22 @@ +PACKAGE_NAME = gnustep-gui +include $(GNUSTEP_MAKEFILES)/common.make + +BUNDLE_NAME = Sndfile AudioOutput +BUNDLE_EXTENSION = .nssound + +OBJCFLAGS += -Wall + +Sndfile_OBJC_FILES = SndfileSource.m +AudioOutput_OBJC_FILES = AudioOutputSink.m + +Sndfile_PRINCIPAL_CLASS = SndfileSource +AudioOutput_PRINCIPAL_CLASS = AudioOutputSink + +Sndfile_BUNDLE_LIBS = -lsndfile +AudioOutput_BUNDLE_LIBS = -lao + +-include GNUmakefile.preamble + +include $(GNUSTEP_MAKEFILES)/bundle.make + +-include GNUmakefile.postamble Index: Tools/gsnd/gsnd.m =================================================================== --- Tools/gsnd/gsnd.m (revision 28389) +++ Tools/gsnd/gsnd.m (working copy) @@ -1,1424 +0,0 @@ -/* gsnd - - GNUstep sound server - - Copyright (C) 2002 Free Software Foundation, Inc. - - Author: Enrico Sersale - Date: August 2002 - - This file is part of the GNUstep Project - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU General Public License - as published by the Free Software Foundation; either version 3 - of the License, or (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public - License along with this library; see the file COPYING. - If not, see or write to the - Free Software Foundation, 51 Franklin Street, Fifth Floor, - Boston, MA 02110-1301, USA. -*/ - -#include -#include -#include -#include -#include - -#ifdef __MINGW__ -#include "process.h" -#endif - -#ifdef HAVE_SYSLOG_H -#include -#endif - -#define GSNDNAME @"GNUstepGSSoundServer" -#define FRAME_SIZE 4 -#define BUFFER_SIZE_IN_FRAMES (1024 * FRAME_SIZE) -#define CHUNK_LENGTH (BUFFER_SIZE_IN_FRAMES * 4) -#define DEFAULT_CHANNELS 2 -#define PLAY_RATE (44100) -#define CACHE_SIZE 4194304 - -/* Conversion constants */ -#define Nhc 8 -#define Na 7 -#define Np (Nhc + Na) -#define Npc (1 << Nhc) -#define Amask ((1 << Na) - 1) -#define Pmask ((1 << Np) - 1) -#define Nh 16 -#define Nb 16 -#define Nhxn 14 -#define Nhg (Nh - Nhxn) -#define NLpScl 13 - -#ifndef INT16_MAX - #define INT16_MAX (32767) - #define INT16_MIN (-32767-1) -#endif - -#define IBUFFSIZE 4096 - -#define CLAMP(x, low, high) \ -(((x) > (high)) ? (high) : (((x) < (low)) ? (low) : (x))) - -static int is_daemon = 0; /* Currently running as daemon. */ - -static short **sX; -static short **sY; -static unsigned int sTime; -static double sfactor; -static BOOL sinitial; - -void clearResamplerMemory(); -void initializeResampler(double fac); -int resample(int inCount, int outCount, short inArray[], short outArray[]); -int resampleFast(int inCount, int outCount, short inArray[], short outArray[]); -int SrcLinear(short X[], short Y[], double factor, unsigned int *Time, - unsigned short Nx, unsigned short Nout); -inline short WordToHword(int v, int scl); -int resamplerReadData(int inCount, short inArray[], short *outPtr[], - int dataArraySize, int Xoff, BOOL init_count); -int resampleError(char *s); - -static char ebuf[2048]; - -#ifdef HAVE_SYSLOG - -static int log_priority; - -static void -gsnd_log (int prio) -{ - if (is_daemon) - { - syslog (log_priority | prio, ebuf); - } - else if (prio == LOG_INFO) - { - write (1, ebuf, strlen (ebuf)); - write (1, "\n", 1); - } - else - { - write (2, ebuf, strlen (ebuf)); - write (2, "\n", 1); - } - - if (prio == LOG_CRIT) - { - if (is_daemon) - { - syslog (LOG_CRIT, "exiting."); - } - else - { - fprintf (stderr, "exiting.\n"); - fflush (stderr); - } - exit(EXIT_FAILURE); - } -} -#else - -#define LOG_CRIT 2 -#define LOG_DEBUG 0 -#define LOG_ERR 1 -#define LOG_INFO 0 -#define LOG_WARNING 0 -void -gsnd_log (int prio) -{ - write (2, ebuf, strlen (ebuf)); - write (2, "\n", 1); - if (prio == LOG_CRIT) - { - fprintf (stderr, "exiting.\n"); - fflush (stderr); - exit(EXIT_FAILURE); - } -} -#endif - address@hidden GSSndObj - -- (void)setIdentifier:(NSString *)identifier; -- (NSString *)identifier; -- (NSString *)name; -- (float)samplingRate; -- (long)frameCount; -- (NSData *)data; - address@hidden - address@hidden Snd : NSObject -{ - NSString *name; - NSString *identifier; - long frameCount; - float rate; - - long startPos; - long endPos; - long posInBytes; - - BOOL playing; - BOOL paused; - - NSMutableData *data; -} - -- (id)initWithNSSoundObject:(id)anobject; - -- (void)setIdentifier:(NSString *)identstr; - -- (void)resampleWithFactor:(float)factor; - -- (void)reset; - -- (NSData *)data; - -- (NSData *)remainingData; - -- (void)setStartPos:(long)p; - -- (void)setEndPos:(long)p; - -- (void)setPlaying:(BOOL)value; - -- (void)setPaused:(BOOL)value; - -- (NSString *)name; - -- (NSString *)identifier; - -- (long)frameCount; - -- (float)rate; - -- (long)startPos; - -- (long)endPos; - -- (long)posInBytes; - -- (void)posInBytesAdd; - -- (BOOL)isPlaying; - -- (BOOL)isPaused; - address@hidden - address@hidden SoundServer : NSObject -{ - NSMutableData *soundData; - NSMutableArray *sounds; - long maxCacheSize; - long frameCount; - - long position; - long posInBytes; - - BOOL isPlaying; - - NSConnection *conn; - NSNotificationCenter *nc; -} - -- (BOOL)playSound:(id)aSound; - -- (BOOL)stopSoundWithIdentifier:(NSString *)identifier; - -- (BOOL)pauseSoundWithIdentifier:(NSString *)identifier; - -- (BOOL)resumeSoundWithIdentifier:(NSString *)identifier; - -- (BOOL)isPlayingSoundWithIdentifier:(NSString *)identifier; - -- (void)play; - -- (void)mixWithSound:(Snd *)snd; - -- (void)unMixSound:(Snd *)snd; - -- (void)updatePosition; - -- (void)stopAll; - -- (void)checkIsPlaying; - -- (void)closeStream; - -- (Snd *)cachedSoundWithName:(NSString *)aName; - -- (void)checkCachedSounds; - -- (Snd *)soundWithIdentifier:(NSString *)identifier; - -- (Snd *)soundWithName:(NSString *)aName identifier:(NSString *)ident; - -- (NSString *)uniqueSoundIdentifier; - -- (BOOL)connection:(NSConnection*)ancestor - shouldMakeNewConnection:(NSConnection*)newConn; - -- (id)connectionBecameInvalid:(NSNotification*)notification; - address@hidden - - -SoundServer *gsnd = nil; -PaStream *pStream = NULL; - -struct SoundServer_t { - @defs(SoundServer) -} *serverPtr; - - address@hidden Snd - -- (void)dealloc -{ - RELEASE (name); - TEST_RELEASE (identifier); - RELEASE (data); - [super dealloc]; -} - -- (id)initWithNSSoundObject:(id)anobject -{ - self = [super init]; - - if (self) { - ASSIGN (name, [anobject name]); - ASSIGN (identifier, [anobject identifier]); - data = [[anobject data] mutableCopy]; - frameCount = [anobject frameCount]; - rate = [anobject samplingRate]; - startPos = 0; - endPos = 0; - posInBytes = 0; - playing = NO; - paused = NO; - - [self resampleWithFactor: (PLAY_RATE / rate)]; - } - - return self; -} - -- (void)setIdentifier:(NSString *)identstr -{ - ASSIGN (identifier, identstr); -} - -- (void)resampleWithFactor:(float)factor -{ - gss16 *outbuff; - int extra_sample; - long out_count; - float out_length; - - rate *= factor; - - if (factor <= 1.0) { - extra_sample = 50; - } else { - extra_sample = (int)factor + 50; - } - - out_count = (long)ceil((frameCount) * factor) + extra_sample; - out_length = out_count * FRAME_SIZE; - - outbuff = NSZoneMalloc(NSDefaultMallocZone(), GS_SIZEOF_SHORT * out_length); - - initializeResampler(factor); - resample(frameCount, out_count, (gss16 *)[data bytes], outbuff); - - TEST_RELEASE (data); - data = [[NSMutableData alloc] initWithCapacity: 1]; - - [data appendBytes: (const void *)outbuff - length: out_length]; - - NSZoneFree(NSDefaultMallocZone(), outbuff); - clearResamplerMemory(); - - frameCount = (long)([data length] / FRAME_SIZE); -} - -- (void)reset -{ - startPos = 0; - endPos = 0; - posInBytes = 0; - playing = NO; - paused = NO; - DESTROY (identifier); -} - -- (NSData *)data -{ - return data; -} - -- (NSData *)remainingData -{ - long start = (long)(posInBytes * FRAME_SIZE); - long length = (long)([data length] - start); - return [data subdataWithRange: NSMakeRange(start, length)]; -} - -- (void)setStartPos:(long)p -{ - startPos = p; -} - -- (void)setEndPos:(long)p -{ - endPos = p; -} - -- (void)setPlaying:(BOOL)value -{ - playing = value; -} - -- (void)setPaused:(BOOL)value -{ - paused = value; -} - -- (NSString *)name -{ - return name; -} - -- (NSString *)identifier -{ - return identifier; -} - -- (long)frameCount -{ - return frameCount; -} - -- (float)rate -{ - return rate; -} - -- (long)startPos -{ - return startPos; -} - -- (long)endPos -{ - return endPos; -} - -- (long)posInBytes -{ - return posInBytes; -} - -- (void)posInBytesAdd -{ - posInBytes++; -} - -- (BOOL)isPlaying -{ - return playing; -} - -- (BOOL)isPaused -{ - return paused; -} - -- (id)copyWithZone:(NSZone *)zone -{ - Snd *snd = (Snd *)NSCopyObject(self, 0, zone); - - snd->name = [name copy]; - snd->identifier = nil; - snd->frameCount = frameCount; - snd->rate = rate; - snd->startPos = 0; - snd->endPos = 0; - snd->posInBytes = 0; - snd->playing = NO; - snd->paused = NO; - snd->data = [data mutableCopy]; - - return snd; -} - address@hidden - - -static int paCallback(void *inputBuffer, - void *outputBuffer, - unsigned long framesPerBuffer, - const PaStreamCallbackTimeInfo *timeInfo, - PaStreamCallbackFlags statusFlags, - void *userData) -{ - NSAutoreleasePool *pool; - NSData *data; - long length; - int chunkLength; - int retvalue; - void *buffer[CHUNK_LENGTH]; - gss16 *in; - int i; - -#ifdef GNUSTEP - GSRegisterCurrentThread(); -#endif - - pool = [[NSAutoreleasePool alloc] init]; - - data = serverPtr->soundData; - length = [data length]; - - if ((serverPtr->position + CHUNK_LENGTH) < length) { - chunkLength = CHUNK_LENGTH; - retvalue = 0; - } else { - chunkLength = length - serverPtr->position; - retvalue = 1; - } - - [data getBytes: &buffer range: NSMakeRange(serverPtr->position, chunkLength)]; - in = (gss16 *)buffer; - - for(i = 0; i < framesPerBuffer; i++) { - ((gss16 *)outputBuffer)[i * 2] = in[i * 2]; - ((gss16 *)outputBuffer)[i * 2 + 1] = in[i * 2 + 1]; - [(SoundServer *)serverPtr updatePosition]; - } - - if (retvalue == 1) { - [(SoundServer *)serverPtr performSelectorOnMainThread: @selector(closeStream) - withObject: (SoundServer *)serverPtr waitUntilDone: NO]; - } - - serverPtr->isPlaying = !retvalue; - - RELEASE (pool); - - return retvalue; -} - - address@hidden SoundServer - -- (void)dealloc -{ - [nc removeObserver: self]; - RELEASE (sounds); - RELEASE (soundData); - [super dealloc]; -} - -- (id)init -{ - self = [super init]; - - if (self) { - NSString *hostname; - NSNumber *csize; - - serverPtr = (struct SoundServer_t *)self; - nc = [NSNotificationCenter defaultCenter]; - - csize = [[NSUserDefaults standardUserDefaults] objectForKey: @"cachesize"]; - maxCacheSize = csize ? [csize longValue] : CACHE_SIZE; - - sounds = [[NSMutableArray alloc] initWithCapacity: 1]; - soundData = [[NSMutableData alloc] initWithCapacity: 1]; - frameCount = 0; - position = 0; - posInBytes = 0; - isPlaying = NO; - - conn = [NSConnection defaultConnection]; - [conn setRootObject: self]; - [conn setDelegate: self]; - [nc addObserver: self - selector: @selector(connectionBecameInvalid:) - name: NSConnectionDidDieNotification - object: (id)conn]; - - hostname = [[NSUserDefaults standardUserDefaults] stringForKey: @"NSHost"]; - - if ([hostname length] == 0 - || [[NSHost hostWithName: hostname] isEqual: [NSHost currentHost]]) { - if ([conn registerName: GSNDNAME] == NO) { - NSLog(@"Unable to register with name server.\n"); - exit(1); - } - } else { - NSHost *host = [NSHost hostWithName: hostname]; - NSPort *port = [conn receivePort]; - NSPortNameServer *ns = [NSPortNameServer systemDefaultPortNameServer]; - NSArray *a; - unsigned c; - - if (host == nil) { - NSLog(@"gsnd - unknown NSHost argument ... %@ - quiting.", hostname); - exit(1); - } - - a = [host names]; - c = [a count]; - while (c-- > 0) { - NSString *name = [a objectAtIndex: c]; - - name = [GSNDNAME stringByAppendingFormat: @"-%@", name]; - if ([ns registerPort: port forName: name] == NO) { - } - } - - a = [host addresses]; - c = [a count]; - while (c-- > 0) { - NSString *name = [a objectAtIndex: c]; - - name = [GSNDNAME stringByAppendingFormat: @"-%@", name]; - if ([ns registerPort: port forName: name] == NO) { - } - } - } - } - - return self; -} - -- (BOOL)playSound:(id)aSound -{ - id obj; - NSString *soundName; - NSString *idendstr; - Snd *snd = nil; - - [self checkCachedSounds]; - - obj = (id)aSound; - soundName = [obj name]; - idendstr = [obj identifier]; - - if (idendstr == nil) { - idendstr = [self uniqueSoundIdentifier]; - [obj setIdentifier: idendstr]; - } else { - snd = [self soundWithIdentifier: idendstr]; - - if ((snd != nil) && (([snd isPlaying]) || ([snd isPaused]))) { - return NO; - } - } - - if (snd == nil) { - snd = [self cachedSoundWithName: soundName]; - - if (snd == nil) { - snd = [[Snd alloc] initWithNSSoundObject: obj]; - [sounds addObject: snd]; - RELEASE (snd); - } else { - if ([snd isPlaying] || [snd isPaused]) { - snd = [snd copy]; - [sounds addObject: snd]; - RELEASE (snd); - } - [snd reset]; - [snd setIdentifier: idendstr]; - } - } - - if (isPlaying == NO) { - frameCount = [snd frameCount]; - TEST_RELEASE (soundData); - soundData = [[snd data] mutableCopy]; - isPlaying = YES; - - [snd setStartPos: 0]; - [snd setEndPos: (long)([soundData length] / FRAME_SIZE)]; - [snd setPlaying: YES]; - - [self play]; - - } else { - [self mixWithSound: snd]; - } - - return YES; -} - -- (BOOL)stopSoundWithIdentifier:(NSString *)identifier -{ - Snd *snd = [self soundWithIdentifier: identifier]; - - if (snd && [snd isPlaying]) { - [self unMixSound: snd]; - [snd reset]; - [self checkIsPlaying]; - return YES; - } - - return NO; -} - -- (BOOL)pauseSoundWithIdentifier:(NSString *)identifier -{ - Snd *snd = [self soundWithIdentifier: identifier]; - - if (snd && [snd isPlaying]) { - [self unMixSound: snd]; - - [snd setPlaying: NO]; - [snd setPaused: YES]; - [snd setStartPos: 0]; - [snd setEndPos: 0]; - - [self checkIsPlaying]; - return YES; - } - - return NO; -} - -- (BOOL)resumeSoundWithIdentifier:(NSString *)identifier -{ - Snd *snd = [self soundWithIdentifier: identifier]; - - if (snd && [snd isPaused]) { - if (isPlaying == NO) { - TEST_RELEASE (soundData); - soundData = [[snd remainingData] mutableCopy]; - - frameCount = (long)([soundData length] / FRAME_SIZE); - - [snd setStartPos: 0]; - [snd setEndPos: (long)([soundData length] / FRAME_SIZE)]; - [snd setPaused: NO]; - [snd setPlaying: YES]; - - isPlaying = YES; - [self play]; - - } else { - [snd setPlaying: YES]; - [snd setPaused: NO]; - [self mixWithSound: snd]; - } - - return YES; - } - - return NO; -} - -- (BOOL)isPlayingSoundWithIdentifier:(NSString *)identifier -{ - Snd *snd = [self soundWithIdentifier: identifier]; - - if (snd) { - return [snd isPlaying]; - } - - return NO; -} - -- (void)play -{ - PaError err; - int d = 0; - - err = Pa_Initialize(); - - if (err != paNoError) { - fprintf(stderr, "An error occured while using the portaudio stream\n"); - fprintf(stderr, "Error number: %d\n", err ); - fprintf(stderr, "Error message: %s\n", Pa_GetErrorText(err)); - - Pa_Terminate(); - return; - } - - err = Pa_OpenDefaultStream(&pStream, 0, DEFAULT_CHANNELS, paInt16, - PLAY_RATE, BUFFER_SIZE_IN_FRAMES, (PaStreamCallback *)paCallback, &d); - - if (err == paNoError) { - err = Pa_StartStream(pStream); - - if (err == paNoError) { - Pa_Sleep(2); - } else { - NSLog(@"PortAudio Pa_StartStream error: %s", Pa_GetErrorText(err)); - } - - } else { - NSLog(@"PortAudio Pa_OpenDefaultStream error: %s", Pa_GetErrorText(err)); - } -} - -- (void)mixWithSound:(Snd *)snd -{ - if (isPlaying) { - NSData *snddata = [snd remainingData]; - long inFrameCount = (long)([snddata length] / FRAME_SIZE); - gss16 *in = (gss16 *)[snddata bytes]; - gss16 *out = (gss16 *)[soundData mutableBytes]; - gss32 sum_l; - gss32 sum_r; - long inPos; - int i, j; - - [snd setStartPos: posInBytes + [snd posInBytes]]; - [snd setEndPos: posInBytes + (long)([snddata length] / FRAME_SIZE)]; - - j = 0; - - for (i = posInBytes; i < frameCount; i++) { - sum_l = out[i * 2] + in[j * 2]; - sum_r = out[i * 2 + 1] + in[j * 2 + 1]; - - out[i * 2] = CLAMP (sum_l, INT16_MIN, INT16_MAX); - out[i * 2 + 1] = CLAMP (sum_r, INT16_MIN, INT16_MAX); - - j++; - if (j == inFrameCount) { - break; - } - } - - inPos = (j * FRAME_SIZE); - - if (inPos < ([snddata length] - 1)) { - long remLength = [snddata length] - inPos; - NSRange range = NSMakeRange(inPos, remLength); - - [soundData appendData: [snddata subdataWithRange: range]]; - frameCount = (long)([soundData length] / FRAME_SIZE); - } - - [snd setPlaying: YES]; - } -} - -- (void)unMixSound:(Snd *)snd -{ - if (isPlaying) { - int *in = (int *)[[snd data] bytes]; - int *out = (int *)[soundData mutableBytes]; - long deleteFrom = [snd startPos] + [snd posInBytes]; - long deleteTo = [snd endPos]; - int i, j; - - j = [snd posInBytes]; - for (i = deleteFrom; i < frameCount; i++) { - out[i] -= in[j]; - j++; - if (j == deleteTo) { - break; - } - } - } -} - -- (void)updatePosition -{ - int i; - - posInBytes++; - position = (long)(posInBytes * FRAME_SIZE); - - for (i = 0; i < [sounds count]; i++) { - Snd *snd = [sounds objectAtIndex: i]; - - if ([snd isPlaying]) { - [snd posInBytesAdd]; - - if (posInBytes == [snd endPos]) { - [snd reset]; - } - } - } -} - -- (void)stopAll -{ - int i; - - [self checkCachedSounds]; - - for (i = 0; i < [sounds count]; i++) { - Snd *snd = [sounds objectAtIndex: i]; - - if ([snd isPaused]) { - [snd setStartPos: 0]; - [snd setEndPos: 0]; - } else { - [snd reset]; - } - } - - TEST_RELEASE (soundData); - soundData = [[NSMutableData alloc] initWithCapacity: 1]; - frameCount = 0; - position = 0; - posInBytes = 0; - isPlaying = NO; -} - -- (void)checkIsPlaying -{ - int i; - BOOL found = NO; - - for (i = 0; i < [sounds count]; i++) { - Snd *snd = [sounds objectAtIndex: i]; - - if ([snd isPlaying]) { - found = YES; - break; - } - } - - if (found == NO) { - [self stopAll]; - } -} - -- (void)closeStream -{ - PaError err; - - err = Pa_StopStream(pStream); - - if (err != paNoError) { - NSLog(@"PortAudio Pa_StopStream error: %s", Pa_GetErrorText(err)); - return; - } - - err = Pa_CloseStream(pStream); - - if (err != paNoError) { - NSLog(@"PortAudio Pa_CloseStream error: %s", Pa_GetErrorText(err)); - return; - } - - err = Pa_Terminate(); - - if (err != paNoError) { - NSLog(@"PortAudio Pa_Terminate error: %s", Pa_GetErrorText(err)); - return; - } - - [self stopAll]; -} - -- (Snd *)cachedSoundWithName:(NSString *)aName -{ - int i; - - for (i = 0; i < [sounds count]; i++) { - Snd *snd = [sounds objectAtIndex: i]; - - if ([[snd name] isEqual: aName]) { - return snd; - } - } - - return nil; -} - -- (void)checkCachedSounds -{ - int i, j, count; - long csize = 0; - - count = [sounds count]; - - for (i = 0; i < count; i++) { - Snd *snd0 = [sounds objectAtIndex: i]; - NSString *name = [snd0 name]; - - for (j = 0; j < count; j++) { - Snd *snd1 = [sounds objectAtIndex: j]; - - if (([[snd1 name] isEqual: name]) && (snd0 != snd1)) { - if (([snd1 isPaused] == NO) && ([snd1 isPlaying] == NO)) { - [sounds removeObject: snd1]; - count--; - i--; - j--; - } - } - } - } - - count = [sounds count]; - - for (i = 0; i < count; i++) { - csize += [[[sounds objectAtIndex: i] data] length]; - } - - if (csize > maxCacheSize) { - for (i = 0; i < count; i++) { - Snd *snd = [sounds objectAtIndex: i]; - - if (([snd isPlaying] == NO) && ([snd isPaused] == NO)) { - csize -= [[snd data] length]; - [sounds removeObject: snd]; - count--; - i--; - } - if (csize <= maxCacheSize) { - break; - } - } - } -} - -- (Snd *)soundWithIdentifier:(NSString *)identifier -{ - int i; - - for (i = 0; i < [sounds count]; i++) { - Snd *snd = [sounds objectAtIndex: i]; - - if ([[snd identifier] isEqual: identifier]) { - return snd; - } - } - - return nil; -} - -- (Snd *)soundWithName:(NSString *)aName identifier:(NSString *)ident -{ - int i; - - for (i = 0; i < [sounds count]; i++) { - Snd *snd = [sounds objectAtIndex: i]; - - if ([[snd identifier] isEqual: ident]) { - NSString *sname = [snd name]; - - if ([sname isEqual: aName]) { - return snd; - } - } - } - - return nil; -} - -- (NSString *)uniqueSoundIdentifier -{ - int identifier = [[NSProcessInfo processInfo] processIdentifier]; - NSString *identstr = [NSString stringWithFormat: @"sound_%i_%i", identifier, random()]; - - return identstr; -} - -- (BOOL)connection:(NSConnection*)ancestor - shouldMakeNewConnection:(NSConnection*)newConn; -{ - [nc addObserver: self - selector: @selector(connectionBecameInvalid:) - name: NSConnectionDidDieNotification - object: newConn]; - - [newConn setDelegate: self]; - - return YES; -} - -- (id)connectionBecameInvalid:(NSNotification*)notification -{ - id connection = [notification object]; - - if (connection == conn) { - NSLog(@"Help - sound server connection has died!\n"); - exit(1); - } - - return self; -} - address@hidden - -int main(int argc, char** argv, char **env) -{ - CREATE_AUTORELEASE_POOL(pool); - BOOL subtask = YES; - NSProcessInfo *pInfo; - NSMutableArray *args; - -#ifdef GS_PASS_ARGUMENTS - [NSProcessInfo initializeWithArguments: argv count: argc environment: env]; -#endif - - pInfo = [NSProcessInfo processInfo]; - args = AUTORELEASE ([[pInfo arguments] mutableCopy]); - - if ([[pInfo arguments] containsObject: @"--daemon"]) { - subtask = NO; - is_daemon = YES; - } - - if (subtask) { - NSFileHandle *null; - NSTask *t; - - t = [NSTask new]; - - NS_DURING - { - [args removeObjectAtIndex: 0]; - [args addObject: @"--daemon"]; - [t setLaunchPath: [[NSBundle mainBundle] executablePath]]; - [t setArguments: args]; - [t setEnvironment: [pInfo environment]]; - null = [NSFileHandle fileHandleWithNullDevice]; - [t setStandardInput: null]; - [t setStandardOutput: null]; - [t setStandardError: null]; - [t launch]; - DESTROY(t); - } - NS_HANDLER - { - gsnd_log(LOG_CRIT); - DESTROY(t); - } - NS_ENDHANDLER - - exit(EXIT_FAILURE); - } - - RELEASE(pool); - - { -#if GS_WITH_GC == 0 - CREATE_AUTORELEASE_POOL(pool); -#endif - - gsnd = [[SoundServer alloc] init]; - - [[NSFileHandle fileHandleWithStandardInput] closeFile]; - [[NSFileHandle fileHandleWithStandardOutput] closeFile]; - #ifndef __MINGW__ - [[NSFileHandle fileHandleWithStandardError] closeFile]; - #endif - - RELEASE (pool); - } - - if (gsnd != nil) { - CREATE_AUTORELEASE_POOL(pool); - [[NSRunLoop currentRunLoop] run]; - RELEASE(pool); - } - - exit(EXIT_SUCCESS); -} - - - -// -// Resampler Functions -// - -void -clearResamplerMemory() -{ - int i; - - if (sX != NULL) - { - for (i = 0; i < DEFAULT_CHANNELS; i++) - { - NSZoneFree(NSDefaultMallocZone(), sX[i]); - sX[i] = NULL; - NSZoneFree(NSDefaultMallocZone(), sY[i]); - sY[i] = NULL; - } - NSZoneFree(NSDefaultMallocZone(), sX); - sX = NULL; - NSZoneFree(NSDefaultMallocZone(), sY); - sY = NULL; - } -} - -void -initializeResampler(double fac) -{ - int i; - - clearResamplerMemory(); - - sfactor = fac; - sinitial = YES; - - // Allocate all new memory - sX = NSZoneMalloc(NSDefaultMallocZone(), GS_SIZEOF_SHORT * DEFAULT_CHANNELS); - sY = NSZoneMalloc(NSDefaultMallocZone(), GS_SIZEOF_SHORT * DEFAULT_CHANNELS); - - for (i = 0; i < DEFAULT_CHANNELS; i++) - { - // Add extra to allow of offset of input data (Xoff in main routine) - sX[i] = NSZoneMalloc(NSDefaultMallocZone(), GS_SIZEOF_SHORT * (IBUFFSIZE + 256)); - sY[i] = NSZoneMalloc(NSDefaultMallocZone(), GS_SIZEOF_SHORT * (int)(((double)IBUFFSIZE) * sfactor)); - memset(sX[i], 0, GS_SIZEOF_SHORT * (IBUFFSIZE + 256)); - } -} - -int -resample(int inCount, int outCount, short inArray[], short outArray[]) -{ - int Ycount = resampleFast(inCount, outCount, inArray, outArray); - sinitial = NO; - return Ycount; -} - -int resampleFast(int inCount, int outCount, short inArray[], short outArray[]) -{ - unsigned int Time2; /* Current time/pos in input sample */ - unsigned short Xp, Xread; - int OBUFFSIZE = (int)(((double)IBUFFSIZE) * sfactor); - unsigned short Nout = 0, Nx, orig_Nx; - unsigned short maxOutput; - int total_inCount = 0; - int c, i, Ycount, last; - BOOL first_pass = YES; - unsigned short Xoff = 10; - - Nx = IBUFFSIZE - 2 * Xoff; /* # of samples to process each iteration */ - last = 0; /* Have not read last input sample yet */ - Ycount = 0; /* Current sample and length of output file */ - - Xp = Xoff; /* Current "now"-sample pointer for input */ - Xread = Xoff; /* Position in input array to read into */ - - if (sinitial == YES) - { - sTime = (Xoff << Np); /* Current-time pointer for converter */ - } - - do - { - if (!last) - { /* If haven't read last sample yet */ - last = resamplerReadData(inCount, inArray, sX, IBUFFSIZE, (int)Xread, first_pass); - first_pass = NO; - - if (last && ((last - Xoff) < Nx)) - { /* If last sample has been read... */ - Nx = last - Xoff; /* ...calc last sample affected by filter */ - if (Nx <= 0) - { - break; - } - } - } - - if ((outCount-Ycount) > (OBUFFSIZE - (2 * Xoff * sfactor))) - { - maxOutput = OBUFFSIZE - (unsigned short)(2 * Xoff * sfactor); - } - else - { - maxOutput = outCount-Ycount; - } - - for (c = 0; c < DEFAULT_CHANNELS; c++) - { - orig_Nx = Nx; - Time2 = sTime; - - /* Resample stuff in input buffer */ - Nout = SrcLinear(sX[c], sY[c], sfactor, &Time2, orig_Nx, maxOutput); - } - - Nx = orig_Nx; - sTime = Time2; - - sTime -= (Nx << Np); /* Move converter Nx samples back in time */ - Xp += Nx; /* Advance by number of samples processed */ - - for (c = 0; c < DEFAULT_CHANNELS; c++) - { - for (i = 0; i < (IBUFFSIZE - Xp + Xoff); i++) - { /* Copy part of input signal */ - sX[c][i] = sX[c][i + Xp - Xoff]; /* that must be re-used */ - } - } - - if (last) - { /* If near end of sample... */ - last -= Xp; /* ...keep track were it ends */ - - if (!last) - { /* Lengthen input by 1 sample if... */ - last++; - } /* ...needed to keep flag YES */ - } - - Xread = IBUFFSIZE - Nx; /* Pos in input buff to read new data into */ - Xp = Xoff; - - Ycount += Nout; - if (Ycount > outCount) - { - Nout -= (Ycount - outCount); - Ycount = outCount; - } - - if (Nout > OBUFFSIZE) - { /* Check to see if output buff overflowed */ - return resampleError("Output array overflow"); - } - - for (c = 0; c < DEFAULT_CHANNELS; c++) - { - for (i = 0; i < Nout; i++) - { - outArray[c * outCount + i + Ycount - Nout] = sY[c][i]; - } - } - - total_inCount += Nx; - - } while (Ycount < outCount); /* Continue until done */ - - inCount = total_inCount; - - return(Ycount); /* Return # of samples in output file */ -} - -int -SrcLinear(short X[], short Y[], double factor, unsigned int *Time, - unsigned short Nx, unsigned short Nout) -{ - short iconst; - short *Xp, *Ystart; - int v, x1, x2; - - double dt; /* Step through input signal */ - unsigned int dtb; /* Fixed-point version of Dt */ - unsigned int start_sample, end_sample; - - dt = 1.0 / factor; /* Output sampling period */ - dtb = (unsigned int)(dt * (1 << Np) + 0.5); /* Fixed-point representation */ - - start_sample = *Time >> Np; - Ystart = Y; - - while (Y - Ystart != Nout) - { - iconst = *Time & Pmask; - Xp = &X[*Time >> Np]; /* Ptr to current input sample */ - x1 = *Xp++; - x2 = *Xp; - x1 *= (1 << Np) - iconst; - x2 *= iconst; - v = x1 + x2; - *Y++ = WordToHword(v, Np); /* Deposit output */ - *Time += dtb; /* Move to next sample by time increment */ - } - - end_sample = *Time >> Np; - Nx = end_sample - start_sample; - return (Y - Ystart); /* Return number of output samples */ -} - -inline short -WordToHword(int v, int scl) -{ - short out; - int llsb; - - llsb = (1 << (scl - 1)); - v += llsb; /* round */ - v >>= scl; - if (v > INT16_MAX) - { - v = INT16_MAX; - } - else if (v < INT16_MIN) - { - v = INT16_MIN; - } - out = (short)v; - - return out; -} - -int -resamplerReadData(int inCount, short inArray[], short *outPtr[], - int dataArraySize, int Xoff, BOOL init_count) -{ - int i, Nsamps, c; - static unsigned int framecount; /* frames previously read */ - short *ptr; - - if (init_count == YES) - { - framecount = 0; /* init this too */ - } - - Nsamps = dataArraySize - Xoff; /* Calculate number of samples to get */ - - // Don't overrun input buffers - if (Nsamps > (inCount - (int)framecount)) - { - Nsamps = inCount - framecount; - } - - for (c = 0; c < DEFAULT_CHANNELS; c++) - { - ptr = outPtr[c]; - ptr += Xoff; /* Start at designated sample number */ - - for (i = 0; i < Nsamps; i++) - { - *ptr++ = (short) inArray[c * inCount + i + framecount]; - } - } - - framecount += Nsamps; - - if ((int)framecount >= inCount) - { /* return index of last samp */ - return (((Nsamps - (framecount - inCount)) - 1) + Xoff); - } - - return 0; -} - -int -resampleError(char *s) -{ - NSLog([NSString stringWithCString: s]); - return -1; -} - Index: Tools/gsnd/GNUmakefile =================================================================== --- Tools/gsnd/GNUmakefile (revision 28389) +++ Tools/gsnd/GNUmakefile (working copy) @@ -1,41 +0,0 @@ -# -# Tools level makefile for gsnd -# -# Copyright (C) 2002 Free Software Foundation, Inc. -# -# This file is part of the GNUstep GUI Library. -# -# This library is free software; you can redistribute it and/or -# modify it under the terms of the GNU Lesser General Public -# License as published by the Free Software Foundation; either -# version 2 of the License, or (at your option) any later version. -# -# This library is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Library General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public -# License along with this library; see the file COPYING.LIB. -# If not, see or write to the -# Free Software Foundation, 51 Franklin Street, Fifth Floor, -# Boston, MA 02110-1301, USA. - -PACKAGE_NAME = gnustep-gui -include $(GNUSTEP_MAKEFILES)/common.make - -# The applications to be compiled -TOOL_NAME = gnustep_sndd - -# The source files to be compiled -gnustep_sndd_OBJC_FILES = gsnd.m - -gnustep_sndd_TOOL_LIBS = -lportaudio - --include GNUmakefile.preamble - --include GNUmakefile.local - -include $(GNUSTEP_MAKEFILES)/tool.make - --include GNUmakefile.postamble Index: Tools/gsnd/GNUmakefile.postamble =================================================================== --- Tools/gsnd/GNUmakefile.postamble (revision 28389) +++ Tools/gsnd/GNUmakefile.postamble (working copy) @@ -1,47 +0,0 @@ -# -# Makefile.postamble -# -# Project specific makefile rules -# -# Copyright (C) 2005 Free Software Foundation, Inc. -# -# Uncomment the targets you want. -# The double colons (::) are important, do not make them single colons -# otherwise the normal makefile rules will not be performed. -# - -# Things to do before compiling -#before-all:: - -# Things to do after compiling -# after-all:: - -# Things to do before installing -#before-install:: - -# Things to do after installing -# after-install:: - -# Things to do before uninstalling -# before-uninstall:: - -# Things to do after uninstalling -# after-uninstall:: - -# Things to do before cleaning -# before-clean:: - -# Things to do after cleaning -# after-clean:: - -# Things to do before distcleaning -# before-distclean:: - -# Things to do after distcleaning -#after-distclean:: - -# Things to do before checking -# before-check:: - -# Things to do after checking -# after-check:: Index: Tools/gsnd/GNUmakefile.preamble =================================================================== --- Tools/gsnd/GNUmakefile.preamble (revision 28389) +++ Tools/gsnd/GNUmakefile.preamble (working copy) @@ -1,42 +0,0 @@ -# -# Makefile.preamble -# -# Project specific makefile variables, and additional -# -# Copyright (C) 2005 Free Software Foundation, Inc. -# -# Do not put any Makefile rules in this file, instead they should -# be put into Makefile.postamble. -# - -# -# Flags dealing with compiling and linking -# - -# Additional flags to pass to the preprocessor -#ADDITIONAL_CPPFLAGS += - -# Additional flags to pass to the Objective-C compiler -#ADDITIONAL_OBJCFLAGS += - -# Additional flags to pass to the C compiler -#ADDITIONAL_CFLAGS += - -# Additional include directories the compiler should search -#ADDITIONAL_INCLUDE_DIRS += - -# Additional LDFLAGS to pass to the linker -#ADDITIONAL_LDFLAGS += - -# Additional library directories the linker should search -# ADDITIONAL_LIB_DIRS += -L../Source/$(GNUSTEP_OBJ_DIR) - -# Additional libraries when linking applications -#ADDITIONAL_TOOL_LIBS += - -# -# Flags dealing with installing and uninstalling -# - -# Additional directories to be created during installation -#ADDITIONAL_INSTALL_DIRS +=