//--------------------------------------------------------------------------------------
// Copyright (C) 2008 http://shift.delblanco.es
//--------------------------------------------------------------------------------------
// SHIFT Videogame 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 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.
//--------------------------------------------------------------------------------------
// 3DSoundMng.cpp
//--------------------------------------------------------------------------------------
#include "dxstdafx.h"
#include "3DSoundMng.h"

#include "3DCamera.h"

//--------------------------------------------------------------------------------------
// Variables Globales
//--------------------------------------------------------------------------------------
extern C3DCamera        *g_Camera;         // Instancia externa para la cámara 3D (3DCamera.cpp)

//--------------------------------------------------------------------------------------
// Espacio de nombres
//--------------------------------------------------------------------------------------
using namespace std;

//--------------------------------------------------------------------------------------
// Variables Globales
//--------------------------------------------------------------------------------------
C3DSoundMng          *g_3DSoundMng;     // Instancia para manejar sonidos 3D

//--------------------------------------------------------------------------------------
// Constructor
//--------------------------------------------------------------------------------------
C3DSoundMng::C3DSoundMng()
{
    m_bInit = false;

    m_pSoundManager = NULL;
    m_pDSListener = NULL;
}

//--------------------------------------------------------------------------------------
// Destructor
//--------------------------------------------------------------------------------------
C3DSoundMng::~C3DSoundMng()
{
    Release();
}

//--------------------------------------------------------------------------------------
// Carga el manejador de sonidos
//--------------------------------------------------------------------------------------
bool C3DSoundMng::initialize(void)
{
    HRESULT hr;

    // Salimos si ya se ha inicializado
    if(m_bInit) return false;
    m_bInit = true;

    // Creamos un una instancia de DirectSound
    if((m_pSoundManager = new CSoundManager()) == NULL)
        {Release(); return false;}

    // Ponemos el nivel cooperativo a DSSCL_PRIORITY
    hr = m_pSoundManager->Initialize(DXUTGetHWND(), DSSCL_PRIORITY);

    // Ponemos el formato del buffer primario a stereo 22kHz y salida de 16-bit
    hr |= m_pSoundManager->SetPrimaryBufferFormat(2, 22050, 16);

    // Tomamos el entorno 3D
    hr |= m_pSoundManager->Get3DListenerInterface(&m_pDSListener);
    if(FAILED(hr))
        {Release(); return false;}
    if(!m_pDSListener)
        {Release(); return false;}

    // Aplicamos los parámetros por defecto para el entorno 3D
    m_pDSListener->SetDopplerFactor(SOUND3D_DOPPLER_FACTOR, DS3D_DEFERRED);
    m_pDSListener->SetRolloffFactor(SOUND3D_ROLL_OFF_FACTOR, DS3D_DEFERRED);

    return true;
}

//--------------------------------------------------------------------------------------
// Crea un nuevo sonido 3D
//--------------------------------------------------------------------------------------
C3DSound *C3DSoundMng::Create(LPWSTR File, C3DObject *Owner)
{
    // Salimos si no se ha inicializado
    if(!m_bInit) return NULL;

    // Datos necesarios para el objeto de sonido
    CSound *pSound;
    LPDIRECTSOUND3DBUFFER pDS3DBuffer;
    long lInitFrequency;

    // Abrimos el fichero de sonido
    CWaveFile waveFile;
    waveFile.Open(File, NULL, WAVEFILE_READ);

    // Comprobamos que el formato sea correcto
    WAVEFORMATEX* pwfx = waveFile.GetFormat();
    if(pwfx == NULL)
        return NULL;
    if(pwfx->nChannels > 1)
        return NULL;
    if(pwfx->wFormatTag != WAVE_FORMAT_PCM)
        return NULL;

    // Tomamos la frecuencia base
    lInitFrequency = waveFile.m_pwfx->nSamplesPerSec;

    // Tomamos el algoritmo de DirectSound3D para usar
    GUID guid3DAlgorithm = DS3DALG_NO_VIRTUALIZATION;
    //GUID guid3DAlgorithm = DS3DALG_HRTF_FULL;
    //GUID guid3DAlgorithm = DS3DALG_HRTF_LIGHT;

    // Cargamos el fichero de sonido en el buffer de DirectSound
    HRESULT hr;
    DWORD dwCreationFlags = 0;
    dwCreationFlags = DSBCAPS_CTRL3D | DSBCAPS_CTRLVOLUME | DSBCAPS_CTRLFREQUENCY;
    hr = m_pSoundManager->Create(&pSound, File, dwCreationFlags, guid3DAlgorithm);  
    if(FAILED(hr) || hr == DS_NO_VIRTUALIZATION)
        return NULL; 

    // Tomamos el buffer 3D
    hr = pSound->Get3DBufferInterface( 0, &pDS3DBuffer);
    if(FAILED(hr))
        return NULL;

    // Aplicamos los parámetros por defecto para el buffer 3D
    //pDS3DBuffer->SetMode(DS3DMODE_HEADRELATIVE, DS3D_DEFERRED);
    pDS3DBuffer->SetMaxDistance(SOUND3D_MAX_DISTANCE, DS3D_DEFERRED);
    pDS3DBuffer->SetMinDistance(SOUND3D_MIN_DISTANCE, DS3D_DEFERRED);

    // Creamos el objeto de sonido
    C3DSound *newSound = new C3DSound(Owner, pSound, pDS3DBuffer, lInitFrequency, m_lVolume);

    // Insertamos el objeto de sonido en la lista de sonidos manejados
    m_aSounds.push_back(newSound);

    // Retornamos el objeto creado
    return newSound;
}

