From 85c9f98b509e8a7870a99fb580775f6fda4cf38e Mon Sep 17 00:00:00 2001 From: David Elliott Date: Wed, 20 Oct 2004 21:04:52 +0000 Subject: [PATCH] Rewrote wxSound: * Get rid of #if wxUSE_SOUND from header. wx/sound.h checks this already. * Get rid of pragma interface/implementation. Apple GCC dislikes them anyway. * Allow source file to use precompiled headers (wx/wxprec.h) * Include only needed AppKit/Foundation headers, not AppKit/AppKit.h. * Implement simple constructors inline in header. * Get rid of m_sndname and m_waveLength instance variables. They aren't used. * Add copy constructor (why not). * Move implementation of byte-array constructor into LoadWAV for consistency with UNIX wxSound. * LoadWAV (what was in the constructor) now properly allocs, inits, and releases NSData. The old code for this was not valid. * Rename lastSound to s_currentSound. * Rename isLastSoundLooping to s_loopCurrentSound. * Ignore the sound:didFinishPlaying: delegate message if it is received for an NSSound other than s_currentSound. * Create should not Stop the current sound. * Don't use NSBundle to get a resource sound but use [NSSound soundNamed:] which will include system sounds. * Playing a sound synchronously uses wxEventLoop::Dispatch which will really block (not spin the CPU like Yield). The sound is considered finished playing when s_currentSound is set to something else. In order to make sure we don't get stuck in this event loop the delegate calls wxApp::WakeUpIdle if it releases s_currentSound. * Have IsPlaying() check to make sure s_currentSound is not nil since only messages returning another object or void are allowed to be sent to nil objects. Changes involving retain/release * Get rid of comment about tricky API, it's not. * Get rid of isLastSoundInScope. Cocoa has proper reference counting. * Add SetNSSound which, like the rest of wxCocoa, retains/releases appropriately, sets the delegate, and logs when WXTRACE=COCOA_RetainRelease. * Destructor does SetNSSound(nil) which will always release the NSSound. Create and LoadWAV use SetNSSound method like the rest of wxCocoa. * Make the delegate always release s_currentSound if not (or if done) looping. DoPlay sets s_currentSound to m_cocoaNSSound after retaining it so that the delegate can always safely release it. * Stop, like everything else, does not need check of isLastSoundInScope git-svn-id: https://svn.wxwidgets.org/svn/wx/wxWidgets/trunk@30043 c3d73ce0-8a6f-49c7-b76d-6d57e0e08775 --- include/wx/cocoa/sound.h | 36 ++++--- src/cocoa/sound.mm | 200 +++++++++++++++++++++------------------ 2 files changed, 123 insertions(+), 113 deletions(-) diff --git a/include/wx/cocoa/sound.h b/include/wx/cocoa/sound.h index 86a2ca9a73..c85e7d3978 100644 --- a/include/wx/cocoa/sound.h +++ b/include/wx/cocoa/sound.h @@ -2,32 +2,33 @@ // Name: sound.h // Purpose: wxSound class (loads and plays short Windows .wav files). // Optional on non-Windows platforms. -// Author: Ryan Norton -// Modified by: +// Authors: David Elliott, Ryan Norton +// Modified by: // Created: 2004-10-02 // RCS-ID: $Id$ -// Copyright: (c) Ryan Norton +// Copyright: (c) 2004 David Elliott, Ryan Norton // Licence: wxWindows licence ///////////////////////////////////////////////////////////////////////////// #ifndef _WX_COCOA_SOUND_H_ #define _WX_COCOA_SOUND_H_ -#if defined(__GNUG__) && !defined(NO_GCC_PRAGMA) -#pragma interface "sound.h" -#endif - -#if wxUSE_SOUND - #include "wx/object.h" #include "wx/cocoa/ObjcRef.h" class WXDLLEXPORT wxSound : public wxSoundBase { public: - wxSound(); - wxSound(const wxString& fileName, bool isResource = false); - wxSound(int size, const wxByte* data); + wxSound() + : m_cocoaNSSound(NULL) + {} + wxSound(const wxString& fileName, bool isResource = false) + : m_cocoaNSSound(NULL) + { Create(fileName, isResource); } + wxSound(int size, const wxByte* data) + : m_cocoaNSSound(NULL) + { LoadWAV(data,size,true); } + wxSound(const wxSound& sound); // why not? ~wxSound(); public: @@ -37,18 +38,15 @@ public: static void Stop(); static bool IsPlaying(); + void SetNSSound(WX_NSSound cocoaNSSound); inline WX_NSSound GetNSSound() { return m_cocoaNSSound; } protected: bool DoPlay(unsigned flags) const; - + bool LoadWAV(const wxUint8 *data, size_t length, bool copyData); private: - WX_NSSound m_cocoaNSSound; //NSSound handle - wxString m_sndname; //file path - int m_waveLength; //size of file in memory mode + WX_NSSound m_cocoaNSSound; static const wxObjcAutoRefFromAlloc sm_cocoaDelegate; }; -#endif -#endif - // _WX_COCOA_SOUND_H_ +#endif //ndef _WX_COCOA_SOUND_H_ diff --git a/src/cocoa/sound.mm b/src/cocoa/sound.mm index abba845d8b..dbafe8f5f6 100644 --- a/src/cocoa/sound.mm +++ b/src/cocoa/sound.mm @@ -1,39 +1,33 @@ ///////////////////////////////////////////////////////////////////////////// // Name: sound.cpp // Purpose: wxSound class implementation: optional -// Author: Ryan Norton +// Authors: David Elliott, Ryan Norton // Modified by: // Created: 2004-10-02 // RCS-ID: $Id$ -// Copyright: (c) Ryan Norton +// Copyright: (c) 2004 David Elliott, Ryan Norton // Licence: wxWindows licence ///////////////////////////////////////////////////////////////////////////// -#ifdef __GNUG__ -#pragma implementation "sound.h" -#endif +#include "wx/wxprec.h" +#if wxUSE_SOUND -#include "wx/object.h" -#include "wx/string.h" +#ifndef WX_PRECOMP + #include "wx/app.h" + #include "wx/log.h" +#endif //ndef WX_PRECOMP #include "wx/sound.h" +#include "wx/evtloop.h" -#if wxUSE_SOUND - -#include "wx/app.h" #include "wx/cocoa/autorelease.h" #include "wx/cocoa/string.h" +#include "wx/cocoa/log.h" -#import +#import +#import -// -// NB: Vaclav's new wxSound API is really tricky - -// Basically, we need to make sure that if the wxSound -// object is still in scope we don't release it's NSSound -// - -WX_NSSound lastSound=NULL; -bool isLastSoundLooping = false; -bool isLastSoundInScope = false; +static WX_NSSound s_currentSound = nil; +static bool s_loopCurrentSound = false; // ======================================================================== // wxNSSoundDelegate @@ -43,18 +37,31 @@ bool isLastSoundInScope = false; } // Delegate methods -- (void)sound:(NSSound *)theSound didFinishPlaying:(BOOL)bOK; +- (void)sound:(NSSound *)theSound didFinishPlaying:(BOOL)finishedPlaying; @end // interface wxNSSoundDelegate : NSObject @implementation wxNSSoundDelegate : NSObject -- (void)sound:(NSSound *)theSound didFinishPlaying:(BOOL)bOK +- (void)sound:(NSSound *)theSound didFinishPlaying:(BOOL)finishedPlaying { - if (bOK && isLastSoundLooping) - [lastSound play]; - else if (!isLastSoundInScope) + // If s_currentSound is not us then some other sound has played. + // We can safely ignore this as s_currentSound will have been released + // before being set to a different value. + if(s_currentSound!=theSound) + return; + // If playing finished successfully and we are looping, play again. + if (finishedPlaying && s_loopCurrentSound) + [s_currentSound play]; + // Otherwise we are done, there is no more current sound playing. + else { - [lastSound release]; + if(s_currentSound) wxLogTrace(wxTRACE_COCOA_RetainRelease,wxT("[wxNSSoundDelegate -sound:didFinishPlaying:] [s_currentSound=%p retainCount]=%d (about to release)"),s_currentSound,[s_currentSound retainCount]); + [s_currentSound release]; + s_currentSound = nil; + // Make sure we get out of any modal event loops immediately. + // NOTE: When the sound finishes playing Cocoa normally does have + // an event so this is probably not necessary. + wxTheApp->WakeUpIdle(); } } @@ -66,109 +73,114 @@ const wxObjcAutoRefFromAlloc wxSound::sm_cocoaDelegate = [[ // wxSound // ------------------------------------------------------------------ -wxSound::wxSound() -: m_cocoaNSSound(nil) -, m_waveLength(0) +wxSound::wxSound(const wxSound& sound) +: m_cocoaNSSound(sound.m_cocoaNSSound) { -} - -wxSound::wxSound(const wxString& sFileName, bool isResource) -: m_cocoaNSSound(nil) -, m_waveLength(0) -{ - Create(sFileName, isResource); -} - -wxSound::wxSound(int size, const wxByte* data) -: m_cocoaNSSound(nil) -, m_waveLength(size) -{ - NSData* theData = [[NSData alloc] dataWithBytesNoCopy:(void*)data length:size]; - m_cocoaNSSound = [[NSSound alloc] initWithData:theData]; - + [m_cocoaNSSound retain]; } wxSound::~wxSound() { - if (m_cocoaNSSound != lastSound) - { - [m_cocoaNSSound release]; - } - else - isLastSoundInScope = false; + SetNSSound(nil); } bool wxSound::Create(const wxString& fileName, bool isResource) { wxAutoNSAutoreleasePool thePool; - Stop(); - if (isResource) + SetNSSound([NSSound soundNamed:wxNSStringWithWxString(fileName)]); + else { - //oftype could be @"snd" @"wav" or @"aiff"; nil or @"" autodetects (?) - m_cocoaNSSound = [[NSSound alloc] - initWithContentsOfFile:[[NSBundle mainBundle] - pathForResource:wxNSStringWithWxString(fileName) - ofType:nil] - byReference:YES]; + SetNSSound([[NSSound alloc] initWithContentsOfFile:wxNSStringWithWxString(fileName) byReference:YES]); + [m_cocoaNSSound release]; } - else - m_cocoaNSSound = [[NSSound alloc] initWithContentsOfFile:wxNSStringWithWxString(fileName) byReference:YES]; - m_sndname = fileName; return m_cocoaNSSound; } +bool wxSound::LoadWAV(const wxUint8 *data, size_t length, bool copyData) +{ + NSData* theData; + if(copyData) + theData = [[NSData alloc] initWithBytes:const_cast(data) length:length]; + else + theData = [[NSData alloc] initWithBytesNoCopy:const_cast(data) length:length]; + SetNSSound([[NSSound alloc] initWithData:theData]); + [m_cocoaNSSound release]; + [theData release]; + return m_cocoaNSSound; +} + +void wxSound::SetNSSound(WX_NSSound cocoaNSSound) +{ + bool need_debug = cocoaNSSound || m_cocoaNSSound; + 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]); + [cocoaNSSound retain]; + [m_cocoaNSSound release]; + m_cocoaNSSound = cocoaNSSound; + [m_cocoaNSSound setDelegate:sm_cocoaDelegate]; + if(need_debug) wxLogTrace(wxTRACE_COCOA_RetainRelease,wxT("wxSound=%p::SetNSSound [cocoaNSSound=%p retainCount]=%d (just retained)"),this,cocoaNSSound,[cocoaNSSound retainCount]); +} + bool wxSound::DoPlay(unsigned flags) const { - wxASSERT_MSG(!( (flags & wxSOUND_SYNC) && (flags & wxSOUND_LOOP)), - wxT("Invalid flag combination passed to wxSound::Play")); + Stop(); // this releases and nils s_currentSound + + // NOTE: We set s_currentSound to the current sound in all cases so that + // functions like Stop and IsPlaying can work. It is NOT necessary for + // the NSSound to be retained by us for it to continue playing. Cocoa + // retains the NSSound when it is played and relases it when finished. - Stop(); + wxASSERT(!s_currentSound); + s_currentSound = [m_cocoaNSSound retain]; + wxLogTrace(wxTRACE_COCOA_RetainRelease,wxT("wxSound=%p::DoPlay [s_currentSound=%p retainCount]=%d (just retained)"),this,s_currentSound,[s_currentSound retainCount]); + s_loopCurrentSound = (flags & wxSOUND_LOOP) == wxSOUND_LOOP; if (flags & wxSOUND_ASYNC) - { - lastSound = m_cocoaNSSound; - isLastSoundLooping = (flags & wxSOUND_LOOP) == wxSOUND_LOOP; - isLastSoundInScope = true; - [m_cocoaNSSound setDelegate:sm_cocoaDelegate]; return [m_cocoaNSSound play]; - } else { - [m_cocoaNSSound setDelegate:nil]; - - //play until done - bool bOK = [m_cocoaNSSound play]; - - while ([m_cocoaNSSound isPlaying]) - { - wxTheApp->Yield(false); - } - return bOK; + wxASSERT_MSG(!s_loopCurrentSound,wxT("It is silly to block waiting for a looping sound to finish. Disabling looping")); + // actually, it'd probably work although it's kind of stupid to + // block here waiting for a sound that's never going to end. + // Granted Stop() could be called somehow, but again, silly. + s_loopCurrentSound = false; + + if(![m_cocoaNSSound play]) + return false; + + // Process events until the delegate sets s_currentSound to nil + // and/or a different sound plays. + while (s_currentSound==m_cocoaNSSound) + wxEventLoop::GetActive()->Dispatch(); + return true; } } bool wxSound::IsPlaying() { - return [lastSound isPlaying]; + // Normally you can send a message to a nil object and it will return + // nil. That behavior would probably be okay here but in general it's + // not recommended to send a message to a nil object if the return + // value is not an object. Better safe than sorry. + if(s_currentSound) + return [s_currentSound isPlaying]; + else + return false; } void wxSound::Stop() { - if (isLastSoundInScope) - { - isLastSoundInScope = false; - - //remember that even though we're - //programatically stopping it, the - //delegate will still be called - - //so it will free the memory here - [((NSSound*&)lastSound) stop]; - } - - lastSound = nil; + // Clear the looping flag so that if the sound finishes playing before + // stop is called the sound will already be released and niled. + s_loopCurrentSound = false; + [s_currentSound stop]; + /* It's possible that sound:didFinishPlaying: was called and released + s_currentSound but it doesn't matter since it will have set it to nil */ + if(s_currentSound) wxLogTrace(wxTRACE_COCOA_RetainRelease,wxT("wxSound::Stop [s_currentSound=%p retainCount]=%d (about to release)"),s_currentSound,[s_currentSound retainCount]); + [s_currentSound release]; + s_currentSound = nil; } #endif //wxUSE_SOUND -- 2.45.2