/*==LICENSE==* CyanWorlds.com Engine - MMOG client, server and tools Copyright (C) 2011 Cyan Worlds, Inc. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . You can contact Cyan Worlds, Inc. by email legal@cyan.com or by snail mail at: Cyan Worlds, Inc. 14617 N Newport Hwy Mead, WA 99021 *==LICENSE==*/ #include #include "hsTypes.h" #include "plSoundBuffer.h" #include "hsStream.h" #include "hsUtils.h" #include "plgDispatch.h" #include "hsResMgr.h" #include "pnMessage/plRefMsg.h" #include "plFile/plFileUtils.h" #include "plFile/hsFiles.h" #include "plUnifiedTime/plUnifiedTime.h" #include "plStatusLog/plStatusLog.h" #include "hsTimer.h" static void GetFullPath( const char filename[], char *destStr ) { char path[ kFolderIterator_MaxPath ]; if( strchr( filename, '\\' ) != nil ) strcpy( path, filename ); else sprintf( path, "sfx\\%s", filename ); strcpy( destStr, path ); } //// IGetReader ////////////////////////////////////////////////////////////// // Makes sure the sound is ready to load without any extra processing (like // decompression or the like), then opens a reader for it. // fullpath tells the function whether to append 'sfx' to the path or not (we don't want to do this if were providing the full path) static plAudioFileReader *CreateReader( hsBool fullpath, const char filename[], plAudioFileReader::StreamType type, plAudioCore::ChannelSelect channel ) { char path[512]; if(fullpath) GetFullPath(filename, path); else strcpy(path, filename); plAudioFileReader* reader = plAudioFileReader::CreateReader(path, channel, type); if( reader == nil || !reader->IsValid() ) { delete reader; return nil; } return reader; } hsError plSoundPreloader::Run() { hsTArray templist; while (fRunning) { fCritSect.Lock(); while (fBuffers.GetCount()) { templist.Append(fBuffers.Pop()); } fCritSect.Unlock(); if (templist.GetCount() == 0) { fEvent.Wait(); } else { plAudioFileReader *reader = nil; while (templist.GetCount()) { plSoundBuffer* buf = templist.Pop(); if (buf->GetData()) { reader = CreateReader(true, buf->GetFileName(), buf->GetAudioReaderType(), buf->GetReaderSelect()); if( reader ) { unsigned readLen = buf->GetAsyncLoadLength() ? buf->GetAsyncLoadLength() : buf->GetDataLength(); reader->Read( readLen, buf->GetData() ); buf->SetAudioReader(reader); // give sound buffer reader, since we may need it later } else { buf->SetError(); } } buf->SetLoaded(true); } } } // we need to be sure that all buffers are removed from our load list when shutting this thread down or we will hang, // since the sound buffer will wait to be destroyed until it is marked as loaded fCritSect.Lock(); while (fBuffers.GetCount()) { plSoundBuffer* buf = fBuffers.Pop(); buf->SetLoaded(true); } fCritSect.Unlock(); return hsOK; } static plSoundPreloader gLoaderThread; void plSoundBuffer::Init() { gLoaderThread.Start(); } void plSoundBuffer::Shutdown() { gLoaderThread.Stop(); } //// Constructor/Destructor ////////////////////////////////////////////////// plSoundBuffer::plSoundBuffer() { IInitBuffer(); } plSoundBuffer::plSoundBuffer( const char *fileName, UInt32 flags ) { IInitBuffer(); SetFileName( fileName ); fFlags = flags; fValid = IGrabHeaderInfo(); } plSoundBuffer::~plSoundBuffer() { // if we are loading a sound we need to wait for the loading thread to be completely done processing this buffer. // otherwise it may try to access this buffer after it's been deleted if(fLoading) { while(!fLoaded) { Sleep(10); } } delete [] fFileName; UnLoad(); } void plSoundBuffer::IInitBuffer() { fError = false; fValid = false; fFileName = nil; fData = nil; fDataLength = 0; fFlags = 0; fDataRead = 0; fReader = nil; fLoaded = 0; fLoading = false; fHeader.fFormatTag = 0; fHeader.fNumChannels = 0; fHeader.fNumSamplesPerSec = 0; fHeader.fAvgBytesPerSec = 0; fHeader.fBlockAlign = 0; fHeader.fBitsPerSample = 0; } //// GetDataLengthInSecs ///////////////////////////////////////////////////// hsScalar plSoundBuffer::GetDataLengthInSecs( void ) const { return (hsScalar)fDataLength / (hsScalar)fHeader.fAvgBytesPerSec; } //// Read/Write ////////////////////////////////////////////////////////////// void plSoundBuffer::Read( hsStream *s, hsResMgr *mgr ) { hsKeyedObject::Read( s, mgr ); s->ReadSwap( &fFlags ); s->ReadSwap( &fDataLength ); fFileName = s->ReadSafeString(); s->ReadSwap( &fHeader.fFormatTag ); s->ReadSwap( &fHeader.fNumChannels ); s->ReadSwap( &fHeader.fNumSamplesPerSec ); s->ReadSwap( &fHeader.fAvgBytesPerSec ); s->ReadSwap( &fHeader.fBlockAlign ); s->ReadSwap( &fHeader.fBitsPerSample ); fValid = false; if( !( fFlags & kIsExternal ) ) { fData = TRACKED_NEW UInt8[ fDataLength ]; if( fData == nil ) fFlags |= kIsExternal; else { s->Read( fDataLength, fData ); fValid = true; SetLoaded(true); } } else { fData = nil; // fValid = EnsureInternal(); fValid = true; } } void plSoundBuffer::Write( hsStream *s, hsResMgr *mgr ) { hsKeyedObject::Write( s, mgr ); if( fData == nil ) fFlags |= kIsExternal; s->WriteSwap( fFlags ); s->WriteSwap( fDataLength ); // Truncate the path to just a file name on write if( fFileName != nil ) { char *nameOnly = strrchr( fFileName, '\\' ); if( nameOnly != nil ) s->WriteSafeString( nameOnly + 1 ); else s->WriteSafeString( fFileName ); } else s->WriteSafeString( nil ); s->WriteSwap( fHeader.fFormatTag ); s->WriteSwap( fHeader.fNumChannels ); s->WriteSwap( fHeader.fNumSamplesPerSec ); s->WriteSwap( fHeader.fAvgBytesPerSec ); s->WriteSwap( fHeader.fBlockAlign ); s->WriteSwap( fHeader.fBitsPerSample ); if( !( fFlags & kIsExternal ) ) s->Write( fDataLength, fData ); } //// SetFileName ///////////////////////////////////////////////////////////// void plSoundBuffer::SetFileName( const char *name ) { if(fLoading) { hsAssert(false, "Unable to set SoundBuffer filename"); return; } delete [] fFileName; if( name != nil ) fFileName = hsStrcpy( name ); else fFileName = nil; // Data is no longer valid UnLoad(); } //// GetReaderSelect ///////////////////////////////////////////////////////// // Translates our flags into the ChannelSelect enum for plAudioFileReader plAudioCore::ChannelSelect plSoundBuffer::GetReaderSelect( void ) const { if( fFlags & kOnlyLeftChannel ) return plAudioCore::kLeft; else if( fFlags & kOnlyRightChannel ) return plAudioCore::kRight; else return plAudioCore::kAll; } //// IGetFullPath //////////////////////////////////////////////////////////// // Construct our current full path to our sound. void plSoundBuffer::IGetFullPath( char *destStr ) { if(!fFileName) { *destStr = 0; return; } char path[ kFolderIterator_MaxPath ]; if( strchr( fFileName, '\\' ) != nil ) strcpy( path, fFileName ); else sprintf( path, "sfx\\%s", fFileName ); strcpy( destStr, path ); } //============================================================================ // Asyncload will queue up a buffer for loading in our loading list the first time it is called. // It will load in "length" number of bytes, if length is non zero. If length is zero the entire file will be loaded // When called subsequent times it will check to see if the data has been loaded. // Returns kPending while still loading the file. Returns kSuccess when the data has been loaded. // While a file is loading(fLoading == true, and fLoaded == false) a buffer, no paremeters of the buffer should be modified. plSoundBuffer::ELoadReturnVal plSoundBuffer::AsyncLoad(plAudioFileReader::StreamType type, unsigned length /* = 0 */ ) { if(!gLoaderThread.IsRunning()) return kError; // we cannot load the data since the load thread is no longer running if(!fLoading && !fLoaded) { fAsyncLoadLength = length; fStreamType = type; if(fData == nil ) { fData = TRACKED_NEW UInt8[ fAsyncLoadLength ? fAsyncLoadLength : fDataLength ]; if( fData == nil ) return kError; } gLoaderThread.AddBuffer(this); fLoading = true; } if(fLoaded) { if(fLoading) // ensures we only do this stuff one time { ELoadReturnVal retVal = kSuccess; if(fError) { retVal = kError; fError = false; } if(fReader) { fHeader = fReader->GetHeader(); SetDataLength(fReader->GetDataSize()); } fFlags &= ~kIsExternal; fLoading = false; return retVal; } return kSuccess; } return kPending; } //// ForceNonInternal //////////////////////////////////////////////////////// // destroys loaded, and frees data void plSoundBuffer::UnLoad( void ) { if(fLoaded) int i = 0; if(fLoading) return; if(fReader) fReader->Close(); delete fReader; fReader = nil; delete [] fData; fData = nil; SetLoaded(false); fFlags |= kIsExternal; } //// IRoundDataPos /////////////////////////////////////////////////////////// void plSoundBuffer::RoundDataPos( UInt32 &pos ) { UInt32 extra = pos & ( fHeader.fBlockAlign - 1 ); pos -= extra; } // transfers ownership to caller plAudioFileReader *plSoundBuffer::GetAudioReader() { plAudioFileReader * reader = fReader; fReader = nil; return reader; } // WARNING: called by the loader thread(only) // the reader will be handed off for later use. This is useful for streaming sound if we want to load the first chunk of data // and the continue streaming the file from disk. void plSoundBuffer::SetAudioReader(plAudioFileReader *reader) { if(fReader) fReader->Close(); delete fReader; fReader = reader; } void plSoundBuffer::SetLoaded(bool loaded) { fLoaded = loaded; } /***************************************************************************** * * for plugins only * ***/ //// SetInternalData ///////////////////////////////////////////////////////// void plSoundBuffer::SetInternalData( plWAVHeader &header, UInt32 length, UInt8 *data ) { if(fLoading) return; fFileName = nil; fHeader = header; fFlags = 0; fDataLength = length; fData = TRACKED_NEW UInt8[ length ]; memcpy( fData, data, length ); fValid = true; } //// EnsureInternal ////////////////////////////////////////////////////////// // for plugins only plSoundBuffer::ELoadReturnVal plSoundBuffer::EnsureInternal() { if( fData == nil ) { fData = TRACKED_NEW UInt8[fDataLength ]; if( fData == nil ) return kError; } if(!fReader) fReader = IGetReader(true); //else // fReader->Open(); if( fReader == nil ) return kError; unsigned readLen = fDataLength; if( !fReader->Read( readLen, fData ) ) { delete [] fData; fData = nil; return kError; } if(fReader) { fReader->Close(); delete fReader; fReader = nil; } return kSuccess; } //// IGrabHeaderInfo ///////////////////////////////////////////////////////// hsBool plSoundBuffer::IGrabHeaderInfo( void ) { static char path[ 512 ]; if( fFileName != nil ) { IGetFullPath( path ); // Go grab from the WAV file if(!fReader) { fReader = plAudioFileReader::CreateReader(path, GetReaderSelect(), plAudioFileReader::kStreamNative); if( fReader == nil || !fReader->IsValid() ) { delete fReader; fReader = nil; return false; } } fHeader = fReader->GetHeader(); fDataLength = fReader->GetDataSize(); RoundDataPos( fDataLength ); fReader->Close(); delete fReader; fReader = nil; } return true; } //// IGetReader ////////////////////////////////////////////////////////////// // Makes sure the sound is ready to load without any extra processing (like // decompression or the like), then opens a reader for it. // fullpath tells the function whether to append 'sfx' to the path or not (we don't want to do this if were providing the full path) plAudioFileReader *plSoundBuffer::IGetReader( hsBool fullpath ) { char path[512]; if(fullpath) IGetFullPath(path); else strcpy(path, fFileName); // Go grab from the WAV file plAudioFileReader::StreamType type = plAudioFileReader::kStreamWAV; if (HasFlag(kStreamCompressed)) type = plAudioFileReader::kStreamNative; plAudioFileReader* reader = plAudioFileReader::CreateReader(path, GetReaderSelect(), type); if( reader == nil || !reader->IsValid() ) { delete reader; return nil; } fHeader = reader->GetHeader(); fDataLength = reader->GetDataSize(); RoundDataPos( fDataLength ); return reader; }