//--------------------------------------------------------------------------------------
// Destruye un sonido 3D existente
//--------------------------------------------------------------------------------------
bool C3DSoundMng::Destroy(C3DSound *Sound)
{
    // Salimos si no se ha inicializado
    if(!m_bInit) return false;

    // Eliminamos el sonido de la lista, si es que existe
    for (vector<C3DSound*>::iterator itr = m_aSounds.begin() ; itr != m_aSounds.end() ; itr++)
        if((*itr) == Sound)
        {
            SAFE_DELETE(*itr);
            m_aSounds.erase(itr);
            return true;
        }

    return false;
}

//--------------------------------------------------------------------------------------
// Pausa y Despausa la reproducción de todos los sonidos que maneja
//--------------------------------------------------------------------------------------
void C3DSoundMng::Pause(bool enabled)
{
    // Salimos si no se ha inicializado
    if(!m_bInit) return;

    // Pausamos o Despausamos los sonidos manejados
    for (vector<C3DSound*>::iterator itr = m_aSounds.begin() ; itr != m_aSounds.end() ; itr++)
        (*itr)->Pause(enabled);
}

//--------------------------------------------------------------------------------------
// Cambia el volumen de reproducción de todos los sonidos que maneja
//--------------------------------------------------------------------------------------
void C3DSoundMng::setVolume(long lVolume)
{
    // Salimos si no se ha inicializado
    if(!m_bInit) return;

    // Normalizamos el volumen del rango [0, 100] al rango [-5000, 0]
    if(lVolume < 0)     lVolume = -5000;
    if(lVolume > 100)   lVolume = 0;
    else                lVolume = long((float(lVolume)/100.0f)*5000 - 5000);

    // Ponemos el volumen a los sonidos manejados
    for (vector<C3DSound*>::iterator itr = m_aSounds.begin() ; itr != m_aSounds.end() ; itr++)
        (*itr)->setVolume(lVolume);

    // Almacenamos el nuevo volumen
    m_lVolume = lVolume;
}

//--------------------------------------------------------------------------------------
// Actualiza los cambios de los sonidos 3D
//--------------------------------------------------------------------------------------
void C3DSoundMng::update(void)
{
    // Salimos si no se ha inicializado
    if(!m_bInit) return;

    // Situamos el 'listener' en la posicición y orientación de la cámara
    D3DXVECTOR3 vPosition, vTo, vUp;
    g_Camera->getPosition(vPosition);
    g_Camera->getTo(vTo);
    g_Camera->getUp(vUp);
    vTo = vTo - vPosition;

    m_pDSListener->SetOrientation(vTo.x, vTo.y, vTo.z, vUp.x, vUp.y, vUp.z, DS3D_DEFERRED);
    m_pDSListener->SetPosition(vPosition.x, vPosition.y, vPosition.z, DS3D_DEFERRED);

    // Actualizamos todos los cambios pendientes realizados con DS3D_DEFERRED
    // en instancias 'IDirectSound3DListener' o 'IDirectSound3DBuffer'
    m_pDSListener->CommitDeferredSettings();

}

//--------------------------------------------------------------------------------------
// Libera todos los sonidos que maneja
//--------------------------------------------------------------------------------------
void C3DSoundMng::Release()
{
    // Salimos si no se ha inicializado
    if(!m_bInit) return;
    m_bInit = false;

    // Liberamos los sonidos manejados
    for (vector<C3DSound*>::iterator itr = m_aSounds.begin() ; itr != m_aSounds.end() ; itr++)
        SAFE_DELETE(*itr);
    m_aSounds.clear();

    // Liberamos los manejadores de sonido
    SAFE_RELEASE(m_pDSListener);
    SAFE_DELETE(m_pSoundManager);
}
