/'===============================================================================================
 Virtual Voices Example
 Copyright (c), Firelight Technologies Pty, Ltd 2004-2011.

 This example shows the virtual channels of FMOD. The listener and sounce sources can be moved
 around by clicking and dragging. Sound sources will change colour depending on whether they
 are virtual or not. Red means they are real, blue means they are virtual channels.
==============================================================================================='/

#include "windows.bi"
#include "win/mmsystem.bi"
#include "crt.bi"

#include "fmodex/fmodex.bi"
#include "fmodex/fmod_errors.bi"


#define TITLE           "Virtual Voices Example. Copyright (c), Firelight Technologies Pty, Ltd 2004-2011."
#define NUMCHANNELS     50
#define NUMREALCHANNELS 10
#define WIDTH_           640
#define HEIGHT          480
#define SOURCESIZE      5
#define LISTENERSIZE    15
#define SELECTTHRESHOLD 5
#define TITLEBAROFFSET  20
#define IDD_DIALOG 101

type SoundSource_ as SoundSource
type Listener_ as Listener

dim shared as HWND         gMainhwnd
dim shared as HINSTANCE    gHinstance
dim shared as bool         gSelected  = false
dim shared as SoundSource_ ptr gSelectedSource = 0
dim shared as SoundSource_ ptr gSoundSource(NUMCHANNELS-1)
dim shared as Listener_    ptr gListener

dim shared as FMOD_System    ptr gSystem = 0
dim shared as FMOD_Sound     ptr gSound = 0


sub ERRCHECK(byval result as FMOD_RESULT)
    if (result <> FMOD_OK) then
        dim as zstring * 1024    errstring = any

        sprintf(errstring, "FMOD error! (%d) %s", result, FMOD_ErrorString(result))

        MessageBox( NULL, errstring, "FMOD error!", MB_ICONHAND or MB_OK or MB_SYSTEMMODAL)

        end -1
    end if
    end sub


type SoundSource
  private:
as FMOD_Channel ptr mChannel
    as FMOD_VECTOR mPos, mVel
    as HBRUSH mBrushBlue
    as HBRUSH mBrushRed
    
  public:
declare constructor(byval posx as float, byval posy as float)
declare destructor()
declare sub     Draw(byval hdc as HDC)
declare sub SetPosition(byval posx as float, byval posy as float)
declare function GetPosition() as FMOD_VECTOR
declare function IsSelected(byval posx as float, byval posy as float) as bool
end type

    constructor SoundSource(byval posx as float, byval posy as float)
        dim as FMOD_RESULT result = any

        mPos.x = posx
        mPos.y = posy
        mPos.z = 0.0f
        mVel.x = 0.0f
        mVel.y = 0.0f
        mVel.z = 0.0f

        result = FMOD_System_PlaySound(gSystem,FMOD_CHANNEL_FREE, gSound, true, @mChannel)
        ERRCHECK(result)

        SetPosition(posx, posy)

        result = FMOD_Channel_SetFrequency(mChannel,22050.0f + (rand() mod 88200))
        ERRCHECK(result)

        result = FMOD_Channel_SetPaused(mChannel, false)
        ERRCHECK(result)

        mBrushBlue = CreateSolidBrush(bgr(0,0,255))
        mBrushRed  = CreateSolidBrush(bgr(255,0,0))
    end constructor

    destructor SoundSource()
        DeleteObject(mBrushBlue)
        DeleteObject(mBrushRed)
    end destructor

    sub SoundSource.Draw(byval hdc as HDC)
        dim as FMOD_RESULT result = any
        dim as bool isvirtual = any

        result = FMOD_Channel_IsVirtual(mChannel, @isvirtual)
        ERRCHECK(result)

        if (isvirtual) then
            SelectObject(hdc, mBrushBlue)
        else
            SelectObject(hdc, mBrushRed)
        end if

        Ellipse(hdc, (mPos.x - SOURCESIZE), _
                     (mPos.y - SOURCESIZE), _
                     (mPos.x + SOURCESIZE), _
                     (mPos.y + SOURCESIZE))
    end sub

    sub SoundSource.SetPosition(byval posx as float, byval posy as float)
        dim as FMOD_RESULT result = any

        mPos.x = posx
        mPos.y = posy

        result = FMOD_Channel_Set3DAttributes(mChannel, @mPos, @mVel)
        ERRCHECK(result)
    end sub

    function SoundSource.GetPosition() as FMOD_VECTOR
        return mPos
    end function

    function SoundSource.IsSelected(byval posx as float, byval posy as float) as bool
        if (posx > mPos.x - SOURCESIZE - SELECTTHRESHOLD andalso _
            posx < mPos.x + SOURCESIZE + SELECTTHRESHOLD andalso _
            posy > mPos.y - SOURCESIZE - SELECTTHRESHOLD andalso _
            posy < mPos.y + SOURCESIZE + SELECTTHRESHOLD) then
            return true
        end if
        
        return false
    end function


