/** NSSound Load, manipulate and play sounds Copyright (C) 2002 Free Software Foundation, Inc. Author: Enrico Sersale Date: Jul 2002 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 Library 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 Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include #include #include #include #ifdef HAVE_AUDIOFILE_H #include #endif #define BUFFER_SIZE_IN_FRAMES 4096 #define DEFAULT_CHANNELS 2 /* Class variables and functions for class methods */ static NSMutableDictionary *nameDict = nil; static NSDictionary *nsmapping = nil; #define GSNDNAME @"GNUstepGSSoundServer" @protocol GSSoundSvr - (BOOL)playSound:(id)aSound; - (BOOL)stopSoundWithIdentifier:(NSString *)identifier; - (BOOL)pauseSoundWithIdentifier:(NSString *)identifier; - (BOOL)resumeSoundWithIdentifier:(NSString *)identifier; - (BOOL)isPlayingSoundWithIdentifier:(NSString *)identifier; @end static id the_server = nil; @interface NSSound (PrivateMethods) + (id)gsnd; + (void)localServer:(id)s; + (id)lostServer:(NSNotification*)notification; - (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 + (id)gsnd { if (the_server == nil) { NSString *host; NSString *description; 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) { #ifdef GNUSTEP_BASE_LIBRARY cmd = RETAIN([[NSSearchPathForDirectoriesInDomains( GSToolsDirectory, NSSystemDomainMask, YES) objectAtIndex: 0] stringByAppendingPathComponent: @"gsnd"]); #else cmd = RETAIN([[@GNUSTEP_INSTALL_PREFIX stringByAppendingPathComponent: @"Tools"] stringByAppendingPathComponent: @"gsnd"]); #endif } if (recursion == YES || cmd == nil) { NSLog(@"Unable to contact sound server - " @"please ensure that gsnd is running for %@.", 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 gsnd from %@ or a machine/operating-system subdirectory.\n" @"It is recommended that you start the sound server (gsnd) 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; } } } return the_server; } + (void)localServer:(id)s { 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 { AFfilehandle file; AFframecount framesRead; void *buffer; #define CHECK_AF_ERR(x) \ if ((x) == -1) { \ afCloseFile(file); \ return NO; \ } if ((file = afOpenFile([path cString], "r", NULL)) == AF_NULL_FILEHANDLE) { return NO; } 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); data = [[NSMutableData alloc] initWithCapacity: 1]; CHECK_AF_ERR (framesRead = afReadFrames(file, AF_DEFAULT_TRACK, buffer, BUFFER_SIZE_IN_FRAMES)); while (framesRead > 0) { [data appendBytes: (const void *)buffer length: framesRead * frameSize]; CHECK_AF_ERR (framesRead = afReadFrames(file, AF_DEFAULT_TRACK, buffer, BUFFER_SIZE_IN_FRAMES)); } dataSize = [data length]; NSZoneFree(NSDefaultMallocZone(), buffer); afCloseFile(file); return YES; } #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]) { #ifdef GNUSTEP_BASE_LIBRARY NSString *path = [NSBundle pathForGNUstepResource: @"nsmapping" ofType: @"strings" inDirectory: @"Sounds"]; #else NSBundle *system = [NSBundle bundleWithPath: @GNUSTEP_INSTALL_LIBDIR]; NSString *path = [system pathForResource: @"nsmapping" ofType: @"strings" inDirectory: @"Sounds"]; #endif [self setVersion: 1]; nameDict = [[NSMutableDictionary alloc] initWithCapacity: 10]; if (path) { nsmapping = RETAIN([[NSString stringWithContentsOfFile: path] propertyListFromStringsFileFormat]); } } } - (void)dealloc { TEST_RELEASE (data); if (name && self == [nameDict objectForKey: name]) { [nameDict removeObjectForKey: name]; } TEST_RELEASE (name); TEST_RELEASE (uniqueIdentifier); [super dealloc]; } // // Creating an NSSound // - (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); } } return self; } - (id)initWithContentsOfURL:(NSURL *)url byReference:(BOOL)byRef { onlyReference = byRef; return [self initWithData: [NSData dataWithContentsOfURL: url]]; } - (id)initWithData:(NSData *)data { [self notImplemented: _cmd]; return nil; } - (id)initWithPasteboard:(NSPasteboard *)pasteboard { if ([NSSound canInitWithPasteboard: pasteboard] == YES) { NSData *d = [pasteboard dataForType: @"NSGeneralPboardType"]; return [self initWithData: d]; } return nil; } // // Playing // - (BOOL)pause { if (uniqueIdentifier) { return [[NSSound gsnd] pauseSoundWithIdentifier: uniqueIdentifier]; } return NO; } - (BOOL)play { return [[NSSound gsnd] playSound: self]; } - (BOOL)resume { if (uniqueIdentifier) { return [[NSSound gsnd] resumeSoundWithIdentifier: uniqueIdentifier]; } return NO; } - (BOOL)stop { if (uniqueIdentifier) { return [[NSSound gsnd] stopSoundWithIdentifier: uniqueIdentifier]; } return NO; } - (BOOL)isPlaying { if (uniqueIdentifier) { return [[NSSound gsnd] isPlayingSoundWithIdentifier: uniqueIdentifier]; } return NO; } // // Working with pasteboards // + (BOOL)canInitWithPasteboard:(NSPasteboard *)pasteboard { NSArray *pbTypes = [pasteboard types]; NSArray *myTypes = [NSSound soundUnfilteredPasteboardTypes]; return ([pbTypes firstObjectCommonWithArray: myTypes] != nil); } + (NSArray *)soundUnfilteredPasteboardTypes { return [NSArray arrayWithObjects: @"NSGeneralPboardType", nil]; } - (void)writeToPasteboard:(NSPasteboard *)pasteboard { NSData *d = [NSArchiver archivedDataWithRootObject: self]; if (d != nil) { [pasteboard declareTypes: [NSSound soundUnfilteredPasteboardTypes] owner: nil]; [pasteboard setData: d forType: @"NSGeneralPboardType"]; } } // // Working with delegates // - (id)delegate { return delegate; } - (void)setDelegate:(id)aDelegate { delegate = aDelegate; } // // Naming Sounds // + (id)soundNamed:(NSString *)aName { NSString *realName = [nsmapping objectForKey: aName]; NSSound *sound; if (realName) { aName = realName; } sound = (NSSound *)[nameDict objectForKey: aName]; if (sound == nil) { NSString *extension; NSString *path = nil; NSBundle *main_bundle; NSArray *array; NSString *the_name = aName; // 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]; extension = [aName pathExtension]; if (extension != nil && [extension length] == 0) { extension = nil; } /* Check if extension is one of the sound types */ array = [NSSound soundUnfilteredFileTypes]; if ([array indexOfObject: extension] != NSNotFound) { /* Extension is one of the sound types So remove from the name */ the_name = [aName stringByDeletingPathExtension]; } else { /* Otherwise extension is not an sound type So leave it alone */ the_name = aName; extension = nil; } /* First search locally */ if (extension) { path = [main_bundle pathForResource: the_name ofType: extension]; } else { id o, e; e = [array objectEnumerator]; while ((o = [e nextObject])) { path = [main_bundle pathForResource: the_name ofType: o]; if (path != nil && [path length] != 0) { break; } } } /* If not found then search in system */ if (!path) { if (extension) { #ifdef GNUSTEP_BASE_LIBRARY path = [NSBundle pathForGNUstepResource: the_name ofType: extension inDirectory: @"Sounds"]; #else NSBundle *system = [NSBundle bundleWithPath: @GNUSTEP_INSTALL_LIBDIR]; path = [system pathForResource: the_name ofType: extension inDirectory: @"Sounds"]; #endif } else { #ifdef GNUSTEP_BASE_LIBRARY id o, e; e = [array objectEnumerator]; while ((o = [e nextObject])) { path = [NSBundle pathForGNUstepResource: the_name ofType: o inDirectory: @"Sounds"]; if (path != nil && [path length] != 0) { break; } } #else NSBundle *system = [NSBundle bundleWithPath: @GNUSTEP_INSTALL_LIBDIR]; id o, e; e = [array objectEnumerator]; while ((o = [e nextObject])) { path = [system pathForResource: the_name ofType: o inDirectory: @"Sounds"]; if (path != nil && [path length] != 0) { break; } } #endif } } if ([path length] != 0) { sound = [[self allocWithZone: NSDefaultMallocZone()] initWithContentsOfFile: path byReference: NO]; if (sound != nil) { [sound setName: aName]; RELEASE(sound); sound->onlyReference = YES; } return sound; } } return sound; } + (NSArray *)soundUnfilteredFileTypes { return [NSArray arrayWithObjects: @"aiff", @"waw", @"snd", @"au", nil]; } - (NSString *)name { return name; } - (BOOL)setName:(NSString *)aName { BOOL retained = NO; if (!aName || [nameDict objectForKey: aName]) { return NO; } if (name && self == [nameDict objectForKey: name]) { /* We retain self in case removing from the dictionary releases us */ RETAIN (self); retained = YES; [nameDict removeObjectForKey: name]; } ASSIGN(name, aName); [nameDict setObject: self forKey: name]; if (retained) { RELEASE (self); } return YES; } // // NSCoding // - (void)encodeWithCoder:(NSCoder *)coder { [coder encodeValueOfObjCType: @encode(BOOL) at: &onlyReference]; [coder encodeObject: name]; if (onlyReference == YES) { return; } if (uniqueIdentifier != nil) { [coder encodeObject: uniqueIdentifier]; } [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]; } - (id)initWithCoder:(NSCoder*)decoder { [decoder decodeValueOfObjCType: @encode(BOOL) at: &onlyReference]; if (onlyReference == YES) { NSString *theName = [decoder decodeObject]; RELEASE (self); self = RETAIN ([NSSound soundNamed: theName]); [self setName: theName]; } else { NSData *d; name = [decoder decodeObject]; TEST_RETAIN (name); uniqueIdentifier = [decoder decodeObject]; if (uniqueIdentifier != nil) { RETAIN (uniqueIdentifier); } else { uniqueIdentifier = nil; } delegate = [decoder decodeObject]; if (delegate != nil) { [self setDelegate: delegate]; } else { delegate = nil; } [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]; d = [decoder decodeObject]; if (d != nil) { data = [d mutableCopy]; } else { data = nil; } } return self; } - (id)awakeAfterUsingCoder:(NSCoder *)coder { return self; } // // NSCopying // - (id)copyWithZone:(NSZone *)zone { NSSound *newSound = (NSSound *)NSCopyObject(self, 0, zone); newSound->dataLocation = dataLocation; newSound->dataSize = dataSize; newSound->dataFormat = dataFormat; newSound->samplingRate = samplingRate; newSound->frameSize = frameSize; newSound->frameCount = frameCount; newSound->channelCount = channelCount; newSound->data = [data mutableCopy]; newSound->name = [name copy]; if (uniqueIdentifier != nil) { newSound->uniqueIdentifier = [uniqueIdentifier copy]; } else { newSound->uniqueIdentifier = nil; } newSound->onlyReference = onlyReference; newSound->delegate = delegate; return newSound; } @end