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
This commit is contained in:
@@ -2,32 +2,33 @@
|
|||||||
// Name: sound.h
|
// Name: sound.h
|
||||||
// Purpose: wxSound class (loads and plays short Windows .wav files).
|
// Purpose: wxSound class (loads and plays short Windows .wav files).
|
||||||
// Optional on non-Windows platforms.
|
// Optional on non-Windows platforms.
|
||||||
// Author: Ryan Norton
|
// Authors: David Elliott, Ryan Norton
|
||||||
// Modified by:
|
// Modified by:
|
||||||
// Created: 2004-10-02
|
// Created: 2004-10-02
|
||||||
// RCS-ID: $Id$
|
// RCS-ID: $Id$
|
||||||
// Copyright: (c) Ryan Norton
|
// Copyright: (c) 2004 David Elliott, Ryan Norton
|
||||||
// Licence: wxWindows licence
|
// Licence: wxWindows licence
|
||||||
/////////////////////////////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
#ifndef _WX_COCOA_SOUND_H_
|
#ifndef _WX_COCOA_SOUND_H_
|
||||||
#define _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/object.h"
|
||||||
#include "wx/cocoa/ObjcRef.h"
|
#include "wx/cocoa/ObjcRef.h"
|
||||||
|
|
||||||
class WXDLLEXPORT wxSound : public wxSoundBase
|
class WXDLLEXPORT wxSound : public wxSoundBase
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
wxSound();
|
wxSound()
|
||||||
wxSound(const wxString& fileName, bool isResource = false);
|
: m_cocoaNSSound(NULL)
|
||||||
wxSound(int size, const wxByte* data);
|
{}
|
||||||
|
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();
|
~wxSound();
|
||||||
|
|
||||||
public:
|
public:
|
||||||
@@ -37,18 +38,15 @@ public:
|
|||||||
static void Stop();
|
static void Stop();
|
||||||
static bool IsPlaying();
|
static bool IsPlaying();
|
||||||
|
|
||||||
|
void SetNSSound(WX_NSSound cocoaNSSound);
|
||||||
inline WX_NSSound GetNSSound()
|
inline WX_NSSound GetNSSound()
|
||||||
{ return m_cocoaNSSound; }
|
{ return m_cocoaNSSound; }
|
||||||
protected:
|
protected:
|
||||||
bool DoPlay(unsigned flags) const;
|
bool DoPlay(unsigned flags) const;
|
||||||
|
bool LoadWAV(const wxUint8 *data, size_t length, bool copyData);
|
||||||
private:
|
private:
|
||||||
WX_NSSound m_cocoaNSSound; //NSSound handle
|
WX_NSSound m_cocoaNSSound;
|
||||||
wxString m_sndname; //file path
|
|
||||||
int m_waveLength; //size of file in memory mode
|
|
||||||
static const wxObjcAutoRefFromAlloc<struct objc_object *> sm_cocoaDelegate;
|
static const wxObjcAutoRefFromAlloc<struct objc_object *> sm_cocoaDelegate;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif //ndef _WX_COCOA_SOUND_H_
|
||||||
#endif
|
|
||||||
// _WX_COCOA_SOUND_H_
|
|
||||||
|
@@ -1,39 +1,33 @@
|
|||||||
/////////////////////////////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////////////////////////////
|
||||||
// Name: sound.cpp
|
// Name: sound.cpp
|
||||||
// Purpose: wxSound class implementation: optional
|
// Purpose: wxSound class implementation: optional
|
||||||
// Author: Ryan Norton
|
// Authors: David Elliott, Ryan Norton
|
||||||
// Modified by:
|
// Modified by:
|
||||||
// Created: 2004-10-02
|
// Created: 2004-10-02
|
||||||
// RCS-ID: $Id$
|
// RCS-ID: $Id$
|
||||||
// Copyright: (c) Ryan Norton
|
// Copyright: (c) 2004 David Elliott, Ryan Norton
|
||||||
// Licence: wxWindows licence
|
// Licence: wxWindows licence
|
||||||
/////////////////////////////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
#ifdef __GNUG__
|
#include "wx/wxprec.h"
|
||||||
#pragma implementation "sound.h"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#include "wx/object.h"
|
|
||||||
#include "wx/string.h"
|
|
||||||
#include "wx/sound.h"
|
|
||||||
|
|
||||||
#if wxUSE_SOUND
|
#if wxUSE_SOUND
|
||||||
|
|
||||||
#include "wx/app.h"
|
#ifndef WX_PRECOMP
|
||||||
|
#include "wx/app.h"
|
||||||
|
#include "wx/log.h"
|
||||||
|
#endif //ndef WX_PRECOMP
|
||||||
|
#include "wx/sound.h"
|
||||||
|
#include "wx/evtloop.h"
|
||||||
|
|
||||||
#include "wx/cocoa/autorelease.h"
|
#include "wx/cocoa/autorelease.h"
|
||||||
#include "wx/cocoa/string.h"
|
#include "wx/cocoa/string.h"
|
||||||
|
#include "wx/cocoa/log.h"
|
||||||
|
|
||||||
#import <AppKit/AppKit.h>
|
#import <AppKit/NSSound.h>
|
||||||
|
#import <Foundation/NSData.h>
|
||||||
|
|
||||||
//
|
static WX_NSSound s_currentSound = nil;
|
||||||
// NB: Vaclav's new wxSound API is really tricky -
|
static bool s_loopCurrentSound = false;
|
||||||
// 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;
|
|
||||||
|
|
||||||
// ========================================================================
|
// ========================================================================
|
||||||
// wxNSSoundDelegate
|
// wxNSSoundDelegate
|
||||||
@@ -43,18 +37,31 @@ bool isLastSoundInScope = false;
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Delegate methods
|
// Delegate methods
|
||||||
- (void)sound:(NSSound *)theSound didFinishPlaying:(BOOL)bOK;
|
- (void)sound:(NSSound *)theSound didFinishPlaying:(BOOL)finishedPlaying;
|
||||||
@end // interface wxNSSoundDelegate : NSObject
|
@end // interface wxNSSoundDelegate : NSObject
|
||||||
|
|
||||||
@implementation wxNSSoundDelegate : NSObject
|
@implementation wxNSSoundDelegate : NSObject
|
||||||
|
|
||||||
- (void)sound:(NSSound *)theSound didFinishPlaying:(BOOL)bOK
|
- (void)sound:(NSSound *)theSound didFinishPlaying:(BOOL)finishedPlaying
|
||||||
{
|
{
|
||||||
if (bOK && isLastSoundLooping)
|
// If s_currentSound is not us then some other sound has played.
|
||||||
[lastSound play];
|
// We can safely ignore this as s_currentSound will have been released
|
||||||
else if (!isLastSoundInScope)
|
// 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::wxSound()
|
wxSound::wxSound(const wxSound& sound)
|
||||||
: m_cocoaNSSound(nil)
|
: m_cocoaNSSound(sound.m_cocoaNSSound)
|
||||||
, m_waveLength(0)
|
|
||||||
{
|
{
|
||||||
}
|
[m_cocoaNSSound retain];
|
||||||
|
|
||||||
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];
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
wxSound::~wxSound()
|
wxSound::~wxSound()
|
||||||
{
|
{
|
||||||
if (m_cocoaNSSound != lastSound)
|
SetNSSound(nil);
|
||||||
{
|
|
||||||
[m_cocoaNSSound release];
|
|
||||||
}
|
|
||||||
else
|
|
||||||
isLastSoundInScope = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool wxSound::Create(const wxString& fileName, bool isResource)
|
bool wxSound::Create(const wxString& fileName, bool isResource)
|
||||||
{
|
{
|
||||||
wxAutoNSAutoreleasePool thePool;
|
wxAutoNSAutoreleasePool thePool;
|
||||||
|
|
||||||
Stop();
|
|
||||||
|
|
||||||
if (isResource)
|
if (isResource)
|
||||||
{
|
SetNSSound([NSSound soundNamed:wxNSStringWithWxString(fileName)]);
|
||||||
//oftype could be @"snd" @"wav" or @"aiff"; nil or @"" autodetects (?)
|
|
||||||
m_cocoaNSSound = [[NSSound alloc]
|
|
||||||
initWithContentsOfFile:[[NSBundle mainBundle]
|
|
||||||
pathForResource:wxNSStringWithWxString(fileName)
|
|
||||||
ofType:nil]
|
|
||||||
byReference:YES];
|
|
||||||
}
|
|
||||||
else
|
else
|
||||||
m_cocoaNSSound = [[NSSound alloc] initWithContentsOfFile:wxNSStringWithWxString(fileName) byReference:YES];
|
{
|
||||||
|
SetNSSound([[NSSound alloc] initWithContentsOfFile:wxNSStringWithWxString(fileName) byReference:YES]);
|
||||||
|
[m_cocoaNSSound release];
|
||||||
|
}
|
||||||
|
|
||||||
m_sndname = fileName;
|
|
||||||
return m_cocoaNSSound;
|
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
|
bool wxSound::DoPlay(unsigned flags) const
|
||||||
{
|
{
|
||||||
wxASSERT_MSG(!( (flags & wxSOUND_SYNC) && (flags & wxSOUND_LOOP)),
|
Stop(); // this releases and nils s_currentSound
|
||||||
wxT("Invalid flag combination passed to wxSound::Play"));
|
|
||||||
|
|
||||||
Stop();
|
// 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.
|
||||||
|
|
||||||
|
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)
|
if (flags & wxSOUND_ASYNC)
|
||||||
{
|
|
||||||
lastSound = m_cocoaNSSound;
|
|
||||||
isLastSoundLooping = (flags & wxSOUND_LOOP) == wxSOUND_LOOP;
|
|
||||||
isLastSoundInScope = true;
|
|
||||||
[m_cocoaNSSound setDelegate:sm_cocoaDelegate];
|
|
||||||
return [m_cocoaNSSound play];
|
return [m_cocoaNSSound play];
|
||||||
}
|
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
[m_cocoaNSSound setDelegate:nil];
|
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;
|
||||||
|
|
||||||
//play until done
|
if(![m_cocoaNSSound play])
|
||||||
bool bOK = [m_cocoaNSSound play];
|
return false;
|
||||||
|
|
||||||
while ([m_cocoaNSSound isPlaying])
|
// Process events until the delegate sets s_currentSound to nil
|
||||||
{
|
// and/or a different sound plays.
|
||||||
wxTheApp->Yield(false);
|
while (s_currentSound==m_cocoaNSSound)
|
||||||
}
|
wxEventLoop::GetActive()->Dispatch();
|
||||||
return bOK;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool wxSound::IsPlaying()
|
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()
|
void wxSound::Stop()
|
||||||
{
|
{
|
||||||
if (isLastSoundInScope)
|
// Clear the looping flag so that if the sound finishes playing before
|
||||||
{
|
// stop is called the sound will already be released and niled.
|
||||||
isLastSoundInScope = false;
|
s_loopCurrentSound = false;
|
||||||
|
[s_currentSound stop];
|
||||||
//remember that even though we're
|
/* It's possible that sound:didFinishPlaying: was called and released
|
||||||
//programatically stopping it, the
|
s_currentSound but it doesn't matter since it will have set it to nil */
|
||||||
//delegate will still be called -
|
if(s_currentSound) wxLogTrace(wxTRACE_COCOA_RetainRelease,wxT("wxSound::Stop [s_currentSound=%p retainCount]=%d (about to release)"),s_currentSound,[s_currentSound retainCount]);
|
||||||
//so it will free the memory here
|
[s_currentSound release];
|
||||||
[((NSSound*&)lastSound) stop];
|
s_currentSound = nil;
|
||||||
}
|
|
||||||
|
|
||||||
lastSound = nil;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif //wxUSE_SOUND
|
#endif //wxUSE_SOUND
|
||||||
|
Reference in New Issue
Block a user