/////////////////////////////////////////////////////////////////////////////
// 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
-// Licence: wxWidgets licence
+// Copyright: (c) Ryan Norton
+// Licence: wxWindows licence
/////////////////////////////////////////////////////////////////////////////
#ifdef __GNUG__
#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"
-#ifndef __DARWIN__
+#include "wx/mac/uma.h"
+#include <Movies.h>
+#include <Gestalt.h>
+#endif
+
+#if defined __WXMAC__ && defined __DARWIN__/*TARGET_CARBON*/
+#ifdef __APPLE_CC__
+#include <Carbon/Carbon.h>
+#else
+#include <Carbon.h>
+#endif
+#else
#include <Sound.h>
#endif
+
+//quicktime media layer only required for mac emulation on pc
+#ifndef __WXMAC__
+#include <qtml.h>
#endif
-wxSound::wxSound()
- : m_sndChan(0), m_hSnd(NULL), m_waveLength(0), m_isResource(true)
-{
-}
+#include <QuickTimeComponents.h>
-wxSound::wxSound(const wxString& sFileName, bool isResource)
- : m_sndChan(0), m_hSnd(NULL), m_waveLength(0), m_isResource(true)
-{
- Create(sFileName, isResource);
-}
+//Time between timer calls
+#define MOVIE_DELAY 100
+static wxTimer* lastSoundTimer=NULL;
+static bool lastSoundIsPlaying=false;
-wxSound::~wxSound()
+// ------------------------------------------------------------------
+// wxQTTimer - Handle Asyncronous Playing
+// ------------------------------------------------------------------
+class wxQTTimer : public wxTimer
{
- FreeData();
-}
+public:
+ wxQTTimer(Movie movie, bool bLoop, bool* playing) :
+ m_movie(movie), m_bLoop(bLoop), m_pbPlaying(playing)
+ {
+ }
-wxSound::wxSound(int size, const wxByte* data)
- : m_sndChan(0), m_hSnd(NULL), m_waveLength(0), m_isResource(false)
-{
- //TODO convert data
-}
+ ~wxQTTimer()
+ {
+ if(m_pbPlaying)
+ *m_pbPlaying = false;
-bool wxSound::Create(const wxString& fileName, bool isResource)
-{
- bool ret = false;
- m_sndname = fileName;
- m_isResource = isResource;
+ StopMovie(m_movie);
+ DisposeMovie(m_movie);
+ Stop();
- 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);
+ //Note that ExitMovies() is not neccessary, but
+ //the docs are fuzzy on whether or not TerminateQTML is
+ ExitMovies();
+
+#ifndef __WXMAC__
+ TerminateQTML();
+#endif
+ }
+
+ void Shutdown()
+ {
+ delete this;
+ }
+
+ void Notify()
+ {
+ if (m_pbPlaying && !*m_pbPlaying)
+ {
+ Shutdown();
}
- if (!lpSnd)
- return true;
+ if(IsMovieDone(m_movie))
+ {
+ 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
+ }
- if (_access(lpSnd,0)) // no file, no service
- return false;
- // Allocate SndChannel
- sndChan = (SndChannelPtr) malloc (sizeof(SndChannel));
+ Movie& GetMovie() {return m_movie;}
- if (!sndChan)
- return false;
+protected:
+ Movie m_movie;
+ bool m_bLoop;
- sndChan->qLength = 128;
+public:
+ bool* m_pbPlaying;
- if (noErr != SndNewChannel (&sndChan, sampledSynth, initMono | initNoInterp, 0))
- {
- free(sndChan);
- sndChan = 0;
- return false;
- }
+};
- if (!(SndRefNum = MacOpenSndFile ((char *)lpSnd)))
- {
- SndDisposeChannel(sndChan, TRUE);
- free(sndChan);
- sndChan = 0;
- return false;
+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)
+ {
+ Shutdown();
}
- bool async = false;
+ SCStatus stat;
- if (fdwSound & SND_ASYNC)
- async = true;
+ if (SndChannelStatus((SndChannelPtr)m_pSndChannel, sizeof(SCStatus), &stat) != 0)
+ Shutdown();
- if (SndStartFilePlay(sndChan, SndRefNum, 0, 81920, 0, 0, 0, async) != noErr)
+ //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;
- return false;
+ if (m_bLoop)
+ {
+ if(SndPlay((SndChannelPtr)m_pSndChannel, (SndListHandle) m_hSnd, true) != noErr)
+ Shutdown();
+ }
+ else
+ Shutdown();
}
+ }
- if (async)
- { // haven't finish yet
- timerID = SetTimer(0, 0, 250, TimerCallBack);
- }
- else
- {
- FSClose (SndRefNum);
- SndRefNum = 0;
- SndDisposeChannel (sndChan, TRUE);
- free (sndChan);
- sndChan = 0;
- }*/
+ void Shutdown()
+ {
+ delete this;
}
- return ret;
-}
+ void* GetChannel() {return m_pSndChannel;}
+protected:
+ void* m_hSnd;
+ void* m_pSndChannel;
+ bool m_bLoop;
-//don't know what to do with looped, wth
-bool wxSound::DoPlay(unsigned flags) const
+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();
-//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
+ Movie movie;
-/*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';
- }
+ 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;
- c2pstr ((char *) name);
- vp.ioCompletion = 0;
- vp.ioVolIndex = -1;
- vp.ioNamePtr = name;
- vp.ioVRefNum = 0;
+ ParseSndHeader((SndListHandle)m_hSnd, &data, &numframes, &offset);
+ //m_waveLength = numFrames * data.numChannels;
- if (PBGetVInfo((ParamBlockRec *)&vp, 0) != noErr)
- return 0;
+ SndChannelPtr pSndChannel;
+ SndNewChannel(&pSndChannel, sampledSynth,
+ initNoInterp
+ + (data.numChannels == 1 ? initMono : initStereo), NULL);
- // 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(SndPlay(pSndChannel, (SndListHandle) m_hSnd, flags & wxSOUND_ASYNC ? 1 : 0) != noErr)
+ return false;
- short frefnum;
- // now open the file, and return it's reference number
- if (FSpOpenDF(&fspec, fsRdPerm, &frefnum) != noErr)
- return 0;
+ if (flags & wxSOUND_ASYNC)
+ {
+ lastSoundTimer = ((wxSMTimer*&)m_pTimer)
+ = new wxSMTimer(pSndChannel, m_hSnd, flags & wxSOUND_LOOP ? 1 : 0,
+ &lastSoundIsPlaying);
+ lastSoundIsPlaying = true;
- return frefnum;
-}
+ ((wxTimer*)m_pTimer)->Start(MOVIE_DELAY, wxTIMER_CONTINUOUS);
+ }
+ else
+ SndDisposeChannel(pSndChannel, TRUE);
+ 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)
-void TimerCallBack(HWND hwnd,UINT uMsg,UINT idEvent,DWORD dwTime)
-{
- if(!sndChan)
+ //Start the movie!
+ StartMovie(movie);
+
+ 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