]> git.saurik.com Git - wxWidgets.git/blobdiff - src/mac/carbon/sound.cpp
fixed crash introduced in v1.41
[wxWidgets.git] / src / mac / carbon / sound.cpp
index d6d13bb595afcd12325c80f404d9f88fa7bccf52..9af87b7e0ed1f892b3005334ee215ec146cce077 100644 (file)
 /////////////////////////////////////////////////////////////////////////////
 // Name:        sound.cpp
 // Purpose:     wxSound class implementation: optional
-// Author:      Stefan Csomor
-// Modified by:
+// Author:      Ryan Norton
+// Modified by: Stefan Csomor
 // Created:     1998-01-01
 // RCS-ID:      $Id$
-// Copyright:   (c) Stefan Csomor
+// Copyright:   (c) Ryan Norton
 // Licence:       wxWindows licence
 /////////////////////////////////////////////////////////////////////////////
 
-#ifdef __GNUG__
-#pragma implementation "sound.h"
-#endif
+#include "wx/wxprec.h"
 
 #include "wx/object.h"
 #include "wx/string.h"
+#include "wx/log.h"
+#include "wx/file.h"
 #include "wx/sound.h"
+#include "wx/timer.h"
+#include "wx/intl.h"
 
 #if wxUSE_SOUND
 
+// Carbon QT Implementation Details -
+//
+// Memory:
+// 1) OpenDefaultComponent(MovieImportType, kQTFileTypeWave);
+// 2) NewMovie(0);
+// 3) MovieImportDataRef() //Pass Memory Location to this
+// 4) PlayMovie();
+// 5) IsMovieDone(), MoviesTask() //2nd param is minimum wait time to allocate to quicktime
+//
+// File:
+// 1) Obtain FSSpec
+// 2) Call OpenMovieFile
+// 3) Call NewMovieFromFile
+// 4) Call CloseMovieFile
+// 4) PlayMovie();
+// 5) IsMovieDone(), MoviesTask() //2nd param is minimum wait time to allocate to quicktime
+//
+
 #ifdef __WXMAC__
-#include "wx/mac/private.h"
+#include "wx/mac/uma.h"
 #ifndef __DARWIN__
-#include <Sound.h>
+#include <Movies.h>
+#include <Gestalt.h>
 #endif
 #endif
 
-wxSound::wxSound()
-  : m_sndChan(0), m_hSnd(NULL), m_waveLength(0), m_isResource(true)
-{
-}
+#if defined __WXMAC__ && defined __DARWIN__/*TARGET_CARBON*/
+#ifdef __APPLE_CC__
+#include <Carbon/Carbon.h>
+#else
+#include <Carbon.h>
+#endif
+#else
+#include <Sound.h>
+#endif
 
-wxSound::wxSound(const wxString& sFileName, bool isResource)
-  : m_sndChan(0), m_hSnd(NULL), m_waveLength(0), m_isResource(true)
-{
-    Create(sFileName, isResource);
-}
+//quicktime media layer only required for mac emulation on pc
+#ifndef __WXMAC__
+#include <qtml.h>
+#endif
 
+#ifndef __DARWIN__
+#include <QuickTimeComponents.h>
+#else
+#include <QuickTime/QuickTimeComponents.h>
+#endif
 
-wxSound::~wxSound()
-{
-    FreeData();
-}
+//Time between timer calls
+#define MOVIE_DELAY 100
 
-wxSound::wxSound(int size, const wxByte* data)
-  : m_sndChan(0), m_hSnd(NULL), m_waveLength(0), m_isResource(false)
-{
-    //TODO convert data
-}
+static wxTimer* lastSoundTimer=NULL;
+static bool lastSoundIsPlaying=false;
 
