| 1 | ///////////////////////////////////////////////////////////////////////////// |
| 2 | // Name: sound.cpp |
| 3 | // Purpose: wxSound class implementation: optional |
| 4 | // Author: Ryan Norton |
| 5 | // Modified by: Stefan Csomor |
| 6 | // Created: 1998-01-01 |
| 7 | // RCS-ID: $Id$ |
| 8 | // Copyright: (c) Ryan Norton |
| 9 | // Licence: wxWindows licence |
| 10 | ///////////////////////////////////////////////////////////////////////////// |
| 11 | |
| 12 | #if defined(__GNUG__) && !defined(NO_GCC_PRAGMA) |
| 13 | #pragma implementation "sound.h" |
| 14 | #endif |
| 15 | |
| 16 | #include "wx/wxprec.h" |
| 17 | |
| 18 | #include "wx/object.h" |
| 19 | #include "wx/string.h" |
| 20 | #include "wx/log.h" |
| 21 | #include "wx/file.h" |
| 22 | #include "wx/sound.h" |
| 23 | #include "wx/timer.h" |
| 24 | #include "wx/intl.h" |
| 25 | |
| 26 | #if wxUSE_SOUND |
| 27 | |
| 28 | // Carbon QT Implementation Details - |
| 29 | // |
| 30 | // Memory: |
| 31 | // 1) OpenDefaultComponent(MovieImportType, kQTFileTypeWave); |
| 32 | // 2) NewMovie(0); |
| 33 | // 3) MovieImportDataRef() //Pass Memory Location to this |
| 34 | // 4) PlayMovie(); |
| 35 | // 5) IsMovieDone(), MoviesTask() //2nd param is minimum wait time to allocate to quicktime |
| 36 | // |
| 37 | // File: |
| 38 | // 1) Obtain FSSpec |
| 39 | // 2) Call OpenMovieFile |
| 40 | // 3) Call NewMovieFromFile |
| 41 | // 4) Call CloseMovieFile |
| 42 | // 4) PlayMovie(); |
| 43 | // 5) IsMovieDone(), MoviesTask() //2nd param is minimum wait time to allocate to quicktime |
| 44 | // |
| 45 | |
| 46 | #ifdef __WXMAC__ |
| 47 | #include "wx/mac/uma.h" |
| 48 | #include <Movies.h> |
| 49 | #include <Gestalt.h> |
| 50 | #endif |
| 51 | |
| 52 | #if defined __WXMAC__ && defined __DARWIN__/*TARGET_CARBON*/ |
| 53 | #ifdef __APPLE_CC__ |
| 54 | #include <Carbon/Carbon.h> |
| 55 | #else |
| 56 | #include <Carbon.h> |
| 57 | #endif |
| 58 | #else |
| 59 | #include <Sound.h> |
| 60 | #endif |
| 61 | |
| 62 | //quicktime media layer only required for mac emulation on pc |
| 63 | #ifndef __WXMAC__ |
| 64 | #include <qtml.h> |
| 65 | #endif |
| 66 | |
| 67 | #include <QuickTimeComponents.h> |
| 68 | |
| 69 | //Time between timer calls |
| 70 | #define MOVIE_DELAY 100 |
| 71 | |
| 72 | static wxTimer* lastSoundTimer=NULL; |
| 73 | static bool lastSoundIsPlaying=false; |
| 74 | |
| 75 | // ------------------------------------------------------------------ |
| 76 | // wxQTTimer - Handle Asyncronous Playing |
| 77 | // ------------------------------------------------------------------ |
| 78 | class wxQTTimer : public wxTimer |
| 79 | { |
| 80 | public: |
| 81 | wxQTTimer(Movie movie, bool bLoop, bool* playing) : |
| 82 | m_movie(movie), m_bLoop(bLoop), m_pbPlaying(playing) |
| 83 | { |
| 84 | } |
| 85 | |
| 86 | ~wxQTTimer() |
| 87 | { |
| 88 | if(m_pbPlaying) |
| 89 | *m_pbPlaying = false; |
| 90 | |
| 91 | StopMovie(m_movie); |
| 92 | DisposeMovie(m_movie); |
| 93 | Stop(); |
| 94 | |
| 95 | //Note that ExitMovies() is not neccessary, but |
| 96 | //the docs are fuzzy on whether or not TerminateQTML is |
| 97 | ExitMovies(); |
| 98 | |
| 99 | #ifndef __WXMAC__ |
| 100 | TerminateQTML(); |
| 101 | #endif |
| 102 | } |
| 103 | |
| 104 | void Shutdown() |
| 105 | { |
| 106 | delete this; |
| 107 | } |
| 108 | |
| 109 | void Notify() |
| 110 | { |
| 111 | if (m_pbPlaying && !*m_pbPlaying) |
| 112 | { |
| 113 | Shutdown(); |
| 114 | } |
| 115 | |
| 116 | if(IsMovieDone(m_movie)) |
| 117 | { |
| 118 | if (!m_bLoop) |
| 119 | Shutdown(); |
| 120 | else |
| 121 | { |
| 122 | StopMovie(m_movie); |
| 123 | GoToBeginningOfMovie(m_movie); |
| 124 | StartMovie(m_movie); |
| 125 | } |
| 126 | } |
| 127 | else |
| 128 | MoviesTask(m_movie, MOVIE_DELAY); //Give QT time to play movie |
| 129 | } |
| 130 | |
| 131 | |
| 132 | Movie& GetMovie() {return m_movie;} |
| 133 | |
| 134 | protected: |
| 135 | Movie m_movie; |
| 136 | bool m_bLoop; |
| 137 | |
| 138 | public: |
| 139 | bool* m_pbPlaying; |
| 140 | |
| 141 | }; |
| 142 | |
| 143 | |
| 144 | class wxSMTimer : public wxTimer |
| 145 | { |
| 146 | public: |
| 147 | wxSMTimer(void* hSnd, void* pSndChannel, bool bLoop, bool* playing) |
| 148 | : m_hSnd(hSnd), m_pSndChannel(pSndChannel), m_bLoop(bLoop), m_pbPlaying(playing) |
| 149 | { |
| 150 | } |
| 151 | |
| 152 | ~wxSMTimer() |
| 153 | { |
| 154 | if(m_pbPlaying) |
| 155 | *m_pbPlaying = false; |
| 156 | SndDisposeChannel((SndChannelPtr)m_pSndChannel, TRUE); |
| 157 | Stop(); |
| 158 | } |
| 159 | |
| 160 | void Notify() |
| 161 | { |
| 162 | if (m_pbPlaying && !*m_pbPlaying) |
| 163 | { |
| 164 | Shutdown(); |
| 165 | } |
| 166 | |
| 167 | SCStatus stat; |
| 168 | |
| 169 | if (SndChannelStatus((SndChannelPtr)m_pSndChannel, sizeof(SCStatus), &stat) != 0) |
| 170 | Shutdown(); |
| 171 | |
| 172 | //if the sound isn't playing anymore, see if it's looped, |
| 173 | //and if so play it again, otherwise close things up |
| 174 | if (stat.scChannelBusy == FALSE) |
| 175 | { |
| 176 | if (m_bLoop) |
| 177 | { |
| 178 | if(SndPlay((SndChannelPtr)m_pSndChannel, (SndListHandle) m_hSnd, true) != noErr) |
| 179 | Shutdown(); |
| 180 | } |
| 181 | else |
| 182 | Shutdown(); |
| 183 | } |
| 184 | } |
| 185 | |
| 186 | void Shutdown() |
| 187 | { |
| 188 | delete this; |
| 189 | } |
| 190 | |
| 191 | void* GetChannel() {return m_pSndChannel;} |
| 192 | |
| 193 | protected: |
| 194 | void* m_hSnd; |
| 195 | void* m_pSndChannel; |
| 196 | bool m_bLoop; |
| 197 | |
| 198 | public: |
| 199 | bool* m_pbPlaying; |
| 200 | }; |
| 201 | |
| 202 | // ------------------------------------------------------------------ |
| 203 | // wxSound |
| 204 | // ------------------------------------------------------------------ |
| 205 | |
| 206 | //Determines whether version 4 of QT is installed |
| 207 | Boolean wxIsQuickTime4Installed (void) |
| 208 | { |
| 209 | #ifdef __WXMAC__ |
| 210 | short error; |
| 211 | long result; |
| 212 | |
| 213 | error = Gestalt (gestaltQuickTime, &result); |
| 214 | return (error == noErr) && (((result >> 16) & 0xffff) >= 0x0400); |
| 215 | #else |
| 216 | return true; |
| 217 | #endif |
| 218 | } |
| 219 | |
| 220 | inline bool wxInitQT () |
| 221 | { |
| 222 | if (wxIsQuickTime4Installed()) |
| 223 | { |
| 224 | #ifndef __WXMAC__ |
| 225 | int nError; |
| 226 | //-2093 no dll |
| 227 | if ((nError = InitializeQTML(0)) != noErr) |
| 228 | wxLogSysError(wxString::Format(wxT("Couldn't Initialize Quicktime-%i"), nError)); |
| 229 | #endif |
| 230 | EnterMovies(); |
| 231 | return true; |
| 232 | } |
| 233 | else |
| 234 | { |
| 235 | wxLogSysError(wxT("Quicktime is not installed, or Your Version of Quicktime is <= 4.")); |
| 236 | return false; |
| 237 | } |
| 238 | } |
| 239 | |
| 240 | wxSound::wxSound() |
| 241 | : m_hSnd(NULL), m_waveLength(0), m_pTimer(NULL), m_type(wxSound_NONE) |
| 242 | { |
| 243 | } |
| 244 | |
| 245 | wxSound::wxSound(const wxString& sFileName, bool isResource) |
| 246 | : m_hSnd(NULL), m_waveLength(0), m_pTimer(NULL), m_type(wxSound_NONE) |
| 247 | { |
| 248 | Create(sFileName, isResource); |
| 249 | } |
| 250 | |
| 251 | wxSound::wxSound(int size, const wxByte* data) |
| 252 | : m_hSnd((char*)data), m_waveLength(size), m_pTimer(NULL), m_type(wxSound_MEMORY) |
| 253 | { |
| 254 | } |
| 255 | |
| 256 | wxSound::~wxSound() |
| 257 | { |
| 258 | } |
| 259 | |
| 260 | bool wxSound::Create(const wxString& fileName, bool isResource) |
| 261 | { |
| 262 | Stop(); |
| 263 | |
| 264 | if (isResource) |
| 265 | { |
| 266 | #ifdef __WXMAC__ |
| 267 | m_type = wxSound_RESOURCE; |
| 268 | |
| 269 | Str255 lpSnd ; |
| 270 | |
| 271 | wxMacStringToPascal( fileName , lpSnd ) ; |
| 272 | |
| 273 | m_sndname = fileName; |
| 274 | m_hSnd = (char*) GetNamedResource('snd ', (const unsigned char *) lpSnd); |
| 275 | #else |
| 276 | return false; |
| 277 | #endif |
| 278 | } |
| 279 | else |
| 280 | { |
| 281 | m_type = wxSound_FILE; |
| 282 | m_sndname = fileName; |
| 283 | } |
| 284 | |
| 285 | return true; |
| 286 | } |
| 287 | |
| 288 | bool wxSound::DoPlay(unsigned flags) const |
| 289 | { |
| 290 | Stop(); |
| 291 | |
| 292 | Movie movie; |
| 293 | |
| 294 | switch(m_type) |
| 295 | { |
| 296 | case wxSound_MEMORY: |
| 297 | { |
| 298 | if (!wxInitQT()) |
| 299 | return false; |
| 300 | Handle myHandle, dataRef = nil; |
| 301 | MovieImportComponent miComponent; |
| 302 | Track targetTrack = nil; |
| 303 | TimeValue addedDuration = 0; |
| 304 | long outFlags = 0; |
| 305 | OSErr err; |
| 306 | ComponentResult result; |
| 307 | |
| 308 | myHandle = NewHandleClear((Size)m_waveLength); |
| 309 | |
| 310 | BlockMove(m_hSnd, *myHandle, m_waveLength); |
| 311 | |
| 312 | err = PtrToHand(&myHandle, &dataRef, sizeof(Handle)); |
| 313 | |
| 314 | if (memcmp(&m_hSnd[8], "WAVE", 4) == 0) |
| 315 | miComponent = OpenDefaultComponent(MovieImportType, kQTFileTypeWave); |
| 316 | else if (memcmp(&m_hSnd[8], "AIFF", 4) == 0) |
| 317 | miComponent = OpenDefaultComponent(MovieImportType, kQTFileTypeAIFF); |
| 318 | else if (memcmp(&m_hSnd[8], "AIFC", 4) == 0) |
| 319 | miComponent = OpenDefaultComponent(MovieImportType, kQTFileTypeAIFC); |
| 320 | else |
| 321 | { |
| 322 | wxLogSysError(wxT("wxSound - Location in memory does not contain valid data")); |
| 323 | return false; |
| 324 | } |
| 325 | |
| 326 | movie = NewMovie(0); |
| 327 | |
| 328 | result = MovieImportDataRef(miComponent, dataRef, |
| 329 | HandleDataHandlerSubType, movie, |
| 330 | nil, &targetTrack, |
| 331 | nil, &addedDuration, |
| 332 | movieImportCreateTrack, &outFlags); |
| 333 | |
| 334 | if (result != noErr) |
| 335 | { |
| 336 | wxLogSysError(wxString::Format(wxT("Couldn't import movie data\nError:%i"), (int)result)); |
| 337 | } |
| 338 | |
| 339 | SetMovieVolume(movie, kFullVolume); |
| 340 | GoToBeginningOfMovie(movie); |
| 341 | |
| 342 | DisposeHandle(myHandle); |
| 343 | } |
| 344 | break; |
| 345 | case wxSound_RESOURCE: |
| 346 | { |
| 347 | SoundComponentData data; |
| 348 | unsigned long numframes, offset; |
| 349 | |
| 350 | ParseSndHeader((SndListHandle)m_hSnd, &data, &numframes, &offset); |
| 351 | //m_waveLength = numFrames * data.numChannels; |
| 352 | |
| 353 | SndChannelPtr pSndChannel; |
| 354 | SndNewChannel(&pSndChannel, sampledSynth, |
| 355 | initNoInterp |
| 356 | + (data.numChannels == 1 ? initMono : initStereo), NULL); |
| 357 | |
| 358 | if(SndPlay(pSndChannel, (SndListHandle) m_hSnd, flags & wxSOUND_ASYNC ? 1 : 0) != noErr) |
| 359 | return false; |
| 360 | |
| 361 | if (flags & wxSOUND_ASYNC) |
| 362 | { |
| 363 | lastSoundTimer = ((wxSMTimer*&)m_pTimer) |
| 364 | = new wxSMTimer(pSndChannel, m_hSnd, flags & wxSOUND_LOOP ? 1 : 0, |
| 365 | &lastSoundIsPlaying); |
| 366 | lastSoundIsPlaying = true; |
| 367 | |
| 368 | ((wxTimer*)m_pTimer)->Start(MOVIE_DELAY, wxTIMER_CONTINUOUS); |
| 369 | } |
| 370 | else |
| 371 | SndDisposeChannel(pSndChannel, TRUE); |
| 372 | |
| 373 | return true; |
| 374 | } |
| 375 | break; |
| 376 | case wxSound_FILE: |
| 377 | { |
| 378 | if (!wxInitQT()) |
| 379 | return false; |
| 380 | |
| 381 | OSErr err = noErr ; |
| 382 | //NB: RN: Stefan - I think the 10.3 path functions are broken if kQTNativeDefaultPathStyle is |
| 383 | //going to trigger a warning every time it is used - where its _supposed to be used_!! |
| 384 | //(kQTNativePathStyle is negative but the function argument is unsigned!) |
| 385 | //../src/mac/carbon/sound.cpp: In member function `virtual bool |
| 386 | // wxSound::DoPlay(unsigned int) const': |
| 387 | //../src/mac/carbon/sound.cpp:387: warning: passing negative value ` |
| 388 | // kQTNativeDefaultPathStyle' for argument passing 2 of `OSErr |
| 389 | // QTNewDataReferenceFromFullPathCFString(const __CFString*, long unsigned int, |
| 390 | // long unsigned int, char***, OSType*)' |
| 391 | //../src/mac/carbon/sound.cpp:387: warning: argument of negative value ` |
| 392 | // kQTNativeDefaultPathStyle' to `long unsigned int' |
| 393 | #if defined( __WXMAC__ ) && TARGET_API_MAC_OSX && ( MAC_OS_X_VERSION_MAX_ALLOWED > MAC_OS_X_VERSION_10_2 ) |
| 394 | if ( UMAGetSystemVersion() >= 0x1030 ) |
| 395 | { |
| 396 | Handle dataRef = NULL; |
| 397 | OSType dataRefType; |
| 398 | |
| 399 | err = QTNewDataReferenceFromFullPathCFString(wxMacCFStringHolder(m_sndname,wxLocale::GetSystemEncoding()), |
| 400 | //FIXME: Why does this have to be casted? |
| 401 | (unsigned int)kQTNativeDefaultPathStyle, |
| 402 | //FIXME: End |
| 403 | 0, &dataRef, &dataRefType); |
| 404 | |
| 405 | wxASSERT(err == noErr); |
| 406 | |
| 407 | if (NULL != dataRef || err != noErr) |
| 408 | { |
| 409 | err = NewMovieFromDataRef( &movie, newMovieDontAskUnresolvedDataRefs , NULL, dataRef, dataRefType ); |
| 410 | wxASSERT(err == noErr); |
| 411 | DisposeHandle(dataRef); |
| 412 | } |
| 413 | } |
| 414 | else |
| 415 | #endif |
| 416 | { |
| 417 | short movieResFile; |
| 418 | FSSpec sfFile; |
| 419 | #ifdef __WXMAC__ |
| 420 | wxMacFilename2FSSpec( m_sndname , &sfFile ) ; |
| 421 | #else |
| 422 | int nError; |
| 423 | if ((nError = NativePathNameToFSSpec ((char*) m_sndname.c_str(), &sfFile, 0)) != noErr) |
| 424 | { |
| 425 | /* |
| 426 | wxLogSysError(wxString::Format(wxT("File:%s does not exist\nError:%i"), |
| 427 | m_sndname.c_str(), nError)); |
| 428 | */ |
| 429 | return false; |
| 430 | } |
| 431 | #endif |
| 432 | if (OpenMovieFile (&sfFile, &movieResFile, fsRdPerm) != noErr) |
| 433 | { |
| 434 | wxLogSysError(wxT("Quicktime couldn't open the file")); |
| 435 | return false; |
| 436 | } |
| 437 | short movieResID = 0; |
| 438 | Str255 movieName; |
| 439 | |
| 440 | err = NewMovieFromFile ( |
| 441 | &movie, |
| 442 | movieResFile, |
| 443 | &movieResID, |
| 444 | movieName, |
| 445 | newMovieActive, |
| 446 | NULL); //wasChanged |
| 447 | |
| 448 | CloseMovieFile (movieResFile); |
| 449 | } |
| 450 | |
| 451 | if (err != noErr) |
| 452 | { |
| 453 | wxLogSysError( |
| 454 | wxString::Format(wxT("wxSound - Could not open file: %s\nError:%i"), m_sndname.c_str(), err ) |
| 455 | ); |
| 456 | return false; |
| 457 | } |
| 458 | } |
| 459 | break; |
| 460 | default: |
| 461 | return false; |
| 462 | }//end switch(m_type) |
| 463 | |
| 464 | //Start the movie! |
| 465 | StartMovie(movie); |
| 466 | |
| 467 | if (flags & wxSOUND_ASYNC) |
| 468 | { |
| 469 | //Start timer and play movie asyncronously |
| 470 | lastSoundTimer = ((wxQTTimer*&)m_pTimer) = |
| 471 | new wxQTTimer(movie, flags & wxSOUND_LOOP ? 1 : 0, |
| 472 | &lastSoundIsPlaying); |
| 473 | lastSoundIsPlaying = true; |
| 474 | ((wxQTTimer*)m_pTimer)->Start(MOVIE_DELAY, wxTIMER_CONTINUOUS); |
| 475 | } |
| 476 | else |
| 477 | { |
| 478 | wxASSERT_MSG(!(flags & wxSOUND_LOOP), wxT("Can't loop and play syncronously at the same time")); |
| 479 | |
| 480 | //Play movie until it ends, then exit |
| 481 | //Note that due to quicktime caching this may not always |
| 482 | //work 100% correctly |
| 483 | while (!IsMovieDone(movie)) |
| 484 | MoviesTask(movie, 1); |
| 485 | |
| 486 | DisposeMovie(movie); |
| 487 | } |
| 488 | |
| 489 | return true; |
| 490 | } |
| 491 | |
| 492 | bool wxSound::IsPlaying() |
| 493 | { |
| 494 | return lastSoundIsPlaying; |
| 495 | } |
| 496 | |
| 497 | void wxSound::Stop() |
| 498 | { |
| 499 | if (lastSoundIsPlaying) |
| 500 | { |
| 501 | delete (wxTimer*&) lastSoundTimer; |
| 502 | lastSoundIsPlaying = false; |
| 503 | lastSoundTimer = NULL; |
| 504 | } |
| 505 | } |
| 506 | |
| 507 | void* wxSound::GetHandle() |
| 508 | { |
| 509 | if(m_type == wxSound_RESOURCE) |
| 510 | return (void*) ((wxSMTimer*)m_pTimer)->GetChannel(); |
| 511 | |
| 512 | return (void*) ((wxQTTimer*) m_pTimer)->GetMovie(); |
| 513 | } |
| 514 | |
| 515 | #endif //wxUSE_SOUND |