Ticket #1223: RealPatch1.diff
File RealPatch1.diff, 200.0 KB (added by , 12 years ago) |
---|
-
binaries/data/config/default.cfg
78 78 79 79 ; GENERAL PREFERENCES: 80 80 81 sound.mastergain = 0.5 81 sound.mastergain = 0.9 82 sound.musicgain = 0.2 83 sound.ambientgain = 0.6 84 sound.actiongain = 0.7 85 sound.bufferCount = 50 86 sound.bufferSize = 65536 82 87 83 88 ; Camera control settings 84 89 view.scroll.speed = 120.0 -
binaries/data/mods/public/audio/voice/hellenes/civ/civ_male_select.xml
8 8 <ConeOuter>360</ConeOuter> 9 9 <Looping>0</Looping> 10 10 <RandOrder>1</RandOrder> 11 <Distanceless>1</Distanceless> 11 12 <RandGain>1</RandGain> 12 13 <GainUpper>1</GainUpper> 13 14 <GainLower>0.8</GainLower> -
binaries/data/mods/public/audio/voice/hellenes/civ/civ_male_attack.xml
8 8 <ConeOuter>360</ConeOuter> 9 9 <Looping>0</Looping> 10 10 <RandOrder>1</RandOrder> 11 <Distanceless>1</Distanceless> 11 12 <RandGain>1</RandGain> 12 13 <GainUpper>1</GainUpper> 13 14 <GainLower>0.8</GainLower> -
binaries/data/mods/public/audio/voice/hellenes/civ/civ_male_ack.xml
8 8 <ConeOuter>360</ConeOuter> 9 9 <Looping>0</Looping> 10 10 <RandOrder>1</RandOrder> 11 <Distanceless>1</Distanceless> 11 12 <RandGain>1</RandGain> 12 13 <GainUpper>1</GainUpper> 13 14 <GainLower>0.8</GainLower> -
binaries/data/mods/public/gui/session/session.js
526 526 // currentAmbient = newRandomSound("ambient", "temperate_", "dayscape"); 527 527 528 528 const AMBIENT = "audio/ambient/dayscape/day_temperate_gen_03.ogg"; 529 currentAmbient = new Sound(AMBIENT);529 currentAmbient = new AmbientSound(AMBIENT); 530 530 531 531 if (currentAmbient) 532 532 { 533 533 currentAmbient.loop(); 534 currentAmbient.setGain(0.8);535 534 } 536 535 break; 537 536 … … 546 545 { 547 546 if (currentAmbient) 548 547 { 549 currentAmbient.f ade(-1, 0.0, 5.0);548 currentAmbient.free(); 550 549 currentAmbient = null; 551 550 } 552 551 } -
binaries/data/mods/public/gui/common/music.js
78 78 case this.states.OFF: 79 79 if (this.isPlaying()) 80 80 { 81 this.currentMusic.fade(-1, 0.0, 3.0);82 th is.currentMusic = null;81 var thePlayer = SoundPlayer(); 82 thePlayer.stopMusic(); 83 83 } 84 84 break; 85 85 … … 146 146 147 147 Music.prototype.switchMusic = function(track, fadeInPeriod, isLooping) 148 148 { 149 if (this.currentMusic) 150 { 151 this.currentMusic.fade(-1, 0.0, 5.0); 152 this.currentMusic = null; 153 } 149 this.currentMusic = new MusicSound(this.RELATIVE_MUSIC_PATH + track); 154 150 155 this.currentMusic = new Sound(this.RELATIVE_MUSIC_PATH + track);156 157 151 if (this.currentMusic) 158 152 { 159 153 if (isLooping) 160 154 this.currentMusic.loop(); 161 155 else 162 156 this.currentMusic.play(); 163 164 if (fadeInPeriod)165 this.currentMusic.fade(0.0, this.musicGain, fadeInPeriod);166 157 } 167 158 }; 168 159 … … 180 171 181 172 Music.prototype.start = function() 182 173 { 174 var thePlayer = SoundPlayer(); 175 thePlayer.startMusic(); 183 176 this.setState(this.states.PEACE); 184 177 }; 185 178 -
binaries/data/mods/public/gui/common/functions_utility_music.js
66 66 67 67 //console.write("Playing " + randomSoundPath + " ..."); 68 68 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 } 69 83 return new Sound(randomSoundPath); 70 84 } 71 85 -
binaries/data/mods/public/hwdetect/hwdetect.js
213 213 } 214 214 215 215 // 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 // } 221 221 222 222 // http://trac.wildfiregames.com/ticket/684 223 223 // https://bugs.freedesktop.org/show_bug.cgi?id=24047 -
build/premake/premake4/build/gmake.unix/Premake4.make
28 28 CPPFLAGS += -MMD -MP $(DEFINES) $(INCLUDES) 29 29 CFLAGS += $(CPPFLAGS) -Wall -Os 30 30 CXXFLAGS += $(CPPFLAGS) -Wall -Os 31 LDFLAGS += -s -rdynamic32 LIBS += -lm -ldl 31 LDFLAGS += 32 LIBS += -lm -ldl -framework CoreServices -framework CoreServices -framework CoreServices -framework CoreServices -framework CoreServices -framework CoreServices 33 33 RESFLAGS += $(DEFINES) $(INCLUDES) 34 34 LDDEPS += 35 35 LINKCMD = $(CC) -o $(TARGET) $(OBJECTS) $(LDFLAGS) $(RESOURCES) $(LDDEPS) $(LIBS) … … 50 50 CPPFLAGS += -MMD -MP $(DEFINES) $(INCLUDES) 51 51 CFLAGS += $(CPPFLAGS) -Wall -g 52 52 CXXFLAGS += $(CPPFLAGS) -Wall -g 53 LDFLAGS += -rdynamic54 LIBS += -lm -ldl 53 LDFLAGS += 54 LIBS += -lm -ldl -framework CoreServices -framework CoreServices -framework CoreServices -framework CoreServices -framework CoreServices -framework CoreServices 55 55 RESFLAGS += $(DEFINES) $(INCLUDES) 56 56 LDDEPS += 57 57 LINKCMD = $(CC) -o $(TARGET) $(OBJECTS) $(LDFLAGS) $(RESOURCES) $(LDDEPS) $(LIBS) … … 302 302 $(SILENT) $(CC) $(PCHINCLUDES) $(CFLAGS) -MF $(OBJDIR)/lauxlib.d -MT "$@" -o "$@" -c "$<" 303 303 304 304 -include $(OBJECTS:%.o=%.d) 305 -include $(GCH:%.h.gch=%.h.d) 306 No newline at end of file 305 -include $(GCH:%.h.gch=%.h.d) -
build/premake/premake4.lua
535 535 "ps/Network", 536 536 "ps/GameSetup", 537 537 "ps/XML", 538 "sound", 538 "soundmanager", 539 "soundmanager/data", 540 "soundmanager/items", 541 "soundmanager/js", 539 542 "scripting", 540 543 "maths", 541 544 "maths/scripting", … … 549 552 "boost", 550 553 "enet", 551 554 "libcurl", 555 "vorbis", 556 "openal" 552 557 } 553 558 setup_static_lib_project("engine", source_dirs, extern_libs, {}) 554 559 … … 569 574 end 570 575 setup_static_lib_project("graphics", source_dirs, extern_libs, {}) 571 576 572 573 577 source_dirs = { 574 578 "tools/atlas/GameInterface", 575 579 "tools/atlas/GameInterface/Handlers" -
source/ps/GameSetup/GameSetup.cpp
25 25 #include "lib/file/common/file_stats.h" 26 26 #include "lib/res/h_mgr.h" 27 27 #include "lib/res/graphics/cursor.h" 28 #include "lib/res/sound/snd_mgr.h" 28 29 29 #include "lib/sysdep/cursor.h" 30 30 #include "lib/sysdep/cpu.h" 31 31 #include "lib/sysdep/gfx.h" … … 86 86 #include "gui/scripting/JSInterface_GUITypes.h" 87 87 #include "gui/scripting/ScriptFunctions.h" 88 88 89 #include "sound/JSI_Sound.h"90 91 89 #include "network/NetServer.h" 92 90 #include "network/NetClient.h" 93 91 … … 102 100 #include "tools/atlas/GameInterface/GameLoop.h" 103 101 #include "tools/atlas/GameInterface/View.h" 104 102 103 #include "soundmanager/CSoundManager.h" 105 104 106 105 #if !(OS_WIN || OS_MACOSX || OS_ANDROID) // assume all other platforms use X11 for wxWidgets 107 106 #define MUST_INIT_X11 1 … … 203 202 { 204 203 PROFILE3("render"); 205 204 205 g_SoundManager->IdleTask(); 206 206 207 ogl_WarnIfError(); 207 208 208 209 g_Profiler2.RecordGPUFrameStart(); … … 330 331 { 331 332 // maths 332 333 JSI_Vector3D::init(); 333 334 335 CSoundManager::ScriptingInit(); 334 336 // graphics 335 337 CGameView::ScriptingInit(); 336 338 … … 338 340 CRenderer::ScriptingInit(); 339 341 340 342 // sound 341 JSI_Sound::ScriptingInit();343 // JSI_Sound::ScriptingInit(); 342 344 343 345 // ps 344 346 JSI_Console::init(); … … 476 478 g_VFS->Mount(L"", modLoosePath / modName/"", flags, priority); 477 479 g_VFS->Mount(L"", modArchivePath / modName/"", flags, priority); 478 480 } 481 482 g_SoundManager = new CSoundManager(); 479 483 480 484 // note: don't bother with g_VFS->TextRepresentation - directories 481 485 // haven't yet been populated and are empty. … … 692 696 // resource 693 697 // first shut down all resource owners, and then the handle manager. 694 698 TIMER_BEGIN(L"resource modules"); 695 snd_shutdown();699 delete g_SoundManager; 696 700 697 701 g_VFS.reset(); 698 702 … … 931 935 // speed up startup by disabling all sound 932 936 // (OpenAL init will be skipped). 933 937 // must be called before first snd_open. 934 snd_disable(true);938 g_SoundManager->SetEnabled( false ); 935 939 } 936 940 937 941 g_GUI = new CGUIManager(g_ScriptingHost.GetScriptInterface()); -
source/ps/GameSetup/Config.cpp
21 21 #include "ps/CConsole.h" 22 22 #include "ps/GameSetup/CmdLineArgs.h" 23 23 #include "lib/timer.h" 24 #include "lib/res/sound/snd_mgr.h"25 24 #include "Config.h" 25 #include "soundmanager/CSoundManager.h" 26 26 27 28 27 // (these variables are documented in the header.) 29 28 30 29 CStrW g_CursorName = L"test"; … … 83 82 CFG_GET_USER_VAL("silhouettes", Bool, g_Silhouettes); 84 83 85 84 float gain = -1.0f; 85 float musicGain = -1.0f; 86 float ambientGain = -1.0f; 87 float actionGain = -1.0f; 88 int bufferCount = 50; 89 unsigned long bufferSize = 65536; 90 86 91 CFG_GET_USER_VAL("sound.mastergain", Float, gain); 87 if(gain >= 0.0f) 88 WARN_IF_ERR(snd_set_master_gain(gain)); 92 CFG_GET_USER_VAL("sound.musicgain", Float, musicGain); 93 CFG_GET_USER_VAL("sound.ambientgain", Float, ambientGain); 94 CFG_GET_USER_VAL("sound.actiongain", Float, actionGain); 95 96 CFG_GET_USER_VAL("sound.bufferCount", Int, bufferCount); 97 CFG_GET_USER_VAL("sound.bufferSize", UnsignedLong, bufferSize); 98 99 g_SoundManager->SetMasterGain( gain ); 100 g_SoundManager->SetMusicGain( musicGain ); 101 g_SoundManager->SetAmbientGain( ambientGain ); 102 g_SoundManager->SetActionGain( actionGain ); 103 104 g_SoundManager->SetMemoryUsage( bufferSize, bufferCount); 89 105 } 90 106 91 107 -
source/ps/Game.cpp
45 45 #include "simulation2/components/ICmpPlayerManager.h" 46 46 47 47 #include "gui/GUIManager.h" 48 #include "soundmanager/CSoundManager.h" 48 49 49 50 extern bool g_GameRestarted; 50 51 … … 290 291 if (doInterpolate) 291 292 { 292 293 m_TurnManager->Interpolate(deltaSimTime, deltaRealTime); 294 g_SoundManager->IdleTask(); 293 295 } 294 296 295 297 // TODO: maybe we should add a CCmpParticleInterface that passes the interpolation commands -
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 25 CSoundItem::CSoundItem() 26 { 27 ResetVars(); 28 } 29 30 CSoundItem::CSoundItem(CSoundData* sndData) 31 { 32 ResetVars(); 33 if ( InitOpenAL() ) 34 Attach( sndData ); 35 } 36 37 CSoundItem::~CSoundItem() 38 { 39 ALuint al_buf; 40 41 Stop(); 42 alSourceUnqueueBuffers(m_ALSource, 1, &al_buf); 43 } 44 45 bool 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 58 void 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 23 CBufferItem::CBufferItem(CSoundData* sndData) 24 { 25 ResetVars(); 26 if ( InitOpenAL() ) 27 Attach( sndData ); 28 } 29 30 31 CBufferItem::~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 47 bool 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 74 void 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 83 void 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 28 class CSoundBase :public ISoundItem 29 { 30 protected: 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 45 public: 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 81 protected: 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 24 CStreamItem::CStreamItem(CSoundData* sndData) 25 { 26 ResetVars(); 27 if ( InitOpenAL() ) 28 Attach( sndData ); 29 } 30 31 CStreamItem::~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 46 bool 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 84 void 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 93 void CStreamItem::SetLooping( bool loops ) 94 { 95 m_Looping = loops; 96 } 97 -
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 26 class CSoundItem :public CSoundBase 27 { 28 protected: 29 30 public: 31 CSoundItem (); 32 CSoundItem (CSoundData* sndData); 33 34 virtual ~CSoundItem (); 35 void Attach ( CSoundData* itemData ); 36 bool IdleTask (); 37 38 protected: 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 24 class CBufferItem : public CSoundBase 25 { 26 public: 27 CBufferItem (CSoundData* sndData); 28 virtual ~CBufferItem (); 29 30 virtual void SetLooping ( bool loops ); 31 virtual bool IdleTask (); 32 33 protected: 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 24 class CStreamItem : public CSoundBase 25 { 26 public: 27 CStreamItem (CSoundData* sndData); 28 virtual ~CStreamItem (); 29 30 virtual void SetLooping ( bool loops ); 31 virtual bool IdleTask (); 32 33 protected: 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 26 class ISoundItem 27 { 28 29 public: 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 28 CSoundBase::CSoundBase() 29 { 30 ResetVars(); 31 } 32 33 CSoundBase::~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 50 void 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 65 void 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 74 void CSoundBase::SetGain(ALfloat gain) 75 { 76 alSourcef(m_ALSource, AL_GAIN, gain); 77 } 78 79 void CSoundBase::SetRollOff(ALfloat rolls) 80 { 81 alSourcef(m_ALSource, AL_ROLLOFF_FACTOR, rolls); 82 } 83 84 void CSoundBase::EnsurePlay() 85 { 86 if ( m_ShouldBePlaying && !IsPlaying() ) 87 Play(); 88 } 89 90 void 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 97 void CSoundBase::SetPitch(ALfloat pitch) 98 { 99 alSourcef( m_ALSource, AL_PITCH, pitch); 100 } 101 102 void CSoundBase::SetDirection(const CVector3D& direction) 103 { 104 alSourcefv( m_ALSource, AL_DIRECTION, direction.GetFloatArray() ); 105 } 106 107 bool 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 131 bool 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 139 void CSoundBase::SetLastPlay( bool last ) 140 { 141 m_LastPlay = last; 142 } 143 144 bool CSoundBase::IdleTask() 145 { 146 return true; 147 } 148 149 void CSoundBase::SetLocation (const CVector3D& position) 150 { 151 alSourcefv( m_ALSource,AL_POSITION, position.GetFloatArray() ); 152 } 153 154 bool 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 176 bool CSoundBase::GetLooping() 177 { 178 return m_Looping; 179 } 180 181 void CSoundBase::SetLooping( bool loops ) 182 { 183 m_Looping = loops; 184 alSourcei( m_ALSource, AL_LOOPING, loops ? AL_TRUE : AL_FALSE ); 185 } 186 187 void CSoundBase::Play() 188 { 189 m_ShouldBePlaying = true; 190 if ( m_ALSource != 0 ) 191 alSourcePlay( m_ALSource ); 192 } 193 194 void CSoundBase::PlayAndDelete() 195 { 196 SetLastPlay( true ); 197 Play(); 198 } 199 200 void CSoundBase::FadeAndDelete( double fadeTime ) 201 { 202 SetLastPlay( true ); 203 FadeToIn( 0, fadeTime ); 204 } 205 206 void CSoundBase::StopAndDelete() 207 { 208 SetLastPlay( true ); 209 Stop(); 210 } 211 212 void CSoundBase::PlayLoop() 213 { 214 if ( m_ALSource != 0 ) 215 { 216 SetLooping( true ); 217 Play(); 218 } 219 } 220 221 void 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 234 void CSoundBase::PlayAsMusic() 235 { 236 g_SoundManager->SetMusicItem( this ); 237 } 238 239 void CSoundBase::PlayAsAmbient() 240 { 241 g_SoundManager->SetAmbientItem( this ); 242 } 243 244 void 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 257 const char* CSoundBase::Name() 258 { 259 return m_Name->c_str(); 260 } 261 262 std::string CSoundBase::GetName() 263 { 264 return std::string( m_Name->c_str() ); 265 } 266 267 void 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/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 31 CSoundManager* g_SoundManager; 32 33 void CSoundManager::ScriptingInit() 34 { 35 JAmbientSound::ScriptingInit(); 36 JMusicSound::ScriptingInit(); 37 JSound::ScriptingInit(); 38 JSoundPlayer::ScriptingInit(); 39 } 40 41 CSoundManager::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 57 CSoundManager::~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 77 Status 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 } 108 void CSoundManager::SetMemoryUsage( long bufferSize, int bufferCount ) 109 { 110 m_BufferCount = bufferCount; 111 m_BufferSize = bufferSize; 112 } 113 long CSoundManager::GetBufferCount() 114 { 115 return m_BufferCount; 116 } 117 long CSoundManager::GetBufferSize() 118 { 119 return m_BufferSize; 120 } 121 122 123 void CSoundManager::SetMasterGain( float gain) 124 { 125 m_Gain = gain; 126 } 127 void CSoundManager::SetMusicGain( float gain) 128 { 129 m_MusicGain = gain; 130 } 131 void CSoundManager::SetAmbientGain( float gain) 132 { 133 m_AmbientGain = gain; 134 } 135 void CSoundManager::SetActionGain( float gain) 136 { 137 m_ActionGain = gain; 138 } 139 140 141 ISoundItem* 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 168 unsigned long CSoundManager::Count() 169 { 170 return m_Items->size(); 171 } 172 173 void 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 205 void 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 215 ISoundItem* CSoundManager::GetSoundItem( unsigned long itemRow ) 216 { 217 return (*m_Items)[itemRow]; 218 } 219 220 void 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 233 void CSoundManager::SetEnabled( bool doEnable ) 234 { 235 m_Enabled = doEnable; 236 } 237 238 void 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 } 249 void 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 260 void 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 270 void 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 293 void 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 26 class COggData : public CSoundData 27 { 28 ALuint m_Format; 29 long m_Frequency; 30 31 public: 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 42 protected: 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 27 DataMap* CSoundData::sSoundData = NULL; 28 29 CSoundData::CSoundData() 30 { 31 InitProperties(); 32 } 33 34 CSoundData::~CSoundData() 35 { 36 if ( m_ALBuffer != 0 ) 37 alDeleteBuffers( 1, &m_ALBuffer ); 38 } 39 40 void CSoundData::InitProperties() 41 { 42 m_ALBuffer = 0; 43 m_RetentionCount = 0; 44 } 45 46 void 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 61 CSoundData* 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 87 bool CSoundData::IsOneShot() 88 { 89 return true; 90 } 91 92 93 CSoundData* 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 111 ALsizei CSoundData::GetBufferCount() 112 { 113 return 1; 114 } 115 116 CStrW CSoundData::GetFileName() 117 { 118 return m_FileName; 119 } 120 121 122 123 CSoundData* CSoundData::IncrementCount() 124 { 125 m_RetentionCount++; 126 return this; 127 } 128 129 bool CSoundData::DecrementCount() 130 { 131 m_RetentionCount--; 132 133 return ( m_RetentionCount <= 0 ); 134 } 135 136 ALuint CSoundData::GetBuffer() 137 { 138 return m_ALBuffer; 139 } 140 ALuint* 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 27 COggData::COggData() 28 { 29 m_OneShot = false; 30 } 31 32 COggData::~COggData() 33 { 34 alDeleteBuffers( m_BuffersUsed, m_Buffer ); 35 ov_clear(&m_vf); 36 } 37 38 void COggData::SetFormatAndFreq( int form, ALsizei freq) 39 { 40 m_Format = form; 41 m_Frequency = freq; 42 } 43 44 bool 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 87 ALsizei COggData::GetBufferCount() 88 { 89 return m_BuffersUsed; 90 } 91 92 bool COggData::IsFileFinished() 93 { 94 return m_FileFinished; 95 } 96 97 void COggData::ResetFile() 98 { 99 ov_time_seek( &m_vf, 0 ); 100 m_current_section = 0; 101 m_FileFinished = false; 102 } 103 104 bool COggData::IsOneShot() 105 { 106 return m_OneShot; 107 } 108 109 int 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 150 ALuint COggData::GetBuffer() 151 { 152 return m_Buffer[0]; 153 } 154 155 ALuint* 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 28 class CSoundData; 29 typedef std::map<std::wstring, CSoundData*> DataMap; 30 31 32 33 class CSoundData 34 { 35 public: 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 56 protected: 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 28 typedef std::vector<ISoundItem*> ItemsList; 29 30 31 class CSoundManager 32 { 33 protected: 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 50 public: 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 ); 84 protected: 85 void InitListener(); 86 virtual Status AlcInit(); 87 88 }; 89 90 91 92 extern CSoundManager* g_SoundManager; 93 94 95 96 97 98 99 100 #endif -
source/soundmanager/js/JSoundPlayer.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 "JSoundPlayer.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 28 JSoundPlayer::JSoundPlayer() 29 { 30 } 31 32 JSoundPlayer::~JSoundPlayer() 33 { 34 } 35 36 bool JSoundPlayer::StartMusic(JSContext* UNUSED(cx), uintN UNUSED(argc), jsval* UNUSED(argv)) 37 { 38 g_SoundManager->SetMusicEnabled( true ); 39 40 return true; 41 } 42 43 // request the sound be played until free() is called. returns immediately. 44 bool JSoundPlayer::StopMusic(JSContext* UNUSED(cx), uintN UNUSED(argc), jsval* UNUSED(argv)) 45 { 46 g_SoundManager->SetMusicEnabled( false ); 47 48 return true; 49 } 50 51 void JSoundPlayer::ScriptingInit() 52 { 53 AddMethod<CStr, &JSoundPlayer::ToString>("toString", 0); 54 AddMethod<bool, &JSoundPlayer::StartMusic>("startMusic", 0); 55 AddMethod<bool, &JSoundPlayer::StopMusic>("stopMusic", 0); 56 57 CJSObject<JSoundPlayer>::ScriptingInit("SoundPlayer", &JSoundPlayer::Construct, 1); 58 } 59 60 JSBool JSoundPlayer::Construct(JSContext* cx, uintN UNUSED(argc), jsval* vp) 61 { 62 JSoundPlayer* newObject = new JSoundPlayer(); 63 newObject->m_EngineOwned = false; 64 JS_SET_RVAL(cx, vp, OBJECT_TO_JSVAL(newObject->GetScript())); 65 66 return JS_TRUE; 67 } 68 69 CStr JSoundPlayer::ToString(JSContext* UNUSED(cx), uintN UNUSED(argc), jsval* UNUSED(argv)) 70 { 71 std::ostringstream stringStream; 72 stringStream << "[object MusicPlayer]"; 73 74 return stringStream.str(); 75 } 76 -
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 27 JAmbientSound::JAmbientSound(const VfsPath& pathname) 28 { 29 m_FileName = new VfsPath( pathname.string().c_str() ); 30 } 31 32 JAmbientSound::~JAmbientSound() 33 { 34 } 35 36 37 // start playing the sound, all ambient sounds loop 38 bool 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 48 bool 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 } 55 bool 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 65 void 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 75 CStr 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 84 JSBool 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 27 JSound::JSound(const VfsPath& pathname) 28 { 29 m_SndItem = g_SoundManager->LoadItem( &pathname ); 30 } 31 32 JSound::~JSound() 33 { 34 if ( m_SndItem ) 35 { 36 m_SndItem->FadeAndDelete(0.2); 37 m_SndItem = 0; 38 } 39 } 40 41 bool JSound::ClearSoundItem() 42 { 43 m_SndItem = 0L; 44 return true; 45 } 46 47 bool 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 60 bool 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 73 bool 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 91 bool 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 110 bool 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 120 bool 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 130 bool 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 141 void 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 155 CStr JSound::ToString(JSContext* UNUSED(cx), uintN UNUSED(argc), jsval* UNUSED(argv)) 156 { 157 return "[object Sound: " + ( m_SndItem ? m_SndItem->GetName() : "(null)" ) + "]"; 158 } 159 160 JSBool 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 28 JMusicSound::JMusicSound(const VfsPath& pathname) 29 { 30 m_FileName = new VfsPath( pathname.string().c_str() ); 31 } 32 33 JMusicSound::~JMusicSound() 34 { 35 delete m_FileName; 36 } 37 38 bool 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. 48 bool 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 57 void 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 66 CStr 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 75 JSBool 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/JSoundPlayer.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_JSOUNDPLAYER 33 #define INCLUDED_JSOUNDPLAYER 34 35 #include "scripting/ScriptableObject.h" 36 #include "soundmanager/items/ISoundItem.h" 37 38 class JSoundPlayer : public CJSObject<JSoundPlayer> 39 { 40 public: 41 JSoundPlayer(); 42 virtual ~JSoundPlayer(); 43 44 // Script-bound functions 45 46 CStr ToString(JSContext* cx, uintN argc, jsval* argv); 47 48 bool StartMusic(JSContext* UNUSED(cx), uintN UNUSED(argc), jsval* UNUSED(argv)); 49 bool StopMusic(JSContext* UNUSED(cx), uintN UNUSED(argc), jsval* UNUSED(argv)); 50 51 static JSBool Construct(JSContext* cx, uintN argc, jsval* vp); 52 static void ScriptingInit(); 53 54 protected: 55 VfsPath* m_FileName; 56 }; 57 58 #endif // #ifndef INCLUDED_JSOUNDPLAYER -
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 45 extern CGame *g_Game; 46 47 #define PI 3.14126f 48 49 50 static const bool DISABLE_INTENSITY = true; // disable for now since it's broken 51 52 void CSMSoundGroup::SetGain(float gain) 53 { 54 gain = std::min(gain, 1.0f); 55 m_Gain = gain; 56 } 57 58 void 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 81 CSMSoundGroup::CSMSoundGroup() 82 { 83 SetDefaultValues(); 84 } 85 86 CSMSoundGroup::CSMSoundGroup(const VfsPath& pathnameXML) 87 { 88 SetDefaultValues(); 89 LoadSoundGroup(pathnameXML); 90 } 91 92 CSMSoundGroup::~CSMSoundGroup() 93 { 94 // clean up all the handles from this group. 95 ReleaseGroup(); 96 } 97 98 static float RandFloat(float min, float max) 99 { 100 return float(rand(min*100.0f, max*100.0f) / 100.0f); 101 } 102 103 float 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 157 void 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 199 static 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 206 void 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 216 void 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 237 void 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 246 void CSMSoundGroup::Update(float UNUSED(TimeSinceLastFrame)) 247 { 248 } 249 250 bool 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 24 class JAmbientSound : public CJSObject<JAmbientSound> 25 { 26 public: 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(); 45 protected: 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 38 class JSound : public CJSObject<JSound> 39 { 40 public: 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 63 protected: 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 38 class JMusicSound : public CJSObject<JMusicSound> 39 { 40 public: 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 55 protected: 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 /* 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_SMSOUNDGROUP 53 #define INCLUDED_SMSOUNDGROUP 54 55 #include "lib/file/vfs/vfs_path.h" 56 57 #include <vector> 58 59 class ISoundItem; 60 61 enum eSndGrpFlags 62 { 63 eRandOrder = 0x01, 64 eRandGain = 0x02, 65 eRandPitch = 0x04, 66 eLoop = 0x08, 67 eOmnipresent = 0x10, 68 eDistanceless = 0x20 69 }; 70 71 72 class CSMSoundGroup 73 { 74 NONCOPYABLE(CSMSoundGroup); 75 public: 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 104 private: 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 modify5 * it under the terms of the GNU General Public License as published by6 * the Free Software Foundation, either version 2 of the License, or7 * (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 of11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the12 * GNU General Public License for more details.13 *14 * You should have received a copy of the GNU General Public License15 * along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.16 */17 18 // JS sound binding19 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 met23 // (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 usefulness27 // (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_SOUND33 #define INCLUDED_JSI_SOUND34 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 functions50 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 modify5 * it under the terms of the GNU General Public License as published by6 * the Free Software Foundation, either version 2 of the License, or7 * (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 of11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the12 * GNU General Public License for more details.13 *14 * You should have received a copy of the GNU General Public License15 * along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.16 */17 18 /**19 * =========================================================================20 * File : SoundGroup.cpp21 * 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 broken41 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 uninitialized69 }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's96 // position, which is achieved by setting position to 0 and97 // 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_GainUpper112 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 file135 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 else147 {148 // if no sounds, return149 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 index180 // get rid of the used handles181 snd_group.clear();182 // clear out the old timers183 playtimes.clear();184 //Reload the sounds185 /*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 file238 #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 AT262 #undef EL263 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 BUILDING2 ==================================================3 4 data flow:5 C++ JS6 > list of visible entities (1)7 < list of sound requests and their weights8 list of changed sounds9 10 C++ code generates list of entities on screen11 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 weird14 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_building17 optimization for later: get all patches within 3d distance cutoff; discard all not within frustum; only test18 entities within those remaining patches.19 pass that to JS decideWhatToPlay20 it returns list of sounds it wants playing21 format: see AmbientSoundReq below22 C++ sound engine compares its list of currently sounds;23 fades out those no longer wanted and starts new ones immediately24 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 quieter33 // values: 0..1; probably calculated from distance to camera34 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 viewer43 // (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) OR45 // building is close to center of screen (as determined via ray cast method; skip this if the camera46 // 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.ambientGroup50 51 forall ambient types (farm, dock etc.) in decreasing order of priority52 stop if >= 3 sounds in list53 if enough buildings on screen54 desired_ambient_sounds[i++] = ent.ambientGroup55 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 SOUNDS84 ==================================================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 modify5 * it under the terms of the GNU General Public License as published by6 * the Free Software Foundation, either version 2 of the License, or7 * (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 of11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the12 * GNU General Public License for more details.13 *14 * You should have received a copy of the GNU General Public License15 * along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.16 */17 18 /**19 * =========================================================================20 * File : SoundGroup.h21 * 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.xml32 <?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_SOUNDGROUP53 #define INCLUDED_SOUNDGROUP54 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 eSndGrpFlags64 {65 eRandOrder = 0x01,66 eRandGain = 0x02,67 eRandPitch = 0x04,68 eLoop = 0x08,69 eOmnipresent = 0x1070 };71 72 73 class CSoundGroup74 {75 NONCOPYABLE(CSoundGroup);76 public:77 CSoundGroup(const VfsPath& pathnameXML);78 CSoundGroup(void);79 ~CSoundGroup(void);80 81 // Play next sound in group82 // @param position world position of the entity generating the sound83 // (ignored if the eOmnipresent flag is set)84 void PlayNext(const CVector3D& position);85 86 // Load a group87 bool LoadSoundGroup(const VfsPath& pathnameXML);88 89 void Reload();90 91 // Release all remaining loaded handles92 void ReleaseGroup();93 94 // Update SoundGroup, remove dead sounds from intensity count95 void Update(float TimeSinceLastFrame);96 97 // Set a flag using a value from eSndGrpFlags98 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 play109 110 Handle m_hReplacement;111 112 std::vector<Handle> snd_group; // we store the handles so we can load now and play later113 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 resources116 std::wstring m_intensity_file; // the replacement aggregate 'intense' sound117 118 float m_CurTime; // Time elapsed since soundgroup was created119 float m_TimeWindow; // The Intensity Threshold Window120 size_t m_IntensityThreshold; // the allowable intensity before a sound switch121 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 modify5 * it under the terms of the GNU General Public License as published by6 * the Free Software Foundation, either version 2 of the License, or7 * (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 of11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the12 * GNU General Public License for more details.13 *14 * You should have received a copy of the GNU General Public License15 * 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_filename25 #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 to33 // the script, so just reset the handle to 0 so all subsequent method34 // calls will do nothing35 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); // FIXME56 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); // FIXME70 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); // FIXME84 85 CVector3D pos;86 // absolute world coords87 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 listener90 // (we don't need displacement from the listener, e.g. always behind)91 else92 (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); // FIXME104 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 a114 // fade to 0 has completed. however, we're still holding on to a115 // reference, which will cause a double-free warning when Free() is116 // called from the dtor. solution is to neuter our Handle by117 // setting it to 0 (ok since it'll be freed). this does mean that118 // 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 to134 // know whether or not it's still valid (since it might have finished playing135 // 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 0161 return true;162 }163 164 165 // Script-bound functions166 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
375 375 g_Game->Update(realTimeSinceLastFrame); 376 376 377 377 g_Game->GetView()->Update(float(realTimeSinceLastFrame)); 378 379 CCamera* camera = g_Game->GetView()->GetCamera();380 CMatrix3D& orientation = camera->m_Orientation;381 float* pos = &orientation._data[12];382 float* dir = &orientation._data[8];383 float* up = &orientation._data[4];384 // HACK: otherwise sound effects are L/R flipped. No idea what else385 // is going wrong, because the listener and camera are supposed to386 // coincide in position and orientation.387 float down[3] = { -up[0], -up[1], -up[2] };388 389 {390 PROFILE3("sound update");391 if (snd_update(pos, dir, down) < 0)392 debug_printf(L"snd_update failed\n");393 }394 378 } 395 else396 {397 PROFILE3("sound update (0)");398 if (snd_update(0, 0, 0) < 0)399 debug_printf(L"snd_update (pos=0 version) failed\n");400 }401 379 402 380 // Immediately flush any messages produced by simulation code 403 381 if (g_NetClient) … … 480 458 // run non-visual simulation replay if requested 481 459 if (args.Has("replay")) 482 460 { 483 snd_disable(true);484 485 461 Paths paths(args); 486 462 g_VFS = CreateVfs(20 * MiB); 487 463 g_VFS->Mount(L"cache/", paths.Cache(), VFS_MOUNT_ARCHIVABLE); -
source/simulation2/components/CCmpSoundManager.cpp
24 24 #include "simulation2/MessageTypes.h" 25 25 #include "simulation2/components/ICmpPosition.h" 26 26 #include "simulation2/components/ICmpRangeManager.h" 27 #include "sound /SoundGroup.h"27 #include "soundmanager/js/SMSoundGroup.h" 28 28 29 29 class CCmpSoundManager : public ICmpSoundManager 30 30 { … … 36 36 37 37 DEFAULT_COMPONENT_ALLOCATOR(SoundManager) 38 38 39 std::map<std::wstring, CS oundGroup*> m_SoundGroups;39 std::map<std::wstring, CSMSoundGroup*> m_SoundGroups; 40 40 41 41 static std::string GetSchema() 42 42 { … … 49 49 50 50 virtual void Deinit() 51 51 { 52 for (std::map<std::wstring, CS oundGroup*>::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) 53 53 delete it->second; 54 54 m_SoundGroups.clear(); 55 55 } … … 76 76 // or on some other timer? 77 77 const CMessageUpdate& msgData = static_cast<const CMessageUpdate&> (msg); 78 78 float t = msgData.turnLength.ToFloat(); 79 for (std::map<std::wstring, CS oundGroup*>::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) 80 80 if (it->second) 81 81 it->second->Update(t); 82 82 break; … … 87 87 virtual void PlaySoundGroup(std::wstring name, entity_id_t source) 88 88 { 89 89 // Make sure the sound group is loaded 90 CS oundGroup* group;90 CSMSoundGroup* group; 91 91 if (m_SoundGroups.find(name) == m_SoundGroups.end()) 92 92 { 93 group = new CS oundGroup();93 group = new CSMSoundGroup(); 94 94 if (!group->LoadSoundGroup(L"audio/" + name)) 95 95 { 96 96 LOGERROR(L"Failed to load sound group '%ls'", name.c_str()); -
source/lib/res/sound/ogg.h
1 #ifndef INCLUDED_OGG2 #define INCLUDED_OGG3 4 #include "lib/config2.h"5 6 #if CONFIG2_AUDIO7 8 #include "lib/external_libraries/openal.h"9 #include "lib/file/vfs/vfs.h"10 11 class OggStream12 {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) Status20 **/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_AUDIO35 36 #endif // INCLUDED_OGG -
source/lib/res/sound/snd_mgr.cpp
1 /* Copyright (c) 2010 Wildfire Games2 *3 * Permission is hereby granted, free of charge, to any person obtaining4 * a copy of this software and associated documentation files (the5 * "Software"), to deal in the Software without restriction, including6 * without limitation the rights to use, copy, modify, merge, publish,7 * distribute, sublicense, and/or sell copies of the Software, and to8 * permit persons to whom the Software is furnished to do so, subject to9 * the following conditions:10 *11 * The above copyright notice and this permission notice shall be included12 * 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 OF16 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.17 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY18 * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,19 * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE20 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.21 */22 23 /*24 * OpenAL sound engine. handles sound I/O, buffer suballocation and25 * 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 contents32 #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_init46 #if OS_WIN47 # include "lib/sysdep/os/win/win.h"48 #endif49 50 #include "lib/timer.h"51 #include "lib/app_hooks.h"52 #include "lib/sysdep/cpu.h" // cpu_CAS53 54 #if CONFIG2_AUDIO55 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 context64 // readies OpenAL for use; allows specifying the device.65 // - al_listener*: OpenAL listener66 // owns position/orientation and master gain.67 // - al_buf*: OpenAL buffer suballocator68 // for convenience; also makes sure all have been freed at exit.69 // - al_src*: OpenAL source suballocator70 // avoids high source alloc cost. also enforces user-set source limit.71 // - al_init*: OpenAL startup mechanism72 // allows deferred init (speeding up start time) and runtime reset.73 // - snd_dev*: device enumeration74 // lists names of all available devices (for sound options screen).75 // - hsd_list*: list of SndData instances76 // ensures all are freed when desired (despite being cached).77 // - snd_data*: sound data provider78 // 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 source82 // owns source properties and queue, references SndData.83 // - vm*: voice management84 // grants the currently most 'important' sounds a hardware voice.85 86 87 // indicates OpenAL is ready for use. checked by other components88 // 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 one112 * 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 default143 * choice, which will also be used if this routine is never called.144 * @return Status145 *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 list148 * 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) and154 * 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 device160 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 device173 else174 {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 Status211 */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 needed220 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 device232 #if OS_UNIX233 ret = INFO::OK;234 #else235 ret = ERR::FAIL;236 #endif237 }238 239 // make note of which sound device is actually being used240 // (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 to253 // 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 OpenAL261 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 Status292 */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 vector313 * @param dir view direction314 * @param up up vector315 */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 vector335 * @return float euclidean distance squared336 */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 we349 // 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 buffer359 * @param size size of buffer in bytes360 * @param fmt AL_FORMAT_ * describing the sound data361 * @param freq sampling frequency (typically 22050 Hz)362 * @return ALuint buffer name363 */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 name383 */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 and413 // pass them out as needed (alGenSources is quite slow, taking 3..5 ms per414 // source returned). also responsible for enforcing user-specified limit415 // 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 AllocationState428 {429 kAvailable = 0, // (must match zero-initialization of al_srcs_allocationStates)430 kInUse431 };432 433 // note: we want to catch double-free bugs and ensure all sources434 // are released at exit, but OpenAL doesn't specify an always-invalid435 // 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 give504 }505 506 507 /**508 * mark a source as free and available for reuse.509 *510 * @param al_src source name511 */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 it518 {519 const size_t i = pos - al_srcs;520 ENSURE(cpu_CAS(&al_srcs_allocationStates[i], kInUse, kAvailable));521 }522 else523 DEBUG_WARN_ERR(ERR::LOGIC); // al_src wasn't in al_srcs524 }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 stricter531 * implementation- defined ceiling anyway.532 *533 * @param limit max. number of sources534 * @return Status535 */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 anything540 // 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 it548 // determines how many sources may be returned.549 // there's no return value to indicate this because the cap is550 // precisely that - an upper limit only, we don't care if it can't be met.551 else552 return INFO::OK;553 }554 555 556 //-----------------------------------------------------------------------------557 // OpenAL startup mechanism: allows deferring init until sounds are actually558 // 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 Status567 */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 Status622 */623 static Status al_reinit()624 {625 // not yet initialized. settings have been saved, and will be626 // 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 disabled644 */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; // NOWARN650 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 pointless662 return INFO::OK;663 }664 else665 return snd_init(); // note: won't return ERR::AGAIN, since snd_disabled == false666 }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_all676 }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 by691 * snd_dev_next).692 *693 * may be called each time the device list is needed.694 *695 * @return Status; always successful unless the requisite device696 * 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 returned717 */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 returns730 // 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 unsafe737 // => Handle-based access is required738 // - 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, but746 * playing multiple instances of them would require separate positions etc.747 * since the same clip is often played concurrently and we can't guarantee748 * they will never exceed the size of a stream, it makes sense to support a749 * separate "clip" data type that allocates enough storage and avoids needing750 * the stream position/list of buffers.751 */752 enum SndDataType753 {754 SD_CLIP,755 SD_STREAM756 };757 758 /**759 * Holder for sound data - either a clip, or stream.760 */761 struct SndData762 {763 ALenum al_fmt;764 ALsizei al_freq;765 766 SndDataType type;767 768 ALuint al_buf; // valid if type == SD_CLIP769 OggStreamPtr ogg; // valid if type == SD_STREAM770 };771 772 H_TYPE_DEFINE(SndData);773 774 775 //-----------------------------------------------------------------------------776 // SndData instance list: ensures all allocated since last al_shutdown777 // are freed when desired (they are cached => extra work needed).778 779 // rationale: all SndData objects (actually, their OpenAL buffers) must be780 // 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 runtime786 // (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 after790 // H_TYPE_DEFINE(SndData), since h_force_free needs the handle type.791 //792 // we never need to delete single entries: hsd_list_free_all793 // (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 SndData802 */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 source822 // was active when al_shutdown was called, it will already have been823 // freed (list_free_all would free the source; it then releases824 // its SndData reference, which closes the instance because it's825 // RES_UNIQUE).826 //827 // NB: re-initializing the sound library (e.g. after changing828 // HW settings) requires all handles to be freed, even if cached.829 // hence, we use h_force_free. unfortunately this causes the830 // handle's tag to be ignored. it is conceivable that the wrong831 // handle could be freed here.832 //833 // we rule this out with the following argument. either we're834 // called when re-initializing sound or at exit. in the former835 // case, h_force_free does check the handle type: only sounds are836 // ever freed. we don't care if the wrong one is closed since all837 // must be stomped upon. in the latter case, it definitely doesn't838 // matter what we free. hence, no problem.839 }840 841 // leave its memory intact, so we don't have to reallocate it later842 // 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 else856 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 archives862 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 infeasible868 // due to a seriously watered down spec with many incompatible variants.869 // pulling in an external library (e.g. freealut) is deemed not worth the870 // 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 #else875 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 #endif880 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't885 // 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 something896 ENSURE(size != data.size()); // shouldn't be limited by buffer size897 sd->al_buf = al_buf_alloc(&data[0], (ALsizei)size, sd->al_fmt, sd->al_freq);898 sd->ogg.reset();899 }900 else901 sd->al_buf = 0;902 903 // note: to avoid polluting hsd_list with invalid handles, we ensure904 // 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) // suspicious916 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 else928 WARN_RETURN(ERR::_15); // invalid type929 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 failure945 */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 Status956 */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 name1000 * @return Status1001 */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 else1012 al_buf_free(al_buf);1013 1014 return INFO::OK;1015 }1016 1017 1018 //-----------------------------------------------------------------------------1019 // fading1020 //-----------------------------------------------------------------------------1021 1022 /**1023 * control block for a fade operation.1024 */1025 struct FadeInfo1026 {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**31042 return t*t*t;1043 }1044 1045 static float fade_factor_s_curve(float t)1046 {1047 // cosine curve1048 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 FadeRet1059 {1060 FADE_NO_CHANGE,1061 FADE_CHANGED,1062 FADE_TO_0_FINISHED1063 };1064 1065 /**1066 * Carry out the requested fade operation.1067 *1068 * This is called for each active VSrc; if they have no fade operation1069 * active, nothing happens. Note: as an optimization, we could make a1070 * list of VSrc with fade active and only call this for those;1071 * not yet necessary, though.1072 *1073 * @param fi Describes the fade operation1074 * @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 FadeRet1077 */1078 static FadeRet fade(FadeInfo& fi, double cur_time, float& out_val)1079 {1080 // no fade in progress - abort immediately. this check is necessary to1081 // 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 the1089 // processing here, and skip the dangerous division1090 if(fi.type == FT_ABORT || (cur_time >= fi.start_time + fi.length))1091 {1092 // make sure exact value is hit1093 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 fading1100 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 calculation1124 // 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 bool1142 */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 update1156 1157 enum VSrcFlags1158 {1159 // (we can't just test if al_src is zero because that might be a1160 // 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 with1167 // 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_STOP1174 };1175 1176 /**1177 * control block for a virtual source, which represents a sound that the1178 * application wants played. it may or may not be played, depending on1179 * priority and whether an actual OpenAL source is available.1180 */1181 struct VSrc1182 {1183 bool HasSource() const1184 {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 data1195 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 management1211 float static_pri; /// as given by snd_play1212 float cur_pri; /// holds newly calculated value1213 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 and1256 // 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 percent1266 }1267 // read the sound file directly and assume default gain (1.0).1268 else1269 {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 the1321 * sound file name and its gain (0.0 .. 1.0).1322 * otherwise, it is taken to be the sound file name and1323 * gain is set to the default of 1.0 (no attenuation).1324 * @return Handle or Status on failure1325 */1326 Handle snd_open(const PIVFS& vfs, const VfsPath& pathname)1327 {1328 // note: RES_UNIQUE forces each instance to get a new resource1329 // (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 Status1341 */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 VSrc1355 1356 // sorted in descending order of current priority1357 // (we sometimes remove low pri items, which requires moving down1358 // 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 deleted1384 // (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 GreaterPriority1394 {1395 bool operator()(VSrc* vs1, VSrc* vs2) const1396 {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 be1410 * 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) -> no1423 // - fill the hole with e.g. the last element1424 // (vsrcs would no longer be sorted by priority) -> no1425 // - replace with 0 (will require prune_removed and1426 // more work in foreach) -> best alternative1427 vsrcs[i] = 0;1428 return;1429 }1430 }1431 1432 DEBUG_WARN_ERR(ERR::LOGIC); // VSrc not found1433 }1434 1435 1436 struct IsNull1437 {1438 bool operator()(VSrc* vs) const1439 {1440 return (vs == 0);1441 }1442 };1443 1444 /**1445 * remove entries that were set to 0 by list_remove, so that1446 * 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 repeated1561 * calls to timer_Time and inconsistencies when crossfading)1562 */1563 class VsrcUpdater1564 {1565 public:1566 VsrcUpdater(double time)1567 : time(time)1568 {1569 }1570 1571 Status operator()(VSrc* vs) const1572 {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 SndData1604 else1605 {1606 // get next buffer1607 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 forthcoming1611 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 long1617 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 playing1640 return INFO::OK;1641 1642 // try to allocate a source. snd_play calls us in the hope that a source1643 // happens to be free, but if not, just skip the remaining steps and1644 // wait for the next update.1645 if(!al_src_alloc(vs->al_src))1646 return ERR::FAIL; // NOWARN1647 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 decides1666 * 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; // NOWARN1672 1673 // clear the source's buffer queue (necessary because buffers cannot1674 // be deleted at shutdown while still attached to a source).1675 // note: OpenAL 1.1 says all buffers become "processed" when the1676 // source is stopped (so vsrc_deque_finished_bufs ought to have the1677 // desired effect), but that isn't the case on some Linux1678 // 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-111681 // to correctly reset AL_BUFFERS_PROCESSED. in "Re: [Openal-devel]1682 // Questionable "invalid value" from alSourceUnqueueBuffers", the1683 // developer recommended working around this bug by rewinding the1684 // source instead of merely issuing alSourceStop.1685 // reference: http://trac.wildfiregames.com/ticket/2971686 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 API1707 //-----------------------------------------------------------------------------1708 1709 /**1710 * Request the sound be played.1711 *1712 * Once done playing, the sound is automatically closed (allows1713 * 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 VSrc1718 * @param static_pri (min 0 .. max 1, default 0) indicates which sounds are1719 * considered more important; this is attenuated by distance to the1720 * listener (see snd_update).1721 * @return Status1722 */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 failed1728 // 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 if1746 * the sound has already been closed (e.g. it never played).1747 *1748 * @param hvs Handle to VSrc1749 * @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 Status1753 */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 been1771 * closed (e.g. it never played).1772 *1773 * @param hvs Handle to VSrc1774 * @param gain modifier; must be non-negative;1775 * 1 -> unattenuated, 0.5 -> -6 dB, 0 -> silence.1776 * @return Status1777 */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 next1786 // snd_update. attempting this indicates a logic error. we abort to1787 // 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 if1802 * the sound has already been closed (e.g. it never played).1803 *1804 * @param hvs Handle to VSrc1805 * @param pitch shift: 1.0 means no change; each doubling/halving equals a1806 * pitch shift of +/-12 semitones (one octave). zero is invalid.1807 * @return Status1808 */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 if1828 * 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 of1832 * 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 VSrc1837 */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 toward1852 * \<final_gain\> over \<length\> seconds.1853 *1854 * may be called at any time; fails with invalid handle return if1855 * 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 while1861 * one is currently in progress; the old one is dropped.1862 *1863 * @param hvs Handle to VSrc1864 * @param initial_gain gain. if < 0 (an otherwise illegal value), the sound's1865 * 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 or1867 * is aborted, thus allowing fire-and-forget fadeouts. no cases are1868 * foreseen where this is undesirable, and it is easier to implement1869 * 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, see1873 * http://www.transom.org/tools/editing_mixing/200309.stupidfadetricks.html1874 * you can also pass FT_ABORT to stop fading (if in progress) and1875 * set gain to the final_gain parameter passed here.1876 * @return Status1877 */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 playing1908 *1909 * @param hvs - handle to the snd to check1910 * @return bool true if playing1911 **/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 playing1922 if(!vs->HasSource())1923 return false;1924 1925 return true;1926 }1927 1928 1929 //-----------------------------------------------------------------------------1930 // voice management: grants the currently most 'important' sounds1931 // 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 and1943 * 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 else1955 d_2 = al_listener_dist_2(vs->pos);1956 1957 // scale priority down exponentially1958 float e = d_2 / MAX_DIST_2; // 0.0f (close) .. 1.0f (far)1959 1960 // assume farther away than OpenAL cutoff - no sound contribution1961 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 source1995 // (if they don't have one already), after reclaiming all sources from1996 // 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 are2011 * ignored and listener position unchanged; this is useful in case the2012 * world isn't initialized yet.2013 * @param dir view direction2014 * @param up up vector2015 * @return Status2016 */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 initialized2020 // yet (most notably, if sound is disabled). we check for this to2021 // avoid confusing the code below. the caller should complain if2022 // this fails, so report success here (everything will work once2023 // 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_AUDIO2040 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_AUDIO5 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 VorbisFileAdapter53 {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 ignored108 }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 VorbisBufferAdapter125 {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 ignored175 }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 OggStream194 {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 bitstream213 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 it233 // 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; // unused241 const int ret = ov_read(&vf, (char*)buffer+bytesRead, int(size-bytesRead), isBigEndian, wordSize, isSigned, &bitstream);242 if(ret == 0) // EOF243 return (Status)bytesRead;244 else if(ret < 0)245 WARN_RETURN(LibErrorFromVorbis(ret));246 else // success247 {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/lib/res/sound/snd_mgr.h
1 /* Copyright (c) 2010 Wildfire Games2 *3 * Permission is hereby granted, free of charge, to any person obtaining4 * a copy of this software and associated documentation files (the5 * "Software"), to deal in the Software without restriction, including6 * without limitation the rights to use, copy, modify, merge, publish,7 * distribute, sublicense, and/or sell copies of the Software, and to8 * permit persons to whom the Software is furnished to do so, subject to9 * the following conditions:10 *11 * The above copyright notice and this permission notice shall be included12 * 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 OF16 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.17 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY18 * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,19 * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE20 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.21 */22 23 /*24 * OpenAL sound engine. handles sound I/O, buffer suballocation and25 * voice management/prioritization.26 */27 28 #ifndef INCLUDED_SND_MGR29 #define INCLUDED_SND_MGR30 31 #include "lib/res/handle.h"32 #include "lib/file/vfs/vfs.h"33 34 /**35 36 overview37 --------38 39 this module provides a moderately high-level sound interface. basic usage40 is opening a sound and requesting it be played; it is closed automatically41 when playback has finished (fire and forget).42 any number of sound play requests may be issued; the most 'important' ones43 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 setup50 -----------51 52 OpenAL provides portable access to the underlying sound hardware, and53 falls back to software mixing if no acceleration is provided.54 we allow the user to specify the device to use (in case the default55 has problems) and maximum number of sources (to reduce mixing cost).56 57 58 performance59 -----------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 are62 streamed from disk to reduce load time and memory usage. hardware mixing63 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 engine66 is actually needed (i.e. upon first open()). perceived startup time is67 therefore reduced - the user sees e.g. our main menu earlier.68 69 70 terminology71 -----------72 73 "hardware voice" refers to mixing resources on the DSP. strictly74 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 priority82 (e.g. voiceover must not be skipped in favor of seagulls) and83 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 mixed86 into the output channel. the most important VSrc receive an al_src.87 "sound instances" store playback parameters (e.g. position), and88 reference the (centrally cached) "sound data" that will be played.89 90 **/91 92 93 //94 // device enumeration95 //96 97 /**98 * prepare to enumerate all device names (this resets the list returned by99 * 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, but104 * 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 setup121 //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 to127 * OpenAL's default choice, which will also be used if128 * 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 list131 * 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) and137 * 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 than149 * an implementation-defined limit anyway.150 * @return Status151 **/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 Status161 **/162 extern Status snd_set_master_gain(float gain);163 164 165 //166 // sound instance167 //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 vfs174 * @param pathname If a text file (extension ".txt"), it is175 * assumed to be a definition file containing the sound file name and176 * its gain (0.0 .. 1.0).177 * Otherwise, it is taken to be the sound file name and178 * gain is set to the default of 1.0 (no attenuation).179 * @return Handle or Status180 **/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 Status191 **/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 (allows199 * fire-and-forget play code).200 * - if no hardware voice is available, this sound may not be201 * 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 are205 * considered more important (i.e. will override others when no hardware206 * voices are available). the static priority is attenuated by207 * distance to the listener; see snd_update.208 *209 * @return Status210 **/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 if217 * the sound has already been closed (e.g. it never played).218 *219 * @param hvs Handle to the sound.220 * @param x,y,z221 * @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 Status224 **/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 been232 * 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 Status237 **/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 if244 * 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 a247 * pitch shift of +/-12 semitones (one octave). zero is invalid.248 * @return Status249 **/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 if257 * 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 for261 * - 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 Status268 **/269 extern Status snd_set_loop(Handle hvs, bool loop);270 271 /// types of fade in/out operations272 enum FadeType273 {274 FT_NONE, /// currently no fade in progres275 FT_LINEAR, /// f(t) = t276 FT_EXPONENTIAL, /// f(t) = t**3277 FT_S_CURVE, /// cosine curve278 279 FT_ABORT /// abort and mark pending fade as complete280 };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 if286 * the sound has already been closed (e.g. it never played).287 *288 * Gain starts at \<initial_gain\> (immediately) and is moved toward289 * \<final_gain\> over \<length\> seconds.290 *291 * @param hvs Handle to the sound.292 * @param initial_gain293 * @param final_gain294 * @param length295 * @param type Type of fade curve: linear, exponential or S-curve.296 *297 * For guidance on which to use, see298 * http://www.transom.org/tools/editing_mixing/200309.stupidfadetricks.html299 * you can also pass FT_ABORT to stop fading (if in progress) and300 * set gain to the current \<final_gain\> parameter.301 * Special cases:302 * - if \<initial_gain\> \< 0 (an otherwise illegal value), the sound's303 * 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 or305 * is aborted, thus allowing fire-and-forget fadeouts. no cases are306 * foreseen where this is undesirable, and it is easier to implement307 * 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 while313 * one is already in progress; the old one will be discarded.314 *315 * @return Status316 **/317 extern Status snd_fade(Handle hvs, float initial_gain, float final_gain,318 float length, FadeType type);319 320 321 //322 // sound engine323 //324 325 /**326 * (temporarily) disable all sound output.327 *328 * because it causes future snd_open calls to immediately abort before they329 * demand-initialize OpenAL, startup is sped up considerably (500..1000ms).330 * therefore, this must be called before the first snd_open to have331 * any effect; otherwise, the cat will already be out of the bag and332 * we raise a warning.333 *334 * rationale: this is a quick'n dirty way of speeding up startup during335 * development without having to change the game's sound code.336 *337 * can later be called to reactivate sound; all settings ever changed338 * will be applied and subsequent sound load / play requests will work.339 *340 * @param disabled341 * @return Status342 **/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 NULL349 * to avoid updating the listener data; this is useful when the game world350 * has not been initialized yet.351 * @param pos listener's position352 * @param dir listener view direction353 * @param up listener's local up vector354 * @return Status355 **/356 extern Status snd_update(const float* pos, const float* dir, const float* up);357 358 /**359 * find out if a sound is still playing360 *361 * @param hvs Handle to the snd to check.362 * @return bool true if playing363 **/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