ifndef                  _SOUNDMIXER_ASM
_SOUNDMIXER_ASM         equ     1

; ------------------------------------------------------
; Constants
MIDI_FREQUENCIES        equ     128

; ------------------------------------------------------
; Structures
CHANNEL                 struct
Instrument              dd      ?
_Size                   real4   ?
Phase                   real4   ?
Volume                  real4   ?
Panning                 real4   ?
Note                    dd      ?
Base_Freq               dd      ?
Looping                 dd      ?
CHANNEL                 ends

; ------------------------------------------------------
; Variables
_DSound_Context         LPDIRECTSOUND 0
_Sound_Buffer_Desc      DSBUFFERDESC <>
_Sound_Buffer           LPDIRECTSOUNDBUFFER 0
_Wave_Format            WAVEFORMATEX <>
_Crit_Section           CRITICAL_SECTION <>
_Done_Critic            dd      0
_Mixer_Play_State       dd      0
_Buffer_Pos             dd      0
_Old_Buffer_Pos         dd      0
_Audio_Ptr1             dd      0
_Audio_Bytes1           dd      0
_Audio_Ptr2             dd      0
_Audio_Bytes2           dd      0
_ThreadId               dd      0
_hReplayThread          dd      0
_Max_Mixer_Rate         dd      0
_Max_Polyphony          dd      0
_Polyphony_Buffer       dd      0
_DSound_Mixer_User      dd      0
_Left_Mixing_Buffer     dd      0
_Right_Mixing_Buffer    dd      0
_Global_Volume          real4   1.0
Midi_Freq_Table         real4   MIDI_FREQUENCIES dup (0.0)

; ------------------------------------------------------
; Includes
                        include Wav.asm

; ------------------------------------------------------
; Name: _Get_Slot
; Desc: Obtain a slot in the Polyphony buffer
_Get_Slot               proc    uses ebx esi
                        ; Look for a free slot
                        xor     ebx, ebx
                        .while  ebx < _Max_Polyphony
                                mov     esi, ebx
                                imul    esi, sizeof CHANNEL
                                add     esi, _Polyphony_Buffer
                                mov     ecx, [esi + CHANNEL.Instrument]
                                .if     ecx == NULL
                                        mov     eax, ebx
                                        ret
                                .endif
                                inc     ebx
                        .endw
                        ; No free slot
                        or      eax, -1
                        ret
_Get_Slot               endp

; ------------------------------------------------------
; Name: Set_Wav_Volume
; Desc: Set the volume of a playing wav
Set_Wav_Volume          proc    uses edi Slot_Number:dword, Volume:real4
                        mov     edi, Slot_Number
                        dec     edi
                        .if     edi >= 0 && edi < _Max_Polyphony
                                imul    edi, sizeof CHANNEL
                                add     edi, _Polyphony_Buffer
                                fld     Volume
                                FCMP    st(0), CFLT(0.0)
                                jae     Min_Volume
                                fstp    st(0)
                                fldz
Min_Volume:                     FCMP    st(0), CFLT(1.0)
                                jbe     Max_Volume
                                fstp    st(0)
                                fld1
Max_Volume:                     fstp    Volume
                                mov     [edi + CHANNEL.Volume], CMEM(Volume)
                        .endif
                        ret
Set_Wav_Volume          endp

; ------------------------------------------------------
; Name: Set_Wav_Panning
; Desc: Set the panning of a playing wav
Set_Wav_Panning         proc    uses edi Slot_Number:dword, Panning:real4
                        mov     edi, Slot_Number
                        dec     edi
                        .if     edi >= 0 && edi < _Max_Polyphony
                                imul    edi, sizeof CHANNEL
                                add     edi, _Polyphony_Buffer
                                fld     Panning
                                FCMP    st(0), CFLT(0.0)
                                jae     Min_Panning
                                fstp    st(0)
                                fldz
Min_Panning:                    FCMP    st(0), CFLT(1.0)
                                jbe     Max_Panning
                                fstp    st(0)
                                fld1
Max_Panning:                    fstp    Panning
                                mov     [edi + CHANNEL.Panning], CMEM(Panning)
                        .endif
                        ret
Set_Wav_Panning         endp

