Skip to content

Commit

Permalink
parse sound metadata
Browse files Browse the repository at this point in the history
  • Loading branch information
Zbizu committed Jan 24, 2025
1 parent 94fd650 commit 80ba60a
Show file tree
Hide file tree
Showing 7 changed files with 288 additions and 9 deletions.
8 changes: 4 additions & 4 deletions meta.lua
Original file line number Diff line number Diff line change
Expand Up @@ -157,10 +157,6 @@ function g_things.loadAppearances(file) end
---@return boolean
function g_things.loadStaticData(file) end

---@param file string
---@return boolean
function g_things.loadSounds(file) end

---@param file string
---@return boolean
function g_things.loadDat(file) end
Expand Down Expand Up @@ -5835,6 +5831,10 @@ function g_sounds.createSoundEffect() end
---@return boolean
function g_sounds.isEaxEnabled() end

---@param file string
---@return boolean
function g_sounds.loadClientFiles(directory) end

--------------------------------
--------- SoundSource ----------
--------------------------------
Expand Down
1 change: 0 additions & 1 deletion src/client/luafunctions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,6 @@ void Client::registerLuaFunctions()
g_lua.registerSingletonClass("g_things");
g_lua.bindSingletonFunction("g_things", "loadAppearances", &ThingTypeManager::loadAppearances, &g_things);
g_lua.bindSingletonFunction("g_things", "loadStaticData", &ThingTypeManager::loadStaticData, &g_things);
g_lua.bindSingletonFunction("g_things", "loadSounds", &ThingTypeManager::loadSounds, &g_things);
g_lua.bindSingletonFunction("g_things", "loadDat", &ThingTypeManager::loadDat, &g_things);
g_lua.bindSingletonFunction("g_things", "loadOtml", &ThingTypeManager::loadOtml, &g_things);
g_lua.bindSingletonFunction("g_things", "isDatLoaded", &ThingTypeManager::isDatLoaded, &g_things);
Expand Down
1 change: 0 additions & 1 deletion src/client/thingtypemanager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@

#include <appearances.pb.h>
#include <staticdata.pb.h>
#include <sounds.pb.h>

#include <nlohmann/json.hpp>

Expand Down
1 change: 1 addition & 0 deletions src/framework/luafunctions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1004,6 +1004,7 @@ void Application::registerLuaFunctions()
g_lua.bindSingletonFunction("g_sounds", "setPosition", &SoundManager::setPosition, &g_sounds);
g_lua.bindSingletonFunction("g_sounds", "createSoundEffect", &SoundManager::createSoundEffect, &g_sounds);
g_lua.bindSingletonFunction("g_sounds", "isEaxEnabled", &SoundManager::isEaxEnabled, &g_sounds);
g_lua.bindSingletonFunction("g_sounds", "loadClientFiles", &SoundManager::loadClientFiles, &g_sounds);

g_lua.registerClass<SoundSource>();
g_lua.bindClassStaticFunction<SoundSource>("create", [] { return std::make_shared<SoundSource>(); });
Expand Down
187 changes: 186 additions & 1 deletion src/framework/sound/soundmanager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,15 @@
#include <framework/core/asyncdispatcher.h>
#include <framework/core/clock.h>
#include <framework/core/resourcemanager.h>
#include <nlohmann/json.hpp>
#include <sounds.pb.h>

#include "soundchannel.h"

using namespace otclient::protobuf;

using json = nlohmann::json;

class StreamSoundSource;
class CombinedSoundSource;
class SoundFile;
Expand Down Expand Up @@ -339,4 +345,183 @@ bool SoundManager::isEaxEnabled()
return true;
}
return false;
}
}

using ProtobufSoundFiles = google::protobuf::RepeatedPtrField<sounds::Sound>;
using ProtobufSoundEffects = google::protobuf::RepeatedPtrField<sounds::NumericSoundEffect>;
using ProtobufLocationAmbiences = google::protobuf::RepeatedPtrField<sounds::AmbienceStream>;
using ProtobufItemAmbiences = google::protobuf::RepeatedPtrField<sounds::AmbienceObjectStream>;
using ProtobufMusicTracks = google::protobuf::RepeatedPtrField<sounds::MusicTemplate>;