-bool wxSound::Create(const wxString& fileName, bool isResource)
+// ------------------------------------------------------------------
+//          wxQTTimer - Handle Asyncronous Playing
+// ------------------------------------------------------------------
+class wxQTTimer : public wxTimer
 {
-    bool ret = false;
-    m_sndname = fileName;
-    m_isResource = isResource;
-
-    if (m_isResource)
-        ret = true;
-    else
-    { /*
-        if (sndChan)
-        {                                                                    // we're playing
-            FSClose(SndRefNum);
-            SndRefNum = 0;
-            SndDisposeChannel(sndChan, TRUE);
-            free(sndChan);
-            sndChan = 0;
-            KillTimer(0,timerID);
-        }
+public:
+    wxQTTimer(Movie movie, bool bLoop, bool* playing) :
+        m_movie(movie), m_bLoop(bLoop), m_pbPlaying(playing)
+    {
+    }
 
-        if (!lpSnd)
-            return true;
+    ~wxQTTimer()
+    {
+        if(m_pbPlaying)
+            *m_pbPlaying = false;
 
-        if (_access(lpSnd,0))                            // no file, no service
-            return false;
+        StopMovie(m_movie);
+        DisposeMovie(m_movie);
+        Stop();
 
-        // Allocate SndChannel
-        sndChan = (SndChannelPtr) malloc (sizeof(SndChannel));
+        //Note that ExitMovies() is not necessary, but
+        //the docs are fuzzy on whether or not TerminateQTML is
+        ExitMovies();
 
-        if (!sndChan)
-            return false;
+#ifndef __WXMAC__
+        TerminateQTML();
+#endif
+    }
 
-        sndChan->qLength = 128;
+    void Shutdown()
+    {
+        delete this;
+    }
 
-        if (noErr != SndNewChannel (&sndChan, sampledSynth, initMono | initNoInterp, 0))
+    void Notify()
+    {
+        if (m_pbPlaying && !*m_pbPlaying)
         {
-            free(sndChan);
-            sndChan = 0;
-            return false;
+            Shutdown();
         }
 
-        if (!(SndRefNum = MacOpenSndFile ((char *)lpSnd)))
+        if(IsMovieDone(m_movie))
         {
-            SndDisposeChannel(sndChan, TRUE);
-            free(sndChan);
-            sndChan = 0;
-
-            return false;
+            if (!m_bLoop)
+                Shutdown();
+            else
+            {
+                StopMovie(m_movie);
+                GoToBeginningOfMovie(m_movie);
+                StartMovie(m_movie);
+            }
         }
+        else
+            MoviesTask(m_movie, MOVIE_DELAY); //Give QT time to play movie
+    }
+
 
-        bool async = false;
+    Movie& GetMovie() {return m_movie;}
 
-        if (fdwSound & SND_ASYNC)
-            async = true;
+protected:
+    Movie m_movie;
+    bool m_bLoop;
 
-        if (SndStartFilePlay(sndChan, SndRefNum, 0, 81920, 0, 0, 0, async) != noErr)
+public:
+    bool* m_pbPlaying;
+
+};
+
+
+class wxSMTimer : public wxTimer
+{
+public:
+    wxSMTimer(void* hSnd, void* pSndChannel, bool bLoop, bool* playing)
+        : m_hSnd(hSnd), m_pSndChannel(pSndChannel), m_bLoop(bLoop), m_pbPlaying(playing)
+    {
+    }
+
+    ~wxSMTimer()
+    {
+        if(m_pbPlaying)
+            *m_pbPlaying = false;
+        SndDisposeChannel((SndChannelPtr)m_pSndChannel, TRUE);
+        Stop();
+    }
+
+    void Notify()
+    {
+        if (m_pbPlaying && !*m_pbPlaying)
         {
-            FSClose (SndRefNum);
-            SndRefNum = 0;
-            SndDisposeChannel (sndChan, TRUE);
-            free (sndChan);
-            sndChan = 0;
-            return false;
+            Shutdown();
         }
 
-        if (async)
-        {  // haven't finish yet
-            timerID = SetTimer(0, 0, 250, TimerCallBack);
-        }
-        else
+        SCStatus stat;
+
+        if (SndChannelStatus((SndChannelPtr)m_pSndChannel, sizeof(SCStatus), &stat) != 0)
+            Shutdown();
+
+        //if the sound isn't playing anymore, see if it's looped,
+        //and if so play it again, otherwise close things up
+        if (stat.scChannelBusy == FALSE)
         {
-            FSClose (SndRefNum);
-            SndRefNum = 0;
-            SndDisposeChannel (sndChan, TRUE);
-            free (sndChan);
-            sndChan = 0;
-        }*/
+            if (m_bLoop)
+            {
+                if(SndPlay((SndChannelPtr)m_pSndChannel, (SndListHandle) m_hSnd, true) != noErr)
+                    Shutdown();
+            }
+            else
+                Shutdown();
+        }
     }
 
-    return ret;
-}
+    void Shutdown()
+    {
+        delete this;
+    }
 
+    void* GetChannel() {return m_pSndChannel;}
 