; ------------------------------------------------------
; Name: Is_Wav_Playing
; Desc: Check if a wav is still beeing played
Is_Wav_Playing          proc    uses edi Slot_Number:dword
                        mov     edi, Slot_Number
                        dec     edi
                        .if     edi >= 0 && edi < _Max_Polyphony
                                imul    edi, sizeof CHANNEL
                                add     edi, _Polyphony_Buffer
                                .if     [edi + CHANNEL.Instrument] != NULL
                                        mov     eax, TRUE
                                        ret
                                .endif
                        .endif
                        mov     eax, FALSE
                        ret
Is_Wav_Playing          endp

; ------------------------------------------------------
; Name: Stop_Wav
; Desc: Stop a playing wav
Stop_Wav                proc    uses edi Slot_Number:dword
                        mov     edi, Slot_Number
                        dec     edi
                        .if     edi >= 0 && edi < _Max_Polyphony
                                imul    edi, sizeof CHANNEL
                                add     edi, _Polyphony_Buffer
                                invoke  EnterCriticalSection, addr _Crit_Section
                                mov     [edi + CHANNEL.Instrument], NULL
                                invoke  LeaveCriticalSection, addr _Crit_Section
                        .endif
                        ret
Stop_Wav                endp

; ------------------------------------------------------
; Name: Play_Wav
; Desc: Send a wav file to the mixer (return the number of the allocated slot)
Play_Wav                proc    uses ebx esi edi Wav_Struct:dword, Volume:real4, Panning:real4, Note:dword, Slot_Number:dword
                        or      eax, -1
                        .if     _Done_Critic != FALSE
                                invoke  EnterCriticalSection, addr _Crit_Section
                                        mov     esi, Wav_Struct
                                        mov     edi, _Polyphony_Buffer
                                        mov     eax, Slot_Number
                                        .if     eax == -1
                                                invoke  _Get_Slot
                                        .endif
                                        mov     ebx, eax
                                        .if     eax != -1
                                                imul    eax, sizeof CHANNEL
                                                add     edi, eax
                                                mov     eax, [esi + WAV._Size]
                                                dec     eax
                                                mov     [edi + CHANNEL._Size], CMEM(INT2FLT(eax))
                                                mov     [edi + CHANNEL.Base_Freq], CMEM([esi + WAV.Base_Freq])
                                                mov     [edi + CHANNEL.Looping], CMEM([esi + WAV.Looping])
                                                mov     [edi + CHANNEL.Phase], 0
                                                mov     [edi + CHANNEL.Volume], CMEM(Volume)
                                                mov     [edi + CHANNEL.Panning], CMEM(Panning)
                                                mov     eax, [esi + WAV.Datas]
                                                mov     [edi + CHANNEL.Instrument], eax
                                                mov     [edi + CHANNEL.Note], CMEM(Note)
                                        .endif
                                invoke  LeaveCriticalSection, addr _Crit_Section
                                mov     eax, ebx
                        .endif
                        inc     eax
                        ret
Play_Wav                endp

; ------------------------------------------------------
; Name: _Default_Mixing_Routine
; Desc: Sound mixing
_Default_Mixing_Routine proc    uses ebx edi esi Buffer:dword, Buffer_Length:dword
                        local   dwBuffer_Length:dword
                        local   iPhase:dword

                        mov     eax, Buffer_Length
                        shr     eax, 2
                        mov     dwBuffer_Length, eax
                        ; Clear the buffer
                        mov     edi, _Left_Mixing_Buffer
                        mov     ecx, dwBuffer_Length
                        xor     eax, eax
                        rep     stosd
                        mov     edi, _Right_Mixing_Buffer
                        mov     ecx, dwBuffer_Length
                        xor     eax, eax
                        rep     stosd
                        xor     ebx, ebx
                        .while  ebx < _Max_Polyphony
                                push    ebx
                                mov     esi, ebx
                                imul    esi, sizeof CHANNEL
                                add     esi, _Polyphony_Buffer
                                mov     ecx, [esi + CHANNEL.Instrument]
                                .if     ecx != NULL
                                        xor     ebx, ebx
                                        mov     edx, _Left_Mixing_Buffer
                                        mov     edi, _Right_Mixing_Buffer
                                        .while  ebx < dwBuffer_Length
                                                fld     [esi + CHANNEL.Phase]
                                                fdiv    [esi + CHANNEL.Base_Freq]
                                                fistp   iPhase
                                                mov     eax, iPhase             ; Left datas
                                                shl     eax, 3
                                                fld     dword ptr [ecx + eax]
                                                push    eax
                                                FCMP    [esi + CHANNEL.Panning], CFLT(-1.0)
                                                je      Surround_Left
                                                fld1
                                                fld     [esi + CHANNEL.Panning]
                                                fsubp   st(1), st(0)
                                                fsqrt
                                                fmulp   st(1), st(0)
