]> git.saurik.com Git - wxWidgets.git/commitdiff
Rewrote wxSound:
authorDavid Elliott <dfe@tgwbd.org>
Wed, 20 Oct 2004 21:04:52 +0000 (21:04 +0000)
committerDavid Elliott <dfe@tgwbd.org>
Wed, 20 Oct 2004 21:04:52 +0000 (21:04 +0000)
* 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
src/cocoa/sound.mm

index 86a2ca9a730aafeb0938bd0ce3259eb16000e308..c85e7d39783f35918b8cc0a52681af9a1b5ad043 100644 (file)
@@ -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<struct objc_object *> sm_cocoaDelegate;
 };
 
-#endif
-#endif
-    // _WX_COCOA_SOUND_H_
+#endif //ndef _WX_COCOA_SOUND_H_
index abba845d8b76f68a2e3dc0db5be6d3e3e4f46e18..dbafe8f5f6eb114de31aa5347cd2d69165b766f9 100644 (file)
@@ -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 <AppKit/AppKit.h>
+#import <AppKit/NSSound.h>
+#import <Foundation/NSData.h>
 
-//
-// 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<struct objc_object*> 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<wxUint8*>(data) length:length];
+    else
+        theData = [[NSData alloc] initWithBytesNoCopy:const_cast<wxUint8*>(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