type Listener
  private:
    as FMOD_VECTOR mListenerPos
     as FMOD_VECTOR mUp, mVel, mForward
    as HBRUSH      mBrush

  public:
    declare constructor(byval posx as float, byval posy as float)
declare destructor()
declare sub Draw(byval hdc as HDC)
declare sub SetPosition(byval posx as float, byval posy as float)
declare function GetPosition() as FMOD_VECTOR
declare function IsSelected(byval posx as float, byval posy as float) as bool
    end type

    constructor Listener(byval posx as float, byval posy as float)
        mUp.x = 0
        mUp.y = 0
        mUp.z = 1

        mForward.x =  0
        mForward.y = -1
        mForward.z =  0

        mVel.x = 0
        mVel.y = 0
        mVel.z = 0

        SetPosition(posx, posy)

        mBrush = CreateSolidBrush(BGR(0,0,0))
    end constructor

    destructor Listener()
        DeleteObject(mBrush)
    end destructor

    sub Listener.Draw(byval hdc as HDC)
        SelectObject(hdc, mBrush)
        
        ' head
        Ellipse(hdc, (mListenerPos.x - LISTENERSIZE), _
                     (mListenerPos.y - LISTENERSIZE), _
                     (mListenerPos.x + LISTENERSIZE), _
                     (mListenerPos.y + LISTENERSIZE))

        ' nose
        Ellipse(hdc, (mListenerPos.x - (LISTENERSIZE / 3)), _
                     (mListenerPos.y - (LISTENERSIZE / 3) - LISTENERSIZE), _
                     (mListenerPos.x + (LISTENERSIZE / 3)), _
                     (mListenerPos.y + (LISTENERSIZE / 3) - LISTENERSIZE))

        ' left ear
        Ellipse(hdc, (mListenerPos.x - (LISTENERSIZE / 3) - LISTENERSIZE), _
                     (mListenerPos.y - (LISTENERSIZE / 3)), _
                     (mListenerPos.x + (LISTENERSIZE / 3) - LISTENERSIZE), _
                     (mListenerPos.y + (LISTENERSIZE / 3)))

        ' right ear
        Ellipse(hdc, (mListenerPos.x - (LISTENERSIZE / 3) + LISTENERSIZE), _
                     (mListenerPos.y - (LISTENERSIZE / 3)), _
                     (mListenerPos.x + (LISTENERSIZE / 3) + LISTENERSIZE), _
                     (mListenerPos.y + (LISTENERSIZE / 3)))
    end sub

    sub Listener.SetPosition(byval posx as float, byval posy as float)
        dim as FMOD_RESULT result = any

        mListenerPos.x = posx
        mListenerPos.y = posy
        mListenerPos.z = 0

        result = FMOD_System_Set3DListenerAttributes(gSystem, 0, @mListenerPos, @mVel, @mForward, @mUp)
        ERRCHECK(result)
    end sub

    function Listener.GetPosition() as FMOD_VECTOR
        return mListenerPos
    end function

    function Listener.IsSelected(byval posx as float, byval posy as float) as bool
        if (posx > mListenerPos.x - LISTENERSIZE - SELECTTHRESHOLD andalso _
            posx < mListenerPos.x + LISTENERSIZE + SELECTTHRESHOLD andalso _
            posy > mListenerPos.y - LISTENERSIZE - SELECTTHRESHOLD andalso _
            posy < mListenerPos.y + LISTENERSIZE + SELECTTHRESHOLD) then
            return true
        end if
        
        return false
    end function