Surround_Left:                                  fmul    dword ptr [esi + CHANNEL.Volume]
                                                fadd    dword ptr [edx]
                                                fstp    dword ptr [edx]
                                                FCMP    dword ptr [edx], CFLT(1.0)
                                                jb      Max_Clamp_Left
                                                fld1
                                                fstp    dword ptr [edx]
Max_Clamp_Left:                                 FCMP    dword ptr [edx], CFLT(-1.0)
                                                ja      Min_Clamp_Left
                                                fld1
                                                fchs
                                                fstp    dword ptr [edx]
Min_Clamp_Left:                                 add     edx, 4
                                                pop     eax
                                                add     eax, 4                  ; Right datas
                                                fld     dword ptr [ecx + eax]
                                                FCMP    [esi + CHANNEL.Panning], CFLT(-1.0)
                                                je      Surround_Right
                                                fld     [esi + CHANNEL.Panning]
                                                fsqrt
                                                fmulp   st(1), st(0)
                                                jmp     No_Surround
Surround_Right:                                 ; Rotate it by 180 degrees
                                                fchs
No_Surround:                                    fmul    dword ptr [esi + CHANNEL.Volume]
                                                fadd    dword ptr [edi]
                                                fstp    dword ptr [edi]
                                                FCMP    dword ptr [edi], CFLT(1.0)
                                                jb      Max_Clamp_Right
                                                fld1
                                                fstp    dword ptr [edi]
Max_Clamp_Right:                                FCMP    dword ptr [edi], CFLT(-1.0)
                                                ja      Min_Clamp_Right
                                                fld1
                                                fchs
                                                fstp    dword ptr [edi]
Min_Clamp_Right:                                add     edi, 4
                                                mov     eax, [esi + CHANNEL.Note]
                                                fld     [Midi_Freq_Table + eax * 4]
                                                fadd    [esi + CHANNEL.Phase]
                                                fst     [esi + CHANNEL.Phase]
                                                fdiv    [esi + CHANNEL.Base_Freq]
                                                FCMP    st(0), [esi + CHANNEL._Size]
                                                fstp    st(0)
                                                jb      Reset_Phase
                                                ; Stop the sample & free the slot
                                                .if     [esi + CHANNEL.Looping]
                                                        fld     [esi + CHANNEL.Phase]
                                                        fld     [esi + CHANNEL._Size]
                                                        fmul    [esi + CHANNEL.Base_Freq]
                                                        fsubp   st(1), st(0)
                                                        fstp    [esi + CHANNEL.Phase]
                                                .else
                                                        mov     [esi + CHANNEL.Instrument], 0
                                                        jmp     Done_Sample
                                                .endif
Reset_Phase:                                    inc     ebx
                                        .endw
Done_Sample:    
                                .endif
                                pop     ebx
                                inc     ebx
                        .endw
                        mov     edi, Buffer
                        mov     ecx, _Left_Mixing_Buffer
                        mov     edx, _Right_Mixing_Buffer
                        xor     ebx, ebx
                        .while  ebx < dwBuffer_Length
                                fld     dword ptr [ecx]
                                fmul    _Global_Volume
                                fmul    CFLT(32767.0)
                                push    eax
                                fistp   dword ptr [esp]
                                pop     eax
                                stosw
                                add     ecx, 4
                                fld     dword ptr [edx]
                                fmul    _Global_Volume
                                fmul    CFLT(32767.0)
                                push    eax
                                add     [edi], ax
                                fistp   dword ptr [esp]
                                pop     eax
                                stosw
                                add     edx, 4
                                inc     ebx
                        .endw
                        ret
_Default_Mixing_Routine endp

; ------------------------------------------------------
; Name: _DSound_Mixer_Run
; Desc: Lock the buffer & call the user's mixing routine
_DSound_Mixer_Run       proc
                        local   _Bytes_To_Lock:dword

                        .if     _Done_Critic != FALSE
                                invoke  EnterCriticalSection, addr _Crit_Section
                                .if     _Mixer_Play_State == TRUE && _Sound_Buffer != NULL
                                        COINVOKE _Sound_Buffer, IDirectSoundBuffer.GetCurrentPosition, addr _Buffer_Pos, NULL
                                        ; Update sound buffer position
                                        mov     eax, _Buffer_Pos
                                        sub     eax, _Old_Buffer_Pos
                                        jns     _Reset_Buffer_Pos
                                        add     eax, _Max_Mixer_Rate