-//don't know what to do with looped, wth
-bool wxSound::DoPlay(unsigned flags) const
+protected:
+    void* m_hSnd;
+    void* m_pSndChannel;
+    bool m_bLoop;
+
+public:
+    bool* m_pbPlaying;
+};
+
+// ------------------------------------------------------------------
+//          wxSound
+// ------------------------------------------------------------------
+
+//Determines whether version 4 of QT is installed
+Boolean wxIsQuickTime4Installed (void)
 {
-    bool ret = false;
+#ifdef __WXMAC__
+    short error;
+    long result;
+
+    error = Gestalt (gestaltQuickTime, &result);
+    return (error == noErr) && (((result >> 16) & 0xffff) >= 0x0400);
+#else
+    return true;
+#endif
+}
 
-    if (m_isResource)
+inline bool wxInitQT ()
+{
+    if (wxIsQuickTime4Installed())
+    {
+        #ifndef __WXMAC__
+        int nError;
+        //-2093 no dll
+            if ((nError = InitializeQTML(0)) != noErr)
+                wxLogSysError(wxString::Format(wxT("Couldn't Initialize Quicktime-%i"), nError));
+        #endif
+        EnterMovies();
+        return true;
+    }
+    else
     {
-       Str255 snd ;
-       wxMacStringToPascal( m_sndname , snd ) ;
-       SndListHandle hSnd;
+        wxLogSysError(wxT("Quicktime is not installed, or Your Version of Quicktime is <= 4."));
+        return false;
+    }
+}
 
-       hSnd = (SndListHandle) GetNamedResource('snd ', snd);
+wxSound::wxSound()
+: m_hSnd(NULL), m_waveLength(0), m_pTimer(NULL), m_type(wxSound_NONE)
+{
+}
 
-       if ((hSnd != NULL) && (SndPlay((SndChannelPtr)m_sndChan, (SndListHandle) hSnd, (flags & wxSOUND_ASYNC)) == noErr))
-               ret = true;
-    }
+wxSound::wxSound(const wxString& sFileName, bool isResource)
+: m_hSnd(NULL), m_waveLength(0), m_pTimer(NULL), m_type(wxSound_NONE)
+{
+    Create(sFileName, isResource);
+}
 
-    return ret;
+wxSound::wxSound(int size, const wxByte* data)
+: m_hSnd((char*)data), m_waveLength(size), m_pTimer(NULL), m_type(wxSound_MEMORY)
+{
 }
 
+wxSound::~wxSound()
+{
+}
 
-bool wxSound::FreeData()
+bool wxSound::Create(const wxString& fileName, bool isResource)
 {
-    bool ret = false;
+    Stop();
 
-    if (m_isResource)
+    if (isResource)
     {
-        m_sndname.Empty();
-        ret = true;
+#ifdef __WXMAC__
+        m_type = wxSound_RESOURCE;
+
+        Str255 lpSnd ;
+
+        wxMacStringToPascal( fileName , lpSnd ) ;
+
+        m_sndname = fileName;
+        m_hSnd = (char*) GetNamedResource('snd ', (const unsigned char *) lpSnd);
+#else
+        return false;
+#endif
     }
     else
     {
-        //TODO,
+        m_type = wxSound_FILE;
+        m_sndname = fileName;
     }
 
-    return ret;
+    return true;
 }
 