sub CloseDown() 
    dim as FMOD_RESULT result = any
    dim as integer         count = any

    /'
        Release listener
    '/
    delete gListener

    /'
        Release sound sources
    '/
    for count = 0 to NUMCHANNELS-1
        if (gSoundSource(count)) then
            delete gSoundSource(count)
        end if
        gSoundSource(count) = 0
    next

    /'
        Shutdown FMOD
    '/
    if (gSound) then
        result = FMOD_Sound_Release(gSound)
        ERRCHECK(result)
    end if
    if (gSystem) then
        result = FMOD_System_Close(gSystem)
        ERRCHECK(result)

        result = FMOD_System_Release(gSystem)
        ERRCHECK(result)
    end if
end sub


function  FMOD_DlgProc(byval hwnd as HWND, byval msg as UINT , byval wParam as WPARAM, byval lParam as LPARAM) as BOOL
    dim as integer          timerid = 0
 
	select case msg
		case WM_INITDIALOG:

			/'
			    SET UP A PAINT TIMER FOR INTERFACE
			'/
			timerid = SetTimer(hwnd, 0, 20, 0) 
			if (timerid = 0) then
    			MessageBox( NULL, "Too many timers in use", "Error", MB_ICONHAND or MB_OK or MB_SYSTEMMODAL)
    			return FALSE
			end if
return TRUE
		case WM_CLOSE:

			KillTimer(hwnd, timerid)

            /'
                The timer doesnt die immediately, sleep for a bit to let it die so we dont free streams then the timer tries to access them
            '/
            Sleep(200)

			CloseDown()
			EndDialog(hwnd,0)
            PostQuitMessage(0)
return TRUE
        case WM_LBUTTONDOWN, WM_RBUTTONDOWN:
            dim as integer count = any
            dim as POINT pt = any
            dim as RECT    rect = any
        
            GetWindowRect(hwnd, @rect)
            GetCursorPos(@pt)

            gSelectedSource = 0
            
            /'
                Check if listener was selected
            '/
            if (gListener->IsSelected(pt.x - rect.left, pt.y - rect.top - TITLEBAROFFSET)) then
                gSelected = true
                exit select
            end if
            /'
                Check if a soundsource was selected
            '/
            for count = 0 to NUMCHANNELS-1
                if (gSoundSource(count)->IsSelected(pt.x - rect.left, pt.y - rect.top - TITLEBAROFFSET)) then
                    gSelectedSource = gSoundSource(count)
                    gSelected = true
exit select
                end if
            next
        case WM_LBUTTONUP, WM_RBUTTONUP:
            gSelected  = false
        case WM_MOUSEMOVE:
            if (gSelected) then
                dim as POINT pt = any
                dim as RECT    rect = any
            
                GetWindowRect(hwnd, @rect)
                GetCursorPos(@pt)
                
                if (gSelectedSource) then
                    gSelectedSource->SetPosition(pt.x - rect.left, pt.y - rect.top - TITLEBAROFFSET)
                else
                    gListener->SetPosition(pt.x - rect.left, pt.y - rect.top - TITLEBAROFFSET)
                end if
            end if
        case WM_PAINT :
            dim as FMOD_RESULT result = any
            dim as integer         count = any
            dim as integer         channels = any
            dim as zstring * 512        text = any
            dim as HDC	        hdc = GetDC(hwnd)

            dim as HDC     hdcbuffer    = CreateCompatibleDC(hdc)
            dim as HBITMAP hbmbuffer    = CreateCompatibleBitmap(hdc, WIDTH_, HEIGHT)
            dim as HBITMAP hbmoldbuffer = SelectObject(hdcbuffer, hbmbuffer)

            dim as HDC     hdcmem = CreateCompatibleDC(hdc)
            dim as HBITMAP hbmold =SelectObject(hdcmem, NULL)
            dim as RECT    rect = any

            GetClientRect(hwnd, @rect)

            FillRect(hdcbuffer, @rect, GetStockObject(WHITE_BRUSH))

            /'
                Draw the sound sources
            '/
            for count = 0 to NUMCHANNELS-1
                gSoundSource(count)->Draw(hdcbuffer)
            next

            /'
                Draw the listener
            '/
            gListener->Draw(hdcbuffer)

            /'
                Print some information
            '/
            result = FMOD_System_GetChannelsPlaying(gSystem, @channels)
            ERRCHECK(result)

            sprintf(text, "Channels Playing: %d", channels)
            TextOut(hdcbuffer, 5, 5, text, strlen(text))

            sprintf(text, "Real Channels:    %d (RED)", NUMREALCHANNELS)
            TextOut(hdcbuffer, 5, 22, text, strlen(text))

            sprintf(text, "Virtual Channels: %d (BLUE)", NUMCHANNELS - NUMREALCHANNELS)
            TextOut(hdcbuffer, 5, 39, text, strlen(text))

            BitBlt(hdc, rect.left, rect.top, WIDTH_, HEIGHT, hdcbuffer, rect.left, rect.top, SRCCOPY)

            SelectObject(hdcmem, hbmold)               
	        DeleteDC(hdcmem)                    
	        SelectObject(hdcbuffer, hbmoldbuffer)      
            DeleteObject(hbmbuffer)                    
            DeleteDC(hdcbuffer)

            ReleaseDC(hwnd, hdc)                       
		case WM_TIMER :
            dim as RECT    rect = any
            
            GetWindowRect(hwnd, @rect)

            InvalidateRect(hwnd, @rect, false)

            FMOD_System_Update(gSystem)
		case else
			return FALSE