_Reset_Buffer_Pos:                      mov     _Bytes_To_Lock, eax
                                        COINVOKE _Sound_Buffer, IDirectSoundBuffer._Lock, _Old_Buffer_Pos, _Bytes_To_Lock, addr _Audio_Ptr1, addr _Audio_Bytes1, addr _Audio_Ptr2, addr _Audio_Bytes2, 0
                                        ; Save old position
                                        mov     eax, _Buffer_Pos
                                        mov     _Old_Buffer_Pos, eax
                                        .if     _DSound_Mixer_User != NULL
                                                .if     _Audio_Ptr1 != NULL
                                                        push    _Audio_Bytes1
                                                        push    _Audio_Ptr1
                                                        call    _DSound_Mixer_User
                                                .endif
                                                .if     _Audio_Ptr2 != NULL
                                                        push    _Audio_Bytes2
                                                        push    _Audio_Ptr2
                                                        call    _DSound_Mixer_User
                                                .endif
                                        .endif
                                        ; Unlock buffer now
                                        COINVOKE _Sound_Buffer, IDirectSoundBuffer.Unlock, _Audio_Ptr1, _Audio_Bytes1, _Audio_Ptr2, _Audio_Bytes2
                                .endif
                                invoke  LeaveCriticalSection, addr _Crit_Section
                        .endif
                        ret
_DSound_Mixer_Run       endp

; ------------------------------------------------------
; Name: _DSound_Mixer_Thread
; Desc: Audio rendering thread
_DSound_Mixer_Thread    proc    Params:dword
_DSound_Mixer_Thread_Loop:
                        .if     _Mixer_Play_State == TRUE
                                invoke  _DSound_Mixer_Run
                                invoke  Sleep, 10
                                jmp     _DSound_Mixer_Thread_Loop
                        .endif
                        xor     eax, eax
                        ret
_DSound_Mixer_Thread    endp