bool SoundManager::loadFromProtobuf(const std::string& directory, const std::string& fileName)
{
/*
////// file structure
<struct> Sounds
|
|
| ////// audio file id -> audio file name (ogg)
|-+- <vector> (Sound) sound
| |----> (u32) id
| |----> (string) filename (sound-abcd.ogg)
| |----> (string) original_filename (unused)
| |----> (bool) is_stream
|
|
| ////// sound effect
|-+- <vector> (NumericSoundEffect) numeric_sound_effect
| |----> (u32) id (the id you request in sound effect packet)
| |----> (enum - ENumericSoundType) numeric_sound_type
| |-+--> (MinMaxFloat) random_pitch
| | |------> (float) min
| | |------> (float) max
| |
| |-+--> (MinMaxFloat) random_volume
| | |------> (float) min
| | |------> (float) max
| |
| |-+--> (SimpleSoundEffect) simple_sound_effect
| | |------> (u32) sound_id (audio file id)
| |
| |-+--> (RandomSoundEffect) random_sound_effect
| | |------> <vector> (u32) random_sound_id (audio file id)
|
|
| ////// ambient sound for location (needs to be triggered with a packet)
|-+- <vector> (AmbienceStream) ambience_stream
| |----> (u32) id
| |----> (u32) looping_sound_id (audio file id)
| |-+--> <vector> (DelayedSoundEffect) delayed_effects
| | |------> (u32) numeric_sound_effect_id (sound effect id)
| | |------> (u32) delay_seconds
|
|
| ////// sound of items placed on the map
|-+- <vector> (AmbienceObjectStream) ambience_object_stream
| |----> (u32) id (ID OF THIS EFFECT, NOT ITEM ID!)
| |----> <vector> (u32) counted_appearance_types (ITEM CLIENT IDS that will have this sound, eg. waterfall or campfire)
| |-+--> <vector> (AppearanceTypesCountSoundEffect) sound_effects
| | |------> (u32) count (how many on the screen are required to trigger, eg. 3 are required for the swamp tiles to play sound)
| | |------> (u32) looping_sound_id (audio file id)
| |----> (u32) max_sound_distance (how far can it be heard)
|
|
| ////// music for location (needs to be triggered with a packet)
|-+- <vector> (MusicTemplate) music_template
|----> (u32) id
|----> (u32) sound_id (audio file id)
|----> (enum - EMusicType) music_type
*/

// create the sound bank from protobuf file
try {
std::stringstream fileInputStream;
g_resources.readFileStream(g_resources.resolvePath(stdext::format("%s%s", directory, fileName)), fileInputStream);

// read the soundbank
auto protobufSounds = sounds::Sounds();
if (!protobufSounds.ParseFromIstream(&fileInputStream)) {
throw stdext::exception("Couldn't parse appearances lib.");
}

// deserialize audio files
for (const auto& protobufAudioFile : protobufSounds.sound()) {
m_clientSoundFiles[protobufAudioFile.id()] = protobufAudioFile.filename();
}

// deserialize sound effects
for (const auto& protobufSoundEffect : protobufSounds.numeric_sound_effect()) {
const auto& pitch = protobufSoundEffect.random_pitch();
const auto& volume = protobufSoundEffect.random_volume();
std::vector<uint32_t> randomSounds = {};
if (protobufSoundEffect.has_random_sound_effect()) {
for (const uint32_t& audioFileId : protobufSoundEffect.random_sound_effect().random_sound_id()) {
randomSounds.push_back(audioFileId);
}
}

uint32_t effectId = protobufSoundEffect.id();
m_clientSoundEffects.emplace(effectId, ClientSoundEffect{
effectId,
static_cast<ClientSoundType>(protobufSoundEffect.numeric_sound_type()),
pitch.min_value(),
pitch.max_value(),
volume.max_value(),
volume.max_value(),
protobufSoundEffect.has_simple_sound_effect() ? protobufSoundEffect.simple_sound_effect().sound_id() : 0,
std::move(randomSounds)
});
}

// deserialize location ambients
for (const auto& protobufLocationAmbient : protobufSounds.ambience_stream()) {
uint32_t effectId = protobufLocationAmbient.id();
DelayedSoundEffects effects = {};
for (const auto& delayedEffect : protobufLocationAmbient.delayed_effects()) {
effects.push_back({delayedEffect.numeric_sound_effect_id(), delayedEffect.delay_seconds()});
}

m_clientAmbientEffects.emplace(effectId, ClientLocationAmbient{
effectId,
protobufLocationAmbient.looping_sound_id(),
std::move(effects)
});
}

// deserialize item ambients
for (const auto& protobufItemAmbient : protobufSounds.ambience_object_stream()) {
std::vector<uint32_t> itemClientIds = {};
for (const auto& itemId : protobufItemAmbient.counted_appearance_types()) {
itemClientIds.push_back(itemId);
}

ItemCountSoundEffects soundEffects = {};
for (const auto& soundEffect : protobufItemAmbient.sound_effects()) {
soundEffects.push_back({soundEffect.looping_sound_id(), soundEffect.count()});
}

uint32_t effectId = protobufItemAmbient.id();
m_clientItemAmbientEffects.emplace(effectId, ClientItemAmbient{
effectId,
std::move(itemClientIds),
std::move(soundEffects)
});
}

// deserialize music
for (const auto& protobufMusicTemplate : protobufSounds.music_template()) {
uint32_t effectId = protobufMusicTemplate.id();
m_clientMusic.emplace(effectId, ClientMusic{
effectId,
protobufMusicTemplate.sound_id(),
static_cast<ClientMusicType>(protobufMusicTemplate.music_type())
});
}

return true;
} catch (const std::exception& e) {
g_logger.error(stdext::format("Failed to load soundbank '%s': %s", fileName, e.what()));
return false;
}
}