+bool wxSound::DoPlay(unsigned flags) const
+{
+    Stop();
+
+    Movie movie;
 
-//code below is from an old implementation used for telinfo with MSVC crossplatform support
-//technology proceeds, so it would be the wisest to drop this code, but it's left here just
-//for the sake of a reference. BTW: Wave files can now be played with QT, starting from V3
+    switch(m_type)
+    {
+    case wxSound_MEMORY:
+        {
+            if (!wxInitQT())
+                return false;
+            Handle myHandle, dataRef = nil;
+            MovieImportComponent miComponent;
+            Track targetTrack = nil;
+            TimeValue addedDuration = 0;
+            long outFlags = 0;
+            OSErr err;
+            ComponentResult result;
+
+            myHandle = NewHandleClear((Size)m_waveLength);
+
+            BlockMove(m_hSnd, *myHandle, m_waveLength);
+
+            err = PtrToHand(&myHandle, &dataRef, sizeof(Handle));
+
+            if (memcmp(&m_hSnd[8], "WAVE", 4) == 0)
+                miComponent = OpenDefaultComponent(MovieImportType, kQTFileTypeWave);
+            else if (memcmp(&m_hSnd[8], "AIFF", 4) == 0)
+                miComponent = OpenDefaultComponent(MovieImportType, kQTFileTypeAIFF);
+            else if (memcmp(&m_hSnd[8], "AIFC", 4) == 0)
+                miComponent = OpenDefaultComponent(MovieImportType, kQTFileTypeAIFC);
+            else
+            {
+                wxLogSysError(wxT("wxSound - Location in memory does not contain valid data"));
+                return false;
+            }
+
+            movie = NewMovie(0);
+
+            result = MovieImportDataRef(miComponent,                dataRef,
+                                        HandleDataHandlerSubType,   movie,
+                                        nil,                        &targetTrack,
+                                        nil,                        &addedDuration,
+                                        movieImportCreateTrack,     &outFlags);
+
+            if (result != noErr)
+            {
+                wxLogSysError(wxString::Format(wxT("Couldn't import movie data\nError:%i"), (int)result));
+            }
+
+            SetMovieVolume(movie, kFullVolume);
+            GoToBeginningOfMovie(movie);
+
+            DisposeHandle(myHandle);
+        }
+        break;
+    case wxSound_RESOURCE:
+        {
+            SoundComponentData data;
+            unsigned long numframes, offset;
 
-/*static short MacOpenSndFile (char * path)
-{
-    VolumeParam vp;
-    FSSpec fspec;
-    Str255 name;
-    char *c;
-
-    // first, get the volume reference number for the file.  Start by
-    // making a Pstring with just the volume name
-    strcpy ((char *) name, path);
-    if (c = strchr ((char *) name, ':'))
-    {
-        c++;
-        *c = '\0';
-    }
+            ParseSndHeader((SndListHandle)m_hSnd, &data, &numframes, &offset);
+            //m_waveLength = numFrames * data.numChannels;
 
-    c2pstr ((char *) name);
-    vp.ioCompletion = 0;
-    vp.ioVolIndex    = -1;
-    vp.ioNamePtr    = name;
-    vp.ioVRefNum    = 0;
+            SndChannelPtr pSndChannel;
+            SndNewChannel(&pSndChannel, sampledSynth,
+                initNoInterp
+                + (data.numChannels == 1 ? initMono : initStereo), NULL);
 
-    if (PBGetVInfo((ParamBlockRec *)&vp, 0) != noErr)
-        return 0;
+            if(SndPlay(pSndChannel, (SndListHandle) m_hSnd, flags & wxSOUND_ASYNC ? 1 : 0) != noErr)
+                return false;
 
-    // next, buld an FSSpec for the file
-    strcpy ((char *) name, path);
-    c2pstr ((char *) name);
-    if (FSMakeFSSpec (vp.ioVRefNum, 0, name, &fspec) != noErr)
-        return 0;
+            if (flags & wxSOUND_ASYNC)
+            {
+                lastSoundTimer = ((wxSMTimer*&)m_pTimer)
+                    = new wxSMTimer(pSndChannel, m_hSnd, flags & wxSOUND_LOOP ? 1 : 0,
+                                    &lastSoundIsPlaying);
+                lastSoundIsPlaying = true;
 
-    short frefnum;
-    // now open the file, and return it's reference number
-    if (FSpOpenDF(&fspec, fsRdPerm, &frefnum) != noErr)
-        return 0;
+                ((wxTimer*)m_pTimer)->Start(MOVIE_DELAY, wxTIMER_CONTINUOUS);
+            }
+            else
+                SndDisposeChannel(pSndChannel, TRUE);
 
-    return frefnum;
-}
+            return true;
+        }
+        break;
+    case wxSound_FILE:
+        {
+            if (!wxInitQT())
+                return false;
+
+            OSErr err = noErr ;
+//NB:  RN: Stefan - I think the 10.3 path functions are broken if kQTNativeDefaultPathStyle is
+//going to trigger a warning every time it is used - where its _supposed to be used_!!
+//(kQTNativePathStyle is negative but the function argument is unsigned!)
+//../src/mac/carbon/sound.cpp: In member function `virtual bool 
+//   wxSound::DoPlay(unsigned int) const':
+//../src/mac/carbon/sound.cpp:387: warning: passing negative value `
+//   kQTNativeDefaultPathStyle' for argument passing 2 of `OSErr 
+//   QTNewDataReferenceFromFullPathCFString(const __CFString*, long unsigned int, 
+//   long unsigned int, char***, OSType*)'
+//../src/mac/carbon/sound.cpp:387: warning: argument of negative value `
+//   kQTNativeDefaultPathStyle' to `long unsigned int'
+#if defined( __WXMAC__ ) && TARGET_API_MAC_OSX && ( MAC_OS_X_VERSION_MAX_ALLOWED > MAC_OS_X_VERSION_10_2 )
+            if ( UMAGetSystemVersion() >= 0x1030 )
+            {
+                Handle dataRef = NULL;
+                OSType dataRefType;            
+                
+                err = QTNewDataReferenceFromFullPathCFString(wxMacCFStringHolder(m_sndname,wxLocale::GetSystemEncoding()),
+                    //FIXME: Why does this have to be casted?
+                    (unsigned int)kQTNativeDefaultPathStyle, 
+                    //FIXME: End
+                    0, &dataRef, &dataRefType);
+
+                wxASSERT(err == noErr);
+                
+                if (NULL != dataRef || err != noErr) 
+                {
+                    err = NewMovieFromDataRef( &movie, newMovieDontAskUnresolvedDataRefs , NULL, dataRef, dataRefType );
+                    wxASSERT(err == noErr);
+                    DisposeHandle(dataRef);
+                }
+            }
+            else
+#endif
+            {
+                short movieResFile;
+                FSSpec sfFile;
+#ifdef __WXMAC__
+                wxMacFilename2FSSpec( m_sndname , &sfFile ) ;
+#else
+                int nError;
+                if ((nError = NativePathNameToFSSpec ((char*) m_sndname.c_str(), &sfFile, 0)) != noErr)
+                {
+/*
+                    wxLogSysError(wxString::Format(wxT("File:%s does not exist\nError:%i"),
+                                    m_sndname.c_str(), nError));
+*/
+                    return false;
+                }
+#endif
+                if (OpenMovieFile (&sfFile, &movieResFile, fsRdPerm) != noErr)
+                {
+                    wxLogSysError(wxT("Quicktime couldn't open the file"));
+                    return false;
+                }
+                short movieResID = 0;
+                Str255 movieName;
+
+                err = NewMovieFromFile (
+                &movie,
+                movieResFile,
+                &movieResID,
+                movieName,
+                newMovieActive,
+                NULL); //wasChanged
+
+                CloseMovieFile (movieResFile);
+            }
+            
+            if (err != noErr)
+            {
+                wxLogSysError(
+                    wxString::Format(wxT("wxSound - Could not open file: %s\nError:%i"), m_sndname.c_str(), err )
+                    );
+                return false;
+            }
+        }
+        break;
+    default:
+        return false;
+    }//end switch(m_type)
 