end select
	return FALSE
end function


function SetupInterface(byval hinst as HINSTANCE, byval lpCmdLine as LPSTR) as integer
	gMainhwnd = CreateDialog(hinst, MAKEINTRESOURCE(IDD_DIALOG), GetDesktopWindow(), @FMOD_DlgProc)

    MoveWindow(gMainhwnd, 0, 0, WIDTH_, HEIGHT, TRUE)
	ShowWindow(gMainhwnd, SW_SHOW)

    SetWindowText(gMainhwnd, @TITLE)

	return TRUE
end function


function WinMain(byval hInstance as HINSTANCE, byval hPrevInstance as HINSTANCE, byval lpCmdLine as LPSTR, byval nCmdShow as integer) as integer
    dim as FMOD_RESULT  result = any
    dim as MSG          msg
    dim as HWND         hwnd = any
    dim as integer          count = any
    dim as uinteger version = any

    gHinstance = hInstance

    /'
        Create a System object and initialize.
    '/
    result = FMOD_System_Create(@gSystem)
    ERRCHECK(result)

    hwnd = FindWindow(0, @TITLE)
    if (hwnd) then
        if (IsIconic(hwnd)) then
            ShowWindow(hwnd, SW_RESTORE)
        else
            SetActiveWindow(hwnd)
        end if

        return 0
    end if

    /'
        Initialise
    '/
    result = FMOD_System_GetVersion(gSystem, @version)
    ERRCHECK(result)

    if (version < FMOD_VERSION) then
        MessageBox(GetForegroundWindow(), "INCORRECT DLL VERSION!!", "FMOD ERROR", MB_OK)
    end if

    result = FMOD_System_SetSoftwareChannels(gSystem, NUMREALCHANNELS)
    ERRCHECK(result)

    result = FMOD_System_Init(gSystem, NUMCHANNELS, FMOD_INIT_NORMAL, 0)
    ERRCHECK(result)

    result = FMOD_System_CreateSound(gSystem, "media/drumloop.wav", FMOD_SOFTWARE or FMOD_3D or FMOD_LOOP_NORMAL, 0, @gSound)
    ERRCHECK(result)
    result = FMOD_SOUND_Set3DMinMaxDistance(gSound, 4.0f, 10000.0f)
    ERRCHECK(result)

    srand(timeGetTime())

    /'
        Create a listener in the middle of the window
    '/
    gListener = new Listener(WIDTH_/2, HEIGHT/2)

    /'
        Initialise all of the sound sources
    '/
    for count = 0 to NUMCHANNELS-1
        dim as integer x = rand() mod WIDTH_
        dim as integer y = rand() mod HEIGHT

        gSoundSource(count) = new SoundSource(x, y)
    next

    /'
        Create interface and start message loop
    '/
    if (SetupInterface(hInstance, lpCmdLine) = 0) then return FALSE

  	while (GetMessage( @msg, 0, 0, 0 ))
    	TranslateMessage( @msg )
      	DispatchMessage( @msg )
  	wend

	return msg.wParam
end function

end WinMain(GetModuleHandle( null ), null, GetCommandLine(), SW_SHOWNORMAL)