bool SoundManager::loadClientFiles(const std::string& directory)
{
// find catalog from json file
try {
int spritesCount = 0;

Check warning on line 512 in src/framework/sound/soundmanager.cpp

View workflow job for this annotation

GitHub Actions / ubuntu-22.04-linux-debug

unused variable ‘spritesCount’ [-Wunused-variable]

Check warning on line 512 in src/framework/sound/soundmanager.cpp

View workflow job for this annotation

GitHub Actions / ubuntu-24.04-linux-debug

unused variable ‘spritesCount’ [-Wunused-variable]
json document = json::parse(g_resources.readFileContents(g_resources.resolvePath(g_resources.guessFilePath(directory + "catalog-sound", "json"))));
for (const auto& obj : document) {
const auto& type = obj["type"];
if (type == "sounds") {
// dat file encoded with protobuf
loadFromProtobuf(directory, obj["file"]);
}
}

return true;
} catch (const std::exception& e) {
g_logger.error(stdext::format("Failed to load '%s' (Sounds): %s", directory, e.what()));
return false;
}
}
95 changes: 95 additions & 0 deletions src/framework/sound/soundmanager.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,91 @@
#include <framework/util/point.h>
#include <future>

using DelayedSoundEffect = std::pair<uint32_t, uint32_t>;
using DelayedSoundEffects = std::vector<DelayedSoundEffect>;
using ItemCountSoundEffect = std::pair<uint32_t, uint32_t>;
using ItemCountSoundEffects = std::vector<ItemCountSoundEffect>;

enum ClientSoundType
{
NUMERIC_SOUND_TYPE_UNKNOWN = 0,
NUMERIC_SOUND_TYPE_SPELL_ATTACK = 1,
NUMERIC_SOUND_TYPE_SPELL_HEALING = 2,
NUMERIC_SOUND_TYPE_SPELL_SUPPORT = 3,
NUMERIC_SOUND_TYPE_WEAPON_ATTACK = 4,
NUMERIC_SOUND_TYPE_CREATURE_NOISE = 5,
NUMERIC_SOUND_TYPE_CREATURE_DEATH = 6,
NUMERIC_SOUND_TYPE_CREATURE_ATTACK = 7,
NUMERIC_SOUND_TYPE_AMBIENCE_STREAM = 8,
NUMERIC_SOUND_TYPE_FOOD_AND_DRINK = 9,
NUMERIC_SOUND_TYPE_ITEM_MOVEMENT = 10,
NUMERIC_SOUND_TYPE_EVENT = 11,
NUMERIC_SOUND_TYPE_UI = 12,
NUMERIC_SOUND_TYPE_WHISPER_WITHOUT_OPEN_CHAT = 13,
NUMERIC_SOUND_TYPE_CHAT_MESSAGE = 14,
NUMERIC_SOUND_TYPE_PARTY = 15,
NUMERIC_SOUND_TYPE_VIP_LIST = 16,
NUMERIC_SOUND_TYPE_RAID_ANNOUNCEMENT = 17,
NUMERIC_SOUND_TYPE_SERVER_MESSAGE = 18,
NUMERIC_SOUND_TYPE_SPELL_GENERIC = 19
};

