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