;3D SoundSystem class, powered by Open Audio Library (OpenAL) ;Written June 2010 by Leith Ketchell ;Classes: ;SoundSystem = main class, also represents the 3D Listener (ie camera) ;SoundSource = 3D audio emitter ifndef DwordCollection LoadObjects DwordCollection endif ;Constants for SoundSource.Controls method SOUND_PLAY equ 0 SOUND_PAUSE equ 1 SOUND_REWIND equ 2 SOUND_STOP equ 3 include SoundSystem_MoreStructs.inc ; 覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧 ;Orientation of the Audio Listener (your 3D camera, whatever) Orient struct vFacing Vec3 <> ;Direction we are facing vUp Vec3 <> ;Direction of UP Orient ends ;The SoundSystem is the main engine component, although theres really not much going on here. ;It also represents the local listener in 3D space, which is worth knowing :) Object SoundSystem, 534534, Primer RedefineMethod Init RedefineMethod Done StaticMethod Update ;<-- call whenever 3D camera moves/rotates Private StaticMethod Monitor PrivateEnd ;You can change these, and then call Update DefineVariable vPosition, Vec3, {<0.0f, 0.0f, 0.0f>} ; Position of the Listener. DefineVariable vVelocity, Vec3, {<0.0f, 0.0f, 0.0f>} ; Velocity of the Listener. DefineVariable vvOrientation, Orient, {<< 0.0f, 0.0f, -1.0f>, <0.0f, 1.0f, 0.0f>>} ;Note: Normalized vectors Embed StreamSources_Playing,DwordCollection ;Monitor uses this to refill streaming buffers ;Internal Use Only DefineVariable Device,Pointer ;-> ALCcontext DefineVariable Context,Pointer;-> ALCdevice DefineVariable bShutDown, BOOL, FALSE ObjectEnd ; 覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧 ;This class represents a 3D audio emitter, complete with position, velocity etc. ;It supports Static WAV, Streaming WAV, and Streaming MP3 Object SoundSource, 435345, Primer RedefineMethod Init, Pointer, SDWORD, BOOL RedefineMethod Done StaticMethod Controls, dword StaticMethod Update StaticMethod GetState StaticMethod Init_MP3_Streaming_Support,Pointer StaticMethod Load_WAV_File_Static, LPSTR StaticMethod Load_WAV_File_Stream, LPSTR StaticMethod Load_MP3_File, LPSTR StaticMethod Refill ;Call this no less than once per second Private StaticMethod Bind_SoundBuffer,BOOL StaticMethod Init_Decompression StaticMethod MapFile,LPSTR,Pointer,Pointer,Pointer StaticMethod Map_WAV_File,LPSTR StaticMethod Map_MP3_File,LPSTR StaticMethod Parse_WAV_Header,Pointer, Pointer, Pointer, Pointer StaticMethod Parse_MP3_Header,Pointer, Pointer, Pointer StaticMethod Parse_MP3_FrameHeader,Pointer, Pointer,Pointer StaticMethod stream, Pointer StaticMethod UnMapFile PrivateEnd DefineVariable bStreaming, BOOL,FALSE ;Flag - use multibuffer queue ('streaming') ?? DefineVariable bDataExhausted, BOOL,FALSE DefineVariable bCompressed, BOOL,FALSE ;Flag - Is the stream compressed ('AUDIO CODEC') ?? DefineVariable bHave_Desired_Codec,BOOL,FALSE ;Flag - Is the required CODEC installed on this system? DefineVariable Desired_Codec_Tag ;FOURCC Identifier of the required Audio Codec DefineVariable ACMStreamHandle,dword,-1 ;Handle of ACM conversion stream DefineVariable Source, ALuint ;Handle to oal source object (sound emitter) DefineVariable vPosition, Vec3, {<0.0f, 0.0f, 0.0f>} ; Position of the source sound. DefineVariable vVelocity, Vec3, {<0.0f, 0.0f, 0.0f>} ; Velocity of the source sound. ;FileMapping stuff DefineVariable pFileBase ;ptr to base of WAV file data (RIFF Header) DefineVariable hFileMapping ;Handle to Mapped View of File DefineVariable hFile ;Handle to File ;Static stuff DefineVariable pPCMData ;ptr to start of PCM/WAV audio data DefineVariable dFileData_Size ;#bytes of PCM data ;Streaming stuff DefineVariable dCompressed_FrameSize ;Size of compressed frame DefineVariable dPCM_FrameSize ;Size of decompressed frame DefineVariable pFrame_PCM, Pointer, NULL DefineVariable pFrame_Compressed, Pointer, NULL DefineVariable ACMStreamHeader, ACMSTREAMHEADER, {<>} DefineVariable dStreamOffset ;WAV format stuff DefineVariable dBytesPerSecond DefineVariable format ;Format (channels/bits) DefineVariable freq ;Sample Rate DefineVariable Buffer, ALuint ;Handle to oal buffer object (sound buffer) Embed Buffers, DwordCollection ;Streaming buffers ObjectEnd ; 覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧 ; 覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧 ;Method: SoundSystem.Init ;Purpose: Initialize OpenAL Audio Library Method SoundSystem.Init, uses esi LOCAL tid SetObject esi OCall [esi].StreamSources_Playing::DwordCollection.Init,esi,16,256,-1 mov [esi].Device, $invoke (alcOpenDevice,"DirectSound3D") .if eax == NULL return FALSE .endif mov [esi].Context,$invoke(alcCreateContext,eax,NULL) .if eax == NULL return FALSE .endif invoke alcMakeContextCurrent,eax .if $invoke(alGetError) != AL_NO_ERROR return AL_FALSE .endif invoke CreateThread,0,0,$MethodAddr(SoundSystem.Monitor),esi,0,addr tid invoke CloseHandle,eax OCall esi.Update MethodEnd ;Method: SoundSystem.Done ;Purpose: Release OpenAL Library Method SoundSystem.Done, uses esi SetObject esi mov [esi].bShutDown,TRUE .repeat DbgWarning "Waiting for Worker Death" invoke Sleep,500 .until [esi].bShutDown==FALSE OCall [esi].StreamSources_Playing::DwordCollection.Done invoke alcDestroyContext, [esi].Context invoke alcCloseDevice, [esi].Device MethodEnd ;Method: SoundSystem.Update ;Purpose: Periodically update the Listener for change in Position, Velocity, etc Method SoundSystem.Update,uses esi SetObject esi invoke alListenerfv,AL_POSITION, addr [esi].vPosition invoke alListenerfv,AL_VELOCITY, addr [esi].vVelocity invoke alListenerfv,AL_ORIENTATION, addr [esi].vvOrientation invoke alGetError MethodEnd ; 覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧 Method SoundSource.Init, uses esi, pOwner, dBufferCount:SDWORD, bStreaming:BOOL LOCAL buffer SetObject esi ACall Init, pOwner m2m [esi].bStreaming,bStreaming,edx invoke alGenSources,1, addr [esi].Source .if $invoke(alGetError) != AL_NO_ERROR return AL_FALSE .endif invoke alSourcef ,[esi].Source, AL_PITCH, r4_1 invoke alSourcef ,[esi].Source, AL_GAIN, r4_1 .if bStreaming OCall [esi].Buffers::DwordCollection.Init,esi,16,256,-1 .if dBufferCount<2 ;Sanity Check: mov dBufferCount,2 ;Need min. 2 buffers for streaming (ie double buffering) .endif xor ebx,ebx .while ebx= pdwChunkTailPtr .endw DbgWarning "Unable to parse wav file" mov eax,FALSE MethodEnd ;Method: SoundSource.MapFile ;Purpose: Opens a File using FileMapping for 'bufferless' read access to the file's data Method SoundSource.MapFile,uses esi, pszFile:LPSTR,phFile,phFM,phFV .if $invoke (CreateFile, pszFile, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_FLAG_RANDOM_ACCESS, 0)==0 DbgWarning "Error - failed to open File" DbgStr pszFile return FALSE .endif mov edx,phFile mov [edx],eax .if $invoke (CreateFileMapping, eax, NULL, PAGE_READONLY, 0, 0, NULL) ==0 mov edx,phFile invoke CloseHandle, dword ptr[edx] DbgWarning "Error - failed to create FileMapping" DbgStr pszFile return FALSE .endif mov edx,phFM mov [edx],eax .if $invoke (MapViewOfFile, eax, FILE_MAP_READ, 0, 0, 0)==0 mov edx,phFM invoke CloseHandle, dword ptr[edx] mov edx,phFile invoke CloseHandle, dword ptr[edx] DbgWarning "Error - failed to Map File" DbgStr pszFile return FALSE .endif mov edx,phFV mov [edx],eax mov eax,TRUE MethodEnd ;Method: SoundSource.UnMapFile ;Purpose: Release FileMapping objects Method SoundSource.UnMapFile, uses esi SetObject esi invoke UnmapViewOfFile, [esi].pFileBase invoke CloseHandle, [esi].hFileMapping invoke CloseHandle, [esi].hFile MethodEnd ;Method: SoundSource.Load_WAV_File ;Purpose: Load WAV into this SoundSource's (only) buffer Method SoundSource.Load_WAV_File_Static, uses esi, pszName:LPSTR SetObject esi OCall esi.Map_WAV_File, pszName .if eax!=0 invoke alBufferData,[esi].Buffer, [esi].format, [esi].pPCMData, [esi].dFileData_Size, [esi].freq .if $invoke (alGetError)==AL_NO_ERROR mov eax,TRUE .else xor eax,eax .endif .else DbgWarning "Failed to Map WAV" .endif MethodEnd ;Method: SoundSource.Load_WAV_File ;Purpose: Load WAV into this SoundSource's (only) buffer Method SoundSource.Load_WAV_File_Stream, uses esi, pszName:LPSTR SetObject esi OCall esi.Map_WAV_File, pszName .if eax!=0 .if ![esi].bStreaming DbgWarning "StreamBuffer Warning: WAV is less than one second long - should use a SoundBuffer" int 3 .endif xor ebx,ebx .while ebx<[esi].Buffers.dCount OCall esi.stream, $OCall([esi].Buffers::DwordCollection.ItemAt,ebx) inc ebx DbgDec ebx,"Buffers Loaded" .endw .if $invoke (alGetError)==AL_NO_ERROR mov eax,TRUE .else xor eax,eax .endif .else DbgWarning "Failed to Map WAV" .endif MethodEnd ;Method: SoundSource.Load_WAV_File ;Purpose: Load WAV into this SoundSource's (only) buffer Method SoundSource.Load_MP3_File, uses esi, pszName:LPSTR SetObject esi DbgWarning "Load MP3" DbgHex esi OCall esi.Map_MP3_File, pszName .if eax!=0 mov [esi].bCompressed,TRUE OCall esi.Init_Decompression xor ebx,ebx .while ebx<[esi].Buffers.dCount OCall esi.stream, $OCall([esi].Buffers::DwordCollection.ItemAt,ebx) inc ebx DbgDec ebx,"Buffers Loaded" .endw .if $invoke (alGetError)==AL_NO_ERROR mov eax,TRUE .else xor eax,eax .endif .else DbgWarning "Failed to Map MP3" .endif MethodEnd ;Method: SoundSource.Init_MP3_Streaming_Support ;Purpose: Attempt to initialize an "MP3 to PCM/WAV" streaming object ; which will be shared by any/all Sounds which require it. ;Returns: TRUE/FALSE Method SoundSource.Init_MP3_Streaming_Support,uses esi,pwfx local pwfxIN:ptr MPEGLAYER3WAVEFORMAT local maxFormatSize SetObject esi DbgWarning "Attempting to initialize MP3 Support" .if [esi].ACMStreamHandle!=-1 DbgWarning "MP3 Streaming Support already initialized" return FALSE .endif ; try to find an MP3 codec mov [esi].Desired_Codec_Tag,WAVE_FORMAT_MPEGLAYER3 mov [esi].bHave_Desired_Codec,FALSE invoke acmDriverEnum, addr Find_Desired_Codec, esi, 0 .if [esi].bHave_Desired_Codec == FALSE DbgWarning "ERROR: No MP3 decoders found" return FALSE .endif ; find the biggest format size mov maxFormatSize , 0 invoke acmMetrics, NULL, ACM_METRIC_MAX_SIZE_FORMAT, addr maxFormatSize invoke acmStreamOpen,addr [esi].ACMStreamHandle, ; open an ACM conversion stream NULL, ; querying all ACM drivers addr Default_Wave_Format_For_MP3_Input, ; converting from MP3 addr Preferred_Wave_Format_For_MP3_PlayBack, ; to WAV NULL, ; with no filter 0, ; or async callbacks 0, ; (and no data for the callback) 0 ; and no flags push eax MemFree pwfxIN pop eax .if eax== MMSYSERR_NOERROR DbgWarning "MP3 to WAV stream ready" return TRUE .elseif eax== MMSYSERR_INVALPARAM DbgWarning "Invalid parameters passed to acmStreamOpen" .elseif eax== ACMERR_NOTPOSSIBLE DbgWarning "No ACM filter found capable of decoding MP3" .else DbgWarning "Some error opening ACM decoding stream" .endif mov eax, FALSE MethodEnd ; Prepare decompression stream header with values obtained from parsing mp3 first frame Method SoundSource.Init_Decompression, uses esi SetObject esi mov [esi].pFrame_PCM, $MemAlloc([esi].dPCM_FrameSize) mov [esi].pFrame_Compressed,$MemAlloc([esi].dCompressed_FrameSize) invoke RtlZeroMemory,addr [esi].ACMStreamHeader, sizeof ACMSTREAMHEADER mov [esi].ACMStreamHeader.cbStruct, sizeof ACMSTREAMHEADER m2m [esi].ACMStreamHeader.pbSrc, [esi].pFrame_Compressed, edx m2m [esi].ACMStreamHeader.cbSrcLength , [esi].dCompressed_FrameSize,edx m2m [esi].ACMStreamHeader.pbDst ,[esi].pFrame_PCM, edx m2m [esi].ACMStreamHeader.cbDstLength , [esi].dPCM_FrameSize, edx invoke acmStreamPrepareHeader, [esi].ACMStreamHandle, addr [esi].ACMStreamHeader, 0 .if eax==0 DbgWarning "MP3 Decompression ConversionStream Header prepared" .else DbgWarning "FAILED to prepare MP3 Decompression ConversionStream Header" .if eax==MMSYSERR_INVALFLAG DbgWarning "Invalid FLAG" .elseif eax==MMSYSERR_INVALHANDLE DbgWarning "Invalid Handle" .elseif eax==MMSYSERR_INVALPARAM DbgWarning "Invalid Param" .else DbgWarning "(I dont know why)" .endif .endif MethodEnd ;Method: SoundSource.stream ;Purpose: Fill given buffer with PCM audio data Method SoundSource.stream,uses esi, buffer SetObject esi .if [esi].bCompressed==FALSE mov edx,[esi].dStreamOffset ;Have we got enough data left for a full buffer? add edx,[esi].dBytesPerSecond .if edx>[esi].dFileData_Size mov edx,[esi].dFileData_Size ;No - calculate whats left sub edx,[esi].dStreamOffset .else mov edx,[esi].dBytesPerSecond ;Yes - use default buffer size .endif mov eax,[esi].pPCMData ;Obtain ptr to source data = base + cursor add eax,[esi].dStreamOffset add [esi].dStreamOffset,edx ;<-- Update Cursor .else mov edx,[esi].dStreamOffset ;Have we got enough data left for a full buffer? add edx,[esi].dCompressed_FrameSize .if edx>[esi].dFileData_Size mov edx,[esi].dFileData_Size ;No - calculate whats left sub edx,[esi].dStreamOffset mov [esi].ACMStreamHeader.cbSrcLength,edx .if eax==0 ;We have run out of MP3 data - theres nothing to decompress DbgWarning "MP3 Data Exhausted" mov [esi].bDataExhausted,TRUE return FALSE ;This will tell the stream method not to requeue the buffer .endif ;Grab chunk of Compressed audio mov eax,[esi].pPCMData ;Obtain ptr to source data = base + cursor add eax,[esi].dStreamOffset invoke RtlMoveMemory,[esi].pFrame_Compressed,eax,[esi].ACMStreamHeader.cbSrcLength ;Decompress audio into PCM format invoke acmStreamConvert,[esi].SoundSource.ACMStreamHandle,addr [esi].ACMStreamHeader,ACM_STREAMCONVERTF_END .else ;Grab chunk of Compressed audio mov eax,[esi].pPCMData ;Obtain ptr to source data = base + cursor add eax,[esi].dStreamOffset invoke RtlMoveMemory,[esi].pFrame_Compressed,eax,[esi].ACMStreamHeader.cbSrcLength ;Decompress audio into PCM format invoke acmStreamConvert,[esi].ACMStreamHandle,addr [esi].ACMStreamHeader,ACM_STREAMCONVERTF_BLOCKALIGN .endif mov eax,[esi].ACMStreamHeader.cbSrcLengthUsed add [esi].dStreamOffset,eax mov edx,[esi].ACMStreamHeader.cbDstLengthUsed mov eax,[esi].pFrame_PCM .endif ;Copy PCM audio data into Buffer invoke alBufferData,buffer,[esi].format, eax,edx, [esi].freq ;Copy data to buffer .if $invoke (alGetError)==AL_NO_ERROR ;Check for error mov eax,TRUE .else mov eax,FALSE .endif MethodEnd ;Method: SoundSource.Refill ;Purpose: Refill and re-queue any buffers which have finished playing Method SoundSource.Refill, uses esi LOCAL buffer, processed,active:BOOL LOCAL src SetObject esi mov active,TRUE m2m src,[esi].Source,eax invoke alGetSourcei,src, AL_BUFFERS_PROCESSED, addr processed .if processed DbgDec processed .repeat invoke alSourceUnqueueBuffers,src, 1, addr buffer .if $invoke(alGetError) != AL_NO_ERROR return FALSE .endif or active, $OCall(esi.stream,buffer) .if eax ;Only requeue the buffer if we succeeded in filling it with PCM data invoke alSourceQueueBuffers,src, 1, addr buffer .if $invoke(alGetError) != AL_NO_ERROR int 3 return FALSE .endif .else DbgText "A buffer was dequeued" .endif dec processed .until processed==0 .endif .if !active OCall esi.Controls,SOUND_STOP .endif return active MethodEnd ; 覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧 ; 覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧 ; 覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧 ; 覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧 ; 覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧 ; Thread to poll refill of streaming buffers Method SoundSystem.Monitor,uses esi ebx LOCAL crit:CRITICAL_SECTION SetObject esi invoke InitializeCriticalSection,addr crit DbgWarning "Worker Active","Worker" .repeat invoke Sleep,200 invoke EnterCriticalSection,addr crit xor ebx,ebx .while ebx<[esi].StreamSources_Playing.dCount OCall [esi].StreamSources_Playing::DwordCollection.ItemAt,ebx .if eax!=-1 OCall eax::SoundSource.Refill .endif inc ebx .endw invoke LeaveCriticalSection,addr crit .if [esi].bShutDown==TRUE DbgWarning "Worker Death","Worker" mov [esi].bShutDown,FALSE invoke DeleteCriticalSection,addr crit invoke ExitThread,0 .endif .until 0 MethodEnd ; 覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧 ; 覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧 ; 覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧 ; 覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧 ; 覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧覧 ;Procedure: Find_Desired_Codec ;Purpose: Enumerate codecs to find one capable of decoding desired compression Find_Desired_Codec proc hadid, dwInstance, fdwSupport local details:ACMDRIVERDETAILS local driver:Handle local fmtDetails:ACMFORMATTAGDETAILS .ifBitSet fdwSupport, ACMDRIVERDETAILS_SUPPORTF_CODEC mov details.cbStruct , sizeof details invoke acmDriverDetails, hadid, addr details, 0 invoke acmDriverOpen, addr driver, hadid, 0 xor ecx,ecx .while ecx < details.cFormatTags push ecx invoke RtlZeroMemory, addr fmtDetails, sizeof fmtDetails pop ecx push ecx mov fmtDetails.cbStruct , sizeof fmtDetails mov fmtDetails.dwFormatTagIndex , ecx invoke acmFormatTagDetails, driver, addr fmtDetails, ACM_FORMATTAGDETAILSF_INDEX pop ecx mov edx,dwInstance mov edx,[edx].SoundSource.Desired_Codec_Tag .if fmtDetails.dwFormatTag == edx DbgWarning "Found a useable codec: " lea eax,details.szLongName DbgStr eax mov edx,dwInstance mov [edx].SoundSource.bHave_Desired_Codec,TRUE .break .endif inc ecx .endw invoke acmDriverClose,addr driver, 0 .endif ret Find_Desired_Codec endp