enum ClientMusicType
{
MUSIC_TYPE_UNKNOWN = 0,
MUSIC_TYPE_MUSIC = 1,
MUSIC_TYPE_MUSIC_IMMEDIATE = 2,
MUSIC_TYPE_MUSIC_TITLE = 3,
};

// client sound effect parsed from the protobuf file
struct ClientSoundEffect
{
uint32_t clientId;
ClientSoundType type;
float pitchMin;
float pitchMax;
float volumeMin;
float volumeMax;
uint32_t soundId = 0;
std::vector<uint32_t> randomSoundId;
};

// client location ambient parsed from the protobuf file
struct ClientLocationAmbient
{
uint32_t clientId;
uint32_t loopedAudioFileId;

// vector of pairs, where the pair is:
// < effect clientId, delay in seconds >
DelayedSoundEffects delayedSoundEffects;
};

// client item ambient parsed from the protobuf file
struct ClientItemAmbient
{
uint32_t id;
std::vector<uint32_t> clientIds;

// this is a very specific client mechanic
// depending on how many items are on the game screen
// a different looped ambient effect will be played
// for example, configuration like this:
// 1 -> 630
// 5 -> 625
// means that when there is one item on the screen, an audio file number 630 should play
// once there are 5 of them, the client should play an audio file number 625
ItemCountSoundEffects itemCountSoundEffects;
};

struct ClientMusic
{
uint32_t id; // track id
uint32_t audioFileId; // audio file id
ClientMusicType musicType;
};

//@bindsingleton g_sounds
class SoundManager
{
Expand All @@ -47,6 +132,7 @@ class SoundManager
void stopAll();
void setPosition(const Point& pos);
bool isEaxEnabled();
bool loadClientFiles(const std::string& directory);

void preload(std::string filename);
SoundSourcePtr play(const std::string& filename, float fadetime = 0, float gain = 0, float pitch = 0);
Expand All @@ -58,6 +144,7 @@ class SoundManager

private:
SoundSourcePtr createSoundSource(const std::string& name);
bool loadFromProtobuf(const std::string& directory, const std::string& fileName);

ALCdevice* m_device{};
ALCcontext* m_context{};
Expand All @@ -69,6 +156,14 @@ class SoundManager
std::unordered_map<int, SoundChannelPtr> m_channels;
std::unordered_map<std::string, SoundEffectPtr> m_effects;

// soundbanks for protocol 13 and newer
std::map<uint32_t, std::string> m_clientSoundFiles;
std::map<uint32_t, ClientSoundEffect> m_clientSoundEffects;
std::map<uint32_t, ClientLocationAmbient> m_clientAmbientEffects;
std::map<uint32_t, ClientItemAmbient> m_clientItemAmbientEffects;
std::map<uint32_t, ClientMusic> m_clientMusic;


std::vector<SoundSourcePtr> m_sources;
bool m_audioEnabled{ true };
};
Expand Down
4 changes: 2 additions & 2 deletions src/protobuf/sounds.proto
Original file line number Diff line number Diff line change
Expand Up @@ -432,6 +432,6 @@ message AppearanceTypesCountSoundEffect {
}

message MinMaxFloat {
optional float min = 1;
optional float max = 2;
optional float min_value = 1;
optional float max_value = 2;
}

0 comments on commit 80ba60a

Please sign in to comment.