diff options
Diffstat (limited to 'engines/sword25/sfx')
-rwxr-xr-x | engines/sword25/sfx/fmodexchannel.cpp | 299 | ||||
-rwxr-xr-x | engines/sword25/sfx/fmodexchannel.h | 69 | ||||
-rwxr-xr-x | engines/sword25/sfx/fmodexexception.h | 51 | ||||
-rwxr-xr-x | engines/sword25/sfx/fmodexresource.cpp | 170 | ||||
-rwxr-xr-x | engines/sword25/sfx/fmodexresource.h | 56 | ||||
-rwxr-xr-x | engines/sword25/sfx/fmodexsound.cpp | 889 | ||||
-rwxr-xr-x | engines/sword25/sfx/fmodexsound.h | 142 | ||||
-rwxr-xr-x | engines/sword25/sfx/soundengine.cpp | 36 | ||||
-rwxr-xr-x | engines/sword25/sfx/soundengine.h | 253 | ||||
-rwxr-xr-x | engines/sword25/sfx/soundengine_script.cpp | 397 |
10 files changed, 2362 insertions, 0 deletions
diff --git a/engines/sword25/sfx/fmodexchannel.cpp b/engines/sword25/sfx/fmodexchannel.cpp new file mode 100755 index 0000000000..88ad9640c5 --- /dev/null +++ b/engines/sword25/sfx/fmodexchannel.cpp @@ -0,0 +1,299 @@ +// ----------------------------------------------------------------------------- +// This file is part of Broken Sword 2.5 +// Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdörfer +// +// Broken Sword 2.5 is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// Broken Sword 2.5 is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Broken Sword 2.5; if not, write to the Free Software +// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +// ----------------------------------------------------------------------------- + +// ----------------------------------------------------------------------------- +// Logging +// ----------------------------------------------------------------------------- + +#define BS_LOG_PREFIX "FMODEXCHANNEL" + +// ----------------------------------------------------------------------------- +// Includes +// ----------------------------------------------------------------------------- + +#include "fmod.h" +#include "fmodexexception.h" +#include "fmodexchannel.h" + +// ----------------------------------------------------------------------------- +// Konstruktion / Destruktion +// ----------------------------------------------------------------------------- + +BS_FMODExChannel::BS_FMODExChannel(FMOD_CHANNEL * ChannelPtr, FMOD_SOUND * SoundPtr) : + m_ChannelPtr(ChannelPtr), + m_SoundPtr(SoundPtr) +{ +} + +// ----------------------------------------------------------------------------- + +BS_FMODExChannel::~BS_FMODExChannel() +{ + if (m_ChannelPtr) FMOD_Channel_Stop(m_ChannelPtr); + if (m_SoundPtr) FMOD_Sound_Release(m_SoundPtr); +} + +// ----------------------------------------------------------------------------- +// FMOD Ex macht alle Kanäle ungültig, sobald sie nicht mehr abgespielt werden, +// oder wenn der Kanal in der zwischenzeit neu vergeben wurde. +// Dann führen alle Aufrufe von Funktionen dieser Kanäle zu dem Fehlern +// FMOD_ERR_INVALID_HANDLE oder FMOD_ERR_CHANNEL_STOLEN +// Dieses Soundsystem entfernt aber nur jeden Frame alle toten Kanäle. Daher +// kann es vorkommen, dass an einem bereits toten Kanal Aufrufe getätigt werden. +// Diese Fehler werden daher von den folgenden Methoden ignoriert. +// ----------------------------------------------------------------------------- + +namespace +{ + bool IsImportantError(FMOD_RESULT Result) + { + return Result != FMOD_OK && Result != FMOD_ERR_INVALID_HANDLE && Result != FMOD_ERR_CHANNEL_STOLEN; + } +} + +// ----------------------------------------------------------------------------- +// Setter +// ----------------------------------------------------------------------------- + +bool BS_FMODExChannel::SetPaused(bool Paused) +{ + BS_ASSERT(m_ChannelPtr); + + FMOD_RESULT Result = FMOD_Channel_SetPaused(m_ChannelPtr, Paused ? 1 : 0); + if (IsImportantError(Result)) + { + BS_FMODExException("FMOD_Channel_SetPaused()", Result).Log(); + return false; + } + else + return true; +} + +// ----------------------------------------------------------------------------- + +bool BS_FMODExChannel::SetVolume(float Volume) +{ + BS_ASSERT(m_ChannelPtr); + + FMOD_RESULT Result = FMOD_Channel_SetVolume(m_ChannelPtr, Volume); + if (IsImportantError(Result)) + { + BS_FMODExException("FMOD_Channel_SetVolume()", Result).Log(); + return false; + } + else + return true; +} + +// ----------------------------------------------------------------------------- + +bool BS_FMODExChannel::SetPanning(float Panning) +{ + BS_ASSERT(m_ChannelPtr); + + FMOD_RESULT Result = FMOD_Channel_SetPan(m_ChannelPtr, Panning); + if (IsImportantError(Result)) + { + BS_FMODExException("FMOD_Channel_SetPan()", Result).Log(); + return false; + } + else + return true; +} + +// ----------------------------------------------------------------------------- + +bool BS_FMODExChannel::SetLoop(bool Loop) +{ + BS_ASSERT(m_ChannelPtr); + + FMOD_RESULT Result = FMOD_Channel_SetLoopCount(m_ChannelPtr, Loop ? -1 : 0); + if (IsImportantError(Result)) + { + BS_FMODExException("FMOD_Channel_SetLoopCount()", Result).Log(); + return false; + } + else + return true; +} + +// ----------------------------------------------------------------------------- + +bool BS_FMODExChannel::SetLoopPoints(unsigned int LoopStart, unsigned int LoopEnd) +{ + BS_ASSERT(m_ChannelPtr); + + FMOD_RESULT Result = FMOD_Channel_SetLoopPoints(m_ChannelPtr, LoopStart, FMOD_TIMEUNIT_PCM, LoopEnd, FMOD_TIMEUNIT_PCM); + if (IsImportantError(Result)) + { + BS_FMODExException("FMOD_Channel_SetLoopPoints()", Result).Log(); + return false; + } + else + return true; +} + +// ----------------------------------------------------------------------------- + +bool BS_FMODExChannel::SetPosition(unsigned int Position) +{ + BS_ASSERT(m_ChannelPtr); + + FMOD_RESULT Result = FMOD_Channel_SetPosition(m_ChannelPtr, Position, FMOD_TIMEUNIT_PCM); + if (IsImportantError(Result)) + { + BS_FMODExException("FMOD_Channel_SetPosition()", Result).Log(); + return false; + } + else + return true; +} + +// ----------------------------------------------------------------------------- + +bool BS_FMODExChannel::Stop() +{ + BS_ASSERT(m_ChannelPtr); + + FMOD_RESULT Result = FMOD_Channel_Stop(m_ChannelPtr); + if (IsImportantError(Result)) + { + BS_FMODExException("FMOD_Channel_Stop()", Result).Log(); + return false; + } + else + return true; +} + +// ----------------------------------------------------------------------------- +// Getter +// ----------------------------------------------------------------------------- + +float BS_FMODExChannel::GetVolume() +{ + BS_ASSERT(m_ChannelPtr); + + float Volume = 0; + FMOD_RESULT Result = FMOD_Channel_GetVolume(m_ChannelPtr, &Volume); + if (IsImportantError(Result)) BS_FMODExException("FMOD_Channel_GetVolume()", Result).Log(); + + return Volume; +} + +// ----------------------------------------------------------------------------- + +float BS_FMODExChannel::GetPanning() +{ + BS_ASSERT(m_ChannelPtr); + + float Panning = 0; + FMOD_RESULT Result = FMOD_Channel_GetPan(m_ChannelPtr, &Panning); + if (IsImportantError(Result)) BS_FMODExException("FMOD_Channel_GetPan()", Result).Log(); + + return Panning; +} + +// ----------------------------------------------------------------------------- + +unsigned int BS_FMODExChannel::GetPosition() +{ + BS_ASSERT(m_ChannelPtr); + + unsigned int Position = 0; + FMOD_RESULT Result = FMOD_Channel_GetPosition(m_ChannelPtr, &Position, FMOD_TIMEUNIT_PCM); + if (IsImportantError(Result)) BS_FMODExException("FMOD_Channel_GetPosition()", Result).Log(); + + return Position; +} + +// ----------------------------------------------------------------------------- + +unsigned int BS_FMODExChannel::GetTime() +{ + BS_ASSERT(m_ChannelPtr); + + unsigned int Time = 0; + FMOD_RESULT Result = FMOD_Channel_GetPosition(m_ChannelPtr, &Time, FMOD_TIMEUNIT_MS); + if (IsImportantError(Result)) BS_FMODExException("FMOD_Channel_GetPosition()", Result).Log(); + + return Time; +} + +// ----------------------------------------------------------------------------- + +unsigned int BS_FMODExChannel::GetLoopStart() +{ + BS_ASSERT(m_ChannelPtr); + unsigned int LoopStart = 0; + FMOD_RESULT Result = FMOD_Channel_GetLoopPoints(m_ChannelPtr, &LoopStart, FMOD_TIMEUNIT_PCM, 0, FMOD_TIMEUNIT_PCM); + if (IsImportantError(Result)) BS_FMODExException("FMOD_Channel_GetLoopPoints()", Result).Log(); + + return LoopStart; +} + +// ----------------------------------------------------------------------------- + +unsigned int BS_FMODExChannel::GetLoopEnd() +{ + BS_ASSERT(m_ChannelPtr); + unsigned int LoopEnd = 0; + FMOD_RESULT Result = FMOD_Channel_GetLoopPoints(m_ChannelPtr, 0, FMOD_TIMEUNIT_PCM, &LoopEnd, FMOD_TIMEUNIT_PCM); + if (IsImportantError(Result)) BS_FMODExException("FMOD_Channel_GetLoopPoints()", Result).Log(); + + return LoopEnd; +} + +// ----------------------------------------------------------------------------- + +bool BS_FMODExChannel::IsLooping() +{ + BS_ASSERT(m_ChannelPtr); + + int LoopCount = 0; + FMOD_RESULT Result = FMOD_Channel_GetLoopCount(m_ChannelPtr, &LoopCount); + if (IsImportantError(Result)) BS_FMODExException("FMOD_Channel_GetLoopCount()", Result).Log(); + + return LoopCount == -1; +} + +// ----------------------------------------------------------------------------- + +bool BS_FMODExChannel::IsPaused() +{ + BS_ASSERT(m_ChannelPtr); + + FMOD_BOOL Paused = 0; + FMOD_RESULT Result = FMOD_Channel_GetPaused(m_ChannelPtr, &Paused); + if (IsImportantError(Result)) BS_FMODExException("FMOD_Channel_GetPaused()", Result).Log(); + + return Paused != 0; +} + +// ----------------------------------------------------------------------------- + +bool BS_FMODExChannel::IsPlaying() +{ + BS_ASSERT(m_ChannelPtr); + + FMOD_BOOL Playing = 0; + FMOD_RESULT Result = FMOD_Channel_IsPlaying(m_ChannelPtr, &Playing); + if (IsImportantError(Result)) BS_FMODExException("FMOD_Channel_IsPlaying()", Result).Log(); + + return Playing != 0; +} diff --git a/engines/sword25/sfx/fmodexchannel.h b/engines/sword25/sfx/fmodexchannel.h new file mode 100755 index 0000000000..9ebf674ff2 --- /dev/null +++ b/engines/sword25/sfx/fmodexchannel.h @@ -0,0 +1,69 @@ +// ----------------------------------------------------------------------------- +// This file is part of Broken Sword 2.5 +// Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdörfer +// +// Broken Sword 2.5 is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// Broken Sword 2.5 is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Broken Sword 2.5; if not, write to the Free Software +// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +// ----------------------------------------------------------------------------- + +#ifndef FMODEXCHANNEL_H +#define FMODEXCHANNEL_H + +// ----------------------------------------------------------------------------- +// Includes +// ----------------------------------------------------------------------------- + +#include "kernel/common.h" + +// ----------------------------------------------------------------------------- +// Forward Declarations +// ----------------------------------------------------------------------------- + +struct FMOD_CHANNEL; +struct FMOD_SOUND; + +// ----------------------------------------------------------------------------- +// Klassendefinition +// ----------------------------------------------------------------------------- + +class BS_FMODExChannel +{ +public: + BS_FMODExChannel(FMOD_CHANNEL * ChannelPtr, FMOD_SOUND * SoundPtr); + virtual ~BS_FMODExChannel(); + + bool SetPaused(bool Paused); + bool SetVolume(float Volume); + bool SetPanning(float Panning); + bool SetLoop(bool Loop); + bool SetLoopPoints(unsigned int LoopStart, unsigned int LoopEnd); + bool SetPosition(unsigned int Position); + bool Stop(); + + float GetVolume(); + float GetPanning(); + unsigned int GetPosition(); + unsigned int GetTime(); + unsigned int GetLoopStart(); + unsigned int GetLoopEnd(); + bool IsLooping(); + bool IsPaused(); + bool IsPlaying(); + +private: + FMOD_CHANNEL * m_ChannelPtr; + FMOD_SOUND * m_SoundPtr; +}; + +#endif diff --git a/engines/sword25/sfx/fmodexexception.h b/engines/sword25/sfx/fmodexexception.h new file mode 100755 index 0000000000..c0541f6d3c --- /dev/null +++ b/engines/sword25/sfx/fmodexexception.h @@ -0,0 +1,51 @@ +// ----------------------------------------------------------------------------- +// This file is part of Broken Sword 2.5 +// Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdörfer +// +// Broken Sword 2.5 is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// Broken Sword 2.5 is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Broken Sword 2.5; if not, write to the Free Software +// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +// ----------------------------------------------------------------------------- + +#ifndef BS_FMODEXEXCEPTION_H +#define BS_FMODEXEXCEPTION_H + +// ----------------------------------------------------------------------------- +// Includes +// ----------------------------------------------------------------------------- + +#include "kernel/common.h" +#include "fmod_errors.h" + +// ----------------------------------------------------------------------------- +// Klassendefinition +// ----------------------------------------------------------------------------- + +class BS_FMODExException +{ +public: + BS_FMODExException(const char * Function_, FMOD_RESULT Result_) : + Function(Function_), + Result(Result_) {} + + const char * Function; + FMOD_RESULT Result; + + void Log() + { + BS_LOG_ERROR("Call to %s failed.", Function); + BS_LOGLN(" FMOD error: %s(%d)", FMOD_ErrorString(Result), Result); + } +}; + +#endif diff --git a/engines/sword25/sfx/fmodexresource.cpp b/engines/sword25/sfx/fmodexresource.cpp new file mode 100755 index 0000000000..3e15dbb260 --- /dev/null +++ b/engines/sword25/sfx/fmodexresource.cpp @@ -0,0 +1,170 @@ +// ----------------------------------------------------------------------------- +// This file is part of Broken Sword 2.5 +// Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdörfer +// +// Broken Sword 2.5 is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// Broken Sword 2.5 is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Broken Sword 2.5; if not, write to the Free Software +// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +// ----------------------------------------------------------------------------- + +// ----------------------------------------------------------------------------- +// Logging +// ----------------------------------------------------------------------------- + +#define BS_LOG_PREFIX "FMODEXRESOURCE" + +// ----------------------------------------------------------------------------- +// Includes +// ----------------------------------------------------------------------------- + +#include <memory> +#include "fmod.h" +#include "fmodexexception.h" +#include "fmodexchannel.h" +#include "package/packagemanager.h" +#include "fmodexresource.h" + +// ----------------------------------------------------------------------------- +// Konstanten +// ----------------------------------------------------------------------------- + +namespace +{ + const unsigned int MAX_SAMPLE_SIZE = 100 * 1024; // Die Dateigröße in Byte ab der ein Sound als Stream abgespielt wird +} + +// ----------------------------------------------------------------------------- +// Konstruktion / Destruktion +// ----------------------------------------------------------------------------- + + +BS_FMODExResource::BS_FMODExResource(const std::string& FileName, FMOD_SYSTEM * FMOD, bool & Success) : + m_SoundPtr(0), + m_SoundDataPtr(0), + BS_Resource(FileName, BS_Resource::TYPE_SOUND) +{ + BS_ASSERT(FMOD); + + // Von Misserfolg ausgehen + Success = false; + + // Pointer auf den Package-Manager bekommen + BS_PackageManager * PackagePtr = BS_Kernel::GetInstance()->GetPackage(); + if (!PackagePtr) + { + BS_LOG_ERRORLN("Package manager not found."); + return; + } + + // Datei laden + unsigned int FileSize; + char * FileDataPtr = (char*) PackagePtr->GetFile(GetFileName(), &FileSize); + if (!FileDataPtr) + { + BS_LOG_ERRORLN("File \"%s\" could not be loaded.", GetFileName().c_str()); + return; + } + + // Ob die Sounddatei als Sample oder als Stream behandelt wird, ist abhängig von der Dateigröße. + // Samples werden sofort intialisiert. + // Für Streams wird hingegen bei jedem Abspielen ein neuer Sound erstellt. Dieses Vorgehen ist notwendig, da FMOD Ex Samples beliebig oft + // gleichzeitig abspielen kann, Streams jedoch nur ein mal. + if (FileSize <= MAX_SAMPLE_SIZE) + { + FMOD_CREATESOUNDEXINFO ExInfo; + memset(&ExInfo, 0, sizeof(ExInfo)); + ExInfo.cbsize = sizeof(ExInfo); + ExInfo.length = FileSize; + + FMOD_RESULT Result = FMOD_System_CreateSound(FMOD, FileDataPtr, + FMOD_CREATESAMPLE | FMOD_OPENMEMORY | FMOD_SOFTWARE | FMOD_2D | FMOD_LOOP_NORMAL, + &ExInfo, + &m_SoundPtr); + if (Result != FMOD_OK) BS_FMODExException("FMOD_System_CreateSound()", Result).Log(); + + Success = Result == FMOD_OK; + + delete FileDataPtr; + } + else + { + m_SoundDataPtr = FileDataPtr; + m_SoundDataSize = FileSize; + + Success = true; + } +} + +// ----------------------------------------------------------------------------- + +BS_FMODExResource::~BS_FMODExResource() +{ + // Sound freigeben, solange des Soundsystem noch läuft. + // Sollte das Soundsystem beendet worden sein müssen und können Sounds nicht mehr freigegeben werden. + if (m_SoundPtr && BS_Kernel::GetInstance()->GetService("sfx")) FMOD_Sound_Release(m_SoundPtr); + if (m_SoundDataPtr) delete [] m_SoundDataPtr; +} + +// ----------------------------------------------------------------------------- +// Abspielen +// ----------------------------------------------------------------------------- + +BS_FMODExChannel * BS_FMODExResource::StartSound(FMOD_SYSTEM * FMOD) +{ + BS_ASSERT(FMOD); + + FMOD_CHANNEL * NewChannelPtr; + FMOD_SOUND * NewSoundPtr = 0; + + // Sample können sofort abgespielt werden. + if (m_SoundPtr) + { + FMOD_RESULT Result = FMOD_System_PlaySound(FMOD, FMOD_CHANNEL_FREE, m_SoundPtr, 1, &NewChannelPtr); + if (Result != FMOD_OK) + { + BS_FMODExException("FMOD_System_PlaySound()", Result).Log(); + return 0; + } + } + // Für Streams muss ein neuer Sound erstellt werden. + else + { + FMOD_CREATESOUNDEXINFO ExInfo; + memset(&ExInfo, 0, sizeof(ExInfo)); + ExInfo.cbsize = sizeof(ExInfo); + ExInfo.length = m_SoundDataSize; + + FMOD_RESULT Result; + Result = FMOD_System_CreateSound(FMOD, + m_SoundDataPtr, + FMOD_CREATESTREAM | FMOD_OPENMEMORY_POINT | FMOD_SOFTWARE | FMOD_2D | FMOD_LOOP_NORMAL, + &ExInfo, + &NewSoundPtr); + if (Result != FMOD_OK) + { + BS_FMODExException("FMOD_System_CreateSound()", Result).Log(); + return 0; + } + + Result = FMOD_System_PlaySound(FMOD, FMOD_CHANNEL_FREE, NewSoundPtr, 1, &NewChannelPtr); + if (Result != FMOD_OK) + { + BS_FMODExException("FMOD_System_PlaySound()", Result).Log(); + return 0; + } + } + + // Der Channel und der Sound (bei Streams) werden an ein BS_FMODExChannel-Objekt übergeben. + // Dieses Sorgt auch dafür, dass Channel und Sound korrekt zerstört werden. + return new BS_FMODExChannel(NewChannelPtr, NewSoundPtr); +} diff --git a/engines/sword25/sfx/fmodexresource.h b/engines/sword25/sfx/fmodexresource.h new file mode 100755 index 0000000000..43ff93e380 --- /dev/null +++ b/engines/sword25/sfx/fmodexresource.h @@ -0,0 +1,56 @@ +// ----------------------------------------------------------------------------- +// This file is part of Broken Sword 2.5 +// Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdörfer +// +// Broken Sword 2.5 is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// Broken Sword 2.5 is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Broken Sword 2.5; if not, write to the Free Software +// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +// ----------------------------------------------------------------------------- + +#ifndef BS_FMODRESOURCE_H +#define BS_FMODRESOURCE_H + +// ----------------------------------------------------------------------------- +// Includes +// ----------------------------------------------------------------------------- + +#include "kernel/common.h" +#include "kernel/resource.h" + +// ----------------------------------------------------------------------------- +// Forward Declarations +// ----------------------------------------------------------------------------- + +class BS_FMODExChannel; +struct FMOD_SOUND; +struct FMOD_SYSTEM; + +// ----------------------------------------------------------------------------- +// Klassendefinition +// ----------------------------------------------------------------------------- + +class BS_FMODExResource : public BS_Resource +{ +public: + BS_FMODExResource(const std::string& FileName, FMOD_SYSTEM * FMOD, bool & Success); + virtual ~BS_FMODExResource(); + + BS_FMODExChannel * StartSound(FMOD_SYSTEM * FMOD); + +private: + FMOD_SOUND * m_SoundPtr; + char * m_SoundDataPtr; + unsigned int m_SoundDataSize; +}; + +#endif diff --git a/engines/sword25/sfx/fmodexsound.cpp b/engines/sword25/sfx/fmodexsound.cpp new file mode 100755 index 0000000000..346d55086b --- /dev/null +++ b/engines/sword25/sfx/fmodexsound.cpp @@ -0,0 +1,889 @@ +// ----------------------------------------------------------------------------- +// This file is part of Broken Sword 2.5 +// Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdörfer +// +// Broken Sword 2.5 is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// Broken Sword 2.5 is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Broken Sword 2.5; if not, write to the Free Software +// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +// ----------------------------------------------------------------------------- + +// Die von der Engine ausgegebenen Soundhandles werden intern auf FMOD Ex Handles gemapped. +// Diese Handles sind nur solange gültig, wie der Sound spielt. Falls danach versucht wird manipulierend auf den Sound zuzugreifen, +// schlägt dieses ohne Fehlermeldung fehl. + +// ----------------------------------------------------------------------------- +// Logging +// ----------------------------------------------------------------------------- + +#define BS_LOG_PREFIX "FMODEXSOUND" + +// ----------------------------------------------------------------------------- +// Includes +// ----------------------------------------------------------------------------- + +#include "fmod.h" +#include "fmod_errors.h" +#include "fmodexexception.h" +#include "fmodexchannel.h" +#include "fmodexresource.h" +#include "kernel/string.h" +#include "kernel/inputpersistenceblock.h" +#include "kernel/outputpersistenceblock.h" +#include "package/packagemanager.h" +#include "fmodexsound.h" + +// ----------------------------------------------------------------------------- +// Konstanten und lokale Funktionen +// ----------------------------------------------------------------------------- + +namespace +{ + const float DEFAULT_MUSIC_VOLUME = 1.0f; + const float DEFAULT_SPEECH_VOLUME = 1.0f; + const float DEFAULT_SFX_VOLUME = 1.0f; + const unsigned int SOUNDTYPE_COUNT = 3; + const unsigned int INVALID_SOUND_HANDLE = 0xffffffff; + + // ------------------------------------------------------------------------- + + inline float NormalizePanning(float Panning) + { + bool Corrected = false; + float Result = Panning; + if (Result > 1.0f) + { + Result = 1.0f; + Corrected = true; + } + if (Result < -1.0f) + { + Result = -1.0f; + Corrected = true; + } + + if (Corrected) BS_LOG_WARNINGLN("Tried to set an invalid panning value of %.2f. It was corrected to %.2f", Panning, Result); + + return Result; + } + + // ------------------------------------------------------------------------- + + inline float NormalizeVolume(float Volume) + { + bool Corrected = false; + float Result = Volume; + if (Result> 1.0f) + { + Result = 1.0f; + Corrected = true; + } + if (Result < 0.0f) + { + Result = 0.0f; + Corrected = true; + } + + if (Corrected) BS_LOG_WARNINGLN("Tried to set an invalid volume value of %.2f. It was corrected to %.2f", Volume, Result); + + return Result; + } + + // ------------------------------------------------------------------------- + + inline FMOD_SOUND_FORMAT BitsPerSampleToFMODExSoundFormat(unsigned int BitsPerSample) + { + switch (BitsPerSample) + { + case 8: return FMOD_SOUND_FORMAT_PCM8; + case 16: return FMOD_SOUND_FORMAT_PCM16; + case 24: return FMOD_SOUND_FORMAT_PCM24; + case 32: return FMOD_SOUND_FORMAT_PCM32; + default: return FMOD_SOUND_FORMAT_NONE; + } + } +} + +// ----------------------------------------------------------------------------- +// Konstruktion / Destruktion +// ----------------------------------------------------------------------------- + +BS_FMODExSound::BS_FMODExSound(BS_Kernel* pKernel) : + BS_SoundEngine(pKernel), + m_FMOD(0), + m_NextHandle(1) +{ + // Lautstärkeneinstellungen auf die Standardwerte setzen + m_Volumes[MUSIC] = DEFAULT_MUSIC_VOLUME; + m_Volumes[SPEECH] = DEFAULT_SPEECH_VOLUME; + m_Volumes[SFX] = DEFAULT_SFX_VOLUME; +} + +// ----------------------------------------------------------------------------- + +BS_FMODExSound::~BS_FMODExSound() +{ + // Alle noch spielenden Sounds stoppen und die Ressourcen freigeben + for (PSM_ITER it = m_PlayingSoundsMap.begin(); it != m_PlayingSoundsMap.end(); ++it) + { + if (it->second.ChannelPtr) delete it->second.ChannelPtr; + if (it->second.ResourcePtr) it->second.ResourcePtr->Release(); + } + + // FMOD Ex deinitialisieren + if (m_FMOD) FMOD_System_Release(m_FMOD); +} + +// ----------------------------------------------------------------------------- + +BS_Service * BS_FMODExSound_CreateObject(BS_Kernel* pKernel) { return new BS_FMODExSound(pKernel); } + +// ----------------------------------------------------------------------------- + +bool BS_FMODExSound::Init(unsigned int SampleRate, unsigned int Channels) +{ + // Eine Warnung ausgeben, wenn dieser Service schon initialisiert wurde. + // Allerdings wird trotzdem true zurückgegeben, weil kein Fehler aufgetreten ist, der Service ist noch benutzbar. + if (m_FMOD) + { + BS_LOG_WARNINGLN("Tried to initialize again. Call ignored."); + return true; + } + else + { + try + { + // Die FMOD Ex mit den übergebenen Werte initialisieren + FMOD_RESULT Result = FMOD_System_Create(&m_FMOD); + if (Result != FMOD_OK) throw(BS_FMODExException("FMOD_System_Create()", Result)); + + Result = FMOD_System_SetSoftwareFormat(m_FMOD, SampleRate, FMOD_SOUND_FORMAT_PCM16, 0, 0, FMOD_DSP_RESAMPLER_LINEAR); + if (Result != FMOD_OK) throw(BS_FMODExException("FMOD_System_SetSoftwareFormat()", Result)); + + Result = FMOD_System_Init(m_FMOD, Channels, FMOD_INIT_NORMAL, 0); + if (Result != FMOD_OK) throw(BS_FMODExException("FMOD_System_Init()", Result)); + } + + catch(BS_FMODExException Ex) + { + Ex.Log(); + BS_LOG_ERRORLN("FMOD Ex could not be initialized."); + + if (m_FMOD) + { + FMOD_System_Release(m_FMOD); + m_FMOD = 0; + } + + return false; + } + + BS_LOGLN("FMOD Ex initialized. Sample rate: %d / Channels: %d", SampleRate, Channels); + return true; + } +} + +// ----------------------------------------------------------------------------- + +void BS_FMODExSound::Update() +{ + BS_ASSERT(m_FMOD); + + FMOD_RESULT Result = FMOD_System_Update(m_FMOD); + if (Result != FMOD_OK) BS_FMODExException("FMOD_System_Update()", Result).Log(); + + RemoveInactiveSounds(); +} + +// ----------------------------------------------------------------------------- +// Sounds abspielen +// ----------------------------------------------------------------------------- + +bool BS_FMODExSound::PlaySound(const std::string& FileName, + SOUND_TYPES Type, + float Volume, + float Pan, + bool Loop, + int LoopStart, int LoopEnd, + unsigned int Layer) +{ + return PlaySoundInternal(FileName, Type, Volume, Pan, Loop, LoopStart, LoopEnd, Layer, 0, 0) != 0; +} + +// ----------------------------------------------------------------------------- + +unsigned int BS_FMODExSound::PlaySoundEx(const std::string& FileName, + SOUND_TYPES Type, + float Volume, + float Pan, + bool Loop, + int LoopStart, int LoopEnd, + unsigned int Layer) +{ + return PlaySoundInternal(FileName, Type, Volume, Pan, Loop, LoopStart, LoopEnd, Layer, 0, 0); +} + +// ------------------------------------------------------------------------- + +FMOD_RESULT F_CALLBACK BS_FMODExSound::FMODExDynamicSoundSetPosCallback(FMOD_SOUND *sound, int subsound, unsigned int position, FMOD_TIMEUNIT postype) +{ + // In dynamischen Sounds wird nicht gesprungen, daher tut dieses Funktion nichts. + return FMOD_OK; +} + +// ------------------------------------------------------------------------- + +FMOD_RESULT F_CALLBACK BS_FMODExSound::FMODExDynamicSoundReadCallback(FMOD_SOUND *sound, void *data, unsigned int datalen) +{ + // Handle auf das aktuelle Soundsystem holen, dies ist wohl dieses hier. + BS_FMODExSound * t = reinterpret_cast<BS_FMODExSound *>(BS_Kernel::GetInstance()->GetSfx()); + + // Handle auf den richtigen Sound holen, wurde als FMOD Ex Benutzerdaten gesetzt. + unsigned int Handle; + FMOD_RESULT Result = FMOD_Sound_GetUserData(sound, reinterpret_cast<void **>(&Handle)); + if (Result != FMOD_OK) + { + BS_FMODExException("FMOD_Sound_GetUserData()", Result).Log(); + return FMOD_OK; + } + + // Sounddaten holen und Callbackfunktion aufrufen. + PlayingSoundData * PSD = t->GetPlayingSoundDataByHandle(Handle); + if (PSD) PSD->ReadCallback(PSD->UserData, data, datalen); + + return FMOD_OK; +} + +// ----------------------------------------------------------------------------- + +unsigned int BS_FMODExSound::PlayDynamicSoundEx(DynamicSoundReadCallback ReadCallback, + void * UserData, + SOUND_TYPES Type, + unsigned int SampleRate, + unsigned int BitsPerSample, + unsigned int Channels, + float Volume, + float Pan, + unsigned int Layer) +{ + // Parameter überprüfen + if (BitsPerSampleToFMODExSoundFormat(BitsPerSample) == FMOD_SOUND_FORMAT_NONE) + { + BS_LOG_ERRORLN("Cannot create a dynamic sound with %d bits per sample.", BitsPerSample); + return 0; + } + if (Channels == 0 || Channels > 2) + { + BS_LOG_ERRORLN("Cannot create a dynamic sound with %d channels.", Channels); + return 0; + } + + // Zu vergebendes Handle bestimmen + unsigned int Handle = m_NextHandle++; + + // Sound in die Sound-Map eintragen mit all den Informationen, die wir bisher haben. + // Dies muss für dynamische Sounds so früh geschehen, da sofort nach dem Aufruf von FMOD_System_CreateSound der Callback aufgerufen wird um + // den Decode-Buffer zu füllen. Unser Callback liest den BS-Callback aus der Sound-Map. Wenn wir den Sound später hinzufügen würden, würde der + // BS-Callback zu diesem Sound nicht in der Map stehen und nicht aufgerufen werden können. + // Den fehlenden ChannelPtr tragen wir später in dieser Funktion ein. + m_PlayingSoundsMap[Handle] = PlayingSoundData(0, 0, Type, Layer, Volume, ReadCallback, UserData); + + // Dynamischen FMOD Ex Sound erstellen + FMOD_CREATESOUNDEXINFO CreateSoundExInfo; + memset(&CreateSoundExInfo, 0, sizeof(FMOD_CREATESOUNDEXINFO)); + CreateSoundExInfo.cbsize = sizeof(FMOD_CREATESOUNDEXINFO); + CreateSoundExInfo.length = 0xffffffff; + CreateSoundExInfo.numchannels = Channels; + CreateSoundExInfo.defaultfrequency = SampleRate; + CreateSoundExInfo.format = BitsPerSampleToFMODExSoundFormat(BitsPerSample); + CreateSoundExInfo.pcmreadcallback = FMODExDynamicSoundReadCallback; + CreateSoundExInfo.pcmsetposcallback = FMODExDynamicSoundSetPosCallback; + CreateSoundExInfo.userdata = reinterpret_cast<void *>(Handle); + + FMOD_SOUND * FMODExSoundPtr; + FMOD_RESULT Result = FMOD_System_CreateSound(m_FMOD, + 0, + FMOD_2D | FMOD_OPENUSER | FMOD_LOOP_NORMAL | FMOD_HARDWARE | FMOD_CREATESTREAM, + &CreateSoundExInfo, + &FMODExSoundPtr); + if (Result != FMOD_OK) + { + BS_FMODExException("FMOD_System_CreateSound() from PlayDynamicSoundEx()", Result).Log(); + return 0; + } + + // Neu erstellten Sound einem Kanal zuweisen + FMOD_CHANNEL * FMODExChannelPtr; + Result = FMOD_System_PlaySound(m_FMOD, FMOD_CHANNEL_FREE, FMODExSoundPtr, 1, &FMODExChannelPtr); + if (Result != FMOD_OK) + { + BS_FMODExException("FMOD_System_PlaySound() from PlayDynamicSoundEx()", Result).Log(); + return 0; + } + + // FMOD Ex Kanal an einen BS_FMODExChannel binden und abspielen + BS_FMODExChannel * ChannelPtr = new BS_FMODExChannel(FMODExChannelPtr, FMODExSoundPtr); + ChannelPtr->SetPaused(false); + + // ChannelPtr in die PlayingSoundData-Struktur eintragen + PlayingSoundData * PSD = GetPlayingSoundDataByHandle(Handle); + if (PSD) PSD->ChannelPtr = ChannelPtr; + + return Handle; +} + +// ----------------------------------------------------------------------------- + +unsigned int BS_FMODExSound::PlaySoundInternal(const std::string& FileName, + SOUND_TYPES Type, + float Volume, + float Pan, + bool Loop, + int LoopStart, int LoopEnd, + unsigned int Layer, + unsigned int Position, + unsigned int Handle) +{ + BS_ASSERT(m_FMOD); + BS_ASSERT(Type < SOUNDTYPE_COUNT); + + // Resource anfordern + BS_Resource * ResourcePtr = BS_Kernel::GetInstance()->GetResourceManager()->RequestResource(FileName); + if (!ResourcePtr) + { + BS_LOG_ERRORLN("Could not request resource \"%s\".", FileName.c_str()); + return 0; + } + if (ResourcePtr->GetType() != BS_Resource::TYPE_SOUND) + { + BS_LOG_ERRORLN("Requested resource \"%s\" is not a sound.", FileName.c_str()); + return 0; + } + BS_FMODExResource * SoundResourcePtr = static_cast<BS_FMODExResource *>(ResourcePtr); + + // Sound im Pause-Modus starten + BS_FMODExChannel * ChannelPtr = SoundResourcePtr->StartSound(m_FMOD); + + if (ChannelPtr) + { + try + { + // Falls der Sound gelooped wird, Loop-Points setzen + if (Loop) + { + // Bestimmen, welche Loop-Points benutzt werden. Falls ein Loop-Point als Parameter nicht spezifiziert wurde (Wert -1), + // wird der Loop-Point von FMOD Ex benutzt. + unsigned int RealLoopStart = (LoopStart > 0) ? LoopStart : ChannelPtr->GetLoopStart(); + unsigned int RealLoopEnd = (LoopEnd > 0) ? LoopEnd : ChannelPtr->GetLoopEnd(); + + // Loop-Points auf Gültigkeit überprüfen + if (RealLoopStart > RealLoopEnd) + { + BS_LOG_ERRORLN("Loop start (%d) was placed after loop end (%d) for sound \"%s\".", + RealLoopStart, RealLoopEnd, + SoundResourcePtr->GetFileName().c_str()); + throw(0); + } + if (RealLoopStart > ChannelPtr->GetLoopEnd()) + { + BS_LOG_ERRORLN("Loop start (%d) was placed after end (%d) of sound \"%s\".", + RealLoopStart, + ChannelPtr->GetLoopEnd(), + SoundResourcePtr->GetFileName().c_str()); + throw(0); + } + if (RealLoopEnd > ChannelPtr->GetLoopEnd()) + { + BS_LOG_ERRORLN("Loop end (%d) was placed after end (%d) of sound \"%s\".", + RealLoopEnd, + ChannelPtr->GetLoopEnd(), + SoundResourcePtr->GetFileName().c_str()); + throw(0); + } + + // Loop-Points setzen + if (!ChannelPtr->SetLoopPoints(RealLoopStart, RealLoopEnd)) throw(0); + } + + // Sound-Parameter gemäß der Übergabeparameter setzen + if (!ChannelPtr->SetVolume(NormalizeVolume(Volume) * m_Volumes[Type])) throw(0); + if (!ChannelPtr->SetPanning(NormalizePanning(Pan))) throw(0); + if (!ChannelPtr->SetLoop(Loop)) throw(0); + if (!ChannelPtr->SetPosition(Position)) throw(0); + } + catch (...) + { + delete ChannelPtr; + SoundResourcePtr->Release(); + return 0; + } + + unsigned int MyLoopStart = ChannelPtr->GetLoopStart(); + unsigned int MyLoopEnd = ChannelPtr->GetLoopEnd(); + ChannelPtr->SetLoopPoints(MyLoopStart, MyLoopEnd); + + // Sound abspielen + ChannelPtr->SetPaused(false); + + // Sound in die Sound-Map eintragen + unsigned int NewHandle = (Handle != 0) ? Handle : m_NextHandle++; + m_PlayingSoundsMap[NewHandle] = PlayingSoundData(SoundResourcePtr, ChannelPtr, Type, Layer, Volume); + + return NewHandle; + } + else + { + SoundResourcePtr->Release(); + return 0; + } +} + +// ----------------------------------------------------------------------------- +// Sonstige Methoden +// ----------------------------------------------------------------------------- + +void BS_FMODExSound::SetVolume(float Volume, SOUND_TYPES Type) +{ + BS_ASSERT(m_FMOD); + BS_ASSERT(Type < SOUNDTYPE_COUNT); + m_Volumes[Type] = NormalizeVolume(Volume); + + // Alle Volumen der Sounds der Kategorie aktualisieren + PSM_CONST_ITER it = m_PlayingSoundsMap.begin(); + while (it != m_PlayingSoundsMap.end()) + { + const PlayingSoundData & PSD = it->second; + if (PSD.ChannelPtr && PSD.Type == Type) PSD.ChannelPtr->SetVolume(Volume * PSD.Volume); + + ++it; + } +} + +// ----------------------------------------------------------------------------- + +float BS_FMODExSound::GetVolume(SOUND_TYPES Type) +{ + BS_ASSERT(m_FMOD); + BS_ASSERT(Type < SOUNDTYPE_COUNT); + return m_Volumes[Type]; +} + +// ----------------------------------------------------------------------------- + +void BS_FMODExSound::PauseAll() +{ + BS_ASSERT(m_FMOD); + + // Alle Sounds durchgehen und alle pausieren. + // Diese werden dann markiert, damit ResumeAll() feststellen kann, welche Sounds mit PauseAll() pausiert wurden. + // ResumeAll() setzt dann nur diejenigen fort, die nur über PauseAll() pausiert wurden. + PSM_ITER it = m_PlayingSoundsMap.begin(); + while (it != m_PlayingSoundsMap.end()) + { + PlayingSoundData & PSD = it->second; + + if (PSD.ChannelPtr) PSD.ChannelPtr->SetPaused(true); + PSD.PausedGlobal = true; + + ++it; + } +} + +// ----------------------------------------------------------------------------- + +void BS_FMODExSound::ResumeAll() +{ + BS_ASSERT(m_FMOD); + + // Alle Sounds durchgehen, die gloable Pause aufheben und diejenigen fortsetzen, + // die keine Pause mehr haben (weder explizit, über den Layer oder global). + PSM_ITER it = m_PlayingSoundsMap.begin(); + while (it != m_PlayingSoundsMap.end()) + { + PlayingSoundData & PSD = it->second; + + if (PSD.PausedGlobal) + { + PSD.PausedGlobal = false; + if (PSD.ChannelPtr && !PSD.PausedLayer && !PSD.Paused) PSD.ChannelPtr->SetPaused(false); + } + + ++it; + } +} + +// ----------------------------------------------------------------------------- + +void BS_FMODExSound::PauseLayer(unsigned int Layer) +{ + BS_ASSERT(m_FMOD); + + // Alle Sounds durchgehen und alle pausieren, die sich auf den angegebenen Layer befinden. + // Diese werden dann markiert, damit ResumeLayer() feststellen kann, welche Sounds mit PauseLayer() pausiert wurden. + // ResumeLayer() setzt dann nur diejenigen fort, die nur über PauseLayer() mit der entsprechenden Layer-Nummer pausiert wurden. + PSM_ITER it = m_PlayingSoundsMap.begin(); + while (it != m_PlayingSoundsMap.end()) + { + PlayingSoundData & PSD = it->second; + + if (PSD.Layer == Layer) + { + if (PSD.ChannelPtr) PSD.ChannelPtr->SetPaused(true); + PSD.PausedLayer = true; + } + + ++it; + } +} + +// ----------------------------------------------------------------------------- + +void BS_FMODExSound::ResumeLayer(unsigned int Layer) +{ + BS_ASSERT(m_FMOD); + + // Alle Sounds durchgehen, die Layer-Pause aufheben und diejenigen fortsetzen, + // die keine Pause mehr haben (weder explizit, über den Layer oder global). + PSM_ITER it = m_PlayingSoundsMap.begin(); + while (it != m_PlayingSoundsMap.end()) + { + PlayingSoundData & PSD = it->second; + + if (PSD.PausedLayer && PSD.Layer == Layer) + { + PSD.PausedLayer = false; + if (PSD.ChannelPtr && !PSD.PausedGlobal && !PSD.Paused) PSD.ChannelPtr->SetPaused(false); + } + + ++it; + } +} + +// ----------------------------------------------------------------------------- +// Sound Setter +// ----------------------------------------------------------------------------- + +void BS_FMODExSound::SetSoundVolume(unsigned int Handle, float Volume) +{ + BS_ASSERT(m_FMOD); + PlayingSoundData * PSDPtr = GetPlayingSoundDataByHandle(Handle); + if (PSDPtr) if (PSDPtr->ChannelPtr && PSDPtr->ChannelPtr->SetVolume(NormalizeVolume(Volume) * m_Volumes[PSDPtr->Type])) PSDPtr->Volume = Volume; +} + +// ----------------------------------------------------------------------------- + +void BS_FMODExSound::SetSoundPanning(unsigned int Handle, float Pan) +{ + BS_ASSERT(m_FMOD); + PlayingSoundData * PSDPtr = GetPlayingSoundDataByHandle(Handle); + if (PSDPtr && PSDPtr->ChannelPtr) PSDPtr->ChannelPtr->SetPanning(NormalizePanning(Pan)); +} + +// ----------------------------------------------------------------------------- + +void BS_FMODExSound::PauseSound(unsigned int Handle) +{ + BS_ASSERT(m_FMOD); + PlayingSoundData * PSDPtr = GetPlayingSoundDataByHandle(Handle); + if (PSDPtr) + { + PSDPtr->Paused = true; + if (PSDPtr->ChannelPtr) PSDPtr->ChannelPtr->SetPaused(true); + } +} + +// ----------------------------------------------------------------------------- + +void BS_FMODExSound::ResumeSound(unsigned int Handle) +{ + BS_ASSERT(m_FMOD); + PlayingSoundData * PSDPtr = GetPlayingSoundDataByHandle(Handle); + if (PSDPtr) + { + PSDPtr->Paused = false; + if (PSDPtr->ChannelPtr && !PSDPtr->PausedGlobal && !PSDPtr->PausedLayer) PSDPtr->ChannelPtr->SetPaused(false); + } +} + +// ----------------------------------------------------------------------------- + +void BS_FMODExSound::StopSound(unsigned int Handle) +{ + BS_ASSERT(m_FMOD); + PlayingSoundData * PSDPtr = GetPlayingSoundDataByHandle(Handle); + if (PSDPtr && PSDPtr->ChannelPtr) PSDPtr->ChannelPtr->Stop(); +} + +// ----------------------------------------------------------------------------- +// Sound Getter +// ----------------------------------------------------------------------------- + +bool BS_FMODExSound::IsSoundPaused(unsigned int Handle) +{ + BS_ASSERT(m_FMOD); + PlayingSoundData * PSDPtr = GetPlayingSoundDataByHandle(Handle); + if (PSDPtr && PSDPtr->ChannelPtr) return PSDPtr->ChannelPtr->IsPaused(); + return false; +} + +// ----------------------------------------------------------------------------- + +bool BS_FMODExSound::IsSoundPlaying(unsigned int Handle) +{ + BS_ASSERT(m_FMOD); + PlayingSoundData * PSDPtr = GetPlayingSoundDataByHandle(Handle); + if (PSDPtr && PSDPtr->ChannelPtr) return PSDPtr->ChannelPtr->IsPlaying(); + return false; +} + +// ----------------------------------------------------------------------------- + +float BS_FMODExSound::GetSoundVolume(unsigned int Handle) +{ + BS_ASSERT(m_FMOD); + PlayingSoundData * PSDPtr = GetPlayingSoundDataByHandle(Handle); + if (PSDPtr) return PSDPtr->Volume; + return 0; +} + +// ----------------------------------------------------------------------------- + +float BS_FMODExSound::GetSoundPanning(unsigned int Handle) +{ + BS_ASSERT(m_FMOD); + PlayingSoundData * PSDPtr = GetPlayingSoundDataByHandle(Handle); + if (PSDPtr && PSDPtr->ChannelPtr) return PSDPtr->ChannelPtr->GetPanning(); + return 0; +} + +// ----------------------------------------------------------------------------- + +float BS_FMODExSound::GetSoundTime(unsigned int Handle) +{ + BS_ASSERT(m_FMOD); + PlayingSoundData * PSDPtr = GetPlayingSoundDataByHandle(Handle); + if (PSDPtr && PSDPtr->ChannelPtr) return static_cast<float>(PSDPtr->ChannelPtr->GetTime()) / 1000.0f; + return 0; +} + +// ----------------------------------------------------------------------------- +// Hilfsmethoden +// ----------------------------------------------------------------------------- + +void BS_FMODExSound::RemoveInactiveSounds() +{ + PSM_ITER it = m_PlayingSoundsMap.begin(); + while (it != m_PlayingSoundsMap.end()) + { + if (!it->second.ChannelPtr || !it->second.ChannelPtr->IsPlaying()) + { + PlayingSoundData & PSD = it->second; + + delete PSD.ChannelPtr; + if (PSD.ResourcePtr) PSD.ResourcePtr->Release(); + + it = m_PlayingSoundsMap.erase(it); + } + else + ++it; + } + + /* + static size_t lastActiveChannels = 0; + if (m_PlayingSoundsMap.size() != lastActiveChannels) + { + BS_LOGLN("Aktive Kanaele: %d", m_PlayingSoundsMap.size()); + lastActiveChannels = m_PlayingSoundsMap.size(); + } + */ +} + +// ----------------------------------------------------------------------------- + +BS_FMODExSound::PlayingSoundData * BS_FMODExSound::GetPlayingSoundDataByHandle(unsigned int Handle) +{ + // Zum Soundhandle gehörige Daten in der Hash-Map finden + PSM_ITER it = m_PlayingSoundsMap.find(Handle); + // Falls die Daten nicht gefunden werden konnten, Fehler zurückgebene, ansonsten ein Pointer auf die Daten. + if (it == m_PlayingSoundsMap.end()) return 0; + return &((*it).second); +} + +// ----------------------------------------------------------------------------- + +unsigned int BS_FMODExSound::CountPlayingDynamicSounds() +{ + unsigned int Result = 0; + for (PSM_CONST_ITER it = m_PlayingSoundsMap.begin(); it != m_PlayingSoundsMap.end(); ++it) if (!it->second.ResourcePtr) ++Result; + + return Result; +} + +// ----------------------------------------------------------------------------- +// Ressourcen-Verwaltung +// ----------------------------------------------------------------------------- + +BS_Resource * BS_FMODExSound::LoadResource(const std::string& FileName) +{ + BS_ASSERT(m_FMOD); + BS_ASSERT(CanLoadResource(FileName)); + + bool Success; + BS_FMODExResource * ResourcePtr = new BS_FMODExResource(FileName, m_FMOD, Success); + if (Success) + return ResourcePtr; + else + { + delete ResourcePtr; + return 0; + } +} +bool BS_FMODExSound::CanLoadResource(const std::string& FileName) +{ + if (FileName.size() >= 4) + { + std::string Extension(FileName.end() - 4, FileName.end()); + BS_String::ToLower(Extension); + + return Extension == ".wav" || + Extension == ".ogg" || + Extension == ".mp3"; + } + else + return false; +} + +// ----------------------------------------------------------------------------- +// Persistenz +// ----------------------------------------------------------------------------- + +bool BS_FMODExSound::Persist(BS_OutputPersistenceBlock & Writer) +{ + BS_ASSERT(m_FMOD); + + // Alle inaktiven Sounds entfernen, damit kein unnötiger Ballast gespeichert wird + RemoveInactiveSounds(); + + // Warnung ausgeben, wenn dynamische Sounds abgespielt werden + unsigned int PlayingDynamicSounds = CountPlayingDynamicSounds(); + if (PlayingDynamicSounds) BS_LOG_WARNINGLN("There are currently dynamic sounds playing. These will not be persisted."); + + // Nächstes Handle speichern + Writer.Write(m_NextHandle); + + // Anzahl spielender (nicht dynamischer) Sounds speichern + Writer.Write(m_PlayingSoundsMap.size() - PlayingDynamicSounds); + + // Informationen für jeden spielenden (nicht dynamischen) Sound speichern + PSM_CONST_ITER it = m_PlayingSoundsMap.begin(); + while (it != m_PlayingSoundsMap.end()) + { + const PlayingSoundData & PSD = it->second; + + if (PSD.ResourcePtr) + { + // Handle speichern + Writer.Write(it->first); + + // Soundeigenschaften speichern + Writer.Write(PSD.ResourcePtr->GetFileName()); + Writer.Write(static_cast<unsigned int>(PSD.Type)); + Writer.Write(PSD.Layer); + + Writer.Write(PSD.Volume); + Writer.Write(PSD.ChannelPtr->GetPanning()); + Writer.Write(PSD.ChannelPtr->IsLooping()); + Writer.Write(PSD.ChannelPtr->GetLoopStart()); + Writer.Write(PSD.ChannelPtr->GetLoopEnd()); + Writer.Write(PSD.ChannelPtr->GetPosition()); + Writer.Write(PSD.Paused); + Writer.Write(PSD.PausedLayer); + Writer.Write(PSD.PausedGlobal); + } + + ++it; + } + + return true; +} + +// ----------------------------------------------------------------------------- + +bool BS_FMODExSound::Unpersist(BS_InputPersistenceBlock & Reader) +{ + BS_ASSERT(m_FMOD); + + // Alle Sounds stoppen + PSM_ITER it = m_PlayingSoundsMap.begin(); + while (it != m_PlayingSoundsMap.end()) + { + const PlayingSoundData & PSD = it->second; + if (PSD.ChannelPtr) delete PSD.ChannelPtr; + if (PSD.ResourcePtr) PSD.ResourcePtr->Release(); + ++it; + } + + // Sound-Map leeren + m_PlayingSoundsMap.clear(); + + // Nächstes Handle laden + Reader.Read(m_NextHandle); + + // Soundanzahl einlesen + unsigned int SoundCount = 0; + Reader.Read(SoundCount); + + // Informationen über jeden spielenden Sound einlesen und ihn mit den Parametern abspielen + for (unsigned int i = 0; i < SoundCount; ++i) + { + unsigned int Handle; + std::string FileName; + unsigned int Type; + unsigned int Layer; + + float Volume; + float Pan; + bool Loop; + unsigned int LoopStart; + unsigned int LoopEnd; + unsigned int Position; + bool Paused; + bool PausedLayer; + bool PausedGlobal; + + Reader.Read(Handle); + Reader.Read(FileName); + Reader.Read(Type); + Reader.Read(Layer); + + Reader.Read(Volume); + Reader.Read(Pan); + Reader.Read(Loop); + Reader.Read(LoopStart); + Reader.Read(LoopEnd); + Reader.Read(Position); + Reader.Read(Paused); + Reader.Read(PausedLayer); + Reader.Read(PausedGlobal); + + if (Reader.IsGood()) + { + PlaySoundInternal(FileName, (SOUND_TYPES) Type, Volume, Pan, Loop, LoopStart, LoopEnd, Layer, Position, Handle); + } + else + { + return false; + } + } + + return Reader.IsGood(); +} diff --git a/engines/sword25/sfx/fmodexsound.h b/engines/sword25/sfx/fmodexsound.h new file mode 100755 index 0000000000..4c0d8fc448 --- /dev/null +++ b/engines/sword25/sfx/fmodexsound.h @@ -0,0 +1,142 @@ +// ----------------------------------------------------------------------------- +// This file is part of Broken Sword 2.5 +// Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdörfer +// +// Broken Sword 2.5 is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// Broken Sword 2.5 is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Broken Sword 2.5; if not, write to the Free Software +// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +// ----------------------------------------------------------------------------- + +#ifndef FMODEXSOUND_H +#define FMODEXSOUND_H + +// ----------------------------------------------------------------------------- +// Includes +// ----------------------------------------------------------------------------- + +#include "kernel/memlog_off.h" +#include <vector> +#include <map> +#include "kernel/memlog_on.h" + +#include "kernel/common.h" +#include "kernel/hashmap.h" +#include "soundengine.h" + +#include "fmod.h" + +// ----------------------------------------------------------------------------- +// Forward Declarations +// ----------------------------------------------------------------------------- + +class BS_FMODExChannel; +struct FMOD_SYSTEM; +struct FMOD_CHANNEL; + +// ----------------------------------------------------------------------------- +// Klassendefinition +// ----------------------------------------------------------------------------- + +class BS_FMODExSound : public BS_SoundEngine +{ +public: + // ----------------------------------------------------------------------------- + // Konstruktion / Destruktion + // ----------------------------------------------------------------------------- + + BS_FMODExSound(BS_Kernel* pKernel); + virtual ~BS_FMODExSound(); + + bool Init(unsigned int SampleRate, unsigned int Channels = 32); + void Update(); + void SetVolume(float Volume, SOUND_TYPES Type); + float GetVolume(SOUND_TYPES Type); + void PauseAll(); + void ResumeAll(); + void PauseLayer(unsigned int Layer); + void ResumeLayer(unsigned int Layer); + bool PlaySound(const std::string& FileName, SOUND_TYPES Type, float Volume, float Pan, bool Loop, int LoopStart, int LoopEnd, unsigned int Layer); + unsigned int PlaySoundEx(const std::string& FileName, SOUND_TYPES Type, float Volume, float Pan, bool Loop, int LoopStart, int LoopEnd, unsigned int Layer); + unsigned int PlayDynamicSoundEx(DynamicSoundReadCallback ReadCallback, void * UserData, SOUND_TYPES Type, unsigned int SampleRate, unsigned int BitsPerSample, unsigned int Channels, float Volume = 1.0f, float Pan = 0.0f, unsigned int Layer = 0); + + void SetSoundVolume(unsigned int Handle, float Volume); + void SetSoundPanning(unsigned int Handle, float Pan); + void PauseSound(unsigned int Handle); + void ResumeSound(unsigned int Handle); + void StopSound(unsigned int Handle); + bool IsSoundPaused(unsigned int Handle); + bool IsSoundPlaying(unsigned int Handle); + float GetSoundVolume(unsigned int Handle); + float GetSoundPanning(unsigned int Handle); + float GetSoundTime(unsigned int Handle); + + BS_Resource * LoadResource(const std::string& FileName); + bool CanLoadResource(const std::string& FileName); + + // ----------------------------------------------------------------------------- + // Persistenz + // ----------------------------------------------------------------------------- + + bool Persist(BS_OutputPersistenceBlock & Writer); + bool Unpersist(BS_InputPersistenceBlock & Reader); + +private: + struct PlayingSoundData + { + PlayingSoundData() {}; + PlayingSoundData(BS_Resource * ResourcePtr_, BS_FMODExChannel * ChannelPtr_, SOUND_TYPES Type_, unsigned int Layer_, float Volume_, DynamicSoundReadCallback ReadCallback_ = 0, void * UserData_ = 0) : + ResourcePtr(ResourcePtr_), + ChannelPtr(ChannelPtr_), + Type(Type_), + Layer(Layer_), + Volume(Volume_), + ReadCallback(ReadCallback_), + UserData(UserData_), + Paused(false), + PausedLayer(false), + PausedGlobal(false) + {} + + BS_Resource * ResourcePtr; + BS_FMODExChannel * ChannelPtr; + SOUND_TYPES Type; + unsigned int Layer; + DynamicSoundReadCallback ReadCallback; + void * UserData; + + float Volume; + bool Paused; + bool PausedLayer; + bool PausedGlobal; + }; + + typedef BS_Hashmap<unsigned int, PlayingSoundData> PSM; + typedef BS_Hashmap<unsigned int, PlayingSoundData>::iterator PSM_ITER; + typedef BS_Hashmap<unsigned int, PlayingSoundData>::const_iterator PSM_CONST_ITER; + PSM m_PlayingSoundsMap; + + FMOD_SYSTEM * m_FMOD; + float m_Volumes[3]; + unsigned int m_NextHandle; + + void RemoveInactiveSounds(); + PlayingSoundData * GetPlayingSoundDataByHandle(unsigned int Handle); + unsigned int PlaySoundInternal(const std::string& FileName, SOUND_TYPES Type, float Volume, float Pan, bool Loop, int LoopStart, int LoopEnd, unsigned int Layer, unsigned int Handle, unsigned int Position); + unsigned int CountPlayingDynamicSounds(); + + static FMOD_RESULT F_CALLBACK FMODExDynamicSoundSetPosCallback(FMOD_SOUND *sound, int subsound, unsigned int position, FMOD_TIMEUNIT postype); + static FMOD_RESULT F_CALLBACK FMODExDynamicSoundReadCallback(FMOD_SOUND *sound, void *data, unsigned int datalen); + static FMOD_RESULT F_CALLBACK DSPReadCallback(FMOD_DSP_STATE * dsp_state, float * inbuffer, float * outbuffer, unsigned int length, int inchannels, int outchannels); +}; + +#endif diff --git a/engines/sword25/sfx/soundengine.cpp b/engines/sword25/sfx/soundengine.cpp new file mode 100755 index 0000000000..d12c1fb604 --- /dev/null +++ b/engines/sword25/sfx/soundengine.cpp @@ -0,0 +1,36 @@ +// ----------------------------------------------------------------------------- +// This file is part of Broken Sword 2.5 +// Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdörfer +// +// Broken Sword 2.5 is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// Broken Sword 2.5 is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Broken Sword 2.5; if not, write to the Free Software +// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +// ----------------------------------------------------------------------------- + +#define BS_LOG_PREFIX "SOUNDENGINE" + +// ----------------------------------------------------------------------------- +// Includes +// ----------------------------------------------------------------------------- + +#include "soundengine.h" + +// ----------------------------------------------------------------------------- + +BS_SoundEngine::BS_SoundEngine(BS_Kernel * pKernel) : BS_ResourceService(pKernel) +{ + if (!_RegisterScriptBindings()) + BS_LOG_ERRORLN("Script bindings could not be registered."); + else + BS_LOGLN("Script bindings registered."); +}
\ No newline at end of file diff --git a/engines/sword25/sfx/soundengine.h b/engines/sword25/sfx/soundengine.h new file mode 100755 index 0000000000..16442c2481 --- /dev/null +++ b/engines/sword25/sfx/soundengine.h @@ -0,0 +1,253 @@ +// ----------------------------------------------------------------------------- +// This file is part of Broken Sword 2.5 +// Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdörfer +// +// Broken Sword 2.5 is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// Broken Sword 2.5 is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Broken Sword 2.5; if not, write to the Free Software +// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +// ----------------------------------------------------------------------------- + +/* + BS_SoundEngine + ------------- + Dies ist das Soundengine Interface, dass alle Methoden enthält, die eine Soundengine + implementieren muss. + Implementationen der Soundengine müssen von dieser Klasse abgeleitet werden. + Es gilt zu beachten, dass eine Soundengine eine Liste mit ALLEN geladenen Samplen enthalten + muss, und dass diese Samples beim Aufruf des Destruktors freigegeben werden. + + Autor: Malte Thiesen +*/ + +#ifndef BS_SOUNDENGINE_H +#define BS_SOUNDENGINE_H + +// ----------------------------------------------------------------------------- +// Includes +// ----------------------------------------------------------------------------- + +#include "kernel/common.h" +#include "kernel/resservice.h" +#include "kernel/persistable.h" + +// ----------------------------------------------------------------------------- +// Klassendefinition +// ----------------------------------------------------------------------------- + +class BS_SoundEngine : public BS_ResourceService, public BS_Persistable +{ +public: + // ----------------------------------------------------------------------------- + // Enums und Typen + // ----------------------------------------------------------------------------- + + enum SOUND_TYPES + { + MUSIC = 0, + SPEECH = 1, + SFX = 2 + }; + + /** + @brief Die Callbackfunktion von PlayDynamicSoundEx + @param UserData Benutzerspezifizierter Pointer + @param Data Pointer auf den zu beschreibenden Puffer + @param DataLength Länge der zu schreibenden Daten in Byte + */ + typedef void (*DynamicSoundReadCallback)(void * UserData, void * Data, unsigned int DataLength); + + // ----------------------------------------------------------------------------- + // Konstruktion / Destruktion + // ----------------------------------------------------------------------------- + + BS_SoundEngine(BS_Kernel* pKernel); + virtual ~BS_SoundEngine() {}; + + // -------------------------------------------------------------- + // DIESE METHODEN MÜSSEN VON DER SOUNDENGINE IMPLEMENTIERT WERDEN + // -------------------------------------------------------------- + + /** + @brief Initialisiert die Sound-Engine + @param SampleRate Gibt die zu nutzende SampleRate an + @param Channels die maximale Anzahl der Kanäle.<br> + Der Standardwert ist 32. + @return Gibt bei Erfolg TRUE, ansonsten FALSE zurück + @remark Aufrufe an allen anderen Methoden dürfen erst erfolgen, wenn diese Methode erfolgreich aufgerufen wurde. + */ + virtual bool Init(unsigned int SampleRate, unsigned int Channels = 32) = 0; + + /** + @brief Führt einen "Tick" der Sound-Engine aus + + Diese Methode sollte ein mal pro Frame aufgerufen werden. Sie dient dazu Implementationen der Sound-Engine zu ermöglichen, + die nicht in einem eigenen Thread laufen oder zusätzliche Verwaltungsaufgaben durchführen müssen. + */ + virtual void Update() = 0; + + /** + @brief Setzt die Standardlautstärke für die verschiedenen Soundtypen + @param Volume die Standardlautstärke (0 = aus, 1 = volle Lautstärke) + @param Type der Soundtyp dessen Lautstärke geändert werden soll + */ + virtual void SetVolume(float Volume, SOUND_TYPES Type) = 0; + + /** + @brief Gibt die Standardlautstärke der verschiedenen Soundtypen + @param Type der Soundtyp + @return Gibt die Standardlautstärke des übergebenen Soundtyps zurück (0 = aus, 1 = volle Laustärke) + */ + virtual float GetVolume(SOUND_TYPES Type) = 0; + + /** + @brief Pausiert alle Sounds die gerade spielen + */ + virtual void PauseAll() = 0; + + /** + @brief Setzt alle gestoppten Sounds fort + */ + virtual void ResumeAll() = 0; + + /** + @brief Pausiert alle Sounds eines bestimmten Sound-Layers + @param Layer ein Soundlayer + */ + virtual void PauseLayer(unsigned int Layer) = 0; + + /** + @brief Setzt alle Sounds eines Layers fort, die mit PauseLayer() zuvor gestoppt wurden + @param Layer ein Soundlayer + */ + virtual void ResumeLayer(unsigned int Layer) = 0; + + + /** + @brief Spielt einen Sound ab + @param FileName der Dateiname des abzuspielenden Sounds + @param Type der Typ des Sounds + @param Volume die Lautstärke mit der der Soundabgespielt werden soll (0 = aus, 1 = volle Lautstärke) + @param Pan das Panning (-1 = ganz links, 1 = ganz rechts) + @param Loop gibt an ob der Sound geloopt werden soll + @param LoopStart gibt den Start-Looppoint an. Wenn ein Wert kleiner als 0 übergeben wird, wird der Start des Sounds benutzt. + @param LoopEnd gibt den End-Looppoint an. Wenn ein Wert kleiner als 0 übergeben wird, wird das Ende des Sounds benutzt. + @param Layer der Soundlayer + @return Gibt true zurück, wenn das Abspielen des Sounds eingeleitet werden konnte. + @remark Falls eine erweiterte Kontrolle über das Abspielen benötigt wird, z.B. das Ändern der Soundparameter + Volume und Panning während des Abspielvorgangens, sollte PlaySoundEx benutzt werden. + */ + virtual bool PlaySound(const std::string& FileName, SOUND_TYPES Type, float Volume = 1.0f, float Pan = 0.0f, bool Loop = false, int LoopStart = -1, int LoopEnd = -1, unsigned int Layer = 0) = 0; + + /** + @brief Spielt einen Sound ab + @param FileName der Dateiname des abzuspielenden Sounds + @param Type der Typ des Sounds + @param Volume die Lautstärke mit der der Soundabgespielt werden soll (0 = aus, 1 = volle Lautstärke) + @param Pan das Panning (-1 = ganz links, 1 = ganz rechts) + @param Loop gibt an ob der Sound geloopt werden soll + @param LoopStart gibt den Start-Looppoint an. Wenn ein Wert kleiner als 0 übergeben wird, wird der Start des Sounds benutzt. + @param LoopEnd gibt den End-Looppoint an. Wenn ein Wert kleiner als 0 übergeben wird, wird das Ende des Sounds benutzt. + @param Layer der Soundlayer + @return Gibt ein Handle auf den Sounds zurück. Mit diesem Handle kann der Sound während des Abspielens manipuliert werden. + @remark Falls eine erweiterte Kontrolle über das Abspielen benötigt wird, z.B. das Ändern der Soundparameter + Volume und Panning während des Abspielvorgangens, sollte PlaySoundEx benutzt werden. + */ + virtual unsigned int PlaySoundEx(const std::string& FileName, SOUND_TYPES Type, float Volume = 1.0f, float Pan = 0.0f, bool Loop = false, int LoopStart = -1, int LoopEnd = -1, unsigned int Layer = 0) = 0; + + /** + @brief Spielt einen zur Laufzeit generierten Sound ab + @param ReadCallback ein Pointer auf eine Callbackfunktion, die aufgerufen wird, wenn Sounddaten benötigt werden.<br> + Nähere Details zu dieser Funktion gibt es bei der Dokumentation von DynamicSoundReadCallback. + @param UserData ein Pointer auf benutzerdefinierte Daten. Diese werden der Callback-Funktion bei jedem Aufruf übergeben.<br> + Falls keine solche Daten benötigt werden, kann dieser Parameter auf 0 gesetzt werden. + @param Type der Typ des Sounds + @param SampleRate die Sample-Rate des Sounds + @param BitsPerSample die Größe eines Samples in Bits. Hiermit sind tatsächlich nur einzelne Sample gemeint, diese Angabe ist unabhängig von der Anzahl der Kanäle.<br> + Erlaubte Werte sind 8, 16, 24 und 32. + @param Channels die Anzahl der Kanäle.<br> + Erlaubte Werte sind 1 und 2. + @param Volume die Lautstärke mit der der Soundabgespielt werden soll (0 = aus, 1 = volle Lautstärke) + @param Pan das Panning (-1 = ganz links, 1 = ganz rechts) + @param Layer der Soundlayer + @return Gibt ein Handle auf den Sounds zurück. Mit diesem Handle kann der Sound während des Abspielens manipuliert werden. + @remark Dynamische Sounds können nicht persistiert werden. + */ + virtual unsigned int PlayDynamicSoundEx(DynamicSoundReadCallback ReadCallback, void * UserData, SOUND_TYPES Type, unsigned int SampleRate, unsigned int BitsPerSample, unsigned int Channels, float Volume = 1.0f, float Pan = 0.0f, unsigned int Layer = 0) = 0; + + /** + @brief Setzt die Lautstärke eines spielenden Sounds + @param Handle das Handle des Sounds + @param Volume die Lautstärke mit der der Soundabgespielt werden soll (0 = aus, 1 = volle Lautstärke) + */ + virtual void SetSoundVolume(unsigned int Handle, float Volume) = 0; + + /** + @brief Setzt das Panning eines spielenden Sounds + @param Handle das Handle des Sounds + @param Pan das Panning (-1 = ganz links, 1 = ganz rechts) + */ + virtual void SetSoundPanning(unsigned int Handle, float Pan) = 0; + + /** + @brief Pausiert einen Sound + @param Handle das Handle des Sounds + */ + virtual void PauseSound(unsigned int Handle) = 0; + + /** + @brief Setzt einen Sound fort + @param Handle das Handle des Sounds + */ + virtual void ResumeSound(unsigned int Handle) = 0; + + /** + @brief Stoppt einen Sound + @param Handle das Handle des Sounds + @remark Nach einem Aufruf dieser Methode ist das Handle ungültig und darf nicht mehr benutzt werden. + */ + virtual void StopSound(unsigned int Handle) = 0; + + /** + @brief Gibt zurück, ob ein Sound pausiert ist + @param Handle das Handle des Sounds + @return Gibt true zurück, wenn der Sound pausiert ist, ansonsten false. + */ + virtual bool IsSoundPaused(unsigned int Handle) = 0; + + /** + @brief Gibt zurück, ob ein Sound noch spielt + @param Handle das Handle des Sounds + @return Gibt true zurück, wenn der Sound noch spielt, ansonsten false. + */ + virtual bool IsSoundPlaying(unsigned int Handle) = 0; + + /** + @brief Gibt die Lautstärke eines spielenden Sounds zurück (0 = aus, 1 = volle Lautstärke) + */ + virtual float GetSoundVolume(unsigned int Handle) = 0; + + /** + @brief Gibt das Panning eines spielenden Sounds zurück (-1 = ganz links, 1 = ganz rechts) + */ + virtual float GetSoundPanning(unsigned int Handle) = 0; + + /** + @brief Gibt die Position innerhalb des abgespielten Sounds in Sekunden zurück + */ + virtual float GetSoundTime(unsigned int Handle) = 0; + +private: + bool _RegisterScriptBindings(); +}; + +#endif diff --git a/engines/sword25/sfx/soundengine_script.cpp b/engines/sword25/sfx/soundengine_script.cpp new file mode 100755 index 0000000000..9cc3fcb153 --- /dev/null +++ b/engines/sword25/sfx/soundengine_script.cpp @@ -0,0 +1,397 @@ +// ----------------------------------------------------------------------------- +// This file is part of Broken Sword 2.5 +// Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdörfer +// +// Broken Sword 2.5 is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// Broken Sword 2.5 is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Broken Sword 2.5; if not, write to the Free Software +// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +// ----------------------------------------------------------------------------- + +// ----------------------------------------------------------------------------- +// Includes +// ----------------------------------------------------------------------------- + +#include "kernel/common.h" +#include "kernel/kernel.h" +#include "script/script.h" +#include "script/luabindhelper.h" + +#include "soundengine.h" + +// ----------------------------------------------------------------------------- + +static int Init(lua_State * L) +{ + BS_Kernel * pKernel = BS_Kernel::GetInstance(); + BS_ASSERT(pKernel); + BS_SoundEngine * pSfx = static_cast<BS_SoundEngine *>(BS_Kernel::GetInstance()->GetService("sfx")); + BS_ASSERT(pSfx); + + if (lua_gettop(L) == 0) + lua_pushbooleancpp(L, pSfx->Init(44100, 32)); + else if (lua_gettop(L) == 1) + lua_pushbooleancpp(L, pSfx->Init(static_cast<unsigned int>(luaL_checknumber(L, 1)), 32)); + else + lua_pushbooleancpp(L, pSfx->Init(static_cast<unsigned int>(luaL_checknumber(L, 1)), static_cast<unsigned int>(luaL_checknumber(L, 2)))); + + return 1; +} + +// ----------------------------------------------------------------------------- + +static int Update(lua_State * L) +{ + BS_Kernel * pKernel = BS_Kernel::GetInstance(); + BS_ASSERT(pKernel); + BS_SoundEngine * pSfx = static_cast<BS_SoundEngine *>(BS_Kernel::GetInstance()->GetService("sfx")); + BS_ASSERT(pSfx); + + pSfx->Update(); + + return 0; +} + +// ----------------------------------------------------------------------------- + +static int SetVolume(lua_State * L) +{ + BS_Kernel * pKernel = BS_Kernel::GetInstance(); + BS_ASSERT(pKernel); + BS_SoundEngine * pSfx = static_cast<BS_SoundEngine *>(BS_Kernel::GetInstance()->GetService("sfx")); + BS_ASSERT(pSfx); + + pSfx->SetVolume(static_cast<float>(luaL_checknumber(L, 1)), + static_cast<BS_SoundEngine::SOUND_TYPES>(static_cast<unsigned int>(luaL_checknumber(L, 2)))); + + return 0; +} + +// ----------------------------------------------------------------------------- + +static int GetVolume(lua_State * L) +{ + BS_Kernel * pKernel = BS_Kernel::GetInstance(); + BS_ASSERT(pKernel); + BS_SoundEngine * pSfx = static_cast<BS_SoundEngine *>(BS_Kernel::GetInstance()->GetService("sfx")); + BS_ASSERT(pSfx); + + lua_pushnumber(L, pSfx->GetVolume(static_cast<BS_SoundEngine::SOUND_TYPES>(static_cast<unsigned int>(luaL_checknumber(L, 1))))); + + return 1; +} + +// ----------------------------------------------------------------------------- + +static int PauseAll(lua_State * L) +{ + BS_Kernel * pKernel = BS_Kernel::GetInstance(); + BS_ASSERT(pKernel); + BS_SoundEngine * pSfx = static_cast<BS_SoundEngine *>(BS_Kernel::GetInstance()->GetService("sfx")); + BS_ASSERT(pSfx); + + pSfx->PauseAll(); + + return 0; +} + +// ----------------------------------------------------------------------------- + +static int ResumeAll(lua_State * L) +{ + BS_Kernel * pKernel = BS_Kernel::GetInstance(); + BS_ASSERT(pKernel); + BS_SoundEngine * pSfx = static_cast<BS_SoundEngine *>(BS_Kernel::GetInstance()->GetService("sfx")); + BS_ASSERT(pSfx); + + pSfx->ResumeAll(); + + return 0; +} + +// ----------------------------------------------------------------------------- + +static int PauseLayer(lua_State * L) +{ + BS_Kernel * pKernel = BS_Kernel::GetInstance(); + BS_ASSERT(pKernel); + BS_SoundEngine * pSfx = static_cast<BS_SoundEngine *>(BS_Kernel::GetInstance()->GetService("sfx")); + BS_ASSERT(pSfx); + + pSfx->PauseLayer(static_cast<int>(luaL_checknumber(L, 1))); + + return 0; +} + +// ----------------------------------------------------------------------------- + +static int ResumeLayer(lua_State * L) +{ + BS_Kernel * pKernel = BS_Kernel::GetInstance(); + BS_ASSERT(pKernel); + BS_SoundEngine * pSfx = static_cast<BS_SoundEngine *>(BS_Kernel::GetInstance()->GetService("sfx")); + BS_ASSERT(pSfx); + + pSfx->ResumeLayer(static_cast<int>(luaL_checknumber(L, 1))); + + return 0; +} + +// ----------------------------------------------------------------------------- + +static void ProcessPlayParams(lua_State * L, std::string & FileName, BS_SoundEngine::SOUND_TYPES & Type, float & Volume, float & Pan, bool & Loop, int & LoopStart, int & LoopEnd, unsigned int & Layer) +{ + FileName = luaL_checkstring(L, 1); + + Type = static_cast<BS_SoundEngine::SOUND_TYPES>(static_cast<unsigned int>(luaL_checknumber(L, 2))); + + if (lua_gettop(L) < 3 || lua_isnil(L, 3)) Volume = 1.0f; + else Volume = static_cast<float>(luaL_checknumber(L, 3)); + + if (lua_gettop(L) < 4 || lua_isnil(L, 4)) Pan = 0.0f; + else Pan = static_cast<float>(luaL_checknumber(L, 4)); + + if (lua_gettop(L) < 5 || lua_isnil(L, 5)) Loop = false; + else Loop = lua_tobooleancpp(L, 5); + + if (lua_gettop(L) < 6 || lua_isnil(L, 6)) LoopStart = -1; + else LoopStart = static_cast<int>(luaL_checknumber(L, 6)); + + if (lua_gettop(L) < 7 || lua_isnil(L, 7)) LoopEnd = -1; + else LoopEnd = static_cast<int>(luaL_checknumber(L, 7)); + + if (lua_gettop(L) < 8 || lua_isnil(L, 8)) Layer = 0; + else Layer = static_cast<unsigned int>(luaL_checknumber(L, 8)); +} + +static int PlaySound(lua_State * L) +{ + BS_Kernel * pKernel = BS_Kernel::GetInstance(); + BS_ASSERT(pKernel); + BS_SoundEngine * pSfx = static_cast<BS_SoundEngine *>(BS_Kernel::GetInstance()->GetService("sfx")); + BS_ASSERT(pSfx); + + std::string FileName; + BS_SoundEngine::SOUND_TYPES Type; + float Volume; + float Pan; + bool Loop; + int LoopStart; + int LoopEnd; + unsigned int Layer; + ProcessPlayParams(L, FileName, Type, Volume, Pan, Loop, LoopStart, LoopEnd, Layer); + + lua_pushbooleancpp(L, pSfx->PlaySound(FileName, Type, Volume, Pan, Loop, LoopStart, LoopEnd, Layer)); + + return 1; +} + +static int PlaySoundEx(lua_State * L) +{ + BS_Kernel * pKernel = BS_Kernel::GetInstance(); + BS_ASSERT(pKernel); + BS_SoundEngine * pSfx = static_cast<BS_SoundEngine *>(BS_Kernel::GetInstance()->GetService("sfx")); + BS_ASSERT(pSfx); + + std::string FileName; + BS_SoundEngine::SOUND_TYPES Type; + float Volume; + float Pan; + bool Loop; + int LoopStart; + int LoopEnd; + unsigned int Layer; + ProcessPlayParams(L, FileName, Type, Volume, Pan, Loop, LoopStart, LoopEnd, Layer); + + lua_pushnumber(L, pSfx->PlaySoundEx(FileName, Type, Volume, Pan, Loop, LoopStart, LoopEnd, Layer)); + + return 1; +} + +// ----------------------------------------------------------------------------- + +static int SetSoundVolume(lua_State * L) +{ + BS_Kernel * pKernel = BS_Kernel::GetInstance(); + BS_ASSERT(pKernel); + BS_SoundEngine * pSfx = static_cast<BS_SoundEngine *>(BS_Kernel::GetInstance()->GetService("sfx")); + BS_ASSERT(pSfx); + + pSfx->SetSoundVolume(static_cast<unsigned int>(luaL_checknumber(L, 1)), static_cast<float>(luaL_checknumber(L, 2))); + + return 0; +} + +// ----------------------------------------------------------------------------- + +static int SetSoundPanning(lua_State * L) +{ + BS_Kernel * pKernel = BS_Kernel::GetInstance(); + BS_ASSERT(pKernel); + BS_SoundEngine * pSfx = static_cast<BS_SoundEngine *>(BS_Kernel::GetInstance()->GetService("sfx")); + BS_ASSERT(pSfx); + + pSfx->SetSoundPanning(static_cast<unsigned int>(luaL_checknumber(L, 1)), static_cast<float>(luaL_checknumber(L, 2))); + + return 0; +} + +// ----------------------------------------------------------------------------- + +static int PauseSound(lua_State * L) +{ + BS_Kernel * pKernel = BS_Kernel::GetInstance(); + BS_ASSERT(pKernel); + BS_SoundEngine * pSfx = static_cast<BS_SoundEngine *>(BS_Kernel::GetInstance()->GetService("sfx")); + BS_ASSERT(pSfx); + + pSfx->PauseSound(static_cast<unsigned int>(luaL_checknumber(L, 1))); + + return 0; +} + +// ----------------------------------------------------------------------------- + +static int ResumeSound(lua_State * L) +{ + BS_Kernel * pKernel = BS_Kernel::GetInstance(); + BS_ASSERT(pKernel); + BS_SoundEngine * pSfx = static_cast<BS_SoundEngine *>(BS_Kernel::GetInstance()->GetService("sfx")); + BS_ASSERT(pSfx); + + pSfx->ResumeSound(static_cast<unsigned int>(luaL_checknumber(L, 1))); + + return 0; +} + +// ----------------------------------------------------------------------------- + +static int StopSound(lua_State * L) +{ + BS_Kernel * pKernel = BS_Kernel::GetInstance(); + BS_ASSERT(pKernel); + BS_SoundEngine * pSfx = static_cast<BS_SoundEngine *>(BS_Kernel::GetInstance()->GetService("sfx")); + BS_ASSERT(pSfx); + + pSfx->StopSound(static_cast<unsigned int>(luaL_checknumber(L, 1))); + + return 0; +} + +// ----------------------------------------------------------------------------- + +static int IsSoundPaused(lua_State * L) +{ + BS_Kernel * pKernel = BS_Kernel::GetInstance(); + BS_ASSERT(pKernel); + BS_SoundEngine * pSfx = static_cast<BS_SoundEngine *>(BS_Kernel::GetInstance()->GetService("sfx")); + BS_ASSERT(pSfx); + + lua_pushbooleancpp(L, pSfx->IsSoundPaused(static_cast<unsigned int>(luaL_checknumber(L, 1)))); + + return 1; +} + +// ----------------------------------------------------------------------------- + +static int IsSoundPlaying(lua_State * L) +{ + BS_Kernel * pKernel = BS_Kernel::GetInstance(); + BS_ASSERT(pKernel); + BS_SoundEngine * pSfx = static_cast<BS_SoundEngine *>(BS_Kernel::GetInstance()->GetService("sfx")); + BS_ASSERT(pSfx); + + lua_pushbooleancpp(L, pSfx->IsSoundPlaying(static_cast<unsigned int>(luaL_checknumber(L, 1)))); + + return 1; +} + +// ----------------------------------------------------------------------------- + +static int GetSoundVolume(lua_State * L) +{ + BS_Kernel * pKernel = BS_Kernel::GetInstance(); + BS_ASSERT(pKernel); + BS_SoundEngine * pSfx = static_cast<BS_SoundEngine *>(BS_Kernel::GetInstance()->GetService("sfx")); + BS_ASSERT(pSfx); + + lua_pushnumber(L, pSfx->GetSoundVolume(static_cast<unsigned int>(luaL_checknumber(L, 1)))); + + return 1; +} + +// ----------------------------------------------------------------------------- + +static int GetSoundPanning(lua_State * L) +{ + BS_Kernel * pKernel = BS_Kernel::GetInstance(); + BS_ASSERT(pKernel); + BS_SoundEngine * pSfx = static_cast<BS_SoundEngine *>(BS_Kernel::GetInstance()->GetService("sfx")); + BS_ASSERT(pSfx); + + lua_pushnumber(L, pSfx->GetSoundPanning(static_cast<unsigned int>(luaL_checknumber(L, 1)))); + + return 1; +} + +// ----------------------------------------------------------------------------- + +static const char * SFX_LIBRARY_NAME = "Sfx"; + +static const luaL_reg SFX_FUNCTIONS[] = +{ + "Init", Init, + "Update", Update, + "__SetVolume", SetVolume, + "__GetVolume", GetVolume, + "PauseAll", PauseAll, + "ResumeAll", ResumeAll, + "PauseLayer", PauseLayer, + "ResumeLayer", ResumeLayer, + "__PlaySound", PlaySound, + "__PlaySoundEx", PlaySoundEx, + "__SetSoundVolume", SetSoundVolume, + "__SetSoundPanning", SetSoundPanning, + "__PauseSound", PauseSound, + "__ResumeSound", ResumeSound, + "__StopSound", StopSound, + "__IsSoundPaused", IsSoundPaused, + "__IsSoundPlaying", IsSoundPlaying, + "__GetSoundVolume", GetSoundVolume, + "__GetSoundPanning", GetSoundPanning, + 0, 0, +}; + +static const lua_constant_reg SFX_CONSTANTS[] = +{ + "MUSIC", BS_SoundEngine::MUSIC, + "SPEECH", BS_SoundEngine::SPEECH, + "SFX", BS_SoundEngine::SFX, + 0, 0, +}; + +// ----------------------------------------------------------------------------- + +bool BS_SoundEngine::_RegisterScriptBindings() +{ + BS_Kernel * pKernel = BS_Kernel::GetInstance(); + BS_ASSERT(pKernel); + BS_ScriptEngine * pScript = static_cast<BS_ScriptEngine *>(pKernel->GetService("script")); + BS_ASSERT(pScript); + lua_State * L = static_cast<lua_State *>(pScript->GetScriptObject()); + BS_ASSERT(L); + + if (!BS_LuaBindhelper::AddFunctionsToLib(L, SFX_LIBRARY_NAME, SFX_FUNCTIONS)) return false; + if (!BS_LuaBindhelper::AddConstantsToLib(L, SFX_LIBRARY_NAME, SFX_CONSTANTS)) return false; + + return true; +} |