Ticket #1223: good_sounds.patch

File good_sounds.patch, 194.5 KB (added by stwf, 12 years ago)

most recent code version

  • build/premake/premake4.lua

     
    519519        "ps/Network",
    520520        "ps/GameSetup",
    521521        "ps/XML",
    522         "sound",
     522        "soundmanager",
     523        "soundmanager/data",
     524        "soundmanager/items",
     525        "soundmanager/js",
    523526        "scripting",
    524527        "maths",
    525528        "maths/scripting",
     
    533536        "boost",
    534537        "enet",
    535538        "libcurl",
     539        "vorbis",
     540        "openal"
    536541    }
    537542    setup_static_lib_project("engine", source_dirs, extern_libs, {})
    538543
     
    553558    end
    554559    setup_static_lib_project("graphics", source_dirs, extern_libs, {})
    555560
    556 
    557561    source_dirs = {
    558562        "tools/atlas/GameInterface",
    559563        "tools/atlas/GameInterface/Handlers"
  • source/ps/GameSetup/Config.cpp

     
    2121#include "ps/CConsole.h"
    2222#include "ps/GameSetup/CmdLineArgs.h"
    2323#include "lib/timer.h"
    24 #include "lib/res/sound/snd_mgr.h"
    2524#include "Config.h"
     25#include "soundmanager/CSoundManager.h"
    2626
    27 
    2827// (these variables are documented in the header.)
    2928
    3029CStrW g_CursorName = L"test";
     
    8180    CFG_GET_USER_VAL("particles", Bool, g_Particles);
    8281
    8382    float gain = -1.0f;
     83    float musicGain = -1.0f;
     84    float ambientGain = -1.0f;
     85    float actionGain = -1.0f;
     86    int bufferCount = 50;
     87    unsigned long   bufferSize = 65536;
     88   
    8489    CFG_GET_USER_VAL("sound.mastergain", Float, gain);
    85     if(gain >= 0.0f)
    86         WARN_IF_ERR(snd_set_master_gain(gain));
     90    CFG_GET_USER_VAL("sound.musicgain", Float, musicGain);
     91    CFG_GET_USER_VAL("sound.ambientgain", Float, ambientGain);
     92    CFG_GET_USER_VAL("sound.actiongain", Float, actionGain);
     93
     94    CFG_GET_USER_VAL("sound.bufferCount", Int, bufferCount);
     95    CFG_GET_USER_VAL("sound.bufferSize", UnsignedLong, bufferSize);
     96
     97    g_SoundManager->SetMasterGain( gain );
     98    g_SoundManager->SetMusicGain( musicGain );
     99    g_SoundManager->SetAmbientGain( ambientGain );
     100    g_SoundManager->SetActionGain( actionGain );
     101
     102    g_SoundManager->SetMemoryUsage( bufferSize, bufferCount);
    87103}
    88104
    89105
  • source/ps/GameSetup/GameSetup.cpp

     
    2525#include "lib/file/common/file_stats.h"
    2626#include "lib/res/h_mgr.h"
    2727#include "lib/res/graphics/cursor.h"
    28 #include "lib/res/sound/snd_mgr.h"
     28
    2929#include "lib/sysdep/cursor.h"
    3030#include "lib/sysdep/cpu.h"
    3131#include "lib/sysdep/gfx.h"
     
    8686#include "gui/scripting/JSInterface_GUITypes.h"
    8787#include "gui/scripting/ScriptFunctions.h"
    8888
    89 #include "sound/JSI_Sound.h"
    90 
    9189#include "network/NetServer.h"
    9290#include "network/NetClient.h"
    9391
     
    102100#include "tools/atlas/GameInterface/GameLoop.h"
    103101#include "tools/atlas/GameInterface/View.h"
    104102
     103#include "soundmanager/CSoundManager.h"
    105104
    106105#if !(OS_WIN || OS_MACOSX || OS_ANDROID) // assume all other platforms use X11 for wxWidgets
    107106#define MUST_INIT_X11 1
     
    203202{
    204203    PROFILE3("render");
    205204
     205    g_SoundManager->IdleTask();
     206
    206207    ogl_WarnIfError();
    207208
    208209    g_Profiler2.RecordGPUFrameStart();
     
    330331{
    331332    // maths
    332333    JSI_Vector3D::init();
    333 
     334   
     335    CSoundManager::ScriptingInit();
    334336    // graphics
    335337    CGameView::ScriptingInit();
    336338
     
    338340    CRenderer::ScriptingInit();
    339341
    340342    // sound
    341     JSI_Sound::ScriptingInit();
     343//  JSI_Sound::ScriptingInit();
    342344
    343345    // ps
    344346    JSI_Console::init();
     
    476478        g_VFS->Mount(L"", modLoosePath / modName/"", flags, priority);
    477479        g_VFS->Mount(L"", modArchivePath / modName/"", flags, priority);
    478480    }
     481   
     482    g_SoundManager = new CSoundManager();
    479483
    480484    // note: don't bother with g_VFS->TextRepresentation - directories
    481485    // haven't yet been populated and are empty.
     
    691695    // resource
    692696    // first shut down all resource owners, and then the handle manager.
    693697    TIMER_BEGIN(L"resource modules");
    694         snd_shutdown();
     698        delete g_SoundManager;
    695699
    696700        g_VFS.reset();
    697701
     
    930934        // speed up startup by disabling all sound
    931935        // (OpenAL init will be skipped).
    932936        // must be called before first snd_open.
    933         snd_disable(true);
     937        g_SoundManager->SetEnabled( false );
    934938    }
    935939
    936940    g_GUI = new CGUIManager(g_ScriptingHost.GetScriptInterface());
  • source/ps/Game.cpp

     
    4545#include "simulation2/components/ICmpPlayerManager.h"
    4646
    4747#include "gui/GUIManager.h"
     48#include "soundmanager/CSoundManager.h"
    4849
    4950extern bool g_GameRestarted;
    5051
     
    299300    if (doInterpolate)
    300301    {
    301302        m_TurnManager->Interpolate(deltaTime);
     303        g_SoundManager->idleTask();
    302304    }
    303305   
    304306    // TODO: maybe we should add a CCmpParticleInterface that passes the interpolation commands
  • source/soundmanager/items/CSoundItem.h

     
     1/* Copyright (C) 2012 Wildfire Games.
     2 * This file is part of 0 A.D.
     3 *
     4 * 0 A.D. is free software: you can redistribute it and/or modify
     5 * it under the terms of the GNU General Public License as published by
     6 * the Free Software Foundation, either version 2 of the License, or
     7 * (at your option) any later version.
     8 *
     9 * 0 A.D. is distributed in the hope that it will be useful,
     10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
     11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     12 * GNU General Public License for more details.
     13 *
     14 * You should have received a copy of the GNU General Public License
     15 * along with 0 A.D.  If not, see <http://www.gnu.org/licenses/>.
     16 */
     17
     18
     19#ifndef SoundTester_CSoundItem_h
     20#define SoundTester_CSoundItem_h
     21
     22#include "CSoundBase.h"
     23#include "soundmanager/data/CSoundData.h"
     24
     25
     26class CSoundItem :public CSoundBase
     27{
     28protected:
     29   
     30public:
     31    CSoundItem      ();
     32    CSoundItem      (CSoundData* sndData);
     33   
     34    virtual ~CSoundItem     ();
     35    void    Attach          ( CSoundData* itemData );
     36    bool    IdleTask        ();
     37
     38protected:
     39
     40   
     41};
     42
     43
     44
     45
     46
     47
     48#endif
  • source/soundmanager/items/CBufferItem.h

     
     1/* Copyright (C) 2012 Wildfire Games.
     2 * This file is part of 0 A.D.
     3 *
     4 * 0 A.D. is free software: you can redistribute it and/or modify
     5 * it under the terms of the GNU General Public License as published by
     6 * the Free Software Foundation, either version 2 of the License, or
     7 * (at your option) any later version.
     8 *
     9 * 0 A.D. is distributed in the hope that it will be useful,
     10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
     11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     12 * GNU General Public License for more details.
     13 *
     14 * You should have received a copy of the GNU General Public License
     15 * along with 0 A.D.  If not, see <http://www.gnu.org/licenses/>.
     16 */
     17
     18
     19#ifndef SoundTester_CBufferItem_h
     20#define SoundTester_CBufferItem_h
     21
     22#include "CSoundBase.h"
     23
     24class CBufferItem : public CSoundBase
     25{
     26public:
     27    CBufferItem             (CSoundData* sndData);
     28    virtual ~CBufferItem    ();
     29   
     30    virtual void    SetLooping   ( bool loops );
     31    virtual bool    IdleTask     ();
     32   
     33protected:   
     34    virtual void    Attach       ( CSoundData* itemData );
     35
     36   
     37};
     38
     39
     40#endif
  • source/soundmanager/items/CStreamItem.h

     
     1/* Copyright (C) 2012 Wildfire Games.
     2 * This file is part of 0 A.D.
     3 *
     4 * 0 A.D. is free software: you can redistribute it and/or modify
     5 * it under the terms of the GNU General Public License as published by
     6 * the Free Software Foundation, either version 2 of the License, or
     7 * (at your option) any later version.
     8 *
     9 * 0 A.D. is distributed in the hope that it will be useful,
     10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
     11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     12 * GNU General Public License for more details.
     13 *
     14 * You should have received a copy of the GNU General Public License
     15 * along with 0 A.D.  If not, see <http://www.gnu.org/licenses/>.
     16 */
     17
     18#ifndef SoundTester_CStreamItem_h
     19#define SoundTester_CStreamItem_h
     20
     21#include "soundmanager/data/CSoundData.h"
     22#include "CSoundBase.h"
     23
     24class CStreamItem : public CSoundBase
     25{
     26public:
     27    CStreamItem                 (CSoundData* sndData);
     28    virtual ~CStreamItem        ();
     29   
     30    virtual void    SetLooping  ( bool loops );
     31    virtual bool    IdleTask    ();
     32   
     33protected:   
     34    virtual void    Attach      ( CSoundData* itemData );
     35
     36};
     37
     38#endif
  • source/soundmanager/items/ISoundItem.h

     
     1/* Copyright (C) 2012 Wildfire Games.
     2 * This file is part of 0 A.D.
     3 *
     4 * 0 A.D. is free software: you can redistribute it and/or modify
     5 * it under the terms of the GNU General Public License as published by
     6 * the Free Software Foundation, either version 2 of the License, or
     7 * (at your option) any later version.
     8 *
     9 * 0 A.D. is distributed in the hope that it will be useful,
     10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
     11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     12 * GNU General Public License for more details.
     13 *
     14 * You should have received a copy of the GNU General Public License
     15 * along with 0 A.D.  If not, see <http://www.gnu.org/licenses/>.
     16 */
     17
     18#ifndef SoundTester_ISoundItem_h
     19#define SoundTester_ISoundItem_h
     20
     21#include <string>
     22#include "lib/external_libraries/openal.h"
     23#include "maths/Vector3D.h"
     24
     25
     26class ISoundItem
     27{
     28   
     29public:
     30    virtual ~ISoundItem(){};
     31    virtual bool GetLooping     () = 0;
     32    virtual void    SetLooping       (bool loop) = 0;
     33    virtual bool    IsPlaying        () = 0;
     34   
     35   
     36    virtual std::string    GetName         () = 0;
     37    virtual bool    IdleTask         () = 0;
     38   
     39    virtual void    Play             () = 0;
     40    virtual void    Stop             () = 0;
     41
     42    virtual void    EnsurePlay       () = 0;
     43    virtual void    PlayAsMusic      () = 0;
     44    virtual void    PlayAsAmbient    () = 0;
     45
     46    virtual void    PlayAndDelete    () = 0;
     47    virtual void    StopAndDelete    () = 0;
     48    virtual void    FadeToIn        ( ALfloat newVolume, double fadeDuration) = 0;
     49    virtual void    FadeAndDelete    ( double fadeTime ) = 0;
     50    virtual void    PlayLoop         () = 0;
     51
     52    virtual void    SetCone     (ALfloat innerCone, ALfloat outerCone, ALfloat coneGain) = 0;
     53    virtual void    SetPitch    (ALfloat pitch) = 0;
     54    virtual void    SetGain     (ALfloat gain) = 0;
     55    virtual void    SetLocation (const CVector3D& position) = 0;
     56    virtual void    SetRollOff     (ALfloat gain) = 0;
     57};
     58
     59
     60#endif //SoundTester_ISoundItem_h
     61 No newline at end of file
  • source/soundmanager/items/CSoundBase.cpp

     
     1/* Copyright (C) 2012 Wildfire Games.
     2 * This file is part of 0 A.D.
     3 *
     4 * 0 A.D. is free software: you can redistribute it and/or modify
     5 * it under the terms of the GNU General Public License as published by
     6 * the Free Software Foundation, either version 2 of the License, or
     7 * (at your option) any later version.
     8 *
     9 * 0 A.D. is distributed in the hope that it will be useful,
     10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
     11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     12 * GNU General Public License for more details.
     13 *
     14 * You should have received a copy of the GNU General Public License
     15 * along with 0 A.D.  If not, see <http://www.gnu.org/licenses/>.
     16 */
     17#include "precompiled.h"
     18
     19#include "CSoundBase.h"
     20#include "soundmanager/CSoundManager.h"
     21#include "soundmanager/data/CSoundData.h"
     22
     23#include <iostream>
     24
     25#include "lib/timer.h"
     26
     27
     28CSoundBase::CSoundBase()
     29{
     30    ResetVars();
     31}
     32
     33CSoundBase::~CSoundBase()
     34{
     35    Stop();
     36    if ( m_ALSource != 0 )
     37    {
     38        alDeleteSources( 1, &m_ALSource);
     39        m_ALSource = 0;
     40    }
     41    if ( m_SoundData != 0 )
     42    {
     43        CSoundData::ReleaseSoundData( m_SoundData );
     44        m_SoundData = 0;
     45    }
     46    if ( m_Name )
     47        delete m_Name;
     48}
     49
     50void CSoundBase::ResetVars()
     51{
     52    m_ALSource = 0;
     53    m_SoundData = 0;
     54    m_LastPlay = false;
     55    m_Looping = false;
     56    m_StartFadeTime = 0;
     57    m_EndFadeTime = 0;
     58    m_StartVolume = 0;
     59    m_EndVolume = 0;
     60
     61    ResetFade();
     62    m_Name = new std::string( "sound name" );
     63}
     64
     65void CSoundBase::ResetFade()
     66{
     67    m_StartFadeTime = 0;
     68    m_EndFadeTime = 0;
     69    m_StartVolume = 0;
     70    m_EndVolume = 0;
     71    m_ShouldBePlaying = false;
     72}
     73
     74void CSoundBase::SetGain(ALfloat gain)
     75{
     76    alSourcef(m_ALSource, AL_GAIN, gain);
     77}
     78
     79void CSoundBase::SetRollOff(ALfloat rolls)
     80{
     81   alSourcef(m_ALSource, AL_ROLLOFF_FACTOR, rolls);
     82}
     83
     84void CSoundBase::EnsurePlay()
     85{
     86    if ( m_ShouldBePlaying && !IsPlaying() )
     87        Play();
     88}
     89
     90void CSoundBase::SetCone(ALfloat innerCone, ALfloat outerCone, ALfloat coneGain)
     91{
     92    alSourcef( m_ALSource, innerCone, AL_CONE_INNER_ANGLE);
     93    alSourcef( m_ALSource, outerCone, AL_CONE_OUTER_ANGLE);
     94    alSourcef( m_ALSource, coneGain, AL_CONE_OUTER_GAIN);
     95}
     96
     97void CSoundBase::SetPitch(ALfloat pitch)
     98{
     99    alSourcef( m_ALSource, AL_PITCH, pitch);
     100}
     101
     102void CSoundBase::SetDirection(const CVector3D& direction)
     103{
     104    alSourcefv( m_ALSource, AL_DIRECTION, direction.GetFloatArray() );
     105}
     106
     107bool CSoundBase::InitOpenAL()
     108{
     109    alGetError(); /* clear error */
     110    alGenSources( 1, &m_ALSource);
     111    long anErr = alGetError();
     112    if ( anErr != AL_NO_ERROR)
     113    {
     114        printf("- Error creating sources %ld !!\n", anErr );
     115    }
     116    else
     117    {
     118        ALfloat source0Pos[]={ -2.0, 0.0, 0.0};
     119        ALfloat source0Vel[]={ 0.0, 0.0, 0.0};
     120       
     121        alSourcef( m_ALSource,AL_PITCH,1.0f);
     122        alSourcef( m_ALSource,AL_GAIN,1.0f);
     123        alSourcefv( m_ALSource,AL_POSITION,source0Pos);
     124        alSourcefv( m_ALSource,AL_VELOCITY,source0Vel);
     125        alSourcei( m_ALSource,AL_LOOPING,AL_FALSE);
     126        return true;
     127    }
     128    return false;
     129}
     130
     131bool CSoundBase::IsPlaying()
     132{
     133    int proc_state;
     134    alGetSourceiv( m_ALSource, AL_SOURCE_STATE, &proc_state);
     135
     136    return ( proc_state == AL_PLAYING );
     137}
     138
     139void CSoundBase::SetLastPlay( bool last )
     140{
     141    m_LastPlay = last;
     142}
     143
     144bool CSoundBase::IdleTask()
     145{
     146    return true;
     147}
     148
     149void CSoundBase::SetLocation (const CVector3D& position)
     150{
     151    alSourcefv( m_ALSource,AL_POSITION, position.GetFloatArray() );
     152}
     153
     154bool CSoundBase::HandleFade()
     155{
     156    if ( m_StartFadeTime != 0 )
     157    {
     158        double currTime = timer_Time();
     159        double pctDone = std::min( 1.0, (currTime - m_StartFadeTime) / (m_EndFadeTime - m_StartFadeTime) );
     160        pctDone = std::max( 0.0, pctDone );
     161        ALfloat curGain = ((m_EndVolume - m_StartVolume ) * pctDone) + m_StartVolume;
     162
     163        if  (curGain == 0 )
     164            Stop();
     165        else if ( curGain == m_EndVolume )
     166        {
     167            alSourcef( m_ALSource, AL_GAIN, curGain);     
     168            ResetFade();
     169        }
     170        else
     171            alSourcef( m_ALSource, AL_GAIN, curGain);     
     172    }
     173    return true;
     174}
     175
     176bool CSoundBase::GetLooping()
     177{
     178    return m_Looping;
     179}
     180
     181void CSoundBase::SetLooping( bool loops )
     182{
     183    m_Looping = loops;
     184    alSourcei( m_ALSource, AL_LOOPING, loops ? AL_TRUE : AL_FALSE );
     185}
     186
     187void CSoundBase::Play()
     188{
     189    m_ShouldBePlaying = true;
     190    if ( m_ALSource != 0 )
     191        alSourcePlay( m_ALSource );
     192}
     193
     194void CSoundBase::PlayAndDelete()
     195{
     196    SetLastPlay( true );
     197    Play();
     198}
     199
     200void CSoundBase::FadeAndDelete( double fadeTime )
     201{
     202    SetLastPlay( true );
     203    FadeToIn( 0, fadeTime );
     204}
     205
     206void    CSoundBase::StopAndDelete()
     207{
     208    SetLastPlay( true );
     209    Stop();
     210}
     211
     212void CSoundBase::PlayLoop()
     213{
     214    if ( m_ALSource != 0 )
     215    {
     216        SetLooping( true );
     217        Play();
     218    }
     219}
     220
     221void CSoundBase::FadeToIn( ALfloat newVolume, double fadeDuration)
     222{
     223    int proc_state;
     224    alGetSourceiv( m_ALSource, AL_SOURCE_STATE, &proc_state);
     225    if ( proc_state == AL_PLAYING )
     226    {
     227        m_StartFadeTime = timer_Time();
     228        m_EndFadeTime = m_StartFadeTime + fadeDuration;
     229        alGetSourcef( m_ALSource, AL_GAIN, &m_StartVolume);
     230        m_EndVolume = newVolume;
     231    }
     232}
     233
     234void CSoundBase::PlayAsMusic()
     235{
     236    g_SoundManager->SetMusicItem( this );
     237}
     238
     239void CSoundBase::PlayAsAmbient()
     240{
     241    g_SoundManager->SetAmbientItem( this );
     242}
     243
     244void CSoundBase::Stop()
     245{
     246    m_ShouldBePlaying = false;
     247    if ( m_ALSource != 0 )
     248    {
     249        int proc_state;
     250        alSourcei( m_ALSource, AL_LOOPING, AL_FALSE );
     251        alGetSourceiv( m_ALSource, AL_SOURCE_STATE, &proc_state);
     252        if ( proc_state == AL_PLAYING )
     253            alSourceStop( m_ALSource );
     254    }
     255}
     256
     257const char* CSoundBase::Name()
     258{
     259    return m_Name->c_str();
     260}
     261
     262std::string CSoundBase::GetName()
     263{
     264    return std::string( m_Name->c_str() );
     265}
     266
     267void CSoundBase::SetNameFromPath(  char* fileLoc )
     268{
     269    std::string anst( fileLoc );
     270    size_t pos = anst.find_last_of("/");
     271    if(pos != std::wstring::npos)
     272        m_Name->assign(anst.begin() + pos + 1, anst.end());
     273    else
     274        m_Name->assign(anst.begin(), anst.end());
     275}
     276
  • source/soundmanager/items/CSoundItem.cpp

     
     1/* Copyright (C) 2012 Wildfire Games.
     2 * This file is part of 0 A.D.
     3 *
     4 * 0 A.D. is free software: you can redistribute it and/or modify
     5 * it under the terms of the GNU General Public License as published by
     6 * the Free Software Foundation, either version 2 of the License, or
     7 * (at your option) any later version.
     8 *
     9 * 0 A.D. is distributed in the hope that it will be useful,
     10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
     11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     12 * GNU General Public License for more details.
     13 *
     14 * You should have received a copy of the GNU General Public License
     15 * along with 0 A.D.  If not, see <http://www.gnu.org/licenses/>.
     16 */
     17#include "precompiled.h"
     18
     19#include "CSoundItem.h"
     20#include "soundmanager/data/CSoundData.h"
     21
     22#include <iostream>
     23
     24
     25CSoundItem::CSoundItem()
     26{
     27    ResetVars();
     28}
     29
     30CSoundItem::CSoundItem(CSoundData* sndData)
     31{
     32    ResetVars();
     33    if ( InitOpenAL() )
     34        Attach( sndData );
     35}
     36
     37CSoundItem::~CSoundItem()
     38{
     39    ALuint al_buf;
     40   
     41    Stop();
     42    alSourceUnqueueBuffers(m_ALSource, 1, &al_buf);
     43}
     44
     45bool CSoundItem::IdleTask()
     46{
     47    HandleFade();
     48
     49    if ( m_LastPlay )
     50    {
     51        int proc_state;
     52        alGetSourceiv( m_ALSource, AL_SOURCE_STATE, &proc_state);
     53        return ( proc_state != AL_STOPPED );
     54    }
     55    return true;
     56}
     57
     58void CSoundItem::Attach( CSoundData* itemData )
     59{
     60    if ( itemData != NULL )
     61    {
     62        m_SoundData = itemData->IncrementCount();
     63        alSourcei( m_ALSource, AL_BUFFER, m_SoundData->GetBuffer() );
     64    }
     65}
  • source/soundmanager/items/CBufferItem.cpp

     
     1/* Copyright (C) 2012 Wildfire Games.
     2 * This file is part of 0 A.D.
     3 *
     4 * 0 A.D. is free software: you can redistribute it and/or modify
     5 * it under the terms of the GNU General Public License as published by
     6 * the Free Software Foundation, either version 2 of the License, or
     7 * (at your option) any later version.
     8 *
     9 * 0 A.D. is distributed in the hope that it will be useful,
     10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
     11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     12 * GNU General Public License for more details.
     13 *
     14 * You should have received a copy of the GNU General Public License
     15 * along with 0 A.D.  If not, see <http://www.gnu.org/licenses/>.
     16 */
     17#include "precompiled.h"
     18#include "CBufferItem.h"
     19#include "soundmanager/data/CSoundData.h"
     20
     21#include <iostream>
     22
     23CBufferItem::CBufferItem(CSoundData* sndData)
     24{
     25    ResetVars();
     26    if ( InitOpenAL() )
     27        Attach( sndData );
     28}
     29
     30
     31CBufferItem::~CBufferItem()
     32{
     33    Stop();
     34    int num_processed;
     35    alGetSourcei( m_ALSource, AL_BUFFERS_PROCESSED, &num_processed);
     36   
     37    if (num_processed > 0)
     38    {
     39        ALuint* al_buf = new ALuint[num_processed];
     40        alSourceUnqueueBuffers(m_ALSource, num_processed, al_buf);
     41
     42        delete[] al_buf;
     43    }
     44}
     45
     46
     47bool CBufferItem::IdleTask()
     48{
     49    HandleFade();
     50   
     51    if ( m_LastPlay )
     52    {
     53        int proc_state;
     54        alGetSourceiv( m_ALSource, AL_SOURCE_STATE, &proc_state);
     55        return ( proc_state != AL_STOPPED );
     56    }
     57   
     58    if ( GetLooping() )
     59    {
     60        int num_processed;
     61        alGetSourcei( m_ALSource, AL_BUFFERS_PROCESSED, &num_processed);
     62       
     63        for ( int i = 0; i < num_processed; i++ )
     64        {
     65            ALuint al_buf;
     66            alSourceUnqueueBuffers(m_ALSource, 1, &al_buf);
     67            alSourceQueueBuffers(m_ALSource, 1, &al_buf);
     68        }
     69    }
     70
     71    return true;
     72}
     73
     74void CBufferItem::Attach( CSoundData* itemData )
     75{
     76    if ( itemData != NULL )
     77    {
     78        m_SoundData = itemData->IncrementCount();
     79        alSourceQueueBuffers(m_ALSource, m_SoundData->GetBufferCount(),(const ALuint *) m_SoundData->GetBufferPtr());
     80    }
     81}
     82
     83void CBufferItem::SetLooping( bool loops )
     84{
     85    m_Looping = loops;
     86}
     87
  • source/soundmanager/items/CSoundBase.h

     
     1/* Copyright (C) 2012 Wildfire Games.
     2 * This file is part of 0 A.D.
     3 *
     4 * 0 A.D. is free software: you can redistribute it and/or modify
     5 * it under the terms of the GNU General Public License as published by
     6 * the Free Software Foundation, either version 2 of the License, or
     7 * (at your option) any later version.
     8 *
     9 * 0 A.D. is distributed in the hope that it will be useful,
     10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
     11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     12 * GNU General Public License for more details.
     13 *
     14 * You should have received a copy of the GNU General Public License
     15 * along with 0 A.D.  If not, see <http://www.gnu.org/licenses/>.
     16 */
     17
     18
     19#ifndef SoundTester_CSoundBase_h
     20#define SoundTester_CSoundBase_h
     21
     22#include <string>
     23#include "lib/external_libraries/openal.h"
     24#include "soundmanager/items/ISoundItem.h"
     25#include "soundmanager/data/CSoundData.h"
     26
     27
     28class CSoundBase :public ISoundItem
     29{
     30protected:
     31   
     32    ALuint              m_ALSource;
     33    CSoundData*         m_SoundData;
     34
     35    std::string*        m_Name;
     36    bool                m_LastPlay;
     37    bool                m_Looping;
     38    bool                m_ShouldBePlaying;
     39   
     40    double          m_StartFadeTime;
     41    double          m_EndFadeTime;
     42    ALfloat         m_StartVolume;
     43    ALfloat         m_EndVolume;
     44
     45public:
     46    CSoundBase      ();
     47   
     48    virtual ~CSoundBase      ();
     49   
     50    virtual bool    InitOpenAL();
     51    virtual void    ResetVars();
     52    virtual void    EnsurePlay();
     53
     54    virtual void SetGain     (ALfloat gain);
     55    virtual void SetRollOff     (ALfloat gain);
     56    virtual void SetPitch(ALfloat pitch);
     57    virtual void SetDirection(const CVector3D& direction);
     58    virtual void SetCone(ALfloat innerCone, ALfloat outerCone, ALfloat coneGain);
     59    virtual void SetLastPlay( bool last );
     60
     61    void    Play            ();
     62    void    PlayAndDelete   ();
     63    bool    IdleTask        ();
     64    void    PlayLoop        ();
     65    void    Stop            ();
     66    void    StopAndDelete    ();
     67    void    FadeToIn        ( ALfloat newVolume, double fadeDuration);
     68
     69    void    PlayAsMusic      ();
     70    void    PlayAsAmbient    ();
     71
     72    const char*   Name();
     73    std::string GetName();
     74
     75    virtual bool GetLooping     ();
     76    virtual void SetLooping     ( bool loops );
     77    virtual bool IsPlaying();
     78    virtual void SetLocation (const CVector3D& position);
     79    virtual void FadeAndDelete    ( double fadeTime );
     80
     81protected:
     82
     83    void SetNameFromPath(  char* fileLoc );
     84    void ResetFade();
     85    bool HandleFade();
     86
     87   
     88};
     89
     90
     91
     92
     93
     94
     95#endif
  • source/soundmanager/items/CStreamItem.cpp

     
     1/* Copyright (C) 2012 Wildfire Games.
     2 * This file is part of 0 A.D.
     3 *
     4 * 0 A.D. is free software: you can redistribute it and/or modify
     5 * it under the terms of the GNU General Public License as published by
     6 * the Free Software Foundation, either version 2 of the License, or
     7 * (at your option) any later version.
     8 *
     9 * 0 A.D. is distributed in the hope that it will be useful,
     10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
     11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     12 * GNU General Public License for more details.
     13 *
     14 * You should have received a copy of the GNU General Public License
     15 * along with 0 A.D.  If not, see <http://www.gnu.org/licenses/>.
     16 */
     17#include "precompiled.h"
     18
     19#include "CStreamItem.h"
     20#include "soundmanager/data/COggData.h"
     21
     22#include <iostream>
     23
     24CStreamItem::CStreamItem(CSoundData* sndData)
     25{
     26    ResetVars();
     27    if ( InitOpenAL() )
     28        Attach( sndData );
     29}
     30
     31CStreamItem::~CStreamItem()
     32{
     33    Stop();
     34
     35    int num_processed;
     36    alGetSourcei( m_ALSource, AL_BUFFERS_PROCESSED, &num_processed);
     37   
     38    if (num_processed > 0)
     39    {
     40        ALuint* al_buf = new ALuint[num_processed];
     41        alSourceUnqueueBuffers(m_ALSource, num_processed, al_buf);
     42        delete[] al_buf;
     43    }
     44}
     45
     46bool CStreamItem::IdleTask()
     47{
     48    HandleFade();
     49
     50    int proc_state;
     51    alGetSourceiv( m_ALSource, AL_SOURCE_STATE, &proc_state);
     52   
     53    if ( proc_state == AL_STOPPED )
     54    {
     55        if ( m_LastPlay )
     56            return ( proc_state != AL_STOPPED );
     57    }
     58    else
     59    {
     60        COggData* tmp = (COggData*)m_SoundData;
     61       
     62        if ( ! tmp->IsFileFinished() )
     63        {
     64            int num_processed;
     65            alGetSourcei( m_ALSource, AL_BUFFERS_PROCESSED, &num_processed);
     66           
     67            if (num_processed > 0)
     68            {
     69                ALuint* al_buf = new ALuint[num_processed];
     70                alSourceUnqueueBuffers(m_ALSource, num_processed, al_buf);
     71                int didWrite = tmp->FetchDataIntoBuffer( num_processed, al_buf);
     72                alSourceQueueBuffers( m_ALSource, didWrite, al_buf);
     73                delete[] al_buf;
     74            }
     75        }
     76        else if ( GetLooping() )
     77        {
     78            tmp->ResetFile();
     79        }
     80    }
     81    return true;
     82}
     83
     84void CStreamItem::Attach( CSoundData* itemData )
     85{
     86    if ( itemData != NULL )
     87    {
     88        m_SoundData = itemData->IncrementCount();
     89        alSourceQueueBuffers(m_ALSource, m_SoundData->GetBufferCount(), (const ALuint *)m_SoundData->GetBufferPtr());
     90    }
     91}
     92
     93void CStreamItem::SetLooping( bool loops )
     94{
     95    m_Looping = loops;
     96}
     97
  • source/soundmanager/CSoundManager.cpp

     
     1/* Copyright (C) 2012 Wildfire Games.
     2 * This file is part of 0 A.D.
     3 *
     4 * 0 A.D. is free software: you can redistribute it and/or modify
     5 * it under the terms of the GNU General Public License as published by
     6 * the Free Software Foundation, either version 2 of the License, or
     7 * (at your option) any later version.
     8 *
     9 * 0 A.D. is distributed in the hope that it will be useful,
     10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
     11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     12 * GNU General Public License for more details.
     13 *
     14 * You should have received a copy of the GNU General Public License
     15 * along with 0 A.D.  If not, see <http://www.gnu.org/licenses/>.
     16 */
     17
     18#include "precompiled.h"
     19
     20#include "CSoundManager.h"
     21#include "soundmanager/items/CSoundItem.h"
     22#include "soundmanager/items/CBufferItem.h"
     23#include "soundmanager/items/CStreamItem.h"
     24#include "soundmanager/js/JSoundPlayer.h"
     25#include "soundmanager/js/JAmbientSound.h"
     26#include "soundmanager/js/JMusicSound.h"
     27#include "soundmanager/js/JSound.h"
     28#include "soundmanager/data/CSoundData.h"
     29
     30
     31CSoundManager*  g_SoundManager;
     32
     33void CSoundManager::ScriptingInit()
     34{
     35    JAmbientSound::ScriptingInit();
     36    JMusicSound::ScriptingInit();
     37    JSound::ScriptingInit();
     38    JSoundPlayer::ScriptingInit();
     39}
     40
     41CSoundManager::CSoundManager()
     42{
     43    m_Items = new ItemsList;
     44    m_CurrentEnvirons    = 0;
     45    m_CurrentTune        = 0;
     46    m_Gain               = 1;
     47    m_MusicGain          = 1;
     48    m_AmbientGain        = 1;
     49    m_ActionGain         = 1;
     50    m_Enabled            = true;
     51    m_BufferCount       = 50;
     52    m_BufferSize            = 65536;
     53    m_MusicEnabled      = true;
     54    AlcInit();
     55}
     56
     57CSoundManager::~CSoundManager()
     58{   
     59    ItemsList::iterator lstr = m_Items->begin();
     60    while ( lstr != m_Items->end() )
     61    {
     62        (*lstr)->Stop();
     63        delete *lstr;
     64        lstr++;
     65    }
     66
     67    alcDestroyContext( m_Context );
     68    alcCloseDevice( m_Device );
     69
     70    delete m_Items;
     71    m_Items = 0L;
     72    m_CurrentEnvirons    = 0;
     73    m_CurrentTune        = 0;
     74}
     75
     76
     77Status CSoundManager::AlcInit()
     78{   
     79    Status ret = INFO::OK;
     80
     81    m_Device = alcOpenDevice(NULL);
     82    if(m_Device)
     83    {
     84        m_Context = alcCreateContext(m_Device, 0);  // no attrlist needed
     85        if(m_Context)
     86            alcMakeContextCurrent(m_Context);
     87    }
     88
     89    // check if init succeeded.
     90    // some OpenAL implementations don't indicate failure here correctly;
     91    // we need to check if the device and context pointers are actually valid.
     92    ALCenum err = alcGetError(m_Device);
     93    if(err != ALC_NO_ERROR || !m_Device || !m_Context)
     94    {
     95#if OS_UNIX
     96        ret = INFO::OK;
     97#else
     98        ret = ERR::FAIL;
     99#endif
     100    }
     101
     102    const char* dev_name = (const char*)alcGetString(m_Device, ALC_DEVICE_SPECIFIER);
     103    wchar_t buf[200];
     104    swprintf(buf, ARRAY_SIZE(buf), L"SND| alc_init: success, using %hs\n", dev_name);
     105
     106    return ret;
     107}
     108void CSoundManager::SetMemoryUsage( long bufferSize, int bufferCount )
     109{
     110    m_BufferCount = bufferCount;
     111    m_BufferSize = bufferSize;
     112}
     113long CSoundManager::GetBufferCount()
     114{
     115    return m_BufferCount;
     116}
     117long CSoundManager::GetBufferSize()
     118{
     119    return m_BufferSize;
     120}
     121
     122
     123void CSoundManager::SetMasterGain( float gain)
     124{
     125    m_Gain = gain;
     126}
     127void CSoundManager::SetMusicGain( float gain)
     128{
     129    m_MusicGain = gain;
     130}
     131void CSoundManager::SetAmbientGain( float gain)
     132{
     133    m_AmbientGain = gain;
     134}
     135void CSoundManager::SetActionGain( float gain)
     136{
     137    m_ActionGain = gain;
     138}
     139
     140
     141ISoundItem* CSoundManager::LoadItem( const VfsPath* itemPath )
     142{   
     143    CSoundData*   itemData = CSoundData::SoundDataFromFile( itemPath );
     144    ISoundItem*   answer  = NULL;
     145   
     146    if ( itemData != NULL )
     147    {
     148        if ( itemData->IsOneShot() )
     149        {
     150            if ( itemData->GetBufferCount() == 1 )
     151                answer = new CSoundItem( itemData );
     152            else
     153                answer = new CBufferItem( itemData );
     154        }
     155        else
     156        {
     157            answer = new CStreamItem( itemData );
     158        }
     159
     160        if ( answer != NULL )
     161            m_Items->push_back( answer );
     162    }
     163
     164   
     165    return answer;
     166}
     167
     168unsigned long CSoundManager::Count()
     169{
     170    return m_Items->size();
     171}
     172
     173void CSoundManager::IdleTask()
     174{
     175    if ( m_Items )
     176    {
     177        ItemsList::iterator lstr = m_Items->begin();
     178        ItemsList  deadItemList;
     179        ItemsList* nextItemList = new ItemsList;
     180
     181
     182        while ( lstr != m_Items->end() ) {
     183            if ( (*lstr)->IdleTask() )
     184                nextItemList->push_back( *lstr );
     185            else
     186                deadItemList.push_back( *lstr );
     187            lstr++;
     188        }
     189        delete m_Items;
     190        m_Items = nextItemList;
     191
     192        ItemsList::iterator deadItems = deadItemList.begin();
     193        while ( deadItems != deadItemList.end() )
     194        {   
     195            delete *deadItems;
     196            deadItems++;
     197        }
     198    }
     199    if ( m_CurrentTune )
     200        m_CurrentTune->EnsurePlay();
     201    if ( m_CurrentEnvirons )
     202        m_CurrentEnvirons->EnsurePlay();
     203}
     204
     205void CSoundManager::DeleteItem( long itemNum )
     206{
     207    ItemsList::iterator lstr = m_Items->begin();
     208    lstr += itemNum;
     209   
     210    delete *lstr;
     211   
     212    m_Items->erase( lstr );
     213}
     214
     215ISoundItem* CSoundManager::GetSoundItem( unsigned long itemRow )
     216{
     217   return (*m_Items)[itemRow];
     218}
     219
     220void CSoundManager::InitListener()
     221{
     222    ALfloat listenerPos[]={0.0,0.0,0.0};
     223    ALfloat listenerVel[]={0.0,0.0,0.0};
     224    ALfloat listenerOri[]={0.0,0.0,-1.0, 0.0,1.0,0.0};
     225
     226    alListenerfv(AL_POSITION,listenerPos);
     227    alListenerfv(AL_VELOCITY,listenerVel);
     228    alListenerfv(AL_ORIENTATION,listenerOri);
     229
     230    alDistanceModel(AL_EXPONENT_DISTANCE);
     231}
     232
     233void CSoundManager::SetEnabled( bool doEnable )
     234{
     235    m_Enabled = doEnable;
     236}
     237
     238void CSoundManager::PlayActionItem( ISoundItem* anItem )
     239{
     240    if ( anItem )
     241    {
     242        if ( m_Enabled && ( m_ActionGain > 0 ) )
     243        {
     244            anItem->SetGain( m_Gain * m_ActionGain );
     245            anItem->Play();
     246        }
     247    }
     248}
     249void CSoundManager::PlayGroupItem( ISoundItem* anItem, ALfloat groupGain)
     250{
     251    if ( anItem )
     252    {
     253        if ( m_Enabled && ( m_ActionGain > 0 ) ) {
     254            anItem->SetGain( m_Gain * groupGain );
     255            anItem->Play();
     256        }
     257    }
     258}
     259
     260void CSoundManager::SetMusicEnabled (bool isEnabled)
     261{
     262    if ( m_CurrentTune && !isEnabled )
     263    {
     264        m_CurrentTune->FadeAndDelete(1.00);
     265        m_CurrentTune = 0L;
     266    }
     267    m_MusicEnabled = isEnabled;
     268}
     269
     270void CSoundManager::SetMusicItem( ISoundItem* anItem )
     271{
     272    if ( m_CurrentTune )
     273    {
     274        m_CurrentTune->FadeAndDelete(3.00);
     275        m_CurrentTune = 0L;
     276    }
     277    IdleTask();
     278    if ( anItem )
     279    {
     280        if ( m_MusicEnabled && m_Enabled )
     281        {
     282            m_CurrentTune = anItem;
     283            m_CurrentTune->PlayLoop();
     284            m_CurrentTune->FadeToIn( m_Gain * m_MusicGain, 3.00 );
     285        }
     286        else
     287        {
     288            anItem->StopAndDelete();
     289        }
     290    }
     291}
     292
     293void CSoundManager::SetAmbientItem( ISoundItem* anItem )
     294{
     295    if ( m_CurrentEnvirons )
     296    {
     297        m_CurrentEnvirons->FadeAndDelete(3.00);
     298        m_CurrentEnvirons = 0L;
     299    }
     300    IdleTask();
     301   
     302    if ( anItem )
     303    {
     304        if ( m_Enabled && ( m_AmbientGain > 0 ) )
     305        {
     306            m_CurrentEnvirons = anItem;
     307            m_CurrentEnvirons->SetGain( 0 );
     308            m_CurrentEnvirons->PlayLoop();
     309            m_CurrentEnvirons->FadeToIn( m_Gain * m_AmbientGain, 3.00 );
     310        }
     311    }
     312}
     313
  • source/soundmanager/data/COggData.h

     
     1/* Copyright (C) 2012 Wildfire Games.
     2 * This file is part of 0 A.D.
     3 *
     4 * 0 A.D. is free software: you can redistribute it and/or modify
     5 * it under the terms of the GNU General Public License as published by
     6 * the Free Software Foundation, either version 2 of the License, or
     7 * (at your option) any later version.
     8 *
     9 * 0 A.D. is distributed in the hope that it will be useful,
     10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
     11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     12 * GNU General Public License for more details.
     13 *
     14 * You should have received a copy of the GNU General Public License
     15 * along with 0 A.D.  If not, see <http://www.gnu.org/licenses/>.
     16 */
     17
     18
     19#ifndef SoundTester_COggData_h
     20#define SoundTester_COggData_h
     21
     22#include "CSoundData.h"
     23#include "lib/external_libraries/openal.h"
     24#include "vorbis/vorbisfile.h"
     25
     26class COggData : public CSoundData
     27{
     28    ALuint m_Format;
     29    long m_Frequency;
     30
     31public:
     32    COggData      ();
     33    virtual ~COggData      ();                                     
     34                                     
     35    virtual bool InitOggFile( const wchar_t* fileLoc );
     36    virtual bool IsFileFinished();
     37    virtual bool IsOneShot();
     38
     39    virtual int FetchDataIntoBuffer( int count, ALuint* buffers);
     40    virtual void ResetFile();
     41
     42protected:
     43    OggVorbis_File  m_vf;
     44    int            m_current_section;
     45    bool           m_FileFinished;
     46    bool           m_OneShot;
     47    ALuint         m_Buffer[100];
     48    int            m_BuffersUsed;
     49
     50    bool AddDataBuffer( char* data, long length);
     51    void SetFormatAndFreq( int form, ALsizei freq);
     52    ALsizei  GetBufferCount();
     53    ALuint GetBuffer();
     54    ALuint* GetBufferPtr(); 
     55};
     56
     57
     58
     59#endif
  • source/soundmanager/data/CSoundData.cpp

     
     1/* Copyright (C) 2012 Wildfire Games.
     2 * This file is part of 0 A.D.
     3 *
     4 * 0 A.D. is free software: you can redistribute it and/or modify
     5 * it under the terms of the GNU General Public License as published by
     6 * the Free Software Foundation, either version 2 of the License, or
     7 * (at your option) any later version.
     8 *
     9 * 0 A.D. is distributed in the hope that it will be useful,
     10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
     11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     12 * GNU General Public License for more details.
     13 *
     14 * You should have received a copy of the GNU General Public License
     15 * along with 0 A.D.  If not, see <http://www.gnu.org/licenses/>.
     16 */
     17
     18#include "precompiled.h"
     19#include "CSoundData.h"
     20
     21
     22#include <iostream>
     23#include "COggData.h"
     24#include "ps/Filesystem.h"
     25#include "lib/file/vfs/vfs_util.h"
     26
     27DataMap* CSoundData::sSoundData = NULL;
     28
     29CSoundData::CSoundData()
     30{
     31    InitProperties();
     32}
     33
     34CSoundData::~CSoundData()
     35{
     36    if ( m_ALBuffer != 0 )
     37        alDeleteBuffers( 1, &m_ALBuffer );
     38}
     39
     40void CSoundData::InitProperties()
     41{
     42    m_ALBuffer = 0;
     43    m_RetentionCount = 0;
     44}
     45
     46void CSoundData::ReleaseSoundData( CSoundData* theData )
     47{
     48    DataMap::iterator   itemFind;
     49
     50    if ( theData->DecrementCount() )
     51    {
     52        if ( ( itemFind = CSoundData::sSoundData->find( theData->GetFileName() ) ) != sSoundData->end() )
     53        {
     54            CSoundData* dier = itemFind->second;
     55            CSoundData::sSoundData->erase( itemFind );
     56            delete dier;
     57        }
     58    }
     59}
     60
     61CSoundData* CSoundData::SoundDataFromFile( const VfsPath* itemPath )
     62{
     63    if ( CSoundData::sSoundData == NULL )
     64        CSoundData::sSoundData = new DataMap;
     65   
     66    Path                fExt = itemPath->Extension();
     67    DataMap::iterator   itemFind;
     68    CSoundData*          answer = NULL;
     69
     70
     71    if ( ( itemFind = CSoundData::sSoundData->find( itemPath->string() ) ) != sSoundData->end() )
     72    {
     73        answer = itemFind->second;
     74    }
     75    else
     76    {
     77        if ( fExt == ".ogg" )
     78            answer = SoundDataFromOgg( itemPath );
     79   
     80        if ( answer && answer->IsOneShot() )
     81            (*CSoundData::sSoundData)[itemPath->string()] = answer;
     82   
     83    }
     84    return answer;
     85}
     86
     87bool CSoundData::IsOneShot()
     88{
     89    return true;
     90}
     91
     92
     93CSoundData* CSoundData::SoundDataFromOgg( const VfsPath* itemPath )
     94{
     95    CSoundData* answer = NULL;
     96    COggData*   oggAnswer = new COggData();
     97
     98    OsPath realPath;
     99    Status ret = g_VFS->GetRealPath( *itemPath, realPath);
     100    if ( ret == INFO::OK )
     101    {
     102        if ( oggAnswer->InitOggFile( realPath.string().c_str() ) )
     103        {
     104            answer = oggAnswer;
     105        }
     106    }   
     107    return answer;
     108}
     109
     110
     111ALsizei CSoundData::GetBufferCount()
     112{
     113    return 1;
     114}
     115
     116CStrW     CSoundData::GetFileName()
     117{
     118    return m_FileName;
     119}
     120
     121
     122
     123CSoundData* CSoundData::IncrementCount()
     124{
     125    m_RetentionCount++;
     126    return this;
     127}
     128
     129bool CSoundData::DecrementCount()
     130{
     131    m_RetentionCount--;
     132   
     133    return ( m_RetentionCount <= 0 );
     134}
     135
     136ALuint CSoundData::GetBuffer()
     137{
     138    return m_ALBuffer;
     139}
     140ALuint* CSoundData::GetBufferPtr()
     141{
     142    return &m_ALBuffer;
     143}
     144
  • source/soundmanager/data/COggData.cpp

     
     1/* Copyright (C) 2012 Wildfire Games.
     2 * This file is part of 0 A.D.
     3 *
     4 * 0 A.D. is free software: you can redistribute it and/or modify
     5 * it under the terms of the GNU General Public License as published by
     6 * the Free Software Foundation, either version 2 of the License, or
     7 * (at your option) any later version.
     8 *
     9 * 0 A.D. is distributed in the hope that it will be useful,
     10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
     11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     12 * GNU General Public License for more details.
     13 *
     14 * You should have received a copy of the GNU General Public License
     15 * along with 0 A.D.  If not, see <http://www.gnu.org/licenses/>.
     16 */
     17#include "precompiled.h"
     18
     19
     20#include "COggData.h"
     21
     22
     23#include <wchar.h>
     24#include <iostream>
     25#include "soundmanager/CSoundManager.h"
     26
     27COggData::COggData()
     28{
     29    m_OneShot = false;
     30}
     31
     32COggData::~COggData()
     33{
     34    alDeleteBuffers( m_BuffersUsed, m_Buffer );
     35    ov_clear(&m_vf);
     36}
     37
     38void COggData::SetFormatAndFreq( int form, ALsizei freq)
     39{
     40    m_Format = form;
     41    m_Frequency = freq;
     42}
     43
     44bool COggData::InitOggFile( const wchar_t* fileLoc )
     45{
     46    int buffersToStart = g_SoundManager->GetBufferCount();
     47    char nameH[300];
     48    sprintf( nameH, "%ls", fileLoc );
     49   
     50    FILE* f = fopen( nameH, "rb");
     51    m_current_section = 0;
     52    int err = ov_open_callbacks(f, &m_vf, NULL, 0, OV_CALLBACKS_DEFAULT);
     53    if ( err < 0)
     54    {
     55        fprintf(stderr,"Input does not appear to be an Ogg bitstream :%d :%d.\n", err, ferror(f) );
     56        return false;
     57    }
     58
     59    m_FileName       = CStrW(fileLoc);
     60
     61    m_FileFinished = false;
     62    SetFormatAndFreq( (m_vf.vi->channels == 1)? AL_FORMAT_MONO16 : AL_FORMAT_STEREO16 , (ALsizei)m_vf.vi->rate );
     63
     64    alGetError(); /* clear error */
     65    alGenBuffers( buffersToStart, m_Buffer);
     66   
     67    if(alGetError() != AL_NO_ERROR)
     68    {
     69        printf("- Error creating initial buffer !!\n");
     70        return false;
     71    }
     72    else
     73    {
     74        m_BuffersUsed = FetchDataIntoBuffer( buffersToStart, m_Buffer);
     75        if ( m_FileFinished )
     76        {
     77            m_OneShot = true;
     78            if ( m_BuffersUsed < buffersToStart )
     79            {
     80                alDeleteBuffers( buffersToStart - m_BuffersUsed, &m_Buffer[m_BuffersUsed] );
     81            }
     82        }
     83    }
     84    return true;
     85}
     86
     87ALsizei COggData::GetBufferCount()
     88{
     89    return m_BuffersUsed;
     90}
     91
     92bool COggData::IsFileFinished()
     93{
     94    return m_FileFinished;
     95}
     96
     97void COggData::ResetFile()
     98{
     99    ov_time_seek( &m_vf, 0 );
     100    m_current_section = 0;
     101    m_FileFinished = false;
     102}
     103
     104bool COggData::IsOneShot()
     105{
     106    return m_OneShot;
     107}
     108
     109int COggData::FetchDataIntoBuffer( int count, ALuint* buffers)
     110{
     111    long bufferSize = g_SoundManager->GetBufferSize();
     112   
     113    char* pcmout = new char[bufferSize + 5000];
     114    int buffersWritten = 0;
     115   
     116    for (int i = 0; ( i < count ) && !m_FileFinished; i++)
     117    {
     118        char*   readDest = pcmout;
     119        long  totalRet = 0;
     120        while (totalRet < bufferSize )
     121        {
     122            long ret=ov_read(&m_vf,readDest, 4096,0,2,1, &m_current_section);
     123            if (ret == 0)
     124            {
     125                m_FileFinished=true;
     126                break;
     127            }
     128            else if (ret < 0)
     129            {
     130                /* error in the stream.  Not a problem, just reporting it in
     131                 case we (the app) cares.  In this case, we don't. */
     132            }
     133            else
     134            {
     135                totalRet += ret;
     136                readDest += ret;
     137            }
     138        }
     139        if ( totalRet > 0 )
     140        {
     141            buffersWritten++;
     142            alBufferData( buffers[i], m_Format, pcmout, (ALsizei)totalRet, (int)m_Frequency);
     143        }
     144    }
     145    delete[] pcmout;
     146    return buffersWritten;
     147}
     148
     149
     150ALuint COggData::GetBuffer()
     151{
     152    return m_Buffer[0];
     153}
     154
     155ALuint* COggData::GetBufferPtr()
     156{
     157    return m_Buffer;
     158}
     159
     160
     161
     162
     163
  • source/soundmanager/data/CSoundData.h

     
     1/* Copyright (C) 2012 Wildfire Games.
     2 * This file is part of 0 A.D.
     3 *
     4 * 0 A.D. is free software: you can redistribute it and/or modify
     5 * it under the terms of the GNU General Public License as published by
     6 * the Free Software Foundation, either version 2 of the License, or
     7 * (at your option) any later version.
     8 *
     9 * 0 A.D. is distributed in the hope that it will be useful,
     10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
     11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     12 * GNU General Public License for more details.
     13 *
     14 * You should have received a copy of the GNU General Public License
     15 * along with 0 A.D.  If not, see <http://www.gnu.org/licenses/>.
     16 */
     17
     18
     19#ifndef SoundTester_CSoundData_h
     20#define SoundTester_CSoundData_h
     21#include "lib/os_path.h"
     22
     23#include "string"
     24#include "map"
     25#include "lib/external_libraries/openal.h"
     26#include "lib/file/vfs/vfs_path.h"
     27
     28class CSoundData;
     29typedef std::map<std::wstring, CSoundData*> DataMap;
     30
     31
     32
     33class CSoundData
     34{
     35public:
     36    static CSoundData* SoundDataFromFile( const VfsPath* itemPath );
     37    static CSoundData* SoundDataFromOgg( const VfsPath* itemPath );
     38
     39    static void ReleaseSoundData( CSoundData* theData );
     40
     41    CSoundData      ();
     42    CSoundData      (ALuint dataSource);
     43    virtual ~CSoundData     ();
     44   
     45    CSoundData*     IncrementCount();
     46    bool            DecrementCount();
     47    void            InitProperties();
     48    virtual bool IsOneShot();
     49
     50   
     51    virtual ALuint      GetBuffer();
     52    virtual ALsizei     GetBufferCount();
     53    CStrW        GetFileName();
     54    virtual ALuint*     GetBufferPtr();
     55
     56protected:
     57    static     DataMap*  sSoundData;
     58
     59    ALuint          m_ALBuffer;
     60    int             m_RetentionCount;
     61    CStrW    m_FileName;
     62   
     63   
     64   
     65};
     66
     67
     68
     69
     70
     71
     72#endif
  • source/soundmanager/CSoundManager.h

     
     1/* Copyright (C) 2012 Wildfire Games.
     2 * This file is part of 0 A.D.
     3 *
     4 * 0 A.D. is free software: you can redistribute it and/or modify
     5 * it under the terms of the GNU General Public License as published by
     6 * the Free Software Foundation, either version 2 of the License, or
     7 * (at your option) any later version.
     8 *
     9 * 0 A.D. is distributed in the hope that it will be useful,
     10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
     11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     12 * GNU General Public License for more details.
     13 *
     14 * You should have received a copy of the GNU General Public License
     15 * along with 0 A.D.  If not, see <http://www.gnu.org/licenses/>.
     16 */
     17
     18
     19#ifndef SoundTester_CSoundManager_h
     20#define SoundTester_CSoundManager_h
     21
     22#include "vector"
     23#include "map"
     24
     25#include "soundmanager/items/ISoundItem.h"
     26#include "lib/file/vfs/vfs_path.h"
     27
     28typedef std::vector<ISoundItem*> ItemsList;
     29
     30
     31class CSoundManager
     32{
     33protected:
     34
     35    ALuint              m_ALEnvironment;
     36    ALCcontext*         m_Context;
     37    ALCdevice*          m_Device;
     38    ISoundItem*         m_CurrentTune;
     39    ISoundItem*         m_CurrentEnvirons;
     40    ItemsList*          m_Items;
     41    float               m_Gain;
     42    float               m_MusicGain;
     43    float               m_AmbientGain;
     44    float               m_ActionGain;
     45    bool                m_Enabled;
     46    long                m_BufferSize;
     47    int                 m_BufferCount;
     48    bool                m_MusicEnabled;
     49
     50public:
     51     CSoundManager      ();
     52    virtual ~CSoundManager      ();
     53
     54    ISoundItem* LoadItem( const VfsPath* itemPath );
     55
     56    static void ScriptingInit();
     57
     58
     59    void SetMusicEnabled (bool isEnabled);
     60
     61    ISoundItem*     ItemFromWAV     ( VfsPath& fname);
     62    ISoundItem*     ItemFromOgg     ( VfsPath& fname);
     63
     64    ISoundItem*     GetSoundItem    ( unsigned long itemRow );
     65    unsigned long   Count           ();
     66    void            IdleTask        ();
     67    void            DeleteItem      ( long itemNum );
     68   
     69    void    SetMemoryUsage( long bufferSize, int bufferCount );
     70    long    GetBufferCount();
     71    long    GetBufferSize();
     72
     73    void        SetMusicItem( ISoundItem* anItem );
     74    void        SetAmbientItem( ISoundItem* anItem );
     75    void        PlayActionItem( ISoundItem* anItem );
     76    void        PlayGroupItem( ISoundItem* anItem, ALfloat groupGain);
     77
     78    void        SetMasterGain( float gain);
     79    void        SetMusicGain( float gain);
     80    void        SetAmbientGain( float gain);
     81    void        SetActionGain( float gain);
     82   
     83    void        SetEnabled( bool doEnable );
     84protected:
     85    void    InitListener();
     86    virtual Status AlcInit();
     87
     88};
     89
     90
     91
     92extern CSoundManager*  g_SoundManager;
     93
     94
     95
     96
     97
     98
     99
     100#endif
  • source/soundmanager/js/JAmbientSound.cpp

     
     1/* Copyright (C) 2009 Wildfire Games.
     2 * This file is part of 0 A.D.
     3 *
     4 * 0 A.D. is free software: you can redistribute it and/or modify
     5 * it under the terms of the GNU General Public License as published by
     6 * the Free Software Foundation, either version 2 of the License, or
     7 * (at your option) any later version.
     8 *
     9 * 0 A.D. is distributed in the hope that it will be useful,
     10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
     11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     12 * GNU General Public License for more details.
     13 *
     14 * You should have received a copy of the GNU General Public License
     15 * along with 0 A.D.  If not, see <http://www.gnu.org/licenses/>.
     16 */
     17#include "precompiled.h"
     18
     19#include "JAmbientSound.h"
     20#include "maths/Vector3D.h"
     21
     22#include "lib/utf8.h"
     23#include "ps/Filesystem.h"
     24
     25#include "soundmanager/CSoundManager.h"
     26
     27JAmbientSound::JAmbientSound(const VfsPath& pathname)
     28{
     29    m_FileName = new VfsPath( pathname.string().c_str() );
     30}
     31
     32JAmbientSound::~JAmbientSound()
     33{
     34}
     35
     36
     37// start playing the sound, all ambient sounds loop
     38bool JAmbientSound::Play(JSContext* UNUSED(cx), uintN UNUSED(argc), jsval* UNUSED(argv))
     39{
     40    ISoundItem* aSnd = g_SoundManager->LoadItem( m_FileName );
     41
     42    aSnd->PlayAsAmbient();
     43
     44    return true;
     45}
     46
     47// start playing the sound, all ambient sounds loop
     48bool JAmbientSound::Loop(JSContext* UNUSED(cx), uintN UNUSED(argc), jsval* UNUSED(argv))
     49{
     50    ISoundItem* aSnd = g_SoundManager->LoadItem( m_FileName );
     51
     52    aSnd->PlayAsAmbient();
     53    return true;
     54}
     55bool JAmbientSound::Free(JSContext* UNUSED(cx), uintN UNUSED(argc), jsval* UNUSED(argv))
     56{
     57    g_SoundManager->SetAmbientItem( 0L );
     58
     59    return true;
     60}
     61
     62// Script-bound functions
     63
     64
     65void JAmbientSound::ScriptingInit()
     66{
     67    AddMethod<CStr, &JAmbientSound::ToString>("toString", 0);
     68    AddMethod<bool, &JAmbientSound::Play>("play", 0);
     69    AddMethod<bool, &JAmbientSound::Loop>("loop", 0);
     70    AddMethod<bool, &JAmbientSound::Free>("free", 0);
     71   
     72    CJSObject<JAmbientSound>::ScriptingInit("AmbientSound", &JAmbientSound::Construct, 1);
     73}
     74
     75CStr JAmbientSound::ToString(JSContext* UNUSED(cx), uintN UNUSED(argc), jsval* UNUSED(argv))
     76{
     77    std::ostringstream stringStream;
     78    stringStream << "[object AmbientSound: ";
     79    stringStream << m_FileName->string().c_str();
     80   
     81    return stringStream.str();
     82}
     83
     84JSBool JAmbientSound::Construct(JSContext* cx, uintN UNUSED(argc), jsval* vp)
     85{
     86//  JSU_REQUIRE_MIN_PARAMS(1);
     87   
     88    CStrW filename;
     89    if (! ToPrimitive<CStrW>(cx, JS_ARGV(cx, vp)[0], filename))
     90        return JS_FALSE;
     91   
     92    JAmbientSound* newObject = new JAmbientSound(filename);
     93    newObject->m_EngineOwned = false;
     94
     95    JS_SET_RVAL(cx, vp, OBJECT_TO_JSVAL(newObject->GetScript()));
     96   
     97    return JS_TRUE;
     98}
  • source/soundmanager/js/JSound.cpp

     
     1/* Copyright (C) 2009 Wildfire Games.
     2 * This file is part of 0 A.D.
     3 *
     4 * 0 A.D. is free software: you can redistribute it and/or modify
     5 * it under the terms of the GNU General Public License as published by
     6 * the Free Software Foundation, either version 2 of the License, or
     7 * (at your option) any later version.
     8 *
     9 * 0 A.D. is distributed in the hope that it will be useful,
     10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
     11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     12 * GNU General Public License for more details.
     13 *
     14 * You should have received a copy of the GNU General Public License
     15 * along with 0 A.D.  If not, see <http://www.gnu.org/licenses/>.
     16 */
     17#include "precompiled.h"
     18#include "JSound.h"
     19#include "maths/Vector3D.h"
     20
     21#include "lib/utf8.h"
     22#include "ps/Filesystem.h"
     23
     24#include "soundmanager/CSoundManager.h"
     25
     26
     27JSound::JSound(const VfsPath& pathname)
     28{
     29    m_SndItem = g_SoundManager->LoadItem( &pathname );
     30}
     31
     32JSound::~JSound()
     33{
     34    if ( m_SndItem )
     35    {
     36        m_SndItem->FadeAndDelete(0.2);
     37        m_SndItem = 0;
     38    }
     39}
     40
     41bool JSound::ClearSoundItem()
     42{
     43    m_SndItem = 0L;
     44    return true;
     45}
     46
     47bool JSound::SetGain(JSContext* cx, uintN UNUSED(argc), jsval* argv)
     48{
     49    if (! m_SndItem )
     50        return false;
     51
     52    float gain;
     53    if (! ToPrimitive<float>(cx, argv[0], gain))
     54        return false;
     55   
     56    m_SndItem->SetGain( gain );
     57    return true;
     58}
     59
     60bool JSound::SetPitch(JSContext* cx, uintN UNUSED(argc), jsval* argv)
     61{
     62    if (! m_SndItem )
     63        return false;
     64
     65    float pitch;
     66    if (! ToPrimitive<float>(cx, argv[0], pitch))
     67        return false;
     68   
     69    m_SndItem->SetPitch( pitch );
     70    return true;
     71}
     72
     73bool JSound::SetPosition(JSContext* cx, uintN argc, jsval* argv)
     74{
     75    if (! m_SndItem )
     76        return false;
     77   
     78    ENSURE(argc >= 1); // FIXME
     79   
     80    CVector3D pos;
     81    // absolute world coords
     82    if (!ToPrimitive<CVector3D>(cx, argv[0], pos))
     83        return false;
     84
     85    m_SndItem->SetLocation( pos );
     86   
     87    return true;
     88}
     89
     90
     91bool JSound::Fade(JSContext* cx, uintN UNUSED(argc), jsval* argv)
     92{
     93    if (! m_SndItem )
     94        return false;
     95   
     96//  ENSURE(argc >= 3); // FIXME
     97    float initial_gain, final_gain;
     98    float length;
     99    if (! (ToPrimitive<float>(cx, argv[0], initial_gain)
     100           && ToPrimitive<float>(cx, argv[1], final_gain)
     101           && ToPrimitive<float>(cx, argv[2], length)))
     102        return false;
     103   
     104    m_SndItem->SetGain( initial_gain );
     105    m_SndItem->FadeToIn( final_gain, length );
     106
     107    return true;
     108}
     109
     110bool JSound::Play(JSContext* UNUSED(cx), uintN UNUSED(argc), jsval* UNUSED(argv))
     111{
     112    if (! m_SndItem )
     113        return false;
     114
     115    m_SndItem->Play();
     116
     117    return true;
     118}
     119
     120bool JSound::Loop(JSContext* UNUSED(cx), uintN UNUSED(argc), jsval* UNUSED(argv))
     121{
     122    if (! m_SndItem )
     123        return false;
     124
     125    m_SndItem->PlayLoop();
     126
     127    return true;
     128}
     129
     130bool JSound::Free(JSContext* UNUSED(cx), uintN UNUSED(argc), jsval* UNUSED(argv))
     131{
     132    if ( m_SndItem )
     133    {
     134        m_SndItem->FadeAndDelete(0.2);
     135        m_SndItem = 0;
     136    }
     137
     138    return true;
     139}
     140
     141void JSound::ScriptingInit()
     142{
     143    AddMethod<CStr, &JSound::ToString>("toString", 0);
     144    AddMethod<bool, &JSound::Play>("play", 0);
     145    AddMethod<bool, &JSound::Loop>("loop", 0);
     146    AddMethod<bool, &JSound::Free>("free", 0);
     147    AddMethod<bool, &JSound::SetGain>("setGain", 0);
     148    AddMethod<bool, &JSound::SetPitch>("setPitch", 0);
     149    AddMethod<bool, &JSound::SetPosition>("setPosition", 0);
     150    AddMethod<bool, &JSound::Fade>("fade", 0);
     151   
     152    CJSObject<JSound>::ScriptingInit("Sound", &JSound::Construct, 1);
     153}
     154
     155CStr JSound::ToString(JSContext* UNUSED(cx), uintN UNUSED(argc), jsval* UNUSED(argv))
     156{
     157    return "[object Sound: " + ( m_SndItem ? m_SndItem->GetName() : "(null)" ) + "]";
     158}
     159
     160JSBool JSound::Construct(JSContext* cx, uintN UNUSED(argc), jsval* vp)
     161{
     162//  JSU_REQUIRE_MIN_PARAMS(1);
     163   
     164    CStrW filename;
     165    if (! ToPrimitive<CStrW>(cx, JS_ARGV(cx, vp)[0], filename))
     166        return JS_FALSE;
     167   
     168    JSound* newObject = new JSound(filename);
     169    newObject->m_EngineOwned = false;
     170    JS_SET_RVAL(cx, vp, OBJECT_TO_JSVAL(newObject->GetScript()));
     171   
     172    return JS_TRUE;
     173}
  • source/soundmanager/js/JMusicSound.cpp

     
     1/* Copyright (C) 2009 Wildfire Games.
     2 * This file is part of 0 A.D.
     3 *
     4 * 0 A.D. is free software: you can redistribute it and/or modify
     5 * it under the terms of the GNU General Public License as published by
     6 * the Free Software Foundation, either version 2 of the License, or
     7 * (at your option) any later version.
     8 *
     9 * 0 A.D. is distributed in the hope that it will be useful,
     10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
     11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     12 * GNU General Public License for more details.
     13 *
     14 * You should have received a copy of the GNU General Public License
     15 * along with 0 A.D.  If not, see <http://www.gnu.org/licenses/>.
     16 */
     17#include "precompiled.h"
     18
     19#include "JMusicSound.h"
     20#include "maths/Vector3D.h"
     21
     22#include "lib/utf8.h"
     23#include "ps/Filesystem.h"
     24
     25#include "soundmanager/CSoundManager.h"
     26
     27
     28JMusicSound::JMusicSound(const VfsPath& pathname)
     29{
     30    m_FileName = new VfsPath( pathname.string().c_str() );
     31}
     32
     33JMusicSound::~JMusicSound()
     34{
     35    delete m_FileName;
     36}
     37
     38bool JMusicSound::Play(JSContext* UNUSED(cx), uintN UNUSED(argc), jsval* UNUSED(argv))
     39{
     40    ISoundItem* aSnd = g_SoundManager->LoadItem( m_FileName );
     41    if ( aSnd != NULL )
     42        aSnd->PlayAsMusic();
     43
     44    return true;
     45}
     46
     47// request the sound be played until free() is called. returns immediately.
     48bool JMusicSound::Loop(JSContext* UNUSED(cx), uintN UNUSED(argc), jsval* UNUSED(argv))
     49{
     50    ISoundItem* aSnd = g_SoundManager->LoadItem( m_FileName );
     51    if ( aSnd != NULL )
     52        aSnd->PlayAsMusic();
     53
     54    return true;
     55}
     56
     57void JMusicSound::ScriptingInit()
     58{
     59    AddMethod<CStr, &JMusicSound::ToString>("toString", 0);
     60    AddMethod<bool, &JMusicSound::Play>("play", 0);
     61    AddMethod<bool, &JMusicSound::Loop>("loop", 0);
     62   
     63    CJSObject<JMusicSound>::ScriptingInit("MusicSound", &JMusicSound::Construct, 1);
     64}
     65
     66CStr JMusicSound::ToString(JSContext* UNUSED(cx), uintN UNUSED(argc), jsval* UNUSED(argv))
     67{
     68    std::ostringstream stringStream;
     69    stringStream << "[object MusicSound: ";
     70    stringStream << m_FileName->string().c_str();
     71   
     72    return stringStream.str();
     73}
     74
     75JSBool JMusicSound::Construct(JSContext* cx, uintN UNUSED(argc), jsval* vp)
     76{   
     77    CStrW filename;
     78    if (! ToPrimitive<CStrW>(cx, JS_ARGV(cx, vp)[0], filename))
     79        return JS_FALSE;
     80   
     81    JMusicSound* newObject = new JMusicSound(filename);
     82    newObject->m_EngineOwned = false;
     83    JS_SET_RVAL(cx, vp, OBJECT_TO_JSVAL(newObject->GetScript()));
     84   
     85    return JS_TRUE;
     86}
  • source/soundmanager/js/SMSoundGroup.cpp

     
     1/* Copyright (C) 2010 Wildfire Games.
     2 * This file is part of 0 A.D.
     3 *
     4 * 0 A.D. is free software: you can redistribute it and/or modify
     5 * it under the terms of the GNU General Public License as published by
     6 * the Free Software Foundation, either version 2 of the License, or
     7 * (at your option) any later version.
     8 *
     9 * 0 A.D. is distributed in the hope that it will be useful,
     10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
     11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     12 * GNU General Public License for more details.
     13 *
     14 * You should have received a copy of the GNU General Public License
     15 * along with 0 A.D.  If not, see <http://www.gnu.org/licenses/>.
     16 */
     17
     18/**
     19* =========================================================================
     20* File        : SoundGroup.cpp
     21* Project     : 0 A.D.
     22* Description : Loads up a group of sound files with shared properties,
     23*               and provides a simple interface for playing them.       
     24* =========================================================================
     25*/
     26
     27#include "precompiled.h"
     28#include "soundmanager/CSoundManager.h"
     29#include "SMSoundGroup.h"
     30
     31#include <algorithm>
     32
     33#include "lib/rand.h"
     34
     35#include "ps/XML/Xeromyces.h"
     36#include "ps/CLogger.h"
     37#include "ps/Filesystem.h"
     38#include "ps/Util.h"
     39#include "ps/Game.h"
     40#include "ps/CLogger.h"
     41#include "graphics/GameView.h"
     42#include "graphics/Camera.h"
     43#include "soundmanager/items/ISoundItem.h"
     44
     45extern CGame *g_Game;
     46
     47#define PI 3.14126f
     48
     49
     50static const bool DISABLE_INTENSITY = true; // disable for now since it's broken
     51
     52void CSMSoundGroup::SetGain(float gain)
     53{
     54    gain = std::min(gain, 1.0f);
     55    m_Gain = gain;
     56}
     57
     58void CSMSoundGroup::SetDefaultValues()
     59{
     60    m_index = 0;
     61    m_Flags = 0;
     62    m_Intensity = 0;
     63    m_CurTime = 0.0f;
     64
     65    // sane defaults; will probably be replaced by the values read during LoadSoundGroup.
     66    SetGain(0.7f);
     67    m_Pitch = 1.0f;
     68    m_Priority = 60;
     69    m_PitchUpper = 1.1f;
     70    m_PitchLower = 0.9f;
     71    m_GainUpper = 1.0f;
     72    m_GainLower = 0.8f;
     73    m_ConeOuterGain = 0.0f;
     74    m_ConeInnerAngle = 360.0f;
     75    m_ConeOuterAngle = 360.0f;
     76    m_Decay = 3.0f;
     77    m_IntensityThreshold = 3;
     78    // WARNING: m_TimeWindow is currently unused and uninitialized
     79}
     80
     81CSMSoundGroup::CSMSoundGroup()
     82{
     83    SetDefaultValues();
     84}
     85
     86CSMSoundGroup::CSMSoundGroup(const VfsPath& pathnameXML)
     87{
     88    SetDefaultValues();
     89    LoadSoundGroup(pathnameXML);
     90}
     91
     92CSMSoundGroup::~CSMSoundGroup()
     93{
     94    // clean up all the handles from this group.
     95    ReleaseGroup();
     96}
     97
     98static float RandFloat(float min, float max)
     99{
     100    return float(rand(min*100.0f, max*100.0f) / 100.0f);
     101}
     102
     103float CSMSoundGroup::RadiansOffCenter( const CVector3D& position, bool& onScreen, float& itemRollOff )
     104{
     105    float x, y;
     106    float answer = 0.0;
     107    const size_t screenWidth = g_Game->GetView()->GetCamera()->GetViewPort().m_Width;
     108    const size_t screenHeight = g_Game->GetView()->GetCamera()->GetViewPort().m_Height;
     109    float bufferSize = screenWidth * 0.10;
     110    const size_t audioWidth = screenWidth;
     111    float radianCap = PI / 3;
     112   
     113    g_Game->GetView()->GetCamera()->GetScreenCoordinates(position, x, y);
     114   
     115    onScreen = true;
     116
     117    if ( x < -bufferSize )
     118    {
     119        onScreen = false;
     120        answer = -radianCap;
     121    }
     122    else if ( x > screenWidth + bufferSize )
     123    {
     124        onScreen = false;
     125        answer = radianCap;
     126    }
     127    else {
     128        if ( ( x < 0 ) || ( x > screenWidth ) )
     129        {
     130            itemRollOff = 0.5;
     131        }
     132        float pixPerRadian = audioWidth / ( radianCap * 2 );
     133        answer = ( x - (screenWidth/2) ) / pixPerRadian;
     134    }
     135
     136    if ( y < -bufferSize )
     137    {
     138        onScreen = false;
     139    }
     140    else if ( y > screenHeight + bufferSize )
     141    {
     142        onScreen = false;
     143    }
     144    else {
     145        if ( ( y < 0 ) || ( y > screenHeight ) )
     146        {
     147            itemRollOff = 0.5;
     148        }
     149    }
     150
     151       
     152 //   debug_printf(L"do play at x portion:%f pts x:%f, y=%f at radians=%f\n\n", answer, x, y, answer );
     153
     154    return answer;
     155}
     156
     157void CSMSoundGroup::UploadPropertiesAndPlay(int theIndex, const CVector3D& position)
     158{
     159    bool    isOnscreen;
     160    ALfloat initialRolllOff = 0.02f;
     161    ALfloat itemRollOff = initialRolllOff;
     162
     163    float   offSet = RadiansOffCenter( position, isOnscreen, itemRollOff);
     164
     165    if ( isOnscreen || TestFlag(eDistanceless) || TestFlag(eOmnipresent) )
     166    {
     167        if ( snd_group.size() == 0 )
     168            Reload();
     169
     170        ISoundItem* hSound = snd_group[theIndex];
     171        CVector3D origin = g_Game->GetView()->GetCamera()->GetOrientation().GetTranslation();
     172        float sndDist = origin.Y;
     173
     174        if (!TestFlag(eOmnipresent))
     175        {
     176            hSound->SetLocation( CVector3D(  (sndDist * sin(offSet)), 0, sndDist * cos(offSet) ));
     177            if ( TestFlag(eDistanceless) )
     178                hSound->SetRollOff( initialRolllOff );
     179            else
     180                hSound->SetRollOff( itemRollOff );
     181        }
     182
     183        if ( TestFlag(eRandPitch) )
     184            hSound->SetPitch( RandFloat( m_PitchLower, m_PitchUpper ) );
     185        else
     186            hSound->SetPitch( m_Pitch );
     187
     188        ALfloat theGain = m_Gain;
     189        if ( TestFlag(eRandGain) )
     190            theGain = RandFloat( m_GainLower, m_GainUpper);
     191
     192        hSound->SetCone( m_ConeInnerAngle, m_ConeOuterAngle, m_ConeOuterGain);
     193
     194        g_SoundManager->PlayGroupItem( hSound, theGain );
     195    }
     196}
     197
     198
     199static void HandleError(const CStrW& message, const VfsPath& pathname, Status err)
     200{
     201    if (err == ERR::AGAIN)
     202        return; // open failed because sound is disabled (don't log this)
     203    LOGERROR(L"%ls: pathname=%ls, error=%ls", message.c_str(), pathname.string().c_str(), ErrorString(err));
     204}
     205
     206void CSMSoundGroup::PlayNext(const CVector3D& position)
     207{
     208    // if no sounds, return
     209    if (filenames.size() == 0)
     210        return;
     211   
     212    m_index = (size_t)rand(0, (size_t)filenames.size());
     213    UploadPropertiesAndPlay( m_index, position);
     214}
     215
     216void CSMSoundGroup::Reload()
     217{
     218    m_index = 0; // reset our index
     219
     220    snd_group.clear();
     221
     222    for (size_t i = 0; i < filenames.size(); i++)
     223    {
     224        VfsPath  thePath =  m_filepath/filenames[i];
     225        ISoundItem* temp = g_SoundManager->LoadItem( &thePath );
     226
     227        if ( temp == NULL )
     228            HandleError( L"error loading sound", thePath, NULL);
     229        else
     230            snd_group.push_back(temp);
     231    }
     232
     233    if (TestFlag(eRandOrder))
     234        random_shuffle(snd_group.begin(), snd_group.end());
     235}
     236
     237void CSMSoundGroup::ReleaseGroup()
     238{
     239    for (size_t i = 0; i < snd_group.size(); i++)
     240    {
     241        snd_group[i]->FadeAndDelete(0.2);
     242    }
     243    snd_group.clear();
     244}
     245
     246void CSMSoundGroup::Update(float UNUSED(TimeSinceLastFrame))
     247{
     248}
     249
     250bool CSMSoundGroup::LoadSoundGroup(const VfsPath& pathnameXML)
     251{
     252//    LOGERROR(L"loading new sound group '%ls'", pathnameXML.string().c_str());
     253
     254    CXeromyces XeroFile;
     255    if (XeroFile.Load(g_VFS, pathnameXML) != PSRETURN_OK)
     256    {
     257        HandleError( L"error loading file", pathnameXML, NULL);
     258        return false;
     259    }
     260    // Define elements used in XML file
     261    #define EL(x) int el_##x = XeroFile.GetElementID(#x)
     262    #define AT(x) int at_##x = XeroFile.GetAttributeID(#x)
     263    EL(soundgroup);
     264    EL(gain);
     265    EL(looping);
     266    EL(omnipresent);
     267    EL(distanceless);
     268    EL(pitch);
     269    EL(priority);
     270    EL(randorder);
     271    EL(randgain);
     272    EL(randpitch);
     273    EL(conegain);
     274    EL(coneinner);
     275    EL(coneouter);
     276    EL(sound);
     277    EL(gainupper);
     278    EL(gainlower);
     279    EL(pitchupper);
     280    EL(pitchlower);
     281    EL(path);
     282    EL(threshold);
     283    EL(decay);
     284    #undef AT
     285    #undef EL
     286
     287    XMBElement root = XeroFile.GetRoot();
     288
     289    if (root.GetNodeName() != el_soundgroup)
     290    {
     291        LOGERROR(L"Invalid SoundGroup format (unrecognised root element '%hs')", XeroFile.GetElementString(root.GetNodeName()).c_str());
     292        return false;
     293    }
     294   
     295    XERO_ITER_EL(root, child)
     296    {
     297   
     298        int child_name = child.GetNodeName();           
     299       
     300        if(child_name == el_gain)
     301        {
     302            SetGain(child.GetText().ToFloat());
     303        }
     304        else if(child_name == el_looping)
     305        {
     306            if(child.GetText().ToInt() == 1)
     307                SetFlag(eLoop);
     308        }
     309        else if(child_name == el_omnipresent)
     310        {
     311            if(child.GetText().ToInt() == 1)
     312                SetFlag(eOmnipresent);
     313        }
     314        else if(child_name == el_distanceless)
     315        {
     316            if(child.GetText().ToInt() == 1)
     317                SetFlag(eDistanceless);
     318        }
     319        else if(child_name == el_pitch)
     320        {
     321            this->m_Pitch = child.GetText().ToFloat();
     322        }
     323        else if(child_name == el_priority)
     324        {
     325            this->m_Priority = child.GetText().ToFloat();
     326        }
     327        else if(child_name == el_randorder)
     328        {
     329            if(child.GetText().ToInt() == 1)
     330                SetFlag(eRandOrder);
     331        }
     332        else if(child_name == el_randgain)
     333        {
     334            if(child.GetText().ToInt() == 1)
     335                SetFlag(eRandGain);
     336        }
     337        else if(child_name == el_gainupper)
     338        {
     339            this->m_GainUpper = child.GetText().ToFloat();
     340        }
     341        else if(child_name == el_gainlower)
     342        {
     343            this->m_GainLower = child.GetText().ToFloat();
     344        }
     345        else if(child_name == el_randpitch)
     346        {
     347            if(child.GetText().ToInt() == 1)
     348                SetFlag(eRandPitch);
     349        }
     350        else if(child_name == el_pitchupper)
     351        {
     352            this->m_PitchUpper = child.GetText().ToFloat();
     353        }
     354        else if(child_name == el_pitchlower)
     355        {
     356            this->m_PitchLower = child.GetText().ToFloat();
     357        }
     358        else if(child_name == el_conegain)
     359        {
     360            this->m_ConeOuterGain = child.GetText().ToFloat();
     361        }
     362        else if(child_name == el_coneinner)
     363        {
     364            this->m_ConeInnerAngle = child.GetText().ToFloat();
     365        }
     366        else if(child_name == el_coneouter)
     367        {
     368            this->m_ConeOuterAngle = child.GetText().ToFloat();
     369        }
     370        else if(child_name == el_sound)
     371        {
     372            this->filenames.push_back(child.GetText().FromUTF8());
     373        }
     374        else if(child_name == el_path)
     375        {
     376            m_filepath = child.GetText().FromUTF8();
     377        }
     378        else if(child_name == el_threshold)
     379        {
     380            m_IntensityThreshold = child.GetText().ToFloat();
     381        }
     382        else if(child_name == el_decay)
     383        {
     384            m_Decay = child.GetText().ToFloat();
     385        }
     386    }
     387
     388    return true;
     389}
  • source/soundmanager/js/JAmbientSound.h

     
     1/* Copyright (C) 2009 Wildfire Games.
     2 * This file is part of 0 A.D.
     3 *
     4 * 0 A.D. is free software: you can redistribute it and/or modify
     5 * it under the terms of the GNU General Public License as published by
     6 * the Free Software Foundation, either version 2 of the License, or
     7 * (at your option) any later version.
     8 *
     9 * 0 A.D. is distributed in the hope that it will be useful,
     10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
     11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     12 * GNU General Public License for more details.
     13 *
     14 * You should have received a copy of the GNU General Public License
     15 * along with 0 A.D.  If not, see <http://www.gnu.org/licenses/>.
     16 */
     17
     18#ifndef INCLUDED_JAMBIENTSOUND
     19#define INCLUDED_JAMBIENTSOUND
     20
     21#include "scripting/ScriptableObject.h"
     22#include "soundmanager/items/ISoundItem.h"
     23
     24class JAmbientSound : public CJSObject<JAmbientSound>
     25{
     26public:   
     27             JAmbientSound  (const VfsPath& pathname);
     28    virtual ~JAmbientSound  ();
     29
     30   
     31    CStr ToString(JSContext* cx, uintN argc, jsval* argv);
     32   
     33    bool Play(JSContext* cx, uintN argc, jsval* argv);
     34   
     35    bool Loop(JSContext* cx, uintN argc, jsval* argv);
     36    bool Free(JSContext* cx, uintN argc, jsval* argv);
     37 
     38    bool SetGain(JSContext* cx, uintN argc, jsval* argv);
     39    bool SetPitch(JSContext* cx, uintN argc, jsval* argv);   
     40    bool Fade(JSContext* cx, uintN argc, jsval* argv);
     41   
     42    static JSBool Construct(JSContext* cx, uintN argc, jsval* vp);
     43    void clearSoundItem();
     44    static void ScriptingInit();
     45protected:
     46   
     47    VfsPath*    m_FileName;
     48
     49};
     50
     51#endif  // #ifndef INCLUDED_JAMBIENTSOUND
  • source/soundmanager/js/JSound.h

     
     1/* Copyright (C) 2009 Wildfire Games.
     2 * This file is part of 0 A.D.
     3 *
     4 * 0 A.D. is free software: you can redistribute it and/or modify
     5 * it under the terms of the GNU General Public License as published by
     6 * the Free Software Foundation, either version 2 of the License, or
     7 * (at your option) any later version.
     8 *
     9 * 0 A.D. is distributed in the hope that it will be useful,
     10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
     11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     12 * GNU General Public License for more details.
     13 *
     14 * You should have received a copy of the GNU General Public License
     15 * along with 0 A.D.  If not, see <http://www.gnu.org/licenses/>.
     16 */
     17
     18// JS sound binding
     19
     20// interface rationale:
     21// - can't just expose fire and forget playSound to script code:
     22//   we sometimes need to loop until a certain condition is met
     23//   (e.g. building is complete) => need means of access (Handle) to sound.
     24//
     25// - the current 64-bit Handle can't be stored as-is by JS code;
     26//   we could make it 32 bit, but that limits its usefulness
     27//   (barely enough tag bits).
     28//
     29// - instead, we provide a thin class wrapper (using scriptableobject.h)
     30//   on top of the snd API that encapsulates the Handle.
     31
     32#ifndef INCLUDED_JSOUND
     33#define INCLUDED_JSOUND
     34
     35#include "scripting/ScriptableObject.h"
     36#include "soundmanager/items/ISoundItem.h"
     37
     38class JSound : public CJSObject<JSound>
     39{
     40public:
     41       
     42    // note: filename is stored by handle manager; no need to keep a copy here.
     43   
     44             JSound(const VfsPath& pathname);
     45    virtual ~JSound();
     46   
     47    CStr ToString(JSContext* cx, uintN argc, jsval* argv);
     48   
     49    bool Play(JSContext* cx, uintN argc, jsval* argv);
     50    bool Loop(JSContext* cx, uintN argc, jsval* argv);
     51   
     52    bool Free(JSContext* cx, uintN argc, jsval* argv);
     53    bool SetGain(JSContext* cx, uintN argc, jsval* argv);
     54    bool SetPitch(JSContext* cx, uintN argc, jsval* argv);
     55    bool SetPosition(JSContext* cx, uintN argc, jsval* argv);
     56    bool ClearSoundItem();
     57   
     58    bool Fade(JSContext* cx, uintN argc, jsval* argv);
     59   
     60    static JSBool Construct(JSContext* cx, uintN argc, jsval* vp);
     61    static void ScriptingInit();
     62
     63protected:
     64    ISoundItem*     m_SndItem;
     65};
     66
     67#endif  // #ifndef INCLUDED_JSOUND
  • source/soundmanager/js/JMusicSound.h

     
     1/* Copyright (C) 2009 Wildfire Games.
     2 * This file is part of 0 A.D.
     3 *
     4 * 0 A.D. is free software: you can redistribute it and/or modify
     5 * it under the terms of the GNU General Public License as published by
     6 * the Free Software Foundation, either version 2 of the License, or
     7 * (at your option) any later version.
     8 *
     9 * 0 A.D. is distributed in the hope that it will be useful,
     10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
     11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     12 * GNU General Public License for more details.
     13 *
     14 * You should have received a copy of the GNU General Public License
     15 * along with 0 A.D.  If not, see <http://www.gnu.org/licenses/>.
     16 */
     17
     18// JS sound binding
     19
     20// interface rationale:
     21// - can't just expose fire and forget playSound to script code:
     22//   we sometimes need to loop until a certain condition is met
     23//   (e.g. building is complete) => need means of access (Handle) to sound.
     24//
     25// - the current 64-bit Handle can't be stored as-is by JS code;
     26//   we could make it 32 bit, but that limits its usefulness
     27//   (barely enough tag bits).
     28//
     29// - instead, we provide a thin class wrapper (using scriptableobject.h)
     30//   on top of the snd API that encapsulates the Handle.
     31
     32#ifndef INCLUDED_JMUSICSOUND
     33#define INCLUDED_JMUSICSOUND
     34
     35#include "scripting/ScriptableObject.h"
     36#include "soundmanager/items/ISoundItem.h"
     37
     38class JMusicSound : public CJSObject<JMusicSound>
     39{
     40public:   
     41             JMusicSound(const VfsPath& pathname);
     42    virtual ~JMusicSound();
     43   
     44    // Script-bound functions
     45   
     46    CStr ToString(JSContext* cx, uintN argc, jsval* argv);
     47   
     48    bool Play(JSContext* cx, uintN argc, jsval* argv);
     49    bool Loop(JSContext* cx, uintN argc, jsval* argv);
     50   
     51    static JSBool Construct(JSContext* cx, uintN argc, jsval* vp);
     52
     53    static void ScriptingInit();
     54   
     55protected:
     56    VfsPath*    m_FileName;
     57};
     58
     59#endif  // #ifndef INCLUDED_JMUSICSOUND
  • source/soundmanager/js/SMSoundGroup.h

     
     1/* Copyright (C) 2009 Wildfire Games.
     2 * This file is part of 0 A.D.
     3 *
     4 * 0 A.D. is free software: you can redistribute it and/or modify
     5 * it under the terms of the GNU General Public License as published by
     6 * the Free Software Foundation, either version 2 of the License, or
     7 * (at your option) any later version.
     8 *
     9 * 0 A.D. is distributed in the hope that it will be useful,
     10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
     11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     12 * GNU General Public License for more details.
     13 *
     14 * You should have received a copy of the GNU General Public License
     15 * along with 0 A.D.  If not, see <http://www.gnu.org/licenses/>.
     16 */
     17
     18/**
     19* =========================================================================
     20* File        : SoundGroup.h
     21* Project     : 0 A.D.
     22* Description : Loads up a group of sound files with shared properties,
     23*               and provides a simple interface for playing them.       
     24* =========================================================================
     25*/
     26
     27/*
     28Example usage:
     29
     30
     31Example SoundGroup.xml
     32    <?xml version="1.0" encoding="utf-8"?>
     33    <SoundGroup>
     34        <Gain>1.0</Gain>
     35        <Looping>0</Looping>
     36        <Pitch>1.0</Pitch>
     37        <Priority>100</Priority>
     38        <RandOrder>0</RandOrder>
     39        <RandGain>0</RandGain>
     40        <RandPitch>0</RandPitch>
     41        <ConeGain>1.0</ConeGain>
     42        <ConeInner>360</ConeInner>
     43        <ConeOuter>360</ConeOuter>
     44        <Sound>audio/voice/hellenes/soldier/Attack_Attackx.ogg</Sound>
     45        <Sound>audio/voice/hellenes/soldier/Attack_Chargex.ogg</Sound>
     46        <Sound>audio/voice/hellenes/soldier/Attack_Engagex.ogg</Sound>
     47        <Sound>audio/voice/hellenes/soldier/Attack_ForMyFamily.ogg</Sound>
     48    </SoundGroup>
     49
     50*/
     51
     52#ifndef INCLUDED_SMSOUNDGROUP
     53#define INCLUDED_SMSOUNDGROUP
     54
     55#include "lib/file/vfs/vfs_path.h"
     56
     57#include <vector>
     58
     59class ISoundItem;
     60
     61enum eSndGrpFlags
     62{
     63    eRandOrder     = 0x01,
     64    eRandGain      = 0x02,
     65    eRandPitch     = 0x04,
     66    eLoop          = 0x08,
     67    eOmnipresent   = 0x10,
     68    eDistanceless  = 0x20
     69};
     70
     71
     72class CSMSoundGroup
     73{
     74    NONCOPYABLE(CSMSoundGroup);
     75public:
     76        CSMSoundGroup(const VfsPath& pathnameXML);
     77    CSMSoundGroup(void);
     78    ~CSMSoundGroup(void);
     79
     80    // Play next sound in group
     81    // @param position world position of the entity generating the sound
     82    // (ignored if the eOmnipresent flag is set)
     83    void PlayNext(const CVector3D& position);
     84
     85    float RadiansOffCenter( const CVector3D& position, bool& onScreen, float& itemRollOff  );
     86
     87    // Load a group
     88    bool LoadSoundGroup(const VfsPath& pathnameXML);
     89
     90    void Reload();
     91
     92    // Release all remaining loaded handles
     93    void ReleaseGroup();
     94
     95    // Update SoundGroup, remove dead sounds from intensity count
     96    void Update(float TimeSinceLastFrame);
     97
     98    // Set a flag using a value from eSndGrpFlags
     99    inline void SetFlag(int flag) { m_Flags = (unsigned char)(m_Flags | flag); }
     100
     101    // Test flag, returns true if flag is set.
     102    inline bool TestFlag(int flag) { return (m_Flags & flag) != 0; }
     103   
     104private:
     105    void SetGain(float gain);
     106    void UploadPropertiesAndPlay(int theIndex, const CVector3D& position);
     107    void SetDefaultValues();
     108
     109    size_t m_index;  // index of the next sound to play
     110       
     111    std::vector<ISoundItem*> snd_group;  // we store the handles so we can load now and play later
     112    std::vector<std::wstring> filenames; // we need the filenames so we can reload when necessary.
     113
     114    VfsPath m_filepath; // the file path for the list of sound file resources
     115
     116    float m_CurTime; // Time elapsed since soundgroup was created
     117    float m_TimeWindow; // The Intensity Threshold Window
     118    size_t m_IntensityThreshold; // the allowable intensity before a sound switch   
     119    size_t m_Intensity;  // our current intensity(number of sounds played since m_CurTime - m_TimeWindow)
     120    float m_Decay; //
     121    unsigned char m_Flags; // up to eight individual parameters, use with eSndGrpFlags.
     122   
     123    float m_Gain; 
     124    float m_Pitch;
     125    float m_Priority;
     126    float m_ConeOuterGain;
     127    float m_PitchUpper;
     128    float m_PitchLower;
     129    float m_GainUpper;
     130    float m_GainLower;
     131    float m_ConeInnerAngle;
     132    float m_ConeOuterAngle;
     133};
     134
     135#endif //#ifndef INCLUDED_SOUNDGROUP
  • source/sound/JSI_Sound.h

     
    1 /* Copyright (C) 2009 Wildfire Games.
    2  * This file is part of 0 A.D.
    3  *
    4  * 0 A.D. is free software: you can redistribute it and/or modify
    5  * it under the terms of the GNU General Public License as published by
    6  * the Free Software Foundation, either version 2 of the License, or
    7  * (at your option) any later version.
    8  *
    9  * 0 A.D. is distributed in the hope that it will be useful,
    10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
    11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    12  * GNU General Public License for more details.
    13  *
    14  * You should have received a copy of the GNU General Public License
    15  * along with 0 A.D.  If not, see <http://www.gnu.org/licenses/>.
    16  */
    17 
    18 // JS sound binding
    19 
    20 // interface rationale:
    21 // - can't just expose fire and forget playSound to script code:
    22 //   we sometimes need to loop until a certain condition is met
    23 //   (e.g. building is complete) => need means of access (Handle) to sound.
    24 //
    25 // - the current 64-bit Handle can't be stored as-is by JS code;
    26 //   we could make it 32 bit, but that limits its usefulness
    27 //   (barely enough tag bits).
    28 //
    29 // - instead, we provide a thin class wrapper (using scriptableobject.h)
    30 //   on top of the snd API that encapsulates the Handle.
    31 
    32 #ifndef INCLUDED_JSI_SOUND
    33 #define INCLUDED_JSI_SOUND
    34 
    35 #include "scripting/ScriptableObject.h"
    36 #include "lib/res/handle.h"
    37 
    38 class JSI_Sound : public CJSObject<JSI_Sound>
    39 {
    40 public:
    41 
    42     Handle m_Handle;
    43 
    44     // note: filename is stored by handle manager; no need to keep a copy here.
    45 
    46     JSI_Sound(const VfsPath& pathname);
    47     ~JSI_Sound();
    48 
    49     // Script-bound functions
    50 
    51     CStr ToString(JSContext* cx, uintN argc, jsval* argv);
    52 
    53     // start playing the sound (one-shot).
    54     // it will automatically be freed when done.
    55     bool Play(JSContext* cx, uintN argc, jsval* argv);
    56 
    57     // request the sound be played until free() is called. returns immediately.
    58     bool Loop(JSContext* cx, uintN argc, jsval* argv);
    59 
    60     // stop sound if currently playing and free resources.
    61     // doesn't need to be called unless played via loop() -
    62     // sounds are freed automatically when done playing.
    63     bool Free(JSContext* cx, uintN argc, jsval* argv);
    64 
    65     bool SetGain(JSContext* cx, uintN argc, jsval* argv);
    66 
    67     bool SetPitch(JSContext* cx, uintN argc, jsval* argv);
    68 
    69     bool SetPosition(JSContext* cx, uintN argc, jsval* argv);
    70 
    71     bool Fade(JSContext* cx, uintN argc, jsval* argv);
    72 
    73     static JSBool Construct(JSContext* cx, uintN argc, jsval* vp);
    74 
    75     static void ScriptingInit();
    76 };
    77 
    78 #endif  // #ifndef INCLUDED_JSI_SOUND
  • source/sound/SoundGroup.cpp

     
    1 /* Copyright (C) 2010 Wildfire Games.
    2  * This file is part of 0 A.D.
    3  *
    4  * 0 A.D. is free software: you can redistribute it and/or modify
    5  * it under the terms of the GNU General Public License as published by
    6  * the Free Software Foundation, either version 2 of the License, or
    7  * (at your option) any later version.
    8  *
    9  * 0 A.D. is distributed in the hope that it will be useful,
    10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
    11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    12  * GNU General Public License for more details.
    13  *
    14  * You should have received a copy of the GNU General Public License
    15  * along with 0 A.D.  If not, see <http://www.gnu.org/licenses/>.
    16  */
    17 
    18 /**
    19 * =========================================================================
    20 * File        : SoundGroup.cpp
    21 * Project     : 0 A.D.
    22 * Description : Loads up a group of sound files with shared properties,
    23 *               and provides a simple interface for playing them.       
    24 * =========================================================================
    25 */
    26 
    27 #include "precompiled.h"
    28 #include "SoundGroup.h"
    29 
    30 #include <algorithm>
    31 
    32 #include "lib/rand.h"
    33 
    34 #include "ps/XML/Xeromyces.h"
    35 #include "ps/CLogger.h"
    36 #include "ps/Filesystem.h"
    37 #include "ps/Util.h"
    38 
    39 
    40 static const bool DISABLE_INTENSITY = true; // disable for now since it's broken
    41 
    42 void CSoundGroup::SetGain(float gain)
    43 {
    44     gain = std::min(gain, 1.0f);
    45     m_Gain = gain;
    46 }
    47 
    48 void CSoundGroup::SetDefaultValues()
    49 {
    50     m_index = 0;
    51     m_Flags = 0;
    52     m_Intensity = 0;
    53     m_CurTime = 0.0f;
    54 
    55     // sane defaults; will probably be replaced by the values read during LoadSoundGroup.
    56     SetGain(0.5f);
    57     m_Pitch = 1.0f;
    58     m_Priority = 60;
    59     m_PitchUpper = 1.1f;
    60     m_PitchLower = 0.9f;
    61     m_GainUpper = 1.0f;
    62     m_GainLower = 0.8f;
    63     m_ConeOuterGain = 0.0f;
    64     m_ConeInnerAngle = 360.0f;
    65     m_ConeOuterAngle = 360.0f;
    66     m_Decay = 3.0f;
    67     m_IntensityThreshold = 3;
    68     // WARNING: m_TimeWindow is currently unused and uninitialized
    69 }
    70 
    71 CSoundGroup::CSoundGroup()
    72 {
    73     SetDefaultValues();
    74 }
    75 
    76 CSoundGroup::CSoundGroup(const VfsPath& pathnameXML)
    77 {
    78     SetDefaultValues();
    79     LoadSoundGroup(pathnameXML);
    80 }
    81 
    82 CSoundGroup::~CSoundGroup()
    83 {
    84     // clean up all the handles from this group.
    85     ReleaseGroup();
    86 }
    87 
    88 static float RandFloat(float min, float max)
    89 {
    90     return float(rand(min*100.0f, max*100.0f) / 100.0f);
    91 }
    92 
    93 void CSoundGroup::UploadPropertiesAndPlay(Handle hSound, const CVector3D& position)
    94 {
    95     // interface/UI sounds should always be played at the listener's
    96     // position, which is achieved by setting position to 0 and
    97     // having that treated as relative to the listener.
    98     float x = 0.0f, y = 0.0f, z = 0.0f;
    99     bool relative = true;
    100     if(!TestFlag(eOmnipresent))
    101     {
    102         x = position.X;
    103         y = position.Y;
    104         z = position.Z;
    105         relative = false;
    106     }
    107 
    108     snd_set_pos(hSound, x, y, z, relative);
    109 
    110     float gain = TestFlag(eRandGain)? RandFloat(m_GainLower, m_GainUpper) : m_Gain;
    111     gain = std::min(gain, 1.0f);    // guard against roundoff error in RandFloat or too high m_GainUpper
    112     snd_set_gain(hSound, gain);
    113 
    114     const float pitch = TestFlag(eRandPitch)? RandFloat(m_PitchLower, m_PitchUpper) : m_Pitch;
    115     snd_set_pitch(hSound, pitch);
    116 
    117     snd_play(hSound, m_Priority);
    118 }
    119 
    120 
    121 static void HandleError(const std::wstring& message, const VfsPath& pathname, Status err)
    122 {
    123     if(err == ERR::AGAIN)
    124         return; // open failed because sound is disabled (don't log this)
    125     LOGERROR(L"%ls: pathname=%ls, error=%ls", message.c_str(), pathname.string().c_str(), ErrorString(err));
    126 }
    127 
    128 void CSoundGroup::PlayNext(const CVector3D& position)
    129 {
    130     if(m_Intensity >= m_IntensityThreshold && !DISABLE_INTENSITY)
    131     {
    132         if(!snd_is_playing(m_hReplacement))
    133         {
    134             // load up replacement file
    135             const VfsPath pathname(m_filepath / m_intensity_file);
    136             m_hReplacement = snd_open(g_VFS, pathname);
    137             if(m_hReplacement < 0)
    138             {
    139                 HandleError(L"PlayNext: snd_open for replacement file failed", pathname, (Status)m_hReplacement);
    140                 return;
    141             }
    142 
    143             UploadPropertiesAndPlay(m_hReplacement, position);
    144         }
    145     }
    146     else
    147     {
    148         // if no sounds, return
    149         if (filenames.size() == 0)
    150             return;
    151 
    152         // try loading on the fly only when we need the sound to see if that fixes release problems...
    153         if(TestFlag(eRandOrder))
    154             m_index = (size_t)rand(0, (size_t)filenames.size());
    155         // (note: previously snd_group[m_index] was used in place of hs)
    156         const VfsPath pathname(m_filepath / filenames[m_index]);
    157         Handle hs = snd_open(g_VFS, pathname);
    158         if(hs < 0)
    159         {
    160             HandleError(L"PlayNext: snd_open failed", pathname, (Status)hs);
    161             return;
    162         }
    163 
    164         UploadPropertiesAndPlay(hs, position);
    165     }
    166    
    167     playtimes.at(m_index) = 0.0f;
    168     m_index++;
    169     m_Intensity++;
    170     if(m_Intensity > m_IntensityThreshold)
    171         m_Intensity = m_IntensityThreshold;
    172 
    173     if(m_index >= filenames.size())
    174         Reload();
    175 }
    176 
    177 void CSoundGroup::Reload()
    178 {
    179     m_index = 0; // reset our index
    180     // get rid of the used handles
    181     snd_group.clear();
    182     // clear out the old timers
    183     playtimes.clear();
    184     //Reload the sounds
    185     /*for(size_t i = 0; i < filenames.size(); i++)
    186     {
    187         string szTemp = m_filepath + filenames[i];
    188         Handle temp = snd_open(m_filepath + filenames[i]);
    189         snd_set_gain(temp, m_Gain);
    190         snd_set_pitch(temp, m_Pitch);
    191         snd_set_cone(temp, m_ConeInnerAngle, m_ConeOuterAngle, m_ConeOuterGain);
    192         snd_group.push_back(temp);
    193 
    194     }*/
    195     while(playtimes.size() < filenames.size())
    196         playtimes.push_back(-1.0f);
    197     //if(TestFlag(eRandOrder))
    198         //random_shuffle(snd_group.begin(), snd_group.end());
    199 }
    200 
    201 void CSoundGroup::ReleaseGroup()
    202 {
    203     for(size_t i = m_index; i<snd_group.size(); i++)
    204     {
    205         //if(!snd_is_playing(snd_group[i]))
    206             snd_free(snd_group[i]);
    207     }
    208     snd_group.clear();
    209     playtimes.clear();
    210     //if(snd_is_playing(m_hReplacement))
    211     //  snd_free(m_hReplacement);
    212     m_index = 0;
    213 
    214 }
    215 
    216 void CSoundGroup::Update(float TimeSinceLastFrame)
    217 {
    218     for(size_t i = 0; i < playtimes.size(); i++)
    219     {
    220         if(playtimes[i] >= 0.0f)
    221             playtimes[i] += TimeSinceLastFrame;
    222 
    223         if(playtimes[i] >= m_Decay)
    224         {
    225             playtimes[i] = -1.0f;
    226             m_Intensity--;
    227         }   
    228     }
    229 }
    230 
    231 bool CSoundGroup::LoadSoundGroup(const VfsPath& pathnameXML)
    232 {
    233     CXeromyces XeroFile;
    234     if (XeroFile.Load(g_VFS, pathnameXML) != PSRETURN_OK)
    235         return false;
    236 
    237     // Define elements used in XML file
    238     #define EL(x) int el_##x = XeroFile.GetElementID(#x)
    239     #define AT(x) int at_##x = XeroFile.GetAttributeID(#x)
    240     EL(soundgroup);
    241     EL(gain);
    242     EL(looping);
    243     EL(omnipresent);
    244     EL(pitch);
    245     EL(priority);
    246     EL(randorder);
    247     EL(randgain);
    248     EL(randpitch);
    249     EL(conegain);
    250     EL(coneinner);
    251     EL(coneouter);
    252     EL(sound);
    253     EL(gainupper);
    254     EL(gainlower);
    255     EL(pitchupper);
    256     EL(pitchlower);
    257     EL(path);
    258     EL(threshold);
    259     EL(decay);
    260     EL(replacement);
    261     #undef AT
    262     #undef EL
    263 
    264     XMBElement root = XeroFile.GetRoot();
    265 
    266     if (root.GetNodeName() != el_soundgroup)
    267     {
    268         LOGERROR(L"Invalid SoundGroup format (unrecognised root element '%hs')", XeroFile.GetElementString(root.GetNodeName()).c_str());
    269         return false;
    270     }
    271    
    272     XERO_ITER_EL(root, child)
    273     {
    274    
    275         int child_name = child.GetNodeName();           
    276        
    277         if(child_name == el_gain)
    278         {
    279             SetGain(child.GetText().ToFloat());
    280         }
    281         else if(child_name == el_looping)
    282         {
    283             if(child.GetText().ToInt() == 1)
    284                 SetFlag(eLoop);
    285         }
    286         else if(child_name == el_omnipresent)
    287         {
    288             if(child.GetText().ToInt() == 1)
    289                 SetFlag(eOmnipresent);
    290         }
    291         else if(child_name == el_pitch)
    292         {
    293             this->m_Pitch = child.GetText().ToFloat();
    294         }
    295         else if(child_name == el_priority)
    296         {
    297             this->m_Priority = child.GetText().ToFloat();
    298         }
    299         else if(child_name == el_randorder)
    300         {
    301             if(child.GetText().ToInt() == 1)
    302                 SetFlag(eRandOrder);
    303         }
    304         else if(child_name == el_randgain)
    305         {
    306             if(child.GetText().ToInt() == 1)
    307                 SetFlag(eRandGain);
    308         }
    309         else if(child_name == el_gainupper)
    310         {
    311             this->m_GainUpper = child.GetText().ToFloat();
    312         }
    313         else if(child_name == el_gainlower)
    314         {
    315             this->m_GainLower = child.GetText().ToFloat();
    316         }
    317         else if(child_name == el_randpitch)
    318         {
    319             if(child.GetText().ToInt() == 1)
    320                 SetFlag(eRandPitch);
    321         }
    322         else if(child_name == el_pitchupper)
    323         {
    324             this->m_PitchUpper = child.GetText().ToFloat();
    325         }
    326         else if(child_name == el_pitchlower)
    327         {
    328             this->m_PitchLower = child.GetText().ToFloat();
    329         }
    330         else if(child_name == el_conegain)
    331         {
    332             this->m_ConeOuterGain = child.GetText().ToFloat();
    333         }
    334         else if(child_name == el_coneinner)
    335         {
    336             this->m_ConeInnerAngle = child.GetText().ToFloat();
    337         }
    338         else if(child_name == el_coneouter)
    339         {
    340             this->m_ConeOuterAngle = child.GetText().ToFloat();
    341         }
    342         else if(child_name == el_sound)
    343         {
    344             this->filenames.push_back(child.GetText().FromUTF8());
    345         }
    346         else if(child_name == el_path)
    347         {
    348             m_filepath = child.GetText().FromUTF8();
    349         }
    350         else if(child_name == el_threshold)
    351         {
    352             m_IntensityThreshold = child.GetText().ToFloat();
    353         }
    354         else if(child_name == el_decay)
    355         {
    356             m_Decay = child.GetText().ToFloat();
    357         }
    358         else if(child_name == el_replacement)
    359         {
    360             m_intensity_file = child.GetText().FromUTF8();
    361         }
    362     }
    363 
    364     Reload();
    365     return true;
    366 }
  • source/sound/ambient_design.txt

     
    1 PLAYING AMBIENT SOUND WHEN CAMERA IS OVER BUILDING
    2 ==================================================
    3 
    4 data flow:
    5 C++                             JS
    6 > list of visible entities (1)
    7                                 < list of sound requests and their weights
    8 list of changed sounds
    9 
    10 C++ code generates list of entities on screen
    11     rationale: done in C++ for performance and to avoid having to expose the "patch" subdivision scheme to JS.
    12     UPDATE: this is much thornier than expected.
    13     1) we only want stuff that is visible to be played. hearing things off screen would be distressing and weird
    14     2) don't play sound for buildings in FoW, to prevent "audio spying"
    15     most straightforward way is simply scan all entities; if building, check against visible frustum.
    16         if inside AND within 3d distance cutoff of viewer pos, add to visible_building
    17     optimization for later: get all patches within 3d distance cutoff; discard all not within frustum; only test
    18         entities within those remaining patches.
    19 pass that to JS decideWhatToPlay
    20 it returns list of sounds it wants playing
    21     format: see AmbientSoundReq below
    22 C++ sound engine compares its list of currently sounds;
    23     fades out those no longer wanted and starts new ones immediately
    24 
    25 
    26 new entity properties needed:
    27 -----------------------------
    28 - ambientGroup: a soundGroup of several sounds; one is picked at random to be played (see below)
    29 - priority: to determine which entity should trump the others in a crowded city.
    30 
    31 
    32 // weight is so that we can play several ambient sounds at a time, but ones for buildings at edge of screen are quieter
    33 // values: 0..1; probably calculated from distance to camera
    34 type AmbientSoundReq = (SoundGroupString, weight)
    35 
    36 // (suggestion only; may be revised if too much cacophony/constant sound results)
    37 JS_decideWhatToPlay(in HEntity visible_ents[], out AmbientSoundReq desired_ambient_sounds[])
    38 {
    39 // * "important" means the building is at center of attention and should mostly override the other sounds.
    40 // this is complicated as well. the camera may have any orientation, especially in cinematic mode;
    41 // that means we can't just rely on casting a ray from viewer through middle screen pixel to terrain.
    42 // our building list already covers only the visible ones, so we can use 3d distance from viewer
    43 // (without worrying about including buildings behind us i.e. out of sight).
    44 // important := fairly small distance (covers the case where users zoom really close to a building) OR
    45 // building is close to center of screen (as determined via ray cast method; skip this if the camera
    46 // is weird, i.e. looking above the horizon, in which case the ray cast would fail).
    47 // note: not playing sounds when looking down vertically at the town but zoomed out very far is ok (desirable even).
    48     if an entity is important(*)
    49         desired_ambient_sounds[i++] = ent.ambientGroup
    50 
    51     forall ambient types (farm, dock etc.) in decreasing order of priority
    52         stop if >= 3 sounds in list
    53         if enough buildings on screen
    54             desired_ambient_sounds[i++] = ent.ambientGroup
    55 
    56 maybe add some random variation to spice things up? (i.e. also play some other building sounds;
    57 don't always have one building override the others)
    58 }
    59 
    60 
    61 C++ code keeps a list of all active sounds:
    62 type ActiveSound = (Handle, SoundGroupString)
    63 ActiveSound active_sound_list[]
    64 
    65 Handle startPlayingAmbient(soundGroup, weight)
    66 {
    67     filename = soundGroup.pickRandom()
    68     return snd_play(filename, globalAmbientVolume*soundGroup.volume*weight)
    69 }
    70 
    71 updateAmbientSounds(desired_ambient_sounds)
    72 {
    73     for all in desired_ambient_sounds but not active_sound_list:
    74         handle = startPlayingAmbient(amb.group, amb.weight)
    75         active_sound_list[i++] = (handle, amb.group)
    76 
    77     for all in active_sound_list but not desired_ambient_sounds:
    78         snd_fade(OUT, active.handle)
    79 }
    80 
    81 
    82 
    83 RANDOMIZED SOUNDS
    84 ==================================================
    85 (needed for ambient and normal battle sounds)
    86  No newline at end of file
  • source/sound/SoundGroup.h

     
    1 /* Copyright (C) 2009 Wildfire Games.
    2  * This file is part of 0 A.D.
    3  *
    4  * 0 A.D. is free software: you can redistribute it and/or modify
    5  * it under the terms of the GNU General Public License as published by
    6  * the Free Software Foundation, either version 2 of the License, or
    7  * (at your option) any later version.
    8  *
    9  * 0 A.D. is distributed in the hope that it will be useful,
    10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
    11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    12  * GNU General Public License for more details.
    13  *
    14  * You should have received a copy of the GNU General Public License
    15  * along with 0 A.D.  If not, see <http://www.gnu.org/licenses/>.
    16  */
    17 
    18 /**
    19 * =========================================================================
    20 * File        : SoundGroup.h
    21 * Project     : 0 A.D.
    22 * Description : Loads up a group of sound files with shared properties,
    23 *               and provides a simple interface for playing them.       
    24 * =========================================================================
    25 */
    26 
    27 /*
    28 Example usage:
    29 
    30 
    31 Example SoundGroup.xml
    32     <?xml version="1.0" encoding="utf-8"?>
    33     <SoundGroup>
    34         <Gain>1.0</Gain>
    35         <Looping>0</Looping>
    36         <Pitch>1.0</Pitch>
    37         <Priority>100</Priority>
    38         <RandOrder>0</RandOrder>
    39         <RandGain>0</RandGain>
    40         <RandPitch>0</RandPitch>
    41         <ConeGain>1.0</ConeGain>
    42         <ConeInner>360</ConeInner>
    43         <ConeOuter>360</ConeOuter>
    44         <Sound>audio/voice/hellenes/soldier/Attack_Attackx.ogg</Sound>
    45         <Sound>audio/voice/hellenes/soldier/Attack_Chargex.ogg</Sound>
    46         <Sound>audio/voice/hellenes/soldier/Attack_Engagex.ogg</Sound>
    47         <Sound>audio/voice/hellenes/soldier/Attack_ForMyFamily.ogg</Sound>
    48     </SoundGroup>
    49 
    50 */
    51 
    52 #ifndef INCLUDED_SOUNDGROUP
    53 #define INCLUDED_SOUNDGROUP
    54 
    55 #include "lib/res/handle.h"
    56 #include "lib/file/vfs/vfs_path.h"
    57 #include "ps/CStr.h"
    58 #include "maths/Vector3D.h"
    59 #include "lib/res/sound/snd_mgr.h"
    60 
    61 #include <vector>
    62 
    63 enum eSndGrpFlags
    64 {
    65     eRandOrder     = 0x01,
    66     eRandGain      = 0x02,
    67     eRandPitch     = 0x04,
    68     eLoop          = 0x08,
    69     eOmnipresent   = 0x10
    70 };
    71 
    72 
    73 class CSoundGroup
    74 {
    75     NONCOPYABLE(CSoundGroup);
    76 public:
    77     CSoundGroup(const VfsPath& pathnameXML);
    78     CSoundGroup(void);
    79     ~CSoundGroup(void);
    80 
    81     // Play next sound in group
    82     // @param position world position of the entity generating the sound
    83     // (ignored if the eOmnipresent flag is set)
    84     void PlayNext(const CVector3D& position);
    85 
    86     // Load a group
    87     bool LoadSoundGroup(const VfsPath& pathnameXML);
    88 
    89     void Reload();
    90 
    91     // Release all remaining loaded handles
    92     void ReleaseGroup();
    93 
    94     // Update SoundGroup, remove dead sounds from intensity count
    95     void Update(float TimeSinceLastFrame);
    96 
    97     // Set a flag using a value from eSndGrpFlags
    98     inline void SetFlag(int flag) { m_Flags = (unsigned char)(m_Flags | flag); }
    99 
    100     // Test flag, returns true if flag is set.
    101     inline bool TestFlag(int flag) { return (m_Flags & flag) != 0; }
    102    
    103 private:
    104     void SetGain(float gain);
    105     void UploadPropertiesAndPlay(Handle hSound, const CVector3D& position);
    106     void SetDefaultValues();
    107 
    108     size_t m_index;  // index of the next sound to play
    109    
    110     Handle m_hReplacement;
    111    
    112     std::vector<Handle> snd_group;  // we store the handles so we can load now and play later
    113     std::vector<std::wstring> filenames; // we need the filenames so we can reload when necessary.
    114     std::vector<float> playtimes; // it would be better to store this in with the Handles perhaps?
    115     VfsPath m_filepath; // the file path for the list of sound file resources
    116     std::wstring m_intensity_file; // the replacement aggregate 'intense' sound
    117 
    118     float m_CurTime; // Time elapsed since soundgroup was created
    119     float m_TimeWindow; // The Intensity Threshold Window
    120     size_t m_IntensityThreshold; // the allowable intensity before a sound switch   
    121     size_t m_Intensity;  // our current intensity(number of sounds played since m_CurTime - m_TimeWindow)
    122     float m_Decay; //
    123     unsigned char m_Flags; // up to eight individual parameters, use with eSndGrpFlags.
    124    
    125     float m_Gain; 
    126     float m_Pitch;
    127     float m_Priority;
    128     float m_ConeOuterGain;
    129     float m_PitchUpper;
    130     float m_PitchLower;
    131     float m_GainUpper;
    132     float m_GainLower;
    133     float m_ConeInnerAngle;
    134     float m_ConeOuterAngle;
    135 };
    136 
    137 #endif //#ifndef INCLUDED_SOUNDGROUP
  • source/sound/JSI_Sound.cpp

     
    1 /* Copyright (C) 2009 Wildfire Games.
    2  * This file is part of 0 A.D.
    3  *
    4  * 0 A.D. is free software: you can redistribute it and/or modify
    5  * it under the terms of the GNU General Public License as published by
    6  * the Free Software Foundation, either version 2 of the License, or
    7  * (at your option) any later version.
    8  *
    9  * 0 A.D. is distributed in the hope that it will be useful,
    10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
    11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    12  * GNU General Public License for more details.
    13  *
    14  * You should have received a copy of the GNU General Public License
    15  * along with 0 A.D.  If not, see <http://www.gnu.org/licenses/>.
    16  */
    17 
    18 #include "precompiled.h"
    19 #include "JSI_Sound.h"
    20 #include "maths/Vector3D.h"
    21 
    22 #include "lib/utf8.h"
    23 #include "lib/res/sound/snd_mgr.h"
    24 #include "lib/res/h_mgr.h"  // h_filename
    25 #include "ps/Filesystem.h"
    26 
    27 
    28 JSI_Sound::JSI_Sound(const VfsPath& pathname)
    29 {
    30     m_Handle = snd_open(g_VFS, pathname);
    31 
    32     // if open failed, we still have to return a valid non-null object to
    33     // the script, so just reset the handle to 0 so all subsequent method
    34     // calls will do nothing
    35     if (m_Handle < 0)
    36     {
    37         m_Handle = 0;
    38         return;
    39     }
    40 
    41     (void)snd_set_pos(m_Handle, 0,0,0, true);
    42 }
    43 
    44 JSI_Sound::~JSI_Sound()
    45 {
    46     (void)this->Free(0, 0, 0);
    47 }
    48 
    49 
    50 bool JSI_Sound::SetGain(JSContext* cx, uintN argc, jsval* argv)
    51 {
    52     if (! m_Handle)
    53         return false;
    54 
    55     ENSURE(argc >= 1); // FIXME
    56     float gain;
    57     if (! ToPrimitive<float>(cx, argv[0], gain))
    58         return false;
    59 
    60     (void)snd_set_gain(m_Handle, gain);
    61     return true;
    62 }
    63 
    64 bool JSI_Sound::SetPitch(JSContext* cx, uintN argc, jsval* argv)
    65 {
    66     if (! m_Handle)
    67         return false;
    68 
    69     ENSURE(argc >= 1); // FIXME
    70     float pitch;
    71     if (! ToPrimitive<float>(cx, argv[0], pitch))
    72         return false;
    73 
    74     (void)snd_set_pitch(m_Handle, pitch);
    75     return true;
    76 }
    77 
    78 bool JSI_Sound::SetPosition(JSContext* cx, uintN argc, jsval* argv)
    79 {
    80     if (! m_Handle)
    81         return false;
    82 
    83     ENSURE(argc >= 1); // FIXME
    84 
    85     CVector3D pos;
    86     // absolute world coords
    87     if (ToPrimitive<CVector3D>(cx, argv[0], pos))
    88         (void)snd_set_pos(m_Handle, pos[0], pos[1], pos[2]);
    89     // relative, 0 offset - right on top of the listener
    90     // (we don't need displacement from the listener, e.g. always behind)
    91     else
    92         (void)snd_set_pos(m_Handle, 0,0,0, true);
    93 
    94     return true;
    95 }
    96 
    97 
    98 bool JSI_Sound::Fade(JSContext* cx, uintN argc, jsval* argv)
    99 {
    100     if (! m_Handle)
    101         return false;
    102 
    103     ENSURE(argc >= 3); // FIXME
    104     float initial_gain, final_gain;
    105     float length;
    106     if (! (ToPrimitive<float>(cx, argv[0], initial_gain)
    107         && ToPrimitive<float>(cx, argv[1], final_gain)
    108         && ToPrimitive<float>(cx, argv[2], length)))
    109         return false;
    110 
    111     (void)snd_fade(m_Handle, initial_gain, final_gain, length, FT_S_CURVE);
    112 
    113     // HACK: snd_fade causes <m_Handle> to be automatically freed when a
    114     // fade to 0 has completed. however, we're still holding on to a
    115     // reference, which will cause a double-free warning when Free() is
    116     // called from the dtor. solution is to neuter our Handle by
    117     // setting it to 0 (ok since it'll be freed). this does mean that
    118     // no further operations can be carried out during that final fade.
    119     if (final_gain == 0.0f)
    120         m_Handle = 0;
    121 
    122     return true;
    123 }
    124 
    125 // start playing the sound (one-shot).
    126 // it will automatically be freed when done.
    127 bool JSI_Sound::Play(JSContext* UNUSED(cx), uintN UNUSED(argc), jsval* UNUSED(argv))
    128 {
    129     if (! m_Handle)
    130         return false;
    131 
    132     (void)snd_play(m_Handle);
    133     // We can't do anything else with this sound now, since it's impossible to
    134     // know whether or not it's still valid (since it might have finished playing
    135     // already). So set it to 0, so we don't try doing anything (like freeing it)
    136     // in the future.
    137     m_Handle = 0;
    138     return true;
    139 }
    140 
    141 // request the sound be played until free() is called. returns immediately.
    142 bool JSI_Sound::Loop(JSContext* UNUSED(cx), uintN UNUSED(argc), jsval* UNUSED(argv))
    143 {
    144     if (! m_Handle)
    145         return false;
    146 
    147     (void)snd_set_loop(m_Handle, true);
    148     (void)snd_play(m_Handle);
    149     return true;
    150 }
    151 
    152 // stop sound if currently playing and free resources.
    153 // doesn't need to be called unless played via loop() -
    154 // sounds are freed automatically when done playing.
    155 bool JSI_Sound::Free(JSContext* UNUSED(cx), uintN UNUSED(argc), jsval* UNUSED(argv))
    156 {
    157     if (! m_Handle)
    158         return false;
    159 
    160     (void)snd_free(m_Handle);   // resets it to 0
    161     return true;
    162 }
    163 
    164 
    165 // Script-bound functions
    166 
    167 
    168 void JSI_Sound::ScriptingInit()
    169 {
    170     AddMethod<CStr, &JSI_Sound::ToString>("toString", 0);
    171     AddMethod<bool, &JSI_Sound::Play>("play", 0);
    172     AddMethod<bool, &JSI_Sound::Loop>("loop", 0);
    173     AddMethod<bool, &JSI_Sound::Free>("free", 0);
    174     AddMethod<bool, &JSI_Sound::SetGain>("setGain", 0);
    175     AddMethod<bool, &JSI_Sound::SetPitch>("setPitch", 0);
    176     AddMethod<bool, &JSI_Sound::SetPosition>("setPosition", 0);
    177     AddMethod<bool, &JSI_Sound::Fade>("fade", 0);
    178 
    179     CJSObject<JSI_Sound>::ScriptingInit("Sound", &JSI_Sound::Construct, 1);
    180 }
    181 
    182 CStr JSI_Sound::ToString(JSContext* UNUSED(cx), uintN UNUSED(argc), jsval* UNUSED(argv))
    183 {
    184     return "[object Sound: " + (m_Handle ? utf8_from_wstring(h_filename(m_Handle).string()) : "(null)") + "]";
    185 }
    186 
    187 JSBool JSI_Sound::Construct(JSContext* cx, uintN argc, jsval* vp)
    188 {
    189     JSU_REQUIRE_MIN_PARAMS(1);
    190 
    191     CStrW filename;
    192     if (! ToPrimitive<CStrW>(cx, JS_ARGV(cx, vp)[0], filename))
    193         return JS_FALSE;
    194 
    195     JSI_Sound* newObject = new JSI_Sound(filename);
    196     newObject->m_EngineOwned = false;
    197     JS_SET_RVAL(cx, vp, OBJECT_TO_JSVAL(newObject->GetScript()));
    198 
    199     return JS_TRUE;
    200 }
  • source/main.cpp

     
    372372        g_Game->Update(TimeSinceLastFrame);
    373373
    374374        g_Game->GetView()->Update(float(TimeSinceLastFrame));
    375 
    376         CCamera* camera = g_Game->GetView()->GetCamera();
    377         CMatrix3D& orientation = camera->m_Orientation;
    378         float* pos = &orientation._data[12];
    379         float* dir = &orientation._data[8];
    380         float* up  = &orientation._data[4];
    381         // HACK: otherwise sound effects are L/R flipped. No idea what else
    382         // is going wrong, because the listener and camera are supposed to
    383         // coincide in position and orientation.
    384         float down[3] = { -up[0], -up[1], -up[2] };
    385 
    386         {
    387             PROFILE3("sound update");
    388             if (snd_update(pos, dir, down) < 0)
    389                 debug_printf(L"snd_update failed\n");
    390         }
    391375    }
    392     else
    393     {
    394         PROFILE3("sound update (0)");
    395         if (snd_update(0, 0, 0) < 0)
    396             debug_printf(L"snd_update (pos=0 version) failed\n");
    397     }
    398376
    399377    // Immediately flush any messages produced by simulation code
    400378    if (g_NetClient)
     
    477455    // run non-visual simulation replay if requested
    478456    if (args.Has("replay"))
    479457    {
    480         snd_disable(true);
    481 
    482458        Paths paths(args);
    483459        g_VFS = CreateVfs(20 * MiB);
    484460        g_VFS->Mount(L"cache/", paths.Cache(), VFS_MOUNT_ARCHIVABLE);
  • source/lib/res/sound/snd_mgr.h

     
    1 /* Copyright (c) 2010 Wildfire Games
    2  *
    3  * Permission is hereby granted, free of charge, to any person obtaining
    4  * a copy of this software and associated documentation files (the
    5  * "Software"), to deal in the Software without restriction, including
    6  * without limitation the rights to use, copy, modify, merge, publish,
    7  * distribute, sublicense, and/or sell copies of the Software, and to
    8  * permit persons to whom the Software is furnished to do so, subject to
    9  * the following conditions:
    10  *
    11  * The above copyright notice and this permission notice shall be included
    12  * in all copies or substantial portions of the Software.
    13  *
    14  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
    15  * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
    16  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
    17  * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
    18  * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
    19  * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
    20  * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
    21  */
    22 
    23 /*
    24  * OpenAL sound engine. handles sound I/O, buffer suballocation and
    25  * voice management/prioritization.
    26  */
    27 
    28 #ifndef INCLUDED_SND_MGR
    29 #define INCLUDED_SND_MGR
    30 
    31 #include "lib/res/handle.h"
    32 #include "lib/file/vfs/vfs.h"
    33 
    34 /**
    35 
    36 overview
    37 --------
    38 
    39 this module provides a moderately high-level sound interface. basic usage
    40 is opening a sound and requesting it be played; it is closed automatically
    41 when playback has finished (fire and forget).
    42 any number of sound play requests may be issued; the most 'important' ones
    43 are actually played (necessary due to limited hardware mixing capacity).
    44 3d positional sounds (heard to emanate from a given spot) are supported.
    45 active sound instances are referenced by Handles, so changing volume etc.
    46 during playback is possible (useful for fadeout).
    47 
    48 
    49 sound setup
    50 -----------
    51 
    52 OpenAL provides portable access to the underlying sound hardware, and
    53 falls back to software mixing if no acceleration is provided.
    54 we allow the user to specify the device to use (in case the default
    55 has problems) and maximum number of sources (to reduce mixing cost).
    56 
    57 
    58 performance
    59 -----------
    60 much effort has been invested in efficiency: all sound data is cached,
    61 so every open() after the first is effectively free. large sound files are
    62 streamed from disk to reduce load time and memory usage. hardware mixing
    63 resources are suballocated to avoid delays when starting to play.
    64 therefore, the user can confidently fire off hundreds of sound requests.
    65 finally, lengthy initialization steps are delayed until the sound engine
    66 is actually needed (i.e. upon first open()). perceived startup time is
    67 therefore reduced - the user sees e.g. our main menu earlier.
    68 
    69 
    70 terminology
    71 -----------
    72 
    73 "hardware voice" refers to mixing resources on the DSP. strictly
    74   speaking, we mean 'OpenAL source', but this term is more clear.
    75   voice ~= source, unless expensive effects (e.g. EAX) are enabled.
    76   note: software mixing usually doesn't have a fixed 'source' cap.
    77 "gain" is quantified volume. 1 is unattenuated, 0.5 corresponds to -6 dB,
    78   and 0 is silence. this can be set per-source as well as globally.
    79 "position" of a sound is within the app's coordinate system,
    80   the orientation of which is passed to snd_update.
    81 "importance" of a sound derives from the app-assigned priority
    82   (e.g. voiceover must not be skipped in favor of seagulls) and
    83   distance from the listener. it's calculated by our prioritizer.
    84 "virtual source" denotes a sound play request issued by the app.
    85   this is in contrast to an actual AL source, which will be mixed
    86   into the output channel. the most important VSrc receive an al_src.
    87 "sound instances" store playback parameters (e.g. position), and
    88   reference the (centrally cached) "sound data" that will be played.
    89 
    90 **/
    91 
    92 
    93 //
    94 // device enumeration
    95 //
    96 
    97 /**
    98  * prepare to enumerate all device names (this resets the list returned by
    99  * snd_dev_next).
    100  * may be called each time the device list is needed.
    101  *
    102  * @return Status; fails iff the requisite OpenAL extension isn't available.
    103  * in that case, a "cannot enum device" message should be displayed, but
    104  * snd_dev_set need not be called; OpenAL will use its default device.
    105  **/
    106 extern Status snd_dev_prepare_enum();
    107 
    108 /**
    109  * get next device name in list.
    110  *
    111  * do not call unless snd_dev_prepare_enum succeeded!
    112  * not thread-safe! (static data from snd_dev_prepare_enum is used)
    113  *
    114  * @return device name string, or 0 if all have been returned.
    115  **/
    116 extern const char* snd_dev_next();
    117 
    118 
    119 //
    120 // sound system setup
    121 //
    122 
    123 /**
    124  * tell OpenAL to use the specified device in future.
    125  *
    126  * @param alc_new_dev_name device name string. if 0, revert to
    127  * OpenAL's default choice, which will also be used if
    128  * this routine is never called.
    129  * the device name is typically taken from a config file at init-time;
    130  * the snd_dev* enumeration routines above are used to present a list
    131  * of choices to the user in the options screen.
    132  *
    133  * if OpenAL hasn't yet been initialized (i.e. no sounds have been opened),
    134  *   this just stores the device name for use when init does occur.
    135  *   note: we can't check now if it's invalid (if so, init will fail).
    136  * otherwise, we shut OpenAL down (thereby stopping all sounds) and
    137  * re-initialize with the new device. that's fairly time-consuming,
    138  * so preferably call this routine before sounds are loaded.
    139  *
    140  * @return Status (the status returned by OpenAL re-init)
    141  **/
    142 extern Status snd_dev_set(const char* alc_new_dev_name);
    143 
    144 /**
    145  * Set maximum number of voices to play simultaneously;
    146  * this can be used to reduce mixing cost on low-end systems.
    147  *
    148  * @param limit Maximum number of voices. Ignored if higher than
    149  *        an implementation-defined limit anyway.
    150  * @return Status
    151  **/
    152 extern Status snd_set_max_voices(size_t limit);
    153 
    154 /**
    155  * set amplitude modifier, which is effectively applied to all sounds.
    156  * this is akin to a global "volume" control.
    157  *
    158  * @param gain amplitude modifier. must be non-negative;
    159  * 1 -> unattenuated, 0.5 -> -6 dB, 0 -> silence.
    160  * @return Status
    161  **/
    162 extern Status snd_set_master_gain(float gain);
    163 
    164 
    165 //
    166 // sound instance
    167 //
    168 
    169 /**
    170  * Open and return a handle to a sound instance.
    171  * This loads the sound data and makes it ready for other snd_* APIs.
    172  *
    173  * @param vfs
    174  * @param pathname If a text file (extension ".txt"), it is
    175  *        assumed to be a definition file containing the sound file name and
    176  *        its gain (0.0 .. 1.0).
    177  *        Otherwise, it is taken to be the sound file name and
    178  *        gain is set to the default of 1.0 (no attenuation).
    179  * @return Handle or Status
    180  **/
    181 extern Handle snd_open(const PIVFS& vfs, const VfsPath& pathname);
    182 
    183 /**
    184  * Close the sound instance. If it was playing, it will be stopped.
    185  *
    186  * Rationale: sounds are already closed automatically when done playing;
    187  * this API is provided for completeness only.
    188  *
    189  * @param hvs Handle to sound instance. Zeroed afterwards.
    190  * @return Status
    191  **/
    192 extern Status snd_free(Handle& hvs);
    193 
    194 /**
    195  * Start playing the sound.
    196  *
    197  * Notes:
    198  * - once done playing, the sound is automatically closed (allows
    199  *   fire-and-forget play code).
    200  * - if no hardware voice is available, this sound may not be
    201  *   played at all, or in the case of looped sounds, start later.
    202  *
    203  * @param hvs Handle to VSrc.
    204  * @param static_pri (min 0 .. max 1, default 0) indicates which sounds are
    205  * considered more important (i.e. will override others when no hardware
    206  * voices are available). the static priority is attenuated by
    207  * distance to the listener; see snd_update.
    208  *
    209  * @return Status
    210  **/
    211 extern Status snd_play(Handle hvs, float static_pri = 0.0f);
    212 
    213 /**
    214  * Change 3d position of the sound source.
    215  *
    216  * May be called at any time; fails with invalid handle return if
    217  * the sound has already been closed (e.g. it never played).
    218  *
    219  * @param hvs Handle to the sound.
    220  * @param x,y,z
    221  * @param relative treat (x,y,z) as relative to the listener;
    222  *        if false (the default), it is the position in world coordinates.
    223  * @return Status
    224  **/
    225 extern Status snd_set_pos(Handle hvs, float x, float y, float z, bool relative = false);
    226 
    227 /**
    228  * change gain (amplitude modifier) of the sound source.
    229  *
    230  * should not be called during a fade (see note in implementation);
    231  * fails with invalid handle return if the sound has already been
    232  * closed (e.g. it never played).
    233  *
    234  * @param gain amplitude modifier. must be non-negative;
    235  * 1 -\> unattenuated, 0.5 -\> -6 dB, 0 -\> silence.
    236  * @return Status
    237  **/
    238 extern Status snd_set_gain(Handle hs, float gain);
    239 
    240 /**
    241  * change pitch shift of the sound source.
    242  *
    243  * may be called at any time; fails with invalid handle return if
    244  * the sound has already been closed (e.g. it never played).
    245  *
    246  * @param pitch shift: 1.0 means no change; each doubling/halving equals a
    247  * pitch shift of +/-12 semitones (one octave). zero is invalid.
    248  * @return Status
    249  **/
    250 extern Status snd_set_pitch(Handle hs, float pitch);
    251 
    252 /**
    253  * Enable/disable looping on the sound source.
    254  * Used to implement variable-length sounds (e.g. while building).
    255  *
    256  * May be called at any time; fails with invalid handle return if
    257  * the sound has already been closed (e.g. it never played).
    258  *
    259  * Notes:
    260  * - looping sounds are not discarded if they cannot be played for
    261  * - lack of a hardware voice at the moment play was requested.
    262  * - once looping is again disabled and the sound has reached its end,
    263  *   the sound instance is freed automatically (as if never looped).
    264  *
    265  * @param hvs Handle to the sound.
    266  * @param loop Boolean to enable/disable lopping on the sound.
    267  * @return Status
    268  **/
    269 extern Status snd_set_loop(Handle hvs, bool loop);
    270 
    271 /// types of fade in/out operations
    272 enum FadeType
    273 {
    274     FT_NONE,        /// currently no fade in progres
    275     FT_LINEAR,      /// f(t) = t
    276     FT_EXPONENTIAL, /// f(t) = t**3
    277     FT_S_CURVE,     /// cosine curve
    278 
    279     FT_ABORT        /// abort and mark pending fade as complete
    280 };
    281 
    282 /**
    283  * Fade the sound source in or out over time.
    284  *
    285  * May be called at any time; fails with invalid handle return if
    286  * the sound has already been closed (e.g. it never played).
    287  *
    288  * Gain starts at \<initial_gain\> (immediately) and is moved toward
    289  * \<final_gain\> over \<length\> seconds.
    290  *
    291  * @param hvs Handle to the sound.
    292  * @param initial_gain
    293  * @param final_gain
    294  * @param length
    295  * @param type Type of fade curve: linear, exponential or S-curve.
    296  *
    297  * For guidance on which to use, see
    298  * http://www.transom.org/tools/editing_mixing/200309.stupidfadetricks.html
    299  * you can also pass FT_ABORT to stop fading (if in progress) and
    300  * set gain to the current \<final_gain\> parameter.
    301  * Special cases:
    302  * - if \<initial_gain\> \< 0 (an otherwise illegal value), the sound's
    303  *   current gain is used as the start value (useful for fading out).
    304  * - if \<final_gain\> is 0, the sound is freed when the fade completes or
    305  *   is aborted, thus allowing fire-and-forget fadeouts. no cases are
    306  *   foreseen where this is undesirable, and it is easier to implement
    307  *   than an extra set-free-after-fade-flag function.
    308  *
    309  * Note that this function doesn't busy-wait until the fade is complete;
    310  * any number of fades may be active at a time (allows cross-fading).
    311  * Each snd_update calculates a new gain value for all pending fades.
    312  * It is safe to start another fade on the same sound source while
    313  * one is already in progress; the old one will be discarded.
    314  *
    315  * @return Status
    316  **/
    317 extern Status snd_fade(Handle hvs, float initial_gain, float final_gain,
    318     float length, FadeType type);
    319 
    320 
    321 //
    322 // sound engine
    323 //
    324 
    325 /**
    326  * (temporarily) disable all sound output.
    327  *
    328  * because it causes future snd_open calls to immediately abort before they
    329  * demand-initialize OpenAL, startup is sped up considerably (500..1000ms).
    330  * therefore, this must be called before the first snd_open to have
    331  * any effect; otherwise, the cat will already be out of the bag and
    332  * we raise a warning.
    333  *
    334  * rationale: this is a quick'n dirty way of speeding up startup during
    335  * development without having to change the game's sound code.
    336  *
    337  * can later be called to reactivate sound; all settings ever changed
    338  * will be applied and subsequent sound load / play requests will work.
    339  *
    340  * @param disabled
    341  * @return Status
    342  **/
    343 extern Status snd_disable(bool disabled);
    344 
    345 /**
    346  * Perform housekeeping (e.g. streaming); call once a frame.
    347  *
    348  * All parameters are expressed in world coordinates. they can all be NULL
    349  * to avoid updating the listener data; this is useful when the game world
    350  * has not been initialized yet.
    351  * @param pos listener's position
    352  * @param dir listener view direction
    353  * @param up listener's local up vector
    354  * @return Status
    355  **/
    356 extern Status snd_update(const float* pos, const float* dir, const float* up);
    357 
    358 /**
    359  * find out if a sound is still playing
    360  *
    361  * @param hvs Handle to the snd to check.
    362  * @return bool true if playing
    363  **/
    364 extern bool snd_is_playing(Handle hvs);
    365 
    366 
    367 /**
    368  * free all resources and shut down the sound system.
    369  * call before h_mgr_shutdown.
    370  **/
    371 extern void snd_shutdown();
    372 
    373 #endif  // #ifndef INCLUDED_SND_MGR
  • source/lib/res/sound/ogg.h

     
    1 #ifndef INCLUDED_OGG
    2 #define INCLUDED_OGG
    3 
    4 #include "lib/config2.h"
    5 
    6 #if CONFIG2_AUDIO
    7 
    8 #include "lib/external_libraries/openal.h"
    9 #include "lib/file/vfs/vfs.h"
    10 
    11 class OggStream
    12 {
    13 public:
    14     virtual ~OggStream() { }
    15     virtual ALenum Format() = 0;
    16     virtual ALsizei SamplingRate() = 0;
    17 
    18     /**
    19      * @return bytes read (<= size) or a (negative) Status
    20      **/
    21     virtual Status GetNextChunk(u8* buffer, size_t size) = 0;
    22 };
    23 
    24 typedef shared_ptr<OggStream> OggStreamPtr;
    25 
    26 extern Status OpenOggStream(const OsPath& pathname, OggStreamPtr& stream);
    27 
    28 /**
    29  * A non-streaming OggStream (reading the whole file in advance)
    30  * that can cope with archived/compressed files.
    31  */
    32 extern Status OpenOggNonstream(const PIVFS& vfs, const VfsPath& pathname, OggStreamPtr& stream);
    33 
    34 #endif  // CONFIG2_AUDIO
    35 
    36 #endif // INCLUDED_OGG
  • source/lib/res/sound/snd_mgr.cpp

     
    1 /* Copyright (c) 2010 Wildfire Games
    2  *
    3  * Permission is hereby granted, free of charge, to any person obtaining
    4  * a copy of this software and associated documentation files (the
    5  * "Software"), to deal in the Software without restriction, including
    6  * without limitation the rights to use, copy, modify, merge, publish,
    7  * distribute, sublicense, and/or sell copies of the Software, and to
    8  * permit persons to whom the Software is furnished to do so, subject to
    9  * the following conditions:
    10  *
    11  * The above copyright notice and this permission notice shall be included
    12  * in all copies or substantial portions of the Software.
    13  *
    14  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
    15  * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
    16  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
    17  * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
    18  * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
    19  * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
    20  * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
    21  */
    22 
    23 /*
    24  * OpenAL sound engine. handles sound I/O, buffer suballocation and
    25  * voice management/prioritization.
    26  */
    27 
    28 #include "precompiled.h"
    29 #include "snd_mgr.h"
    30 
    31 #include <sstream>  // to extract snd_open's definition file contents
    32 #include <string>
    33 #include <vector>
    34 #include <algorithm>
    35 #include <deque>
    36 #include <cmath>
    37 #include <cstdio>
    38 #include <cfloat>
    39 
    40 #include "lib/alignment.h"
    41 #include "lib/res/h_mgr.h"
    42 #include "lib/file/vfs/vfs.h"
    43 
    44 
    45 // for DLL-load hack in alc_init
    46 #if OS_WIN
    47 # include "lib/sysdep/os/win/win.h"
    48 #endif
    49 
    50 #include "lib/timer.h"
    51 #include "lib/app_hooks.h"
    52 #include "lib/sysdep/cpu.h" // cpu_CAS
    53 
    54 #if CONFIG2_AUDIO
    55 
    56 #include "ogg.h"
    57 #include "lib/external_libraries/openal.h"
    58 
    59 
    60 static const size_t maxBufferSize = 64*KiB;
    61 
    62 // components:
    63 // - alc*: OpenAL context
    64 //   readies OpenAL for use; allows specifying the device.
    65 // - al_listener*: OpenAL listener
    66 //   owns position/orientation and master gain.
    67 // - al_buf*: OpenAL buffer suballocator
    68 //   for convenience; also makes sure all have been freed at exit.
    69 // - al_src*: OpenAL source suballocator
    70 //   avoids high source alloc cost. also enforces user-set source limit.
    71 // - al_init*: OpenAL startup mechanism
    72 //   allows deferred init (speeding up start time) and runtime reset.
    73 // - snd_dev*: device enumeration
    74 //   lists names of all available devices (for sound options screen).
    75 // - hsd_list*: list of SndData instances
    76 //   ensures all are freed when desired (despite being cached).
    77 // - snd_data*: sound data provider
    78 //   holds audio data (clip or stream) and returns OpenAL buffers on request.
    79 // - list*: list of active sounds.
    80 //   sorts by priority for voice management, and has each VSrc update itself.
    81 // - vsrc*: audio source
    82 //   owns source properties and queue, references SndData.
    83 // - vm*: voice management
    84 //   grants the currently most 'important' sounds a hardware voice.
    85 
    86 
    87 // indicates OpenAL is ready for use. checked by other components
    88 // when deciding if they can pass settings changes to OpenAL directly,
    89 // or whether they need to be saved until init.
    90 static bool al_initialized = false;
    91 
    92 
    93 // used by snd_dev_set to reset OpenAL after device has been changed.
    94 static Status al_reinit();
    95 
    96 // used by al_shutdown to free all VSrc and SndData objects, respectively,
    97 // so that they release their OpenAL sources and buffers.
    98 static Status list_free_all();
    99 static void hsd_list_free_all();
    100 
    101 
    102 static void al_ReportError(ALenum err, const char* caller, int line)
    103 {
    104     ENSURE(al_initialized);
    105 
    106     debug_printf(L"OpenAL error: %hs; called from %hs (line %d)\n", alGetString(err), caller, line);
    107     DEBUG_WARN_ERR(ERR::LOGIC);
    108 }
    109 
    110 /**
    111  * check if OpenAL indicates an error has occurred. it can only report one
    112  * error at a time, so this is called before and after every OpenAL request.
    113  *
    114  * @param caller Name of calling function (typically passed via __func__)
    115  * @param line line number of the call site (typically passed via __LINE__)
    116  * (identifies the exact call site since there may be several per caller)
    117  */
    118 static void al_check(const char* caller, int line)
    119 {
    120     ALenum err = alGetError();
    121     if(err != AL_NO_ERROR)
    122         al_ReportError(err, caller, line);
    123 }
    124 
    125 // convenience version that automatically passes in function name.
    126 #define AL_CHECK al_check(__func__, __LINE__)
    127 
    128 
    129 //-----------------------------------------------------------------------------
    130 // OpenAL context: readies OpenAL for use; allows specifying the device,
    131 // in case there are problems with OpenAL's default choice.
    132 //-----------------------------------------------------------------------------
    133 
    134 // default (0): use OpenAL default device.
    135 // note: that's why this needs to be a pointer instead of an array.
    136 static const char* alc_dev_name = 0;
    137 
    138 
    139 /**
    140  * tell OpenAL to use the specified device in future.
    141  *
    142  * @param alc_new_dev_name Device name; if 0, it reverts to OpenAL's default
    143  *  choice, which will also be used if this routine is never called.
    144  * @return Status
    145  *
    146  * the device name is typically taken from a config file at init-time;
    147  * the snd_dev * enumeration routines below are used to present a list
    148  * of choices to the user in the options screen.
    149  *
    150  * if OpenAL hasn't yet been initialized (i.e. no sounds have been opened),
    151  *   this just stores the device name for use when init does occur.
    152  *   note: we can't check now if it's invalid (if so, init will fail).
    153  * otherwise, we shut OpenAL down (thereby stopping all sounds) and
    154  * re-initialize with the new device. that's fairly time-consuming,
    155  * so preferably call this routine before sounds are loaded.
    156  */
    157 Status snd_dev_set(const char* alc_new_dev_name)
    158 {
    159     // requesting a specific device
    160     if(alc_new_dev_name)
    161     {
    162         // already using that device - done. (don't re-init)
    163         if(alc_dev_name && !strcmp(alc_dev_name, alc_new_dev_name))
    164             return INFO::OK;
    165 
    166         // store name (need to copy it, since alc_init is called later,
    167         // and it must then still be valid)
    168         static char buf[32];
    169         strcpy_s(buf, sizeof(buf), alc_new_dev_name);
    170         alc_dev_name = buf;
    171     }
    172     // requesting default device
    173     else
    174     {
    175         // already using default device - done. (don't re-init)
    176         if(alc_dev_name == 0)
    177             return INFO::OK;
    178 
    179         alc_dev_name = 0;
    180     }
    181 
    182     // no-op if not initialized yet, otherwise re-init.
    183     return al_reinit();
    184 }
    185 
    186 
    187 static ALCcontext* alc_ctx = 0;
    188 static ALCdevice* alc_dev = 0;
    189 
    190 
    191 /**
    192  * free the OpenAL context and device.
    193  */
    194 static void alc_shutdown()
    195 {
    196     if(alc_ctx)
    197     {
    198         alcMakeContextCurrent(0);
    199         alcDestroyContext(alc_ctx);
    200     }
    201 
    202     if(alc_dev)
    203         alcCloseDevice(alc_dev);
    204 }
    205 
    206 
    207 /**
    208  * Ready OpenAL for use by setting up a device and context.
    209  *
    210  * @return Status
    211  */
    212 static Status alc_init()
    213 {
    214     Status ret = INFO::OK;
    215 
    216     alc_dev = alcOpenDevice((alcString)alc_dev_name);
    217     if(alc_dev)
    218     {
    219         alc_ctx = alcCreateContext(alc_dev, 0); // no attrlist needed
    220         if(alc_ctx)
    221             alcMakeContextCurrent(alc_ctx);
    222     }
    223 
    224     // check if init succeeded.
    225     // some OpenAL implementations don't indicate failure here correctly;
    226     // we need to check if the device and context pointers are actually valid.
    227     ALCenum err = alcGetError(alc_dev);
    228     if(err != ALC_NO_ERROR || !alc_dev || !alc_ctx)
    229     {
    230         debug_printf(L"alc_init failed. alc_dev=%p alc_ctx=%p alc_dev_name=%hs err=%d\n", alc_dev, alc_ctx, alc_dev_name, err);
    231 // FIXME Hack to get around exclusive access to the sound device
    232 #if OS_UNIX
    233         ret = INFO::OK;
    234 #else
    235         ret = ERR::FAIL;
    236 #endif
    237     }
    238 
    239     // make note of which sound device is actually being used
    240     // (e.g. DS3D, native, MMSYSTEM) - needed when reporting OpenAL bugs.
    241     const char* dev_name = (const char*)alcGetString(alc_dev, ALC_DEVICE_SPECIFIER);
    242     wchar_t buf[200];
    243     swprintf_s(buf, ARRAY_SIZE(buf), L"SND| alc_init: success, using %hs\n", dev_name);
    244     ah_log(buf);
    245 
    246     return ret;
    247 }
    248 
    249 
    250 //-----------------------------------------------------------------------------
    251 // listener: owns position/orientation and master gain.
    252 // if they're set before al_initialized, we pass the saved values to
    253 // OpenAL immediately after init (instead of waiting until next update).
    254 //-----------------------------------------------------------------------------
    255 
    256 static float al_listener_gain = 1.0f;
    257 static float al_listener_position[3] = { 0.0f, 0.0f, 0.0f };
    258 static float al_listener_velocity[3] = { 0.0f, 0.0f, 0.0f };
    259 
    260 // float view_direction[3], up_vector[3]; passed directly to OpenAL
    261 static float al_listener_orientation[6] = { 0.0f, 0.0f, -1.0f, 0.0f, 1.0f, 0.0f };
    262 
    263 
    264 /**
    265  * send the current listener properties to OpenAL.
    266  *
    267  * also called from al_init.
    268  */
    269 static void al_listener_latch()
    270 {
    271     if(al_initialized)
    272     {
    273         AL_CHECK;
    274 
    275         alListenerf(AL_GAIN, al_listener_gain);
    276         alListenerfv(AL_POSITION, al_listener_position);
    277         alListenerfv(AL_VELOCITY, al_listener_velocity);
    278         alListenerfv(AL_ORIENTATION, al_listener_orientation);
    279 
    280         AL_CHECK;
    281     }
    282 }
    283 
    284 
    285 /**
    286  * set amplitude modifier, which is effectively applied to all sounds.
    287  * in layman's terms, this is the global "volume".
    288  *
    289  * @param gain Modifier: must be non-negative;
    290  * 1 -> unattenuated, 0.5 -> -6 dB, 0 -> silence.
    291  * @return Status
    292  */
    293 Status snd_set_master_gain(float gain)
    294 {
    295     if(gain < 0)
    296         WARN_RETURN(ERR::INVALID_PARAM);
    297 
    298     al_listener_gain = gain;
    299 
    300     // position will get sent too.
    301     // this isn't called often, so we don't care.
    302     al_listener_latch();
    303 
    304     return INFO::OK;
    305 }
    306 
    307 
    308 /**
    309  * set position of the listener (corresponds to camera in graphics).
    310  * coordinates are in world space; the system doesn't matter.
    311  *
    312  * @param pos position support vector
    313  * @param dir view direction
    314  * @param up up vector
    315  */
    316 static void al_listener_set_pos(const float pos[3], const float dir[3], const float up[3])
    317 {
    318     int i;
    319     for(i = 0; i < 3; i++)
    320         al_listener_position[i] = pos[i];
    321     for(i = 0; i < 3; i++)
    322         al_listener_orientation[i] = dir[i];
    323     for(i = 0; i < 3; i++)
    324         al_listener_orientation[3+i] = up[i];
    325 
    326     al_listener_latch();
    327 }
    328 
    329 
    330 /**
    331  * get distance between listener and point.
    332  * this is used to determine sound priority.
    333  *
    334  * @param point position support vector
    335  * @return float euclidean distance squared
    336  */
    337 static float al_listener_dist_2(const float point[3])
    338 {
    339     const float dx = al_listener_position[0] - point[0];
    340     const float dy = al_listener_position[1] - point[1];
    341     const float dz = al_listener_position[2] - point[2];
    342     return dx*dx + dy*dy + dz*dz;
    343 }
    344 
    345 
    346 //-----------------------------------------------------------------------------
    347 // AL buffer suballocator: allocates buffers as needed (alGenBuffers is fast).
    348 // this interface is a bit more convenient than the OpenAL routines, and we
    349 // verify that all buffers have been freed at exit.
    350 //-----------------------------------------------------------------------------
    351 
    352 static int al_bufs_outstanding;
    353 
    354 
    355 /**
    356  * allocate a new buffer, and fill it with the specified data.
    357  *
    358  * @param data raw sound data buffer
    359  * @param size size of buffer in bytes
    360  * @param fmt AL_FORMAT_ * describing the sound data
    361  * @param freq sampling frequency (typically 22050 Hz)
    362  * @return ALuint buffer name
    363  */
    364 static ALuint al_buf_alloc(ALvoid* data, ALsizei size, ALenum fmt, ALsizei freq)
    365 {
    366     AL_CHECK;
    367     ALuint al_buf;
    368     alGenBuffers(1, &al_buf);
    369     alBufferData(al_buf, fmt, data, size, freq);
    370     AL_CHECK;
    371 
    372     ENSURE(alIsBuffer(al_buf));
    373     al_bufs_outstanding++;
    374 
    375     return al_buf;
    376 }
    377 
    378 
    379 /**
    380  * free the buffer and its contained sound data.
    381  *
    382  * @param al_buf buffer name
    383  */
    384 static void al_buf_free(ALuint al_buf)
    385 {
    386     // no-op if 0 (needed in case SndData_reload fails -
    387     // sd->al_buf will not have been set)
    388     if(!al_buf)
    389         return;
    390 
    391     ENSURE(alIsBuffer(al_buf));
    392 
    393     AL_CHECK;
    394     alDeleteBuffers(1, &al_buf);
    395     AL_CHECK;
    396 
    397     al_bufs_outstanding--;
    398 }
    399 
    400 
    401 /**
    402  * make sure all buffers have been returned to us via al_buf_free.
    403  * called from al_shutdown.
    404  */
    405 static void al_buf_shutdown()
    406 {
    407     ENSURE(al_bufs_outstanding == 0);
    408 }
    409 
    410 
    411 //-----------------------------------------------------------------------------
    412 // AL source suballocator: allocate all available sources up-front and
    413 // pass them out as needed (alGenSources is quite slow, taking 3..5 ms per
    414 // source returned). also responsible for enforcing user-specified limit
    415 // on total number of sources (to reduce mixing cost on low-end systems).
    416 //-----------------------------------------------------------------------------
    417 
    418 // regardless of HW capabilities, we won't use more than this ("enough").
    419 // necessary in case OpenAL doesn't limit #sources (e.g. if SW mixing).
    420 static const size_t AL_SRC_MAX = 64;
    421 
    422 // (allow changing at runtime)
    423 static size_t al_src_maxNumToUse = AL_SRC_MAX;
    424 
    425 static size_t al_src_numPreallocated;
    426 
    427 enum AllocationState
    428 {
    429     kAvailable = 0, // (must match zero-initialization of al_srcs_allocationStates)
    430     kInUse
    431 };
    432 
    433 // note: we want to catch double-free bugs and ensure all sources
    434 // are released at exit, but OpenAL doesn't specify an always-invalid
    435 // source name, so we need a separate array of AllocationState.
    436 static ALuint al_srcs[AL_SRC_MAX];
    437 static intptr_t al_srcs_allocationStates[AL_SRC_MAX];
    438 
    439 /**
    440  * grab as many sources as possible up to the limit.
    441  * called from al_init.
    442  */
    443 static void al_src_init()
    444 {
    445     // grab as many sources as possible and count how many we get.
    446     for(size_t i = 0; i < al_src_maxNumToUse; i++)
    447     {
    448         ALuint al_src;
    449         alGenSources(1, &al_src);
    450         // we've reached the limit, no more are available.
    451         if(alGetError() != AL_NO_ERROR)
    452             break;
    453         ENSURE(alIsSource(al_src));
    454         al_srcs[i] = al_src;
    455         al_src_numPreallocated++;
    456     }
    457 
    458     // limit user's cap to what we actually got.
    459     // (in case snd_set_max_src was called before this)
    460     if(al_src_maxNumToUse > al_src_numPreallocated)
    461         al_src_maxNumToUse = al_src_numPreallocated;
    462 
    463     // make sure we got the minimum guaranteed by OpenAL.
    464     ENSURE(al_src_numPreallocated >= 16);
    465 }
    466 
    467 
    468 /**
    469  * release all sources on free list.
    470  * all sources must already have been released via al_src_free.
    471  * called from al_shutdown.
    472  */
    473 static void al_src_shutdown()
    474 {
    475     for(size_t i = 0; i < al_src_numPreallocated; i++)
    476         ENSURE(al_srcs_allocationStates[i] == kAvailable);
    477 
    478     AL_CHECK;
    479     alDeleteSources((ALsizei)al_src_numPreallocated, al_srcs);
    480     AL_CHECK;
    481 
    482     al_src_numPreallocated = 0;
    483 }
    484 
    485 
    486 /**
    487  * try to allocate a source.
    488  *
    489  * @return whether a source was allocated (see al_srcs_allocationStates).
    490  * @param al_src receives the new source name iff true is returned.
    491  */
    492 static bool al_src_alloc(ALuint& al_src)
    493 {
    494     for(size_t i = 0; i < al_src_numPreallocated; i++)
    495     {
    496         if(cpu_CAS(&al_srcs_allocationStates[i], kAvailable, kInUse))
    497         {
    498             al_src = al_srcs[i];
    499             return true;
    500         }
    501     }
    502 
    503     return false;   // no more to give
    504 }
    505 
    506 
    507 /**
    508  * mark a source as free and available for reuse.
    509  *
    510  * @param al_src source name
    511  */
    512 static void al_src_free(ALuint al_src)
    513 {
    514     ENSURE(alIsSource(al_src));
    515 
    516     const ALuint* pos = std::find(al_srcs, al_srcs+al_src_numPreallocated, al_src);
    517     if(pos != al_srcs+al_src_numPreallocated)   // found it
    518     {
    519         const size_t i = pos - al_srcs;
    520         ENSURE(cpu_CAS(&al_srcs_allocationStates[i], kInUse, kAvailable));
    521     }
    522     else
    523         DEBUG_WARN_ERR(ERR::LOGIC); // al_src wasn't in al_srcs
    524 }
    525 
    526 
    527 /**
    528  * set maximum number of voices to play simultaneously,
    529  * to reduce mixing cost on low-end systems.
    530  * this limit may be ignored if e.g. there's a stricter
    531  * implementation- defined ceiling anyway.
    532  *
    533  * @param limit max. number of sources
    534  * @return Status
    535  */
    536 Status snd_set_max_voices(size_t limit)
    537 {
    538     // valid if cap is legit (less than what we allocated in al_src_init),
    539     // or if al_src_init hasn't been called yet. note: we accept anything
    540     // in the second case, as al_src_init will sanity-check al_src_cap.
    541     if(!al_src_numPreallocated || limit < al_src_numPreallocated)
    542     {
    543         al_src_maxNumToUse = limit;
    544         return INFO::OK;
    545     }
    546     // user is requesting a cap higher than what we actually allocated.
    547     // that's fine (not an error), but we won't set the cap, since it
    548     // determines how many sources may be returned.
    549     // there's no return value to indicate this because the cap is
    550     // precisely that - an upper limit only, we don't care if it can't be met.
    551     else
    552         return INFO::OK;
    553 }
    554 
    555 
    556 //-----------------------------------------------------------------------------
    557 // OpenAL startup mechanism: allows deferring init until sounds are actually
    558 // played, therefore speeding up perceived game start time.
    559 // also resets OpenAL when settings (e.g. device) are changed at runtime.
    560 //-----------------------------------------------------------------------------
    561 
    562 /**
    563  * master OpenAL init; makes sure all subsystems are ready for use.
    564  * called from each snd_open; no harm if called more than once.
    565  *
    566  * @return Status
    567  */
    568 static Status al_init()
    569 {
    570     // only take action on first call, OR when re-initializing.
    571     if(al_initialized)
    572         return INFO::OK;
    573 
    574     RETURN_STATUS_IF_ERR(alc_init());
    575 
    576     al_initialized = true;
    577 
    578     // these can't fail:
    579     al_src_init();
    580     al_listener_latch();
    581 
    582     alDistanceModel(AL_LINEAR_DISTANCE_CLAMPED);
    583 
    584     return INFO::OK;
    585 }
    586 
    587 
    588 /**
    589  * shut down all module subsystems.
    590  */
    591 static void al_shutdown()
    592 {
    593     // was never initialized - nothing to do.
    594     if(!al_initialized)
    595         return;
    596 
    597     // somewhat tricky: go through gyrations to free OpenAL resources.
    598 
    599     // .. free all active sounds so that they release their source.
    600     //    the SndData reference is also removed,
    601     //    but these remain open, since they are cached.
    602     list_free_all();
    603 
    604     // .. actually free all (still cached) SndData instances.
    605     hsd_list_free_all();
    606 
    607     // .. all sources and buffers have been returned to their suballocators.
    608     //    now free them all.
    609     al_src_shutdown();
    610     al_buf_shutdown();
    611 
    612     alc_shutdown();
    613 
    614     al_initialized = false;
    615 }
    616 
    617 
    618 /**
    619  * re-initialize OpenAL. currently only required for changing devices.
    620  *
    621  * @return Status
    622  */
    623 static Status al_reinit()
    624 {
    625     // not yet initialized. settings have been saved, and will be
    626     // applied by the component init routines called from al_init.
    627     if(!al_initialized)
    628         return INFO::OK;
    629 
    630     // re-init (stops all currently playing sounds)
    631     al_shutdown();
    632     return al_init();
    633 }
    634 
    635 
    636 // prevent OpenAL from being initialized when snd_init is called.
    637 static bool snd_disabled = false;
    638 
    639 /**
    640  * extra layer on top of al_init that allows 'disabling' sound.
    641  * called from each snd_open.
    642  *
    643  * @return Status from al_init, or ERR::AGAIN if sound disabled
    644  */
    645 static Status snd_init()
    646 {
    647     // (note: each VSrc_reload and therefore snd_open will fail)
    648     if(snd_disabled)
    649         return ERR::AGAIN;  // NOWARN
    650 
    651     return al_init();
    652 }
    653 
    654 
    655 Status snd_disable(bool disabled)
    656 {
    657     snd_disabled = disabled;
    658 
    659     if(snd_disabled)
    660     {
    661         ENSURE(!al_initialized);    // already initialized => disable is pointless
    662         return INFO::OK;
    663     }
    664     else
    665         return snd_init();  // note: won't return ERR::AGAIN, since snd_disabled == false
    666 }
    667 
    668 
    669 /**
    670  * free all resources and shut down the sound system.
    671  * call before h_mgr_shutdown.
    672  */
    673 void snd_shutdown()
    674 {
    675     al_shutdown();  // calls list_free_all
    676 }
    677 
    678 
    679 //-----------------------------------------------------------------------------
    680 // device enumeration: list all devices and allow the user to choose one,
    681 // in case the default device has problems.
    682 //-----------------------------------------------------------------------------
    683 
    684 // set by snd_dev_prepare_enum; used by snd_dev_next.
    685 // consists of back-to-back C strings, terminated by an extra '\0'.
    686 // (this is taken straight from OpenAL; the spec says the format may change).
    687 static const char* devs;
    688 
    689 /**
    690  * Prepare to enumerate all device names (this resets the list returned by
    691  * snd_dev_next).
    692  *
    693  * may be called each time the device list is needed.
    694  *
    695  * @return Status; always successful unless the requisite device
    696  * enumeration extension isn't available. in the latter case,
    697  * a "cannot enum device" message should be presented to the user,
    698  * and snd_dev_set need not be called; OpenAL will use its default device.
    699  */
    700 Status snd_dev_prepare_enum()
    701 {
    702     if(alcIsExtensionPresent(0, (alcString)"ALC_ENUMERATION_EXT") != AL_TRUE)
    703         WARN_RETURN(ERR::NOT_SUPPORTED);
    704 
    705     devs = (const char*)alcGetString(0, ALC_DEVICE_SPECIFIER);
    706     return INFO::OK;
    707 }
    708 
    709 
    710 /**
    711  * Get next device name.
    712  *
    713  * do not call unless snd_dev_prepare_enum succeeded!
    714  * not thread-safe! (static data from snd_dev_prepare_enum is used)
    715  *
    716  * @return const char* device name, or 0 if all have been returned
    717  */
    718 const char* snd_dev_next()
    719 {
    720     if(!*devs)
    721         return 0;
    722     const char* dev = devs;
    723     devs += strlen(dev)+1;
    724     return dev;
    725 }
    726 
    727 
    728 //-----------------------------------------------------------------------------
    729 // sound data provider: holds audio data (clip or stream) and returns
    730 // OpenAL buffers on request.
    731 //-----------------------------------------------------------------------------
    732 
    733 // rationale for separate VSrc (instance) and SndData resources:
    734 // - we need to be able to fade out and cancel loops.
    735 //   => VSrc isn't fire and forget; we need to access sounds at runtime.
    736 // - allowing access via direct pointer is unsafe
    737 //   => Handle-based access is required
    738 // - we don't want to reload sound data on every play()
    739 //   => need either a separate caching mechanism or one central data resource.
    740 // - we want to support reloading (for consistency if not necessity)
    741 //   => can't hack via h_find / setting fn_key to 0; need a separate instance.
    742 
    743 /**
    744  * rationale for supporting both clips and streams:
    745  * streams avoid delays while reading+decompressing large files, but
    746  * playing multiple instances of them would require separate positions etc.
    747  * since the same clip is often played concurrently and we can't guarantee
    748  * they will never exceed the size of a stream, it makes sense to support a
    749  * separate "clip" data type that allocates enough storage and avoids needing
    750  * the stream position/list of buffers.
    751  */
    752 enum SndDataType
    753 {
    754     SD_CLIP,
    755     SD_STREAM
    756 };
    757 
    758 /**
    759  * Holder for sound data - either a clip, or stream.
    760  */
    761 struct SndData
    762 {
    763     ALenum al_fmt;
    764     ALsizei al_freq;
    765 
    766     SndDataType type;
    767 
    768     ALuint al_buf;  // valid if type == SD_CLIP
    769     OggStreamPtr ogg;   // valid if type == SD_STREAM
    770 };
    771 
    772 H_TYPE_DEFINE(SndData);
    773 
    774 
    775 //-----------------------------------------------------------------------------
    776 // SndData instance list: ensures all allocated since last al_shutdown
    777 // are freed when desired (they are cached => extra work needed).
    778 
    779 // rationale: all SndData objects (actually, their OpenAL buffers) must be
    780 // freed during al_shutdown, to prevent leaks. we can't rely on list*
    781 // to free all VSrc, and thereby their associated SndData objects -
    782 // completed sounds are no longer in the list.
    783 //
    784 // nor can we use the h_mgr_shutdown automatic leaked resource cleanup:
    785 // we need to be able to al_shutdown at runtime
    786 // (when resetting OpenAL, after e.g. device change).
    787 //
    788 // h_mgr support is required to forcibly close SndData objects,
    789 // since they are cached (kept open). it requires that this come after
    790 // H_TYPE_DEFINE(SndData), since h_force_free needs the handle type.
    791 //
    792 // we never need to delete single entries: hsd_list_free_all
    793 // (called by al_shutdown) frees each entry and clears the entire list.
    794 
    795 typedef std::vector<Handle> Handles;
    796 static Handles hsd_list;
    797 
    798 /**
    799  * Add hsd to the list.
    800  * called from SndData_reload; will later be removed via hsd_list_free_all.
    801  * @param hsd Handle to SndData
    802  */
    803 static void hsd_list_add(Handle hsd)
    804 {
    805     hsd_list.push_back(hsd);
    806 }
    807 
    808 
    809 /**
    810  * Free all sounds on list.
    811  * called by al_shutdown (at exit, or when reinitializing OpenAL).
    812  * (definition must come after H_TYPE_DEFINE(SndData))
    813  */
    814 static void hsd_list_free_all()
    815 {
    816     for(Handles::iterator it = hsd_list.begin(); it != hsd_list.end(); ++it)
    817     {
    818         Handle& hsd = *it;
    819 
    820         (void)h_force_free(hsd, H_SndData);
    821         // ignore errors - if hsd was a stream, and its associated source
    822         // was active when al_shutdown was called, it will already have been
    823         // freed (list_free_all would free the source; it then releases
    824         // its SndData reference, which closes the instance because it's
    825         // RES_UNIQUE).
    826         //
    827         // NB: re-initializing the sound library (e.g. after changing
    828         // HW settings) requires all handles to be freed, even if cached.
    829         // hence, we use h_force_free. unfortunately this causes the
    830         // handle's tag to be ignored. it is conceivable that the wrong
    831         // handle could be freed here.
    832         //
    833         // we rule this out with the following argument. either we're
    834         // called when re-initializing sound or at exit. in the former
    835         // case, h_force_free does check the handle type: only sounds are
    836         // ever freed. we don't care if the wrong one is closed since all
    837         // must be stomped upon. in the latter case, it definitely doesn't
    838         // matter what we free. hence, no problem.
    839     }
    840 
    841     // leave its memory intact, so we don't have to reallocate it later
    842     // if we are now reinitializing OpenAL (not exiting).
    843     hsd_list.resize(0);
    844 }
    845 
    846 
    847 static void SndData_init(SndData* UNUSED(sd), va_list UNUSED(args))
    848 {
    849 }
    850 
    851 static void SndData_dtor(SndData* sd)
    852 {
    853     if(sd->type == SD_CLIP)
    854         al_buf_free(sd->al_buf);
    855     else
    856         sd->ogg.reset();
    857 }
    858 
    859 static Status SndData_reload(SndData* sd, const PIVFS& vfs, const VfsPath& pathname, Handle hsd)
    860 {
    861 #if 0 // HACK: streaming disabled because it breaks archives
    862 
    863     // (OGG streaming requires a real POSIX pathname - see OpenOggStream)
    864     fs::wpath real_pathname;
    865     RETURN_STATUS_IF_ERR(vfs->GetRealPath(pathname, real_pathname));
    866 
    867     // currently only supports OGG; WAV is no longer supported. writing our own loader is infeasible
    868     // due to a seriously watered down spec with many incompatible variants.
    869     // pulling in an external library (e.g. freealut) is deemed not worth the
    870     // effort - OGG should be better in all cases.
    871 
    872     RETURN_STATUS_IF_ERR(OpenOggStream(real_pathname, sd->ogg));
    873     const size_t size = fs::file_size(real_pathname);
    874 #else
    875     RETURN_STATUS_IF_ERR(OpenOggNonstream(vfs, pathname, sd->ogg));
    876     FileInfo fileInfo;
    877     RETURN_STATUS_IF_ERR(vfs->GetFileInfo(pathname, &fileInfo));
    878     const size_t size = fileInfo.Size();
    879 #endif
    880 
    881     sd->al_freq = sd->ogg->SamplingRate();
    882     sd->al_fmt  = sd->ogg->Format();
    883 
    884     // HACK - it would be nicer for callers to confirm they won't
    885     // open the same (streamed) file multiple times,
    886     // but that's not possible with the current JSI_Sound.
    887     sd->type = (size > 500*KiB)? SD_STREAM : SD_CLIP;
    888 
    889     if(sd->type == SD_CLIP)
    890     {
    891         std::vector<u8> data(50*MiB);   // max. size of any clip (anything larger should be streamed)
    892         const Status ret = sd->ogg->GetNextChunk(&data[0], data.size());
    893         RETURN_STATUS_IF_ERR(ret);
    894         const size_t size = (size_t)ret;
    895         ENSURE(size != 0);  // must have read something
    896         ENSURE(size != data.size());    // shouldn't be limited by buffer size
    897         sd->al_buf = al_buf_alloc(&data[0], (ALsizei)size, sd->al_fmt, sd->al_freq);
    898         sd->ogg.reset();
    899     }
    900     else
    901         sd->al_buf = 0;
    902 
    903     // note: to avoid polluting hsd_list with invalid handles, we ensure
    904     // all of the above succeeded before adding to the list.
    905     // (c.f. topic#10719, "Problem freeing sounds loaded by JavaScript")
    906     hsd_list_add(hsd);
    907 
    908     return INFO::OK;
    909 }
    910 
    911 static Status SndData_validate(const SndData* sd)
    912 {
    913     if(sd->al_fmt == 0)
    914         WARN_RETURN(ERR::_11);
    915     if((size_t)sd->al_freq > 100000)    // suspicious
    916         WARN_RETURN(ERR::_12);
    917     if(sd->type == SD_CLIP)
    918     {
    919         if(!sd->al_buf)
    920             WARN_RETURN(ERR::_13);
    921     }
    922     else if(sd->type == SD_STREAM)
    923     {
    924         if(!sd->ogg)
    925             WARN_RETURN(ERR::_14);
    926     }
    927     else
    928         WARN_RETURN(ERR::_15);  // invalid type
    929     return INFO::OK;
    930 }
    931 
    932 
    933 static Status SndData_to_string(const SndData* sd, wchar_t* buf)
    934 {
    935     const wchar_t* type = (sd->type == SD_CLIP)? L"clip" : L"stream";
    936     swprintf_s(buf, H_STRING_LEN, L"%ls; al_buf=%u", type, sd->al_buf);
    937     return INFO::OK;
    938 }
    939 
    940 
    941 /**
    942  * open and return a handle to a sound file's data.
    943  *
    944  * @return Handle or Status on failure
    945  */
    946 static Handle snd_data_load(const PIVFS& vfs, const VfsPath& pathname)
    947 {
    948     return h_alloc(H_SndData, vfs, pathname);
    949 }
    950 
    951 /**
    952  * Free the sound.
    953  *
    954  * @param hsd Handle to SndData; set to 0 afterwards.
    955  * @return Status
    956  */
    957 static Status snd_data_free(Handle& hsd)
    958 {
    959     return h_free(hsd, H_SndData);
    960 }
    961 
    962 
    963 //-----------------------------------------------------------------------------
    964 
    965 /**
    966  * Get the sound's AL buffer (typically to play it)
    967  *
    968  * @param hsd Handle to SndData.
    969  * @param al_buf buffer name.
    970  * @return Status, most commonly:
    971  * INFO::OK = buffer has been returned; more are expected to be available.
    972  * INFO::ALL_COMPLETE = buffer has been returned but is the last one (EOF).
    973  */
    974 static Status snd_data_buf_get(Handle hsd, ALuint& al_buf)
    975 {
    976     H_DEREF(hsd, SndData, sd);
    977     if(sd->type == SD_CLIP)
    978     {
    979         al_buf = sd->al_buf;
    980         return INFO::ALL_COMPLETE;  // "EOF"
    981     }
    982 
    983     if(!sd->ogg)
    984         WARN_RETURN(ERR::INVALID_HANDLE);
    985     u8 data[maxBufferSize];
    986     const Status ret = sd->ogg->GetNextChunk(data, maxBufferSize);
    987     RETURN_STATUS_IF_ERR(ret);
    988     const size_t size = (size_t)ret;
    989     al_buf = al_buf_alloc(data, (ALsizei)size, sd->al_fmt, sd->al_freq);
    990 
    991     return (size < maxBufferSize)? INFO::ALL_COMPLETE : INFO::OK;
    992 }
    993 
    994 
    995 /**
    996  * Indicate the sound's buffer is no longer needed.
    997  *
    998  * @param hsd Handle to SndData.
    999  * @param al_buf buffer name
    1000  * @return Status
    1001  */
    1002 static Status snd_data_buf_free(Handle hsd, ALuint al_buf)
    1003 {
    1004     H_DEREF(hsd, SndData, sd);
    1005 
    1006     if(sd->type == SD_CLIP)
    1007     {
    1008         // no-op (caller will later release hsd reference;
    1009         // when hsd actually unloads, sd->al_buf will be freed).
    1010     }
    1011     else
    1012         al_buf_free(al_buf);
    1013 
    1014     return INFO::OK;
    1015 }
    1016 
    1017 
    1018 //-----------------------------------------------------------------------------
    1019 // fading
    1020 //-----------------------------------------------------------------------------
    1021 
    1022 /**
    1023  * control block for a fade operation.
    1024  */
    1025 struct FadeInfo
    1026 {
    1027     double start_time;
    1028     FadeType type;
    1029     float length;
    1030     float initial_val;
    1031     float final_val;
    1032 };
    1033 
    1034 static float fade_factor_linear(float t)
    1035 {
    1036     return t;
    1037 }
    1038 
    1039 static float fade_factor_exponential(float t)
    1040 {
    1041     // t**3
    1042     return t*t*t;
    1043 }
    1044 
    1045 static float fade_factor_s_curve(float t)
    1046 {
    1047     // cosine curve
    1048     const double pi = 3.14159265358979323846;
    1049     float y = cos(t*pi + pi);
    1050     // map [-1,1] to [0,1]
    1051     return (y + 1.0f) / 2.0f;
    1052 }
    1053 
    1054 
    1055 /**
    1056  * fade() return value; indicates if the fade operation is complete.
    1057  */
    1058 enum FadeRet
    1059 {
    1060     FADE_NO_CHANGE,
    1061     FADE_CHANGED,
    1062     FADE_TO_0_FINISHED
    1063 };
    1064 
    1065 /**
    1066  * Carry out the requested fade operation.
    1067  *
    1068  * This is called for each active VSrc; if they have no fade operation
    1069  * active, nothing happens. Note: as an optimization, we could make a
    1070  * list of VSrc with fade active and only call this for those;
    1071  * not yet necessary, though.
    1072  *
    1073  * @param fi Describes the fade operation
    1074  * @param cur_time typically returned via timer_Time()
    1075  * @param out_val Output gain value, i.e. the current result of the fade.
    1076  * @return FadeRet
    1077  */
    1078 static FadeRet fade(FadeInfo& fi, double cur_time, float& out_val)
    1079 {
    1080     // no fade in progress - abort immediately. this check is necessary to
    1081     // avoid division-by-zero below.
    1082     if(fi.type == FT_NONE)
    1083         return FADE_NO_CHANGE;
    1084 
    1085     ENSURE(0.0f <= fi.initial_val && fi.initial_val <= 1.0f);
    1086     ENSURE(0.0f <= fi.final_val && fi.final_val <= 1.0f);
    1087 
    1088     // end reached - if fi.length is 0, but the fade is "in progress", do the
    1089     // processing here, and skip the dangerous division
    1090     if(fi.type == FT_ABORT || (cur_time >= fi.start_time + fi.length))
    1091     {
    1092         // make sure exact value is hit
    1093         out_val = fi.final_val;
    1094 
    1095         // special case: we were fading out; caller will free the sound.
    1096         if(fi.final_val == 0.0f)
    1097             return FADE_TO_0_FINISHED;
    1098 
    1099         // wipe out all values amd mark as no longer actively fading
    1100         memset(&fi, 0, sizeof(fi));
    1101         fi.type = FT_NONE;
    1102 
    1103         return FADE_CHANGED;
    1104     }
    1105 
    1106     // how far into the fade are we? [0,1]
    1107     const float t = (cur_time - fi.start_time) / fi.length;
    1108     ENSURE(0.0f <= t && t <= 1.0f);
    1109 
    1110     float factor;
    1111     switch(fi.type)
    1112     {
    1113     case FT_LINEAR:
    1114         factor = fade_factor_linear(t);
    1115         break;
    1116     case FT_EXPONENTIAL:
    1117         factor = fade_factor_exponential(t);
    1118         break;
    1119     case FT_S_CURVE:
    1120         factor = fade_factor_s_curve(t);
    1121         break;
    1122 
    1123     // initialize with anything at all, just so that the calculation
    1124     // below runs through; we reset out_val after that.
    1125     case FT_ABORT:
    1126         factor = 0.0f;
    1127         break;
    1128 
    1129     NODEFAULT;
    1130     }
    1131 
    1132     out_val = fi.initial_val + factor*(fi.final_val - fi.initial_val);
    1133 
    1134     return FADE_CHANGED;
    1135 }
    1136 
    1137 
    1138 /**
    1139  * Is the fade operation currently active?
    1140  *
    1141  * @return bool
    1142  */
    1143 static bool fade_is_active(FadeInfo& fi)
    1144 {
    1145     return (fi.type != FT_NONE);
    1146 }
    1147 
    1148 
    1149 //-----------------------------------------------------------------------------
    1150 // virtual sound source: a sound the user wants played.
    1151 // owns source properties, buffer queue, and references SndData.
    1152 //-----------------------------------------------------------------------------
    1153 
    1154 // rationale: combine Src and VSrc - best interface, due to needing hsd,
    1155 // buffer queue (# processed) in update
    1156 
    1157 enum VSrcFlags
    1158 {
    1159     // (we can't just test if al_src is zero because that might be a
    1160     // valid source name!)
    1161     VS_HAS_AL_SRC  = 1,
    1162 
    1163     // SndData has reported EOF. will close down after last buffer completes.
    1164     VS_EOF         = 2,
    1165 
    1166     // this VSrc was added via list_add and needs to be removed with
    1167     // list_remove in VSrc_dtor.
    1168     // not set if load fails somehow (avoids list_remove "not found" error).
    1169     VS_IN_LIST     = 4,
    1170 
    1171     VS_SHOULD_STOP = 8,
    1172 
    1173     VS_ALL_FLAGS = VS_HAS_AL_SRC|VS_EOF|VS_IN_LIST|VS_SHOULD_STOP
    1174 };
    1175 
    1176 /**
    1177  * control block for a virtual source, which represents a sound that the
    1178  * application wants played. it may or may not be played, depending on
    1179  * priority and whether an actual OpenAL source is available.
    1180  */
    1181 struct VSrc
    1182 {
    1183     bool HasSource() const
    1184     {
    1185         if((flags & VS_HAS_AL_SRC) == 0)
    1186             return false;
    1187         ENSURE(alIsSource(al_src));
    1188         return true;
    1189     }
    1190 
    1191     /// handle to this VSrc, so that it can close itself.
    1192     Handle hvs;
    1193 
    1194     /// associated sound data
    1195     Handle hsd;
    1196 
    1197     // AL source properties (set via snd_set*)
    1198     ALfloat pos[3];
    1199     ALfloat gain;   /// [0,inf)
    1200     ALfloat pitch;  /// (0,1]
    1201     ALboolean loop;
    1202     ALboolean relative;
    1203 
    1204     /// controls vsrc_update behavior (VSrcFlags)
    1205     size_t flags;
    1206 
    1207     // valid iff HasSource()
    1208     ALuint al_src;
    1209 
    1210     // priority for voice management
    1211     float static_pri;   /// as given by snd_play
    1212     float cur_pri;      /// holds newly calculated value
    1213 
    1214     FadeInfo fade;
    1215 };
    1216 
    1217 H_TYPE_DEFINE(VSrc);
    1218 
    1219 static void VSrc_init(VSrc* vs, va_list UNUSED(args))
    1220 {
    1221     vs->flags = 0;
    1222     vs->fade.type = FT_NONE;
    1223 }
    1224 
    1225 static void list_remove(VSrc* vs);
    1226 static Status vsrc_reclaim(VSrc* vs);
    1227 
    1228 static void VSrc_dtor(VSrc* vs)
    1229 {
    1230     // only remove if added (not the case if load failed)
    1231     if(vs->flags & VS_IN_LIST)
    1232     {
    1233         list_remove(vs);
    1234         vs->flags &= ~VS_IN_LIST;
    1235     }
    1236 
    1237     // these are safe, even if reload (partially) failed:
    1238     vsrc_reclaim(vs);
    1239     (void)snd_data_free(vs->hsd);
    1240 }
    1241 
    1242 static Status VSrc_reload(VSrc* vs, const PIVFS& vfs, const VfsPath& pathname, Handle hvs)
    1243 {
    1244     // cannot wait till play(), need to init here:
    1245     // must load OpenAL so that snd_data_load can check for OGG extension.
    1246     Status err = snd_init();
    1247     // .. don't complain if sound is disabled; fail silently.
    1248     if(err == ERR::AGAIN)
    1249         return err;
    1250     // .. catch genuine errors during init.
    1251     RETURN_STATUS_IF_ERR(err);
    1252 
    1253     VfsPath dataPathname;
    1254 
    1255     // pathname is a definition file containing the data file name and
    1256     // its gain.
    1257     if(pathname.Extension() == L".txt")
    1258     {
    1259         shared_ptr<u8> buf; size_t size;
    1260         RETURN_STATUS_IF_ERR(vfs->LoadFile(pathname, buf, size));
    1261         std::wistringstream def(std::wstring((wchar_t*)buf.get(), (int)size));
    1262 
    1263         def >> dataPathname;
    1264         def >> vs->gain;
    1265         vs->gain /= 100.0f; // is stored as percent
    1266     }
    1267     // read the sound file directly and assume default gain (1.0).
    1268     else
    1269     {
    1270         dataPathname = pathname;
    1271         vs->gain = 1.0f;
    1272     }
    1273 
    1274     // note: vs->gain can legitimately be > 1.0 - don't clamp.
    1275 
    1276     vs->pitch = 1.0f;
    1277 
    1278     vs->hvs = hvs;  // allows calling snd_free when done playing.
    1279 
    1280     vsrc_reclaim(vs);
    1281 
    1282     vs->hsd = snd_data_load(vfs, dataPathname);
    1283     RETURN_STATUS_IF_ERR(vs->hsd);
    1284 
    1285     return INFO::OK;
    1286 }
    1287 
    1288 static bool IsValidBoolean(ALboolean b)
    1289 {
    1290     return (b == AL_FALSE || b == AL_TRUE);
    1291 }
    1292 
    1293 static Status VSrc_validate(const VSrc* vs)
    1294 {
    1295     // al_src can legitimately be 0 (if vs is low-pri)
    1296     if(vs->flags & ~VS_ALL_FLAGS)
    1297         WARN_RETURN(ERR::_1);
    1298     // no limitations on <pos>
    1299     if(!(0.0f <= vs->gain && vs->gain <= 1.0f))
    1300         WARN_RETURN(ERR::_2);
    1301     if(!(0.0f < vs->pitch && vs->pitch <= 2.0f))
    1302         WARN_RETURN(ERR::_3);
    1303     if(!IsValidBoolean(vs->loop) || !IsValidBoolean(vs->relative))
    1304         WARN_RETURN(ERR::_4);
    1305     // <static_pri> and <cur_pri> have no invariant we could check.
    1306     return INFO::OK;
    1307 }
    1308 
    1309 static Status VSrc_to_string(const VSrc* vs, wchar_t* buf)
    1310 {
    1311     swprintf_s(buf, H_STRING_LEN, L"al_src = %u", vs->al_src);
    1312     return INFO::OK;
    1313 }
    1314 
    1315 
    1316 /**
    1317  * open and return a handle to a sound instance.
    1318  *
    1319  * @param pathname if a text file (extension ".txt"),
    1320  * it is assumed to be a definition file containing the
    1321  * sound file name and its gain (0.0 .. 1.0).
    1322  * otherwise, it is taken to be the sound file name and
    1323  * gain is set to the default of 1.0 (no attenuation).
    1324  * @return Handle or Status on failure
    1325  */
    1326 Handle snd_open(const PIVFS& vfs, const VfsPath& pathname)
    1327 {
    1328     // note: RES_UNIQUE forces each instance to get a new resource
    1329     // (which is of course what we want).
    1330     return h_alloc(H_VSrc, vfs, pathname, RES_UNIQUE);
    1331 }
    1332 
    1333 
    1334 /**
    1335  * Free the sound; if it was playing, it will be stopped.
    1336  * Note: sounds are closed automatically when done playing;
    1337  * this is provided for completeness only.
    1338  *
    1339  * @param hvs Handle to VSrc. will be set to 0 afterwards.
    1340  * @return Status
    1341  */
    1342 Status snd_free(Handle& hvs)
    1343 {
    1344     if(!hvs)
    1345         return INFO::OK;
    1346     return h_free(hvs, H_VSrc);
    1347 }
    1348 
    1349 
    1350 //-----------------------------------------------------------------------------
    1351 // list of active sounds. used by voice management component,
    1352 // and to have each VSrc update itself (queue new buffers).
    1353 
    1354 // VSrc fields are used -> must come after struct VSrc
    1355 
    1356 // sorted in descending order of current priority
    1357 // (we sometimes remove low pri items, which requires moving down
    1358 // everything that comes after them, so we want those to come last).
    1359 //
    1360 // don't use list, to avoid lots of allocs (expect thousands of VSrcs).
    1361 typedef std::deque<VSrc*> VSrcs;
    1362 typedef VSrcs::iterator VSrcIt;
    1363 static VSrcs vsrcs;
    1364 
    1365 // don't need to sort now - caller will list_SortByDescendingPriority() during update.
    1366 static void list_add(VSrc* vs)
    1367 {
    1368     vsrcs.push_back(vs);
    1369 }
    1370 
    1371 
    1372 /**
    1373  * call back for each VSrc entry in the list.
    1374  *
    1375  * @param end_idx if not the default value of 0, stop before that entry.
    1376  */
    1377 template<class Func>
    1378 static void list_foreach(Func callback, size_t numToSkip = 0, size_t end_idx = 0)
    1379 {
    1380     const VSrcIt begin = vsrcs.begin() + numToSkip;
    1381     const VSrcIt end = end_idx? begin+end_idx : vsrcs.end();
    1382 
    1383     // can't use std::for_each: some entries may have been deleted
    1384     // (i.e. set to 0) since last update.
    1385     for(VSrcIt it = begin; it != end; ++it)
    1386     {
    1387         VSrc* vs = *it;
    1388         if(vs)
    1389             callback(vs);
    1390     }
    1391 }
    1392 
    1393 struct GreaterPriority
    1394 {
    1395     bool operator()(VSrc* vs1, VSrc* vs2) const
    1396     {
    1397         return vs1->cur_pri > vs2->cur_pri;
    1398     }
    1399 };
    1400 
    1401 /// sort list by decreasing 'priority' (most important first)
    1402 static void list_SortByDescendingPriority()
    1403 {
    1404     std::sort(vsrcs.begin(), vsrcs.end(), GreaterPriority());
    1405 }
    1406 
    1407 
    1408 /**
    1409  * scan list and remove the given VSrc (by setting it to 0; list will be
    1410  * pruned later (see rationale below).
    1411  * O(N)!
    1412  *
    1413  * @param vs VSrc to remove.
    1414  */
    1415 static void list_remove(VSrc* vs)
    1416 {
    1417     for(size_t i = 0; i < vsrcs.size(); i++)
    1418     {
    1419         if(vsrcs[i] == vs)
    1420         {
    1421             // found it; several ways we could remove:
    1422             // - shift everything else down (slow) -> no
    1423             // - fill the hole with e.g. the last element
    1424             //   (vsrcs would no longer be sorted by priority) -> no
    1425             // - replace with 0 (will require prune_removed and
    1426             //   more work in foreach) -> best alternative
    1427             vsrcs[i] = 0;
    1428             return;
    1429         }
    1430     }
    1431 
    1432     DEBUG_WARN_ERR(ERR::LOGIC); // VSrc not found
    1433 }
    1434 
    1435 
    1436 struct IsNull
    1437 {
    1438     bool operator()(VSrc* vs) const
    1439     {
    1440         return (vs == 0);
    1441     }
    1442 };
    1443 
    1444 /**
    1445  * remove entries that were set to 0 by list_remove, so that
    1446  * code below can grant the first al_src_cap entries a source.
    1447  */
    1448 static void list_prune_removed()
    1449 {
    1450     VSrcIt new_end = remove_if(vsrcs.begin(), vsrcs.end(), IsNull());
    1451     vsrcs.erase(new_end, vsrcs.end());
    1452 }
    1453 
    1454 
    1455 static void vsrc_free(VSrc* vs)
    1456 {
    1457     snd_free(vs->hvs);
    1458 }
    1459 
    1460 static Status list_free_all()
    1461 {
    1462     list_foreach(vsrc_free);
    1463     return INFO::OK;
    1464 }
    1465 
    1466 
    1467 //-----------------------------------------------------------------------------
    1468 
    1469 /**
    1470  * Send the VSrc properties to OpenAL (when we actually have a source).
    1471  * called by snd_set * and vsrc_grant.
    1472  *
    1473  */
    1474 static void vsrc_latch(VSrc* vs)
    1475 {
    1476     if(!vs->HasSource())
    1477         return;
    1478 
    1479     float rolloff = 1.0f;
    1480     float referenceDistance = 125.0f;
    1481     float maxDistance = 500.0f;
    1482     if(vs->relative)
    1483     {
    1484         rolloff = 0.0f;
    1485         referenceDistance = 1.0f;
    1486         maxDistance = FLT_MAX;
    1487     }
    1488 
    1489     AL_CHECK;
    1490 
    1491     alSourcefv(vs->al_src, AL_POSITION,           vs->pos);
    1492     alSource3f(vs->al_src, AL_VELOCITY,           0.0f, 0.0f, 0.0f);
    1493     alSourcei (vs->al_src, AL_SOURCE_RELATIVE,    vs->relative);
    1494     alSourcef (vs->al_src, AL_ROLLOFF_FACTOR,     rolloff);
    1495     alSourcef (vs->al_src, AL_REFERENCE_DISTANCE, referenceDistance);
    1496     alSourcef (vs->al_src, AL_MAX_DISTANCE,       maxDistance);
    1497     alSourcef (vs->al_src, AL_GAIN,               vs->gain);
    1498     alSourcef (vs->al_src, AL_PITCH,              vs->pitch);
    1499     alSourcei (vs->al_src, AL_LOOPING,            vs->loop);
    1500 
    1501     //alSourcei (vs->al_src, AL_MIN_GAIN,           0.0f);
    1502     //alSourcei (vs->al_src, AL_MAX_GAIN,           1.0f);
    1503     //alSource3f(vs->al_src, AL_DIRECTION,          0.0f, 0.0f, 0.0f);
    1504     //alSourcef (vs->al_src, AL_CONE_INNER_ANGLE,   360.0f);
    1505     //alSourcef (vs->al_src, AL_CONE_OUTER_ANGLE,   360.0f);
    1506     //alSourcef (vs->al_src, AL_CONE_OUTER_GAIN,    0.0f);
    1507     //alSourcef (vs->al_src, AL_SEC_OFFSET,         0.0f);
    1508     //alSourcef (vs->al_src, AL_SAMPLE_OFFSET,      0.0f);
    1509     //alSourcef (vs->al_src, AL_BYTE_OFFSET,        0.0f);
    1510 
    1511     ALenum err = alGetError();
    1512     if(err != AL_NO_ERROR)
    1513     {
    1514         debug_printf(L"vsrc_latch: one of the below is invalid:\n");
    1515         debug_printf(L"  al_src: 0x%x\n", vs->al_src);
    1516         debug_printf(L"  position: %f %f %f\n", vs->pos[0], vs->pos[1], vs->pos[2]);
    1517         debug_printf(L"  velocity: %f %f %f\n", 0.0f, 0.0f, 0.0f);
    1518         debug_printf(L"  relative: %d\n", (int)vs->relative);
    1519         debug_printf(L"  rolloff: %f\n", rolloff);
    1520         debug_printf(L"  ref dist: %f\n", referenceDistance);
    1521         debug_printf(L"  max dist: %f\n", maxDistance);
    1522         debug_printf(L"  gain: %f\n", vs->gain);
    1523         debug_printf(L"  pitch: %f\n", vs->pitch);
    1524         debug_printf(L"  loop: %d\n", (int)vs->loop);
    1525 
    1526         al_ReportError(err, __func__, __LINE__);
    1527     }
    1528 }
    1529 
    1530 
    1531 /**
    1532  * Dequeue any of the VSrc's sound buffers that are finished playing.
    1533  *
    1534  * @return int number of entries that were removed.
    1535  */
    1536 static int vsrc_deque_finished_bufs(VSrc* vs)
    1537 {
    1538     ENSURE(vs->HasSource());    // (otherwise there's no sense in calling this function)
    1539 
    1540     AL_CHECK;
    1541     int num_processed;
    1542     alGetSourcei(vs->al_src, AL_BUFFERS_PROCESSED, &num_processed);
    1543     AL_CHECK;
    1544 
    1545     for(int i = 0; i < num_processed; i++)
    1546     {
    1547         ALuint al_buf;
    1548         alSourceUnqueueBuffers(vs->al_src, 1, &al_buf);
    1549         snd_data_buf_free(vs->hsd, al_buf);
    1550     }
    1551 
    1552     AL_CHECK;
    1553     return num_processed;
    1554 }
    1555 
    1556 
    1557 /**
    1558  * Update the VSrc - perform fade (if active), queue/unqueue buffers.
    1559  * Called once a frame.
    1560  * must be a functor so that each call receives the same time (avoids repeated
    1561  * calls to timer_Time and inconsistencies when crossfading)
    1562  */
    1563 class VsrcUpdater
    1564 {
    1565 public:
    1566     VsrcUpdater(double time)
    1567         : time(time)
    1568     {
    1569     }
    1570 
    1571     Status operator()(VSrc* vs) const
    1572     {
    1573         if(!vs->HasSource())
    1574             return INFO::OK;
    1575 
    1576         FadeRet fade_ret = fade(vs->fade, time, vs->gain);
    1577         // auto-free after fadeout.
    1578         if(fade_ret == FADE_TO_0_FINISHED)
    1579         {
    1580             vsrc_free(vs);
    1581             return INFO::OK;    // don't continue - <vs> has been freed.
    1582         }
    1583         // fade in progress; latch current gain value.
    1584         else if(fade_ret == FADE_CHANGED)
    1585             vsrc_latch(vs);
    1586 
    1587         int num_queued;
    1588         alGetSourcei(vs->al_src, AL_BUFFERS_QUEUED, &num_queued);
    1589         AL_CHECK;
    1590 
    1591         int num_processed = vsrc_deque_finished_bufs(vs);
    1592         UNUSED2(num_processed);
    1593 
    1594         if(vs->flags & VS_EOF)
    1595         {
    1596             // no more buffers left, and EOF reached - done playing.
    1597             if(num_queued == 0)
    1598             {
    1599                 snd_free(vs->hvs);
    1600                 return INFO::OK;
    1601             }
    1602         }
    1603         // can still read from SndData
    1604         else
    1605         {
    1606             // get next buffer
    1607             ALuint al_buf;
    1608             Status ret = snd_data_buf_get(vs->hsd, al_buf);
    1609             RETURN_STATUS_IF_ERR(ret);
    1610             if(ret == INFO::ALL_COMPLETE)   // no further buffers will be forthcoming
    1611                 vs->flags |= VS_EOF;
    1612 
    1613             alSourceQueueBuffers(vs->al_src, 1, &al_buf);
    1614             AL_CHECK;
    1615 
    1616             // HACK: OpenAL stops the source if reloading took too long
    1617             ALint state;
    1618             alGetSourcei(vs->al_src, AL_SOURCE_STATE, &state);
    1619             if((state == AL_STOPPED) && !(vs->flags & VS_SHOULD_STOP))
    1620                 alSourcePlay(vs->al_src);
    1621         }
    1622 
    1623         return INFO::OK;
    1624     }
    1625 
    1626 private:
    1627     double time;
    1628 };
    1629 
    1630 
    1631 /**
    1632  * Try to give the VSrc an AL source so that it can (re)start playing.
    1633  * called by snd_play and voice management.
    1634  *
    1635  * @return Status (ERR::FAIL if no AL source is available)
    1636  */
    1637 static Status vsrc_grant(VSrc* vs)
    1638 {
    1639     if(vs->HasSource()) // already playing
    1640         return INFO::OK;
    1641 
    1642     // try to allocate a source. snd_play calls us in the hope that a source
    1643     // happens to be free, but if not, just skip the remaining steps and
    1644     // wait for the next update.
    1645     if(!al_src_alloc(vs->al_src))
    1646         return ERR::FAIL;   // NOWARN
    1647     vs->flags |= VS_HAS_AL_SRC;
    1648 
    1649     // pass (user-specifiable) properties on to OpenAL.
    1650     vsrc_latch(vs);
    1651 
    1652     // queue up some buffers (enough to start playing, at least).
    1653     VsrcUpdater updater(timer_Time());
    1654     updater(vs);
    1655     AL_CHECK;
    1656 
    1657     alSourcePlay(vs->al_src);
    1658     AL_CHECK;
    1659     return INFO::OK;
    1660 }
    1661 
    1662 
    1663 /**
    1664  * stop playback, and reclaim the OpenAL source.
    1665  * called when closing the VSrc, or when voice management decides
    1666  * this VSrc must yield to others of higher priority.
    1667  */
    1668 static Status vsrc_reclaim(VSrc* vs)
    1669 {
    1670     if(!vs->HasSource())
    1671         return ERR::FAIL;   // NOWARN
    1672 
    1673     // clear the source's buffer queue (necessary because buffers cannot
    1674     // be deleted at shutdown while still attached to a source).
    1675     // note: OpenAL 1.1 says all buffers become "processed" when the
    1676     // source is stopped (so vsrc_deque_finished_bufs ought to have the
    1677     // desired effect), but that isn't the case on some Linux
    1678     // implementations (OpenALsoft and PulseAudio with on-board NVidia).
    1679     // wiping out the entire queue by attaching the null buffer is safer,
    1680     // but still doesn't cause versions of OpenALsoft older than 2009-08-11
    1681     // to correctly reset AL_BUFFERS_PROCESSED. in "Re: [Openal-devel]
    1682     // Questionable "invalid value" from alSourceUnqueueBuffers", the
    1683     // developer recommended working around this bug by rewinding the
    1684     // source instead of merely issuing alSourceStop.
    1685     // reference: http://trac.wildfiregames.com/ticket/297
    1686     vs->loop = false;
    1687     vsrc_latch(vs);
    1688 
    1689     vs->flags |= VS_SHOULD_STOP;
    1690 
    1691     alSourceStop(vs->al_src);
    1692 
    1693     vsrc_deque_finished_bufs(vs);
    1694     alSourcei(vs->al_src, AL_BUFFER, AL_NONE);
    1695 
    1696     alSourceRewind(vs->al_src);
    1697 
    1698     al_src_free(vs->al_src);
    1699     vs->flags &= ~VS_HAS_AL_SRC;
    1700 
    1701     return INFO::OK;
    1702 }
    1703 
    1704 
    1705 //-----------------------------------------------------------------------------
    1706 // snd_mgr API
    1707 //-----------------------------------------------------------------------------
    1708 
    1709 /**
    1710  * Request the sound be played.
    1711  *
    1712  * Once done playing, the sound is automatically closed (allows
    1713  * fire-and-forget play code).
    1714  * if no hardware voice is available, this sound may not be played at all,
    1715  * or in the case of looped sounds, start later.
    1716  *
    1717  * @param hvs Handle to VSrc
    1718  * @param static_pri (min 0 .. max 1, default 0) indicates which sounds are
    1719  * considered more important; this is attenuated by distance to the
    1720  * listener (see snd_update).
    1721  * @return Status
    1722  */
    1723 Status snd_play(Handle hvs, float static_pri)
    1724 {
    1725     H_DEREF(hvs, VSrc, vs);
    1726 
    1727     // note: vs->hsd is valid, otherwise snd_open would have failed
    1728     // and returned an invalid handle (caught above).
    1729 
    1730     vs->static_pri = static_pri;
    1731     list_add(vs);
    1732     vs->flags |= VS_IN_LIST;
    1733 
    1734     // optimization (don't want to do full update here - too slow)
    1735     // either we get a source and playing begins immediately,
    1736     // or it'll be taken care of on next update.
    1737     vsrc_grant(vs);
    1738     return INFO::OK;
    1739 }
    1740 
    1741 
    1742 /**
    1743  * Change 3d position of the sound source.
    1744  *
    1745  * May be called at any time; fails with invalid handle return if
    1746  * the sound has already been closed (e.g. it never played).
    1747  *
    1748  * @param hvs Handle to VSrc
    1749  * @param x,y,z coordinates (interpretation: see below)
    1750  * @param relative if true, (x,y,z) is treated as relative to the listener;
    1751  * otherwise, it is the position in world coordinates (default).
    1752  * @return Status
    1753  */
    1754 Status snd_set_pos(Handle hvs, float x, float y, float z, bool relative)
    1755 {
    1756     H_DEREF(hvs, VSrc, vs);
    1757 
    1758     vs->pos[0] = x; vs->pos[1] = y; vs->pos[2] = z;
    1759     vs->relative = relative;
    1760 
    1761     vsrc_latch(vs);
    1762     return INFO::OK;
    1763 }
    1764 
    1765 
    1766 /**
    1767  * Change gain (amplitude modifier) of the sound source.
    1768  *
    1769  * should not be called during a fade (see note in implementation);
    1770  * fails with invalid handle return if the sound has already been
    1771  * closed (e.g. it never played).
    1772  *
    1773  * @param hvs Handle to VSrc
    1774  * @param gain modifier; must be non-negative;
    1775  * 1 -> unattenuated, 0.5 -> -6 dB, 0 -> silence.
    1776  * @return Status
    1777  */
    1778 Status snd_set_gain(Handle hvs, float gain)
    1779 {
    1780     H_DEREF(hvs, VSrc, vs);
    1781 
    1782     if(!(0.0f <= gain && gain <= 1.0f))
    1783         WARN_RETURN(ERR::INVALID_PARAM);
    1784 
    1785     // if fading, gain changes would be overridden during the next
    1786     // snd_update. attempting this indicates a logic error. we abort to
    1787     // avoid undesired jumps in gain that might surprise (and deafen) users.
    1788     if(fade_is_active(vs->fade))
    1789         WARN_RETURN(ERR::LOGIC);
    1790 
    1791     vs->gain = gain;
    1792 
    1793     vsrc_latch(vs);
    1794     return INFO::OK;
    1795 }
    1796 
    1797 
    1798 /**
    1799  * Change pitch shift of the sound source.
    1800  *
    1801  * may be called at any time; fails with invalid handle return if
    1802  * the sound has already been closed (e.g. it never played).
    1803  *
    1804  * @param hvs Handle to VSrc
    1805  * @param pitch shift: 1.0 means no change; each doubling/halving equals a
    1806  * pitch shift of +/-12 semitones (one octave). zero is invalid.
    1807  * @return Status
    1808  */
    1809 Status snd_set_pitch(Handle hvs, float pitch)
    1810 {
    1811     H_DEREF(hvs, VSrc, vs);
    1812 
    1813     if(pitch <= 0.0f)
    1814         WARN_RETURN(ERR::INVALID_PARAM);
    1815 
    1816     vs->pitch = pitch;
    1817 
    1818     vsrc_latch(vs);
    1819     return INFO::OK;
    1820 }
    1821 
    1822 
    1823 /**
    1824  * Enable/disable looping on the sound source.
    1825  * used to implement variable-length sounds (e.g. while building).
    1826  *
    1827  * may be called at any time; fails with invalid handle return if
    1828  * the sound has already been closed (e.g. it never played).
    1829  *
    1830  * notes:
    1831  * - looping sounds are not discarded if they cannot be played for lack of
    1832  *   a hardware voice at the moment play was requested.
    1833  * - once looping is again disabled and the sound has reached its end,
    1834  *   the sound instance is freed automatically (as if never looped).
    1835  *
    1836  * @param hvs Handle to VSrc
    1837  */
    1838 Status snd_set_loop(Handle hvs, bool loop)
    1839 {
    1840     H_DEREF(hvs, VSrc, vs);
    1841 
    1842     vs->loop = loop;
    1843 
    1844     vsrc_latch(vs);
    1845     return INFO::OK;
    1846 }
    1847 
    1848 
    1849 /**
    1850  * Fade the sound source in or out over time.
    1851  * Its gain starts at \<initial_gain\> immediately and is moved toward
    1852  * \<final_gain\> over \<length\> seconds.
    1853  *
    1854  * may be called at any time; fails with invalid handle return if
    1855  * the sound has already been closed (e.g. it never played).
    1856  *
    1857  * note that this function doesn't busy-wait until the fade is complete;
    1858  * any number of fades may be active at a time (allows cross-fading).
    1859  * each snd_update calculates a new gain value for all pending fades.
    1860  * it is safe to start another fade on the same sound source while
    1861  * one is currently in progress; the old one is dropped.
    1862  *
    1863  * @param hvs Handle to VSrc
    1864  * @param initial_gain gain. if < 0 (an otherwise illegal value), the sound's
    1865  * current gain is used as the start value (useful for fading out).
    1866  * @param final_gain gain. if 0, the sound is freed when the fade completes or
    1867  * is aborted, thus allowing fire-and-forget fadeouts. no cases are
    1868  * foreseen where this is undesirable, and it is easier to implement
    1869  * than an extra set-free-after-fade-flag function.
    1870  * @param length duration of fade [s]
    1871  * @param type determines the fade curve: linear, exponential or S-curve.
    1872  * for guidance on which to use, see
    1873  * http://www.transom.org/tools/editing_mixing/200309.stupidfadetricks.html
    1874  * you can also pass FT_ABORT to stop fading (if in progress) and
    1875  * set gain to the final_gain parameter passed here.
    1876  * @return Status
    1877  */
    1878 Status snd_fade(Handle hvs, float initial_gain, float final_gain,
    1879     float length, FadeType type)
    1880 {
    1881     H_DEREF(hvs, VSrc, vs);
    1882 
    1883     if(type != FT_LINEAR && type != FT_EXPONENTIAL && type != FT_S_CURVE && type != FT_ABORT)
    1884         WARN_RETURN(ERR::INVALID_PARAM);
    1885 
    1886     // special case - set initial value to current gain (see above).
    1887     if(initial_gain < 0.0f)
    1888         initial_gain = vs->gain;
    1889 
    1890     const double cur_time = timer_Time();
    1891 
    1892     FadeInfo& fi = vs->fade;
    1893     fi.type        = type;
    1894     fi.start_time  = cur_time;
    1895     fi.initial_val = initial_gain;
    1896     fi.final_val   = final_gain;
    1897     fi.length      = length;
    1898 
    1899     (void)fade(fi, cur_time, vs->gain);
    1900     vsrc_latch(vs);
    1901 
    1902     return INFO::OK;
    1903 }
    1904 
    1905 
    1906 /** --TODO: Test to ensure this works (not currently necessary for intensity)
    1907  * find out if a sound is still playing
    1908  *
    1909  * @param hvs - handle to the snd to check
    1910  * @return bool true if playing
    1911  **/
    1912 bool snd_is_playing(Handle hvs)
    1913 {
    1914     // (can't use H_DEREF due to bool return value)
    1915     VSrc* vs = H_USER_DATA(hvs, VSrc);
    1916 
    1917     // sound has played and was already freed or is otherwise not loaded.
    1918     if(!vs)
    1919         return false;
    1920 
    1921     // 'just' finished playing
    1922     if(!vs->HasSource())
    1923         return false;
    1924 
    1925     return true;
    1926 }
    1927 
    1928 
    1929 //-----------------------------------------------------------------------------
    1930 // voice management: grants the currently most 'important' sounds
    1931 // a hardware voice.
    1932 //-----------------------------------------------------------------------------
    1933 
    1934 /// length of vector squared (avoids costly sqrt)
    1935 static float magnitude_2(const float v[3])
    1936 {
    1937     return v[0]*v[0] + v[1]*v[1] + v[2]*v[2];
    1938 }
    1939 
    1940 
    1941 /**
    1942  * Determine new priority of the VSrc based on distance to listener and
    1943  * static priority.
    1944  * Called via list_foreach.
    1945  */
    1946 static void calc_cur_pri(VSrc* vs)
    1947 {
    1948     const float MAX_DIST_2 = 1000.0f;
    1949     const float falloff = 10.0f;
    1950 
    1951     float d_2;  // euclidean distance to listener (squared)
    1952     if(vs->relative)
    1953         d_2 = magnitude_2(vs->pos);
    1954     else
    1955         d_2 = al_listener_dist_2(vs->pos);
    1956 
    1957     // scale priority down exponentially
    1958     float e = d_2 / MAX_DIST_2; // 0.0f (close) .. 1.0f (far)
    1959 
    1960     // assume farther away than OpenAL cutoff - no sound contribution
    1961     float cur_pri = -1.0f;
    1962     if(e < 1.0f)
    1963         cur_pri = vs->static_pri / pow(falloff, e);
    1964     vs->cur_pri = cur_pri;
    1965 }
    1966 
    1967 
    1968 /**
    1969  * convenience function that strips all unimportant VSrc of their AL source.
    1970  * called via list_foreach; also immediately frees discarded clips.
    1971  */
    1972 static void reclaim(VSrc* vs)
    1973 {
    1974     vsrc_reclaim(vs);
    1975 
    1976     if(!vs->loop)
    1977         snd_free(vs->hvs);
    1978 }
    1979 
    1980 
    1981 /**
    1982  * update voice management, i.e. recalculate priority and assign AL sources.
    1983  * no-op if OpenAL not yet initialized.
    1984  */
    1985 static Status vm_update()
    1986 {
    1987     list_prune_removed();
    1988 
    1989     // update current priorities (a function of static priority and distance).
    1990     list_foreach(calc_cur_pri);
    1991 
    1992     list_SortByDescendingPriority();
    1993 
    1994     // partition list; the first ones will be granted a source
    1995     // (if they don't have one already), after reclaiming all sources from
    1996     // the remainder of the VSrc list entries.
    1997     size_t first_unimportant = std::min((size_t)vsrcs.size(), al_src_maxNumToUse);
    1998     list_foreach(reclaim, first_unimportant, 0);
    1999     list_foreach(vsrc_grant, 0, first_unimportant);
    2000 
    2001     return INFO::OK;
    2002 }
    2003 
    2004 
    2005 //-----------------------------------------------------------------------------
    2006 
    2007 /**
    2008  * perform housekeeping (e.g. streaming); call once a frame.
    2009  *
    2010  * @param pos position support vector. if NULL, all parameters are
    2011  * ignored and listener position unchanged; this is useful in case the
    2012  * world isn't initialized yet.
    2013  * @param dir view direction
    2014  * @param up up vector
    2015  * @return Status
    2016  */
    2017 Status snd_update(const float* pos, const float* dir, const float* up)
    2018 {
    2019     // there's no sense in updating anything if we weren't initialized
    2020     // yet (most notably, if sound is disabled). we check for this to
    2021     // avoid confusing the code below. the caller should complain if
    2022     // this fails, so report success here (everything will work once
    2023     // sound is re-enabled).
    2024     if(!al_initialized)
    2025         return INFO::OK;
    2026 
    2027     if(pos)
    2028         al_listener_set_pos(pos, dir, up);
    2029 
    2030     vm_update();
    2031 
    2032     // for each source: add / remove buffers; carry out fading.
    2033     list_foreach(VsrcUpdater(timer_Time()));
    2034 
    2035     return INFO::OK;
    2036 }
    2037 
    2038 
    2039 #else   // CONFIG2_AUDIO
    2040 
    2041 // Stub implementations of snd_mgr API:
    2042 
    2043 Status snd_dev_prepare_enum() { return ERR::NOT_SUPPORTED; }
    2044 const char* snd_dev_next() { return NULL; }
    2045 Status snd_dev_set(const char* alc_new_dev_name) { return INFO::OK; }
    2046 Status snd_set_max_voices(size_t limit) { return INFO::OK; }
    2047 Status snd_set_master_gain(float gain) { return INFO::OK; }
    2048 Handle snd_open(const PIVFS& vfs, const VfsPath& pathname) { return 0; }
    2049 Status snd_free(Handle& hvs) { return INFO::OK; }
    2050 Status snd_play(Handle hvs, float static_pri) { return INFO::OK; }
    2051 Status snd_set_pos(Handle hvs, float x, float y, float z, bool relative) { return INFO::OK; }
    2052 Status snd_set_gain(Handle hs, float gain) { return INFO::OK; }
    2053 Status snd_set_pitch(Handle hs, float pitch) { return INFO::OK; }
    2054 Status snd_set_loop(Handle hvs, bool loop) { return INFO::OK; }
    2055 Status snd_fade(Handle hvs, float initial_gain, float final_gain, float length, FadeType type) { return INFO::OK; }
    2056 Status snd_disable(bool disabled) { return INFO::OK; }
    2057 Status snd_update(const float* pos, const float* dir, const float* up) { return INFO::OK; }
    2058 bool snd_is_playing(Handle hvs) { return false; }
    2059 void snd_shutdown() { }
    2060 
    2061 #endif  // CONFIG2_AUDIO
  • source/lib/res/sound/ogg.cpp

     
    1 #include "precompiled.h"
    2 #include "ogg.h"
    3 
    4 #if CONFIG2_AUDIO
    5 
    6 #include "lib/external_libraries/openal.h"
    7 #include "lib/external_libraries/vorbis.h"
    8 
    9 #include "lib/byte_order.h"
    10 #include "lib/file/io/io.h"
    11 #include "lib/file/file_system.h"
    12 
    13 
    14 static Status LibErrorFromVorbis(int err)
    15 {
    16     switch(err)
    17     {
    18     case 0:
    19         return INFO::OK;
    20     case OV_HOLE:
    21         return ERR::AGAIN;
    22     case OV_EREAD:
    23         return ERR::IO;
    24     case OV_EFAULT:
    25         return ERR::LOGIC;
    26     case OV_EIMPL:
    27         return ERR::NOT_SUPPORTED;
    28     case OV_EINVAL:
    29         return ERR::INVALID_PARAM;
    30     case OV_ENOTVORBIS:
    31         return ERR::NOT_SUPPORTED;
    32     case OV_EBADHEADER:
    33         return ERR::CORRUPTED;
    34     case OV_EVERSION:
    35         return ERR::INVALID_VERSION;
    36     case OV_ENOTAUDIO:
    37         return ERR::_1;
    38     case OV_EBADPACKET:
    39         return ERR::_2;
    40     case OV_EBADLINK:
    41         return ERR::_3;
    42     case OV_ENOSEEK:
    43         return ERR::_4;
    44     default:
    45         return ERR::FAIL;
    46     }
    47 }
    48 
    49 
    50 //-----------------------------------------------------------------------------
    51 
    52 class VorbisFileAdapter
    53 {
    54 public:
    55     VorbisFileAdapter(const PFile& openedFile)
    56         : file(openedFile)
    57         , size(FileSize(openedFile->Pathname()))
    58         , offset(0)
    59     {
    60     }
    61 
    62     static size_t Read(void* bufferToFill, size_t itemSize, size_t numItems, void* context)
    63     {
    64         VorbisFileAdapter* adapter = (VorbisFileAdapter*)context;
    65         const off_t sizeRequested = numItems*itemSize;
    66         const off_t sizeRemaining = adapter->size - adapter->offset;
    67         const size_t sizeToRead = (size_t)std::min(sizeRequested, sizeRemaining);
    68 
    69         io::Operation op(*adapter->file.get(), bufferToFill, sizeToRead, adapter->offset);
    70         if(io::Run(op) == INFO::OK)
    71         {
    72             adapter->offset += sizeToRead;
    73             return sizeToRead;
    74         }
    75 
    76         errno = EIO;
    77         return 0;
    78     }
    79 
    80     static int Seek(void* context, ogg_int64_t offset, int whence)
    81     {
    82         VorbisFileAdapter* adapter = (VorbisFileAdapter*)context;
    83 
    84         off_t origin = 0;
    85         switch(whence)
    86         {
    87         case SEEK_SET:
    88             origin = 0;
    89             break;
    90         case SEEK_CUR:
    91             origin = adapter->offset;
    92             break;
    93         case SEEK_END:
    94             origin = adapter->size+1;
    95             break;
    96             NODEFAULT;
    97         }
    98 
    99         adapter->offset = Clamp(off_t(origin+offset), off_t(0), adapter->size);
    100         return 0;
    101     }
    102 
    103     static int Close(void* context)
    104     {
    105         VorbisFileAdapter* adapter = (VorbisFileAdapter*)context;
    106         adapter->file.reset();
    107         return 0;   // return value is ignored
    108     }
    109 
    110     static long Tell(void* context)
    111     {
    112         VorbisFileAdapter* adapter = (VorbisFileAdapter*)context;
    113         return adapter->offset;
    114     }
    115 
    116 private:
    117     PFile file;
    118     off_t size;
    119     off_t offset;
    120 };
    121 
    122 //-----------------------------------------------------------------------------
    123 
    124 class VorbisBufferAdapter
    125 {
    126 public:
    127     VorbisBufferAdapter(const shared_ptr<u8>& buffer, size_t size)
    128         : buffer(buffer)
    129         , size(size)
    130         , offset(0)
    131     {
    132     }
    133 
    134     static size_t Read(void* bufferToFill, size_t itemSize, size_t numItems, void* context)
    135     {
    136         VorbisBufferAdapter* adapter = (VorbisBufferAdapter*)context;
    137         const off_t sizeRequested = numItems*itemSize;
    138         const off_t sizeRemaining = adapter->size - adapter->offset;
    139         const size_t sizeToRead = (size_t)std::min(sizeRequested, sizeRemaining);
    140 
    141         memcpy(bufferToFill, adapter->buffer.get() + adapter->offset, sizeToRead);
    142 
    143         adapter->offset += sizeToRead;
    144         return sizeToRead;
    145     }
    146 
    147     static int Seek(void* context, ogg_int64_t offset, int whence)
    148     {
    149         VorbisBufferAdapter* adapter = (VorbisBufferAdapter*)context;
    150 
    151         off_t origin = 0;
    152         switch(whence)
    153         {
    154         case SEEK_SET:
    155             origin = 0;
    156             break;
    157         case SEEK_CUR:
    158             origin = adapter->offset;
    159             break;
    160         case SEEK_END:
    161             origin = adapter->size+1;
    162             break;
    163             NODEFAULT;
    164         }
    165 
    166         adapter->offset = Clamp(off_t(origin+offset), off_t(0), adapter->size);
    167         return 0;
    168     }
    169 
    170     static int Close(void* context)
    171     {
    172         VorbisBufferAdapter* adapter = (VorbisBufferAdapter*)context;
    173         adapter->buffer.reset();
    174         return 0;   // return value is ignored
    175     }
    176 
    177     static long Tell(void* context)
    178     {
    179         VorbisBufferAdapter* adapter = (VorbisBufferAdapter*)context;
    180         return adapter->offset;
    181     }
    182 
    183 private:
    184     shared_ptr<u8> buffer;
    185     off_t size;
    186     off_t offset;
    187 };
    188 
    189 
    190 //-----------------------------------------------------------------------------
    191 
    192 template <typename Adapter>
    193 class OggStreamImpl : public OggStream
    194 {
    195 public:
    196     OggStreamImpl(const Adapter& adapter)
    197         : adapter(adapter)
    198     {
    199     }
    200 
    201     Status Open()
    202     {
    203         ov_callbacks callbacks;
    204         callbacks.read_func = Adapter::Read;
    205         callbacks.close_func = Adapter::Close;
    206         callbacks.seek_func = Adapter::Seek;
    207         callbacks.tell_func = Adapter::Tell;
    208         const int ret = ov_open_callbacks(&adapter, &vf, 0, 0, callbacks);
    209         if(ret != 0)
    210             WARN_RETURN(LibErrorFromVorbis(ret));
    211 
    212         const int link = -1;    // retrieve info for current bitstream
    213         info = ov_info(&vf, link);
    214         if(!info)
    215             WARN_RETURN(ERR::INVALID_HANDLE);
    216 
    217         return INFO::OK;
    218     }
    219 
    220     virtual ALenum Format()
    221     {
    222         return (info->channels == 1)? AL_FORMAT_MONO16 : AL_FORMAT_STEREO16;
    223     }
    224 
    225     virtual ALsizei SamplingRate()
    226     {
    227         return info->rate;
    228     }
    229 
    230     virtual Status GetNextChunk(u8* buffer, size_t size)
    231     {
    232         // we may have to call ov_read multiple times because it
    233         // treats the buffer size "as a limit and not a request"
    234         size_t bytesRead = 0;
    235         for(;;)
    236         {
    237             const int isBigEndian = (BYTE_ORDER == BIG_ENDIAN);
    238             const int wordSize = sizeof(i16);
    239             const int isSigned = 1;
    240             int bitstream;  // unused
    241             const int ret = ov_read(&vf, (char*)buffer+bytesRead, int(size-bytesRead), isBigEndian, wordSize, isSigned, &bitstream);
    242             if(ret == 0)    // EOF
    243                 return (Status)bytesRead;
    244             else if(ret < 0)
    245                 WARN_RETURN(LibErrorFromVorbis(ret));
    246             else    // success
    247             {
    248                 bytesRead += ret;
    249                 if(bytesRead == size)
    250                     return (Status)bytesRead;
    251             }
    252         }
    253     }
    254 
    255 private:
    256     Adapter adapter;
    257     OggVorbis_File vf;
    258     vorbis_info* info;
    259 };
    260 
    261 
    262 //-----------------------------------------------------------------------------
    263 
    264 Status OpenOggStream(const OsPath& pathname, OggStreamPtr& stream)
    265 {
    266     PFile file(new File);
    267     RETURN_STATUS_IF_ERR(file->Open(pathname, L'r'));
    268 
    269     shared_ptr<OggStreamImpl<VorbisFileAdapter> > tmp(new OggStreamImpl<VorbisFileAdapter>(VorbisFileAdapter(file)));
    270     RETURN_STATUS_IF_ERR(tmp->Open());
    271     stream = tmp;
    272     return INFO::OK;
    273 }
    274 
    275 Status OpenOggNonstream(const PIVFS& vfs, const VfsPath& pathname, OggStreamPtr& stream)
    276 {
    277     shared_ptr<u8> contents;
    278     size_t size;
    279     RETURN_STATUS_IF_ERR(vfs->LoadFile(pathname, contents, size));
    280 
    281     shared_ptr<OggStreamImpl<VorbisBufferAdapter> > tmp(new OggStreamImpl<VorbisBufferAdapter>(VorbisBufferAdapter(contents, size)));
    282     RETURN_STATUS_IF_ERR(tmp->Open());
    283     stream = tmp;
    284     return INFO::OK;
    285 }
    286 
    287 #endif  // CONFIG2_AUDIO
  • source/simulation2/components/CCmpSoundManager.cpp

     
    2424#include "simulation2/MessageTypes.h"
    2525#include "simulation2/components/ICmpPosition.h"
    2626#include "simulation2/components/ICmpRangeManager.h"
    27 #include "sound/SoundGroup.h"
     27#include "soundmanager/js/SMSoundGroup.h"
    2828
    2929class CCmpSoundManager : public ICmpSoundManager
    3030{
     
    3636
    3737    DEFAULT_COMPONENT_ALLOCATOR(SoundManager)
    3838
    39     std::map<std::wstring, CSoundGroup*> m_SoundGroups;
     39    std::map<std::wstring, CSMSoundGroup*> m_SoundGroups;
    4040
    4141    static std::string GetSchema()
    4242    {
     
    4949
    5050    virtual void Deinit()
    5151    {
    52         for (std::map<std::wstring, CSoundGroup*>::iterator it = m_SoundGroups.begin(); it != m_SoundGroups.end(); ++it)
     52        for (std::map<std::wstring, CSMSoundGroup*>::iterator it = m_SoundGroups.begin(); it != m_SoundGroups.end(); ++it)
    5353            delete it->second;
    5454        m_SoundGroups.clear();
    5555    }
     
    7676            // or on some other timer?
    7777            const CMessageUpdate& msgData = static_cast<const CMessageUpdate&> (msg);
    7878            float t = msgData.turnLength.ToFloat();
    79             for (std::map<std::wstring, CSoundGroup*>::iterator it = m_SoundGroups.begin(); it != m_SoundGroups.end(); ++it)
     79            for (std::map<std::wstring, CSMSoundGroup*>::iterator it = m_SoundGroups.begin(); it != m_SoundGroups.end(); ++it)
    8080                if (it->second)
    8181                    it->second->Update(t);
    8282            break;
     
    8787    virtual void PlaySoundGroup(std::wstring name, entity_id_t source)
    8888    {
    8989        // Make sure the sound group is loaded
    90         CSoundGroup* group;
     90        CSMSoundGroup* group;
    9191        if (m_SoundGroups.find(name) == m_SoundGroups.end())
    9292        {
    93             group = new CSoundGroup();
     93            group = new CSMSoundGroup();
    9494            if (!group->LoadSoundGroup(L"audio/" + name))
    9595            {
    9696                LOGERROR(L"Failed to load sound group '%ls'", name.c_str());
  • binaries/data/mods/public/audio/voice/hellenes/civ/civ_male_attack.xml

     
    88    <ConeOuter>360</ConeOuter>
    99    <Looping>0</Looping>
    1010    <RandOrder>1</RandOrder>
     11    <Distanceless>1</Distanceless>
    1112    <RandGain>1</RandGain>
    1213    <GainUpper>1</GainUpper>
    1314    <GainLower>0.8</GainLower>
  • binaries/data/mods/public/audio/voice/hellenes/civ/civ_female_select.xml

     
    99    <Looping>0</Looping>
    1010    <RandOrder>1</RandOrder>
    1111    <RandGain>1</RandGain>
     12    <Distanceless>1</Distanceless>
    1213    <GainUpper>1</GainUpper>
    1314    <GainLower>0.8</GainLower>
    1415    <RandPitch>1</RandPitch>
  • binaries/data/mods/public/audio/voice/hellenes/civ/civ_male_ack.xml

     
    88    <ConeOuter>360</ConeOuter>
    99    <Looping>0</Looping>
    1010    <RandOrder>1</RandOrder>
     11    <Distanceless>1</Distanceless>
    1112    <RandGain>1</RandGain>
    1213    <GainUpper>1</GainUpper>
    1314    <GainLower>0.8</GainLower>
  • binaries/data/mods/public/audio/voice/hellenes/civ/civ_male_select.xml

     
    88    <ConeOuter>360</ConeOuter>
    99    <Looping>0</Looping>
    1010    <RandOrder>1</RandOrder>
     11    <Distanceless>1</Distanceless>
    1112    <RandGain>1</RandGain>
    1213    <GainUpper>1</GainUpper>
    1314    <GainLower>0.8</GainLower>
  • binaries/data/mods/public/gui/session/session.js

     
    408408//          currentAmbient = newRandomSound("ambient", "temperate_", "dayscape");
    409409
    410410            const AMBIENT = "audio/ambient/dayscape/day_temperate_gen_03.ogg";
    411             currentAmbient = new Sound(AMBIENT);
     411            currentAmbient = new AmbientSound(AMBIENT);
    412412
    413413            if (currentAmbient)
    414414            {
    415415                currentAmbient.loop();
    416                 currentAmbient.setGain(0.8);
    417416            }
    418417            break;
    419418
     
    428427{
    429428    if (currentAmbient)
    430429    {
    431         currentAmbient.fade(-1, 0.0, 5.0);
     430        currentAmbient.free();
    432431        currentAmbient = null;
    433432    }
    434433}
  • binaries/data/mods/public/gui/common/music.js

     
    7272        switch (this.currentState)
    7373        {
    7474            case this.states.OFF:
    75                     if (this.isPlaying())
    7675                {
    77                     this.currentMusic.fade(-1, 0.0, 3.0);
    78                     this.currentMusic = null;
     76                    var thePlayer = SoundPlayer();
     77                    thePlayer.stopMusic();
    7978                }
    8079                break;
    8180
     
    146145
    147146Music.prototype.switchMusic = function(track, fadeInPeriod, isLooping)
    148147{
    149     if (this.currentMusic)
    150     {
    151         this.currentMusic.fade(-1, 0.0, 5.0);
    152         this.currentMusic = null;
    153     }
     148    this.currentMusic = new MusicSound(this.RELATIVE_MUSIC_PATH + track);
    154149
    155     this.currentMusic = new Sound(this.RELATIVE_MUSIC_PATH + track);
    156 
    157150    if (this.currentMusic)
    158151    {
    159152        if (isLooping)
    160153            this.currentMusic.loop();
    161154        else
    162155            this.currentMusic.play();
    163 
    164         if (fadeInPeriod)
    165             this.currentMusic.fade(0.0, this.musicGain, fadeInPeriod);
    166156    }
    167157};
    168158
     
    180170
    181171Music.prototype.start = function()
    182172{
     173    var thePlayer = SoundPlayer();
     174    thePlayer.startMusic();
    183175    this.setState(this.states.PEACE);
    184176};
    185177
  • binaries/data/mods/public/gui/common/functions_utility_music.js

     
    6666
    6767    //console.write("Playing " + randomSoundPath + " ...");
    6868
     69    switch (soundType)
     70    {
     71        case "music":
     72            return new MusicSound(randomSoundPath);
     73        break;
     74        case "ambient":
     75            return new AmbientSound(randomSoundPath);
     76        break;
     77        case "effect":
     78            console.write ("am loading effect '*"+randomSoundPath+"*'");
     79        break;
     80        default:
     81        break;
     82    }
    6983    return new Sound(randomSoundPath);
    7084}
    7185
  • binaries/data/mods/public/hwdetect/hwdetect.js

     
    213213    }
    214214
    215215    // http://trac.wildfiregames.com/ticket/685
    216     if (os_macosx)
    217     {
    218         warnings.push("Audio has been disabled, due to problems with OpenAL on OS X.");
    219         disable_audio = true;
    220     }
     216//  if (os_macosx)
     217//  {
     218//      warnings.push("Audio has been disabled, due to problems with OpenAL on OS X.");
     219//      disable_audio = true;
     220//  }
    221221
    222222    // http://trac.wildfiregames.com/ticket/684
    223223    // https://bugs.freedesktop.org/show_bug.cgi?id=24047
  • binaries/data/config/default.cfg

     
    7373
    7474; GENERAL PREFERENCES:
    7575
    76 sound.mastergain = 0.5
     76sound.mastergain = 0.9
     77sound.musicgain = 0.2
     78sound.ambientgain = 0.6
     79sound.actiongain = 0.7
     80sound.bufferCount = 50
     81sound.bufferSize = 65536
    7782
    7883; Camera control settings
    7984view.scroll.speed = 120.0