+    //Start the movie!
+    StartMovie(movie);
 
-void TimerCallBack(HWND hwnd,UINT uMsg,UINT idEvent,DWORD dwTime)
-{
-    if(!sndChan)
+    if (flags & wxSOUND_ASYNC)
     {
-        KillTimer(0,timerID);
-        return;
+        //Start timer and play movie asyncronously
+        lastSoundTimer = ((wxQTTimer*&)m_pTimer) =
+            new wxQTTimer(movie, flags & wxSOUND_LOOP ? 1 : 0,
+                          &lastSoundIsPlaying);
+        lastSoundIsPlaying = true;
+        ((wxQTTimer*)m_pTimer)->Start(MOVIE_DELAY, wxTIMER_CONTINUOUS);
     }
+    else
+    {
+        wxASSERT_MSG(!(flags & wxSOUND_LOOP), wxT("Can't loop and play syncronously at the same time"));
 
-    SCStatus    scstat;
+        //Play movie until it ends, then exit
+        //Note that due to quicktime caching this may not always 
+        //work 100% correctly
+        while (!IsMovieDone(movie))
+            MoviesTask(movie, 1);
 
-    if (noErr == SndChannelStatus (sndChan, sizeof (SCStatus), &scstat)) {
-        if (scstat.scChannelPaused || scstat.scChannelBusy)
-            return;                            // not done yet
+        DisposeMovie(movie);
     }
 
-    // either error or done.
-    FSClose (SndRefNum);
-    SndRefNum = 0;
-    SndDisposeChannel (sndChan, TRUE);
-    free (sndChan);
-    sndChan = 0;
-    KillTimer(0,timerID);
-}*/
+    return true;
+}
 
+bool wxSound::IsPlaying()
+{
+    return lastSoundIsPlaying;
+}
 
-#endif
+void wxSound::Stop()
+{
+    if (lastSoundIsPlaying)
+    {
+        delete (wxTimer*&) lastSoundTimer;
+        lastSoundIsPlaying = false;
+        lastSoundTimer = NULL;
+    }
+}
+
+void* wxSound::GetHandle()
+{
+    if(m_type == wxSound_RESOURCE)
+        return (void*)  ((wxSMTimer*)m_pTimer)->GetChannel();
+
+    return (void*) ((wxQTTimer*) m_pTimer)->GetMovie();
+}
+
+#endif //wxUSE_SOUND