1///////////////////////////////////////////////////////////////////////////// 2// Name: src/cocoa/sound.mm 3// Purpose: wxSound class implementation: optional 4// Authors: David Elliott, Ryan Norton 5// Modified by: 6// Created: 2004-10-02 7// Copyright: (c) 2004 David Elliott, Ryan Norton 8// Licence: wxWindows licence 9///////////////////////////////////////////////////////////////////////////// 10 11#include "wx/wxprec.h" 12#if wxUSE_SOUND 13 14#ifndef WX_PRECOMP 15 #include "wx/app.h" 16 #include "wx/log.h" 17#endif //ndef WX_PRECOMP 18#include "wx/sound.h" 19#include "wx/evtloop.h" 20 21#include "wx/cocoa/autorelease.h" 22#include "wx/cocoa/string.h" 23#include "wx/cocoa/log.h" 24 25#include "wx/cocoa/objc/objc_uniquifying.h" 26 27#import <AppKit/NSSound.h> 28#import <Foundation/NSData.h> 29 30static WX_NSSound s_currentSound = nil; 31static bool s_loopCurrentSound = false; 32 33// ======================================================================== 34// wxNSSoundDelegate 35// ======================================================================== 36@interface wxNSSoundDelegate : NSObject 37{ 38} 39 40// Delegate methods 41- (void)sound:(NSSound *)theSound didFinishPlaying:(BOOL)finishedPlaying; 42@end // interface wxNSSoundDelegate : NSObject 43WX_DECLARE_GET_OBJC_CLASS(wxNSSoundDelegate,NSObject) 44 45@implementation wxNSSoundDelegate : NSObject 46 47- (void)sound:(NSSound *)theSound didFinishPlaying:(BOOL)finishedPlaying 48{ 49 // If s_currentSound is not us then some other sound has played. 50 // We can safely ignore this as s_currentSound will have been released 51 // before being set to a different value. 52 if(s_currentSound!=theSound) 53 return; 54 // If playing finished successfully and we are looping, play again. 55 if (finishedPlaying && s_loopCurrentSound) 56 [s_currentSound play]; 57 // Otherwise we are done, there is no more current sound playing. 58 else 59 { 60 if(s_currentSound) wxLogTrace(wxTRACE_COCOA_RetainRelease,wxT("[wxNSSoundDelegate -sound:didFinishPlaying:] [s_currentSound=%p retainCount]=%d (about to release)"),s_currentSound,[s_currentSound retainCount]); 61 [s_currentSound release]; 62 s_currentSound = nil; 63 // Make sure we get out of any modal event loops immediately. 64 // NOTE: When the sound finishes playing Cocoa normally does have 65 // an event so this is probably not necessary. 66 wxTheApp->WakeUpIdle(); 67 } 68} 69 70@end // wxNSSoundDelegate 71WX_IMPLEMENT_GET_OBJC_CLASS(wxNSSoundDelegate,NSObject) 72 73const wxObjcAutoRefFromAlloc<struct objc_object*> wxSound::sm_cocoaDelegate = [[WX_GET_OBJC_CLASS(wxNSSoundDelegate) alloc] init]; 74 75// ------------------------------------------------------------------ 76// wxSound 77// ------------------------------------------------------------------ 78 79wxSound::wxSound(const wxSound& sound) 80: m_cocoaNSSound(sound.m_cocoaNSSound) 81{ 82 [m_cocoaNSSound retain]; 83} 84 85wxSound::~wxSound() 86{ 87 SetNSSound(nil); 88} 89 90bool wxSound::Create(const wxString& fileName, bool isResource) 91{ 92 wxAutoNSAutoreleasePool thePool; 93 94 if (isResource) 95 SetNSSound([NSSound soundNamed:wxNSStringWithWxString(fileName)]); 96 else 97 { 98 SetNSSound([[NSSound alloc] initWithContentsOfFile:wxNSStringWithWxString(fileName) byReference:YES]); 99 [m_cocoaNSSound release]; 100 } 101 102 return m_cocoaNSSound; 103} 104 105bool wxSound::LoadWAV(const wxUint8 *data, size_t length, bool copyData) 106{ 107 NSData* theData; 108 if(copyData) 109 theData = [[NSData alloc] initWithBytes:const_cast<wxUint8*>(data) length:length]; 110 else 111 theData = [[NSData alloc] initWithBytesNoCopy:const_cast<wxUint8*>(data) length:length]; 112 SetNSSound([[NSSound alloc] initWithData:theData]); 113 [m_cocoaNSSound release]; 114 [theData release]; 115 return m_cocoaNSSound; 116} 117 118void wxSound::SetNSSound(WX_NSSound cocoaNSSound) 119{ 120 bool need_debug = cocoaNSSound || m_cocoaNSSound; 121 if(need_debug) wxLogTrace(wxTRACE_COCOA_RetainRelease,wxT("wxSound=%p::SetNSSound [m_cocoaNSSound=%p retainCount]=%d (about to release)"),this,m_cocoaNSSound,[m_cocoaNSSound retainCount]); 122 [cocoaNSSound retain]; 123 [m_cocoaNSSound release]; 124 m_cocoaNSSound = cocoaNSSound; 125 [m_cocoaNSSound setDelegate:sm_cocoaDelegate]; 126 if(need_debug) wxLogTrace(wxTRACE_COCOA_RetainRelease,wxT("wxSound=%p::SetNSSound [cocoaNSSound=%p retainCount]=%d (just retained)"),this,cocoaNSSound,[cocoaNSSound retainCount]); 127} 128 129bool wxSound::DoPlay(unsigned flags) const 130{ 131 Stop(); // this releases and nils s_currentSound 132 133 // NOTE: We set s_currentSound to the current sound in all cases so that 134 // functions like Stop and IsPlaying can work. It is NOT necessary for 135 // the NSSound to be retained by us for it to continue playing. Cocoa 136 // retains the NSSound when it is played and relases it when finished. 137 138 wxASSERT(!s_currentSound); 139 s_currentSound = [m_cocoaNSSound retain]; 140 wxLogTrace(wxTRACE_COCOA_RetainRelease,wxT("wxSound=%p::DoPlay [s_currentSound=%p retainCount]=%d (just retained)"),this,s_currentSound,[s_currentSound retainCount]); 141 s_loopCurrentSound = (flags & wxSOUND_LOOP) == wxSOUND_LOOP; 142 143 if (flags & wxSOUND_ASYNC) 144 return [m_cocoaNSSound play]; 145 else 146 { 147 wxASSERT_MSG(!s_loopCurrentSound,wxT("It is silly to block waiting for a looping sound to finish. Disabling looping")); 148 // actually, it'd probably work although it's kind of stupid to 149 // block here waiting for a sound that's never going to end. 150 // Granted Stop() could be called somehow, but again, silly. 151 s_loopCurrentSound = false; 152 153 if(![m_cocoaNSSound play]) 154 return false; 155 156 // Process events until the delegate sets s_currentSound to nil 157 // and/or a different sound plays. 158 while (s_currentSound==m_cocoaNSSound) 159 wxEventLoop::GetActive()->Dispatch(); 160 return true; 161 } 162} 163 164bool wxSound::IsPlaying() 165{ 166 // Normally you can send a message to a nil object and it will return 167 // nil. That behaviour would probably be okay here but in general it's 168 // not recommended to send a message to a nil object if the return 169 // value is not an object. Better safe than sorry. 170 if(s_currentSound) 171 return [s_currentSound isPlaying]; 172 else 173 return false; 174} 175 176void wxSound::Stop() 177{ 178 // Clear the looping flag so that if the sound finishes playing before 179 // stop is called the sound will already be released and niled. 180 s_loopCurrentSound = false; 181 [s_currentSound stop]; 182 /* It's possible that sound:didFinishPlaying: was called and released 183 s_currentSound but it doesn't matter since it will have set it to nil */ 184 if(s_currentSound) wxLogTrace(wxTRACE_COCOA_RetainRelease,wxT("wxSound::Stop [s_currentSound=%p retainCount]=%d (about to release)"),s_currentSound,[s_currentSound retainCount]); 185 [s_currentSound release]; 186 s_currentSound = nil; 187} 188 189#endif //wxUSE_SOUND 190