; ------------------------------------------------------
; Name: DSound_Mixer_Create
; Desc: Init the directsound mixer
DSound_Mixer_Create     proc    uses ebx hWnd:HWND, Mixing_Routine:dword, Mixing_Rate:dword, Mixing_Channels:dword, Mixing_Resolution:dword, Max_Polyphony:dword
                        mov     eax, Mixing_Routine
                        .if     eax == NULL
                                ; Set the default mixing routine if nothing specified
                                mov     eax, offset _Default_Mixing_Routine
                                mov     Mixing_Rate, 44100
                                mov     Mixing_Channels, 2
                                mov     Mixing_Resolution, 16
                                .if     sdword ptr Max_Polyphony < 1
                                        mov     Max_Polyphony, 1
                                .endif
                        .else
                                mov     Max_Polyphony, 0
                        .endif
                        mov     _DSound_Mixer_User, eax
                        mov     _Polyphony_Buffer, 0
                        mov     _Max_Mixer_Rate, CMEM(Mixing_Rate)

                        invoke  DirectSoundCreate, NULL, addr _DSound_Context, NULL
                        .if     eax == DS_OK
                                COINVOKE _DSound_Context, IDirectSound.SetCooperativeLevel, hWnd, DSSCL_EXCLUSIVE
                                .if eax == DS_OK
                                        mov     _Sound_Buffer_Desc.dwSize, sizeof DSBUFFERDESC
                                        mov     _Sound_Buffer_Desc.dwFlags, DSBCAPS_STICKYFOCUS
                                        ; hz
                                        mov     _Sound_Buffer_Desc.dwBufferBytes, CMEM(_Max_Mixer_Rate)
                                        mov     _Wave_Format.wFormatTag, WAVE_FORMAT_PCM
                                        ; Mono / Stereo
                                        mov     eax, Mixing_Channels
                                        mov     _Wave_Format.nChannels, ax
                                        mov     eax, _Max_Mixer_Rate
                                        mov     _Wave_Format.nSamplesPerSec, eax
                                        mov     ecx, Mixing_Resolution
                                        imul    ecx, Mixing_Channels
                                        shr     ecx, 3
                                        imul    eax, ecx
                                        mov     _Wave_Format.nAvgBytesPerSec, eax
                                        mov     _Wave_Format.nBlockAlign, cx
                                        ; 8 / 16 bits
                                        mov     eax, Mixing_Resolution
                                        mov     _Wave_Format.wBitsPerSample, ax
                                        mov     _Sound_Buffer_Desc.lpwfxFormat, offset _Wave_Format
                                        COINVOKE _DSound_Context, IDirectSound.CreateSoundBuffer, addr _Sound_Buffer_Desc, addr _Sound_Buffer, NULL
                                        .if eax == DS_OK
                                                COINVOKE _Sound_Buffer, IDirectSoundBuffer.Play, 0, 0, DSBPLAY_LOOPING
                                                .if eax == DS_OK
                                                        mov     _Mixer_Play_State, TRUE
                                                        invoke  CreateThread, NULL, 0, addr _DSound_Mixer_Thread, 0, 0, addr _ThreadId
                                                        mov     _hReplayThread, eax
                                                        .if     eax != NULL
                                                                invoke  SetThreadPriority, _hReplayThread, THREAD_PRIORITY_ABOVE_NORMAL
                                                                ; Create the midi frequencies table
                                                                xor     ebx, ebx
                                                                .while  ebx < MIDI_FREQUENCIES
                                                                        fld     INT2DBL(ebx)
                                                                        fdiv    CDBL(12.0)
                                                                        push    eax
                                                                        push    eax
                                                                        fstp    qword ptr [esp]
                                                                        fld     CDBL(2.0)
                                                                        push    eax
                                                                        push    eax
                                                                        fstp    qword ptr [esp]
                                                                        call    pow
                                                                        add     esp, 16
                                                                        fmul    CFLT(8.1758)
                                                                        fstp    [Midi_Freq_Table + ebx * 4]
                                                                        inc     ebx
                                                                .endw
                                                                invoke  InitializeCriticalSection, addr _Crit_Section
                                                                mov     eax, Max_Polyphony
                                                                .if     eax == 0
                                                                        mov     _Done_Critic, TRUE
                                                                        mov     eax, TRUE
                                                                        ret
                                                                .endif
                                                                ; Alloc the pool of channels
                                                                mov     _Max_Polyphony, eax
                                                                imul    eax, sizeof CHANNEL
                                                                mov     eax, ALLOCMEM(eax)
                                                                mov     _Polyphony_Buffer, eax
                                                                .if     eax != 0
                                                                        ; Alloc the mixing buffers
                                                                        mov     ebx, Mixing_Rate
                                                                        shl     ebx, 2
                                                                        mov     _Left_Mixing_Buffer, ALLOCMEM(ebx)
                                                                        mov     _Right_Mixing_Buffer, ALLOCMEM(ebx)
                                                                        .if     _Left_Mixing_Buffer != NULL && _Right_Mixing_Buffer != NULL
                                                                                mov     _Done_Critic, TRUE
                                                                                mov     eax, TRUE
                                                                                ret
                                                                        .endif
                                                                .endif
                                                        .endif
                                                .endif
                                        .endif
                                .endif
                        .endif
                        xor     eax,eax
                        ret
DSound_Mixer_Create     endp

; ------------------------------------------------------
; Name: DSound_Mixer_Destroy
; Desc: Stop/Release the sound buffer & release the mixer
DSound_Mixer_Destroy    proc
                        mov     _Mixer_Play_State, FALSE
                        FREEMEM _Left_Mixing_Buffer
                        FREEMEM _Right_Mixing_Buffer
                        FREEMEM _Polyphony_Buffer
                        .if     _hReplayThread != NULL
                                invoke  WaitForSingleObject, _hReplayThread, INFINITE
                                invoke  TerminateThread, _hReplayThread, 0
                                invoke  CloseHandle, _hReplayThread
                                mov     _hReplayThread, NULL
                        .endif
                        .if     _Done_Critic != FALSE
                                invoke  DeleteCriticalSection, addr _Crit_Section
                                mov     _Done_Critic, FALSE
                        .endif
                        .if     _Sound_Buffer != NULL
                                COINVOKE _Sound_Buffer, IDirectSoundBuffer.Stop
                                COINVOKE _Sound_Buffer, IDirectSoundBuffer.Release

                                mov     _Sound_Buffer, NULL
                        .endif
                        .if     _DSound_Context != NULL
                                COINVOKE _DSound_Context, IDirectSound.Release
                                mov     _DSound_Context, NULL
                        .endif
                        ret
DSound_Mixer_Destroy    endp

endif