feat(audio): 添加音频数据库支持及XML解析功能
实现音频数据库系统,支持从XML文件加载音频配置 添加RapidXML库用于XML解析 提供统一的音频查询接口,支持音效、音乐和随机音效组
This commit is contained in:
323
Frostbite2D/include/frostbite2D/resource/audio_database.h
Normal file
323
Frostbite2D/include/frostbite2D/resource/audio_database.h
Normal file
@@ -0,0 +1,323 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <frostbite2D/types/type_alias.h>
|
||||||
|
#include <optional>
|
||||||
|
#include <random>
|
||||||
|
#include <string>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace frostbite2D {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 音频项类型枚举
|
||||||
|
*/
|
||||||
|
enum class AudioEntryType {
|
||||||
|
None, ///< 无效
|
||||||
|
Effect, ///< 音效
|
||||||
|
Music, ///< 背景音乐
|
||||||
|
Random ///< 随机音效组
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 音效配置
|
||||||
|
*/
|
||||||
|
struct AudioEffect {
|
||||||
|
std::string id; ///< 音效 ID
|
||||||
|
std::string file; ///< 文件路径
|
||||||
|
std::string subgroup; ///< 子组(可选)
|
||||||
|
int32 loopDelay = -1; ///< 循环延迟(-1 表示未设置)
|
||||||
|
int32 loopDelayRange = -1;///< 循环延迟范围(-1 表示未设置)
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 音乐配置
|
||||||
|
*/
|
||||||
|
struct AudioMusic {
|
||||||
|
std::string id; ///< 音乐 ID
|
||||||
|
std::string file; ///< 文件路径
|
||||||
|
int32 loopDelay = -1; ///< 循环延迟(-1 表示未设置)
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 随机音效项
|
||||||
|
*/
|
||||||
|
struct RandomItem {
|
||||||
|
std::string tag; ///< 音效标签(ID)
|
||||||
|
int32 probability = 0; ///< 概率权重
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 随机音效组
|
||||||
|
*/
|
||||||
|
struct AudioRandom {
|
||||||
|
std::string id; ///< 随机组 ID
|
||||||
|
std::vector<RandomItem> items; ///< 随机项列表
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 统一的音频条目(包含类型和数据)
|
||||||
|
*/
|
||||||
|
struct AudioEntry {
|
||||||
|
AudioEntryType type = AudioEntryType::None; ///< 条目类型
|
||||||
|
|
||||||
|
const AudioEffect* effect = nullptr; ///< 音效指针(type == Effect 时有效)
|
||||||
|
const AudioMusic* music = nullptr; ///< 音乐指针(type == Music 时有效)
|
||||||
|
const AudioRandom* random = nullptr; ///< 随机组指针(type == Random 时有效)
|
||||||
|
|
||||||
|
/// @brief 判断是否有效
|
||||||
|
bool isValid() const { return type != AudioEntryType::None; }
|
||||||
|
|
||||||
|
/// @brief 判断是否为音效
|
||||||
|
bool isEffect() const { return type == AudioEntryType::Effect; }
|
||||||
|
|
||||||
|
/// @brief 判断是否为音乐
|
||||||
|
bool isMusic() const { return type == AudioEntryType::Music; }
|
||||||
|
|
||||||
|
/// @brief 判断是否为随机组
|
||||||
|
bool isRandom() const { return type == AudioEntryType::Random; }
|
||||||
|
|
||||||
|
/// @brief 安全获取文件路径
|
||||||
|
std::string getFilePath() const {
|
||||||
|
if (type == AudioEntryType::Effect && effect)
|
||||||
|
return effect->file;
|
||||||
|
if (type == AudioEntryType::Music && music)
|
||||||
|
return music->file;
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @brief 安全获取子组
|
||||||
|
std::string getSubgroup() const {
|
||||||
|
if (type == AudioEntryType::Effect && effect)
|
||||||
|
return effect->subgroup;
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 音频数据库(单例)
|
||||||
|
*
|
||||||
|
* 用于解析和访问 audio.xml 文件中的音频配置。
|
||||||
|
* 提供统一的查询接口,自动判断音频类型。
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* auto& audioDB = AudioDatabase::get();
|
||||||
|
* if (audioDB.loadFromFile("audio.xml")) {
|
||||||
|
* // 统一查询
|
||||||
|
* if (auto entry = audioDB.get("GN_FINAL_SHOT_01")) {
|
||||||
|
* // 使用 entry
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* // 获取文件路径
|
||||||
|
* if (auto filePath = audioDB.getFilePath("GN_FINAL_SHOT_01")) {
|
||||||
|
* // 使用 *filePath
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* // 获取音效(自动处理随机组)
|
||||||
|
* if (auto soundId = audioDB.getSound("R_GN_FINAL_SHOT")) {
|
||||||
|
* // 使用 *soundId
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
class AudioDatabase {
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* @brief 获取单例实例
|
||||||
|
* @return 音频数据库实例引用
|
||||||
|
*/
|
||||||
|
static AudioDatabase& get();
|
||||||
|
|
||||||
|
AudioDatabase(const AudioDatabase&) = delete;
|
||||||
|
AudioDatabase& operator=(const AudioDatabase&) = delete;
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// 加载和解析
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 从文件加载音频数据库
|
||||||
|
* @param path XML 文件路径
|
||||||
|
* @return 加载成功返回 true
|
||||||
|
*/
|
||||||
|
bool loadFromFile(const std::string& path);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 从字符串加载音频数据库
|
||||||
|
* @param xmlContent XML 内容字符串
|
||||||
|
* @return 加载成功返回 true
|
||||||
|
*/
|
||||||
|
bool loadFromString(const std::string& xmlContent);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 清空数据库
|
||||||
|
*/
|
||||||
|
void clear();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 检查数据库是否已加载
|
||||||
|
* @return 已加载返回 true
|
||||||
|
*/
|
||||||
|
bool isLoaded() const;
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// 统一查询接口
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 统一查询:输入任意 ID,自动判断类型
|
||||||
|
* @param id 音频 ID(可以是 EFFECT、MUSIC 或 RANDOM 的 ID)
|
||||||
|
* @return 音频条目,未找到返回 std::nullopt
|
||||||
|
*/
|
||||||
|
std::optional<AudioEntry> get(const std::string& id);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取音效 ID(自动处理 RANDOM 类型)
|
||||||
|
*
|
||||||
|
* 如果 id 是 RANDOM 类型,会按概率随机选择一个音效;
|
||||||
|
* 如果 id 是 EFFECT 类型,直接返回该 ID。
|
||||||
|
*
|
||||||
|
* @param id 音效 ID 或随机组 ID
|
||||||
|
* @return 音效 ID,未找到返回 std::nullopt
|
||||||
|
*/
|
||||||
|
std::optional<std::string> getSound(const std::string& id);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取文件路径
|
||||||
|
*
|
||||||
|
* 对于 EFFECT 和 MUSIC 类型,返回对应的文件路径;
|
||||||
|
* 对于 RANDOM 类型,先随机选择一个音效再返回其文件路径。
|
||||||
|
*
|
||||||
|
* @param id 音频 ID
|
||||||
|
* @return 文件路径,未找到返回 std::nullopt
|
||||||
|
*/
|
||||||
|
std::optional<std::string> getFilePath(const std::string& id);
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// 类型特定查询(高级用法)
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取音效配置
|
||||||
|
* @param id 音效 ID
|
||||||
|
* @return 音效配置指针,未找到返回 std::nullopt
|
||||||
|
*/
|
||||||
|
std::optional<const AudioEffect*> getEffect(const std::string& id);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取音乐配置
|
||||||
|
* @param id 音乐 ID
|
||||||
|
* @return 音乐配置指针,未找到返回 std::nullopt
|
||||||
|
*/
|
||||||
|
std::optional<const AudioMusic*> getMusic(const std::string& id);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取随机音效组配置
|
||||||
|
* @param id 随机组 ID
|
||||||
|
* @return 随机组配置指针,未找到返回 std::nullopt
|
||||||
|
*/
|
||||||
|
std::optional<const AudioRandom*> getRandom(const std::string& id);
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// 统计信息
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取音效数量
|
||||||
|
*/
|
||||||
|
size_t effectCount() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取音乐数量
|
||||||
|
*/
|
||||||
|
size_t musicCount() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取随机组数量
|
||||||
|
*/
|
||||||
|
size_t randomCount() const;
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// 便捷访问方法(推荐使用)
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 检查 ID 是否存在
|
||||||
|
* @param id 音频 ID
|
||||||
|
* @return 存在返回 true
|
||||||
|
*/
|
||||||
|
bool has(const std::string& id);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取文件路径(不存在返回空字符串)
|
||||||
|
* @param id 音频 ID
|
||||||
|
* @return 文件路径,未找到返回空字符串
|
||||||
|
*/
|
||||||
|
std::string filePath(const std::string& id);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取音效 ID(自动处理随机组,不存在返回空字符串)
|
||||||
|
* @param idOrRandomId 音效 ID 或随机组 ID
|
||||||
|
* @return 音效 ID,未找到返回空字符串
|
||||||
|
*/
|
||||||
|
std::string soundId(const std::string& idOrRandomId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取子组(EFFECT 的 SUBGROUP 属性)
|
||||||
|
* @param id 音频 ID
|
||||||
|
* @return 子组名,未找到或不存在返回空字符串
|
||||||
|
*/
|
||||||
|
std::string subgroup(const std::string& id);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取条目类型
|
||||||
|
* @param id 音频 ID
|
||||||
|
* @return 条目类型,未找到返回 AudioEntryType::None
|
||||||
|
*/
|
||||||
|
AudioEntryType typeOf(const std::string& id);
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// 带检查的访问方法
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 尝试获取文件路径,带成功标志
|
||||||
|
* @param id 音频 ID
|
||||||
|
* @param outPath 输出文件路径
|
||||||
|
* @return 成功返回 true
|
||||||
|
*/
|
||||||
|
bool tryGetFilePath(const std::string& id, std::string& outPath);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 尝试获取音效 ID,带成功标志
|
||||||
|
* @param id 音效 ID 或随机组 ID
|
||||||
|
* @param outSoundId 输出音效 ID
|
||||||
|
* @return 成功返回 true
|
||||||
|
*/
|
||||||
|
bool tryGetSoundId(const std::string& id, std::string& outSoundId);
|
||||||
|
|
||||||
|
private:
|
||||||
|
AudioDatabase() = default;
|
||||||
|
~AudioDatabase() = default;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 初始化随机数生成器
|
||||||
|
*/
|
||||||
|
void initializeRNG();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 从随机组中按概率选择一个音效
|
||||||
|
* @param randomGroup 随机组配置
|
||||||
|
* @return 选中的音效 ID
|
||||||
|
*/
|
||||||
|
std::string selectFromRandom(const AudioRandom& randomGroup);
|
||||||
|
|
||||||
|
std::unordered_map<std::string, AudioEffect> effectMap_; ///< 音效映射
|
||||||
|
std::unordered_map<std::string, AudioMusic> musicMap_; ///< 音乐映射
|
||||||
|
std::unordered_map<std::string, AudioRandom> randomMap_; ///< 随机组映射
|
||||||
|
|
||||||
|
std::mt19937 rng_; ///< 随机数生成器
|
||||||
|
bool rngInitialized_ = false; ///< 随机数生成器是否已初始化
|
||||||
|
bool loaded_ = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace frostbite2D
|
||||||
2596
Frostbite2D/include/rapidxml/rapidxml.hpp
Normal file
2596
Frostbite2D/include/rapidxml/rapidxml.hpp
Normal file
File diff suppressed because it is too large
Load Diff
174
Frostbite2D/include/rapidxml/rapidxml_iterators.hpp
Normal file
174
Frostbite2D/include/rapidxml/rapidxml_iterators.hpp
Normal file
@@ -0,0 +1,174 @@
|
|||||||
|
#ifndef RAPIDXML_ITERATORS_HPP_INCLUDED
|
||||||
|
#define RAPIDXML_ITERATORS_HPP_INCLUDED
|
||||||
|
|
||||||
|
// Copyright (C) 2006, 2009 Marcin Kalicinski
|
||||||
|
// Version 1.13
|
||||||
|
// Revision $DateTime: 2009/05/13 01:46:17 $
|
||||||
|
//! \file rapidxml_iterators.hpp This file contains rapidxml iterators
|
||||||
|
|
||||||
|
#include "rapidxml.hpp"
|
||||||
|
|
||||||
|
namespace rapidxml
|
||||||
|
{
|
||||||
|
|
||||||
|
//! Iterator of child nodes of xml_node
|
||||||
|
template<class Ch>
|
||||||
|
class node_iterator
|
||||||
|
{
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
typedef typename xml_node<Ch> value_type;
|
||||||
|
typedef typename xml_node<Ch> &reference;
|
||||||
|
typedef typename xml_node<Ch> *pointer;
|
||||||
|
typedef std::ptrdiff_t difference_type;
|
||||||
|
typedef std::bidirectional_iterator_tag iterator_category;
|
||||||
|
|
||||||
|
node_iterator()
|
||||||
|
: m_node(0)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
node_iterator(xml_node<Ch> *node)
|
||||||
|
: m_node(node->first_node())
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
reference operator *() const
|
||||||
|
{
|
||||||
|
assert(m_node);
|
||||||
|
return *m_node;
|
||||||
|
}
|
||||||
|
|
||||||
|
pointer operator->() const
|
||||||
|
{
|
||||||
|
assert(m_node);
|
||||||
|
return m_node;
|
||||||
|
}
|
||||||
|
|
||||||
|
node_iterator& operator++()
|
||||||
|
{
|
||||||
|
assert(m_node);
|
||||||
|
m_node = m_node->next_sibling();
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
node_iterator operator++(int)
|
||||||
|
{
|
||||||
|
node_iterator tmp = *this;
|
||||||
|
++this;
|
||||||
|
return tmp;
|
||||||
|
}
|
||||||
|
|
||||||
|
node_iterator& operator--()
|
||||||
|
{
|
||||||
|
assert(m_node && m_node->previous_sibling());
|
||||||
|
m_node = m_node->previous_sibling();
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
node_iterator operator--(int)
|
||||||
|
{
|
||||||
|
node_iterator tmp = *this;
|
||||||
|
++this;
|
||||||
|
return tmp;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator ==(const node_iterator<Ch> &rhs)
|
||||||
|
{
|
||||||
|
return m_node == rhs.m_node;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator !=(const node_iterator<Ch> &rhs)
|
||||||
|
{
|
||||||
|
return m_node != rhs.m_node;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
xml_node<Ch> *m_node;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
//! Iterator of child attributes of xml_node
|
||||||
|
template<class Ch>
|
||||||
|
class attribute_iterator
|
||||||
|
{
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
typedef typename xml_attribute<Ch> value_type;
|
||||||
|
typedef typename xml_attribute<Ch> &reference;
|
||||||
|
typedef typename xml_attribute<Ch> *pointer;
|
||||||
|
typedef std::ptrdiff_t difference_type;
|
||||||
|
typedef std::bidirectional_iterator_tag iterator_category;
|
||||||
|
|
||||||
|
attribute_iterator()
|
||||||
|
: m_attribute(0)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
attribute_iterator(xml_node<Ch> *node)
|
||||||
|
: m_attribute(node->first_attribute())
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
reference operator *() const
|
||||||
|
{
|
||||||
|
assert(m_attribute);
|
||||||
|
return *m_attribute;
|
||||||
|
}
|
||||||
|
|
||||||
|
pointer operator->() const
|
||||||
|
{
|
||||||
|
assert(m_attribute);
|
||||||
|
return m_attribute;
|
||||||
|
}
|
||||||
|
|
||||||
|
attribute_iterator& operator++()
|
||||||
|
{
|
||||||
|
assert(m_attribute);
|
||||||
|
m_attribute = m_attribute->next_attribute();
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
attribute_iterator operator++(int)
|
||||||
|
{
|
||||||
|
attribute_iterator tmp = *this;
|
||||||
|
++this;
|
||||||
|
return tmp;
|
||||||
|
}
|
||||||
|
|
||||||
|
attribute_iterator& operator--()
|
||||||
|
{
|
||||||
|
assert(m_attribute && m_attribute->previous_attribute());
|
||||||
|
m_attribute = m_attribute->previous_attribute();
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
attribute_iterator operator--(int)
|
||||||
|
{
|
||||||
|
attribute_iterator tmp = *this;
|
||||||
|
++this;
|
||||||
|
return tmp;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator ==(const attribute_iterator<Ch> &rhs)
|
||||||
|
{
|
||||||
|
return m_attribute == rhs.m_attribute;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator !=(const attribute_iterator<Ch> &rhs)
|
||||||
|
{
|
||||||
|
return m_attribute != rhs.m_attribute;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
xml_attribute<Ch> *m_attribute;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
421
Frostbite2D/include/rapidxml/rapidxml_print.hpp
Normal file
421
Frostbite2D/include/rapidxml/rapidxml_print.hpp
Normal file
@@ -0,0 +1,421 @@
|
|||||||
|
#ifndef RAPIDXML_PRINT_HPP_INCLUDED
|
||||||
|
#define RAPIDXML_PRINT_HPP_INCLUDED
|
||||||
|
|
||||||
|
// Copyright (C) 2006, 2009 Marcin Kalicinski
|
||||||
|
// Version 1.13
|
||||||
|
// Revision $DateTime: 2009/05/13 01:46:17 $
|
||||||
|
//! \file rapidxml_print.hpp This file contains rapidxml printer implementation
|
||||||
|
|
||||||
|
#include "rapidxml.hpp"
|
||||||
|
|
||||||
|
// Only include streams if not disabled
|
||||||
|
#ifndef RAPIDXML_NO_STREAMS
|
||||||
|
#include <ostream>
|
||||||
|
#include <iterator>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace rapidxml
|
||||||
|
{
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////
|
||||||
|
// Printing flags
|
||||||
|
|
||||||
|
const int print_no_indenting = 0x1; //!< Printer flag instructing the printer to suppress indenting of XML. See print() function.
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////
|
||||||
|
// Internal
|
||||||
|
|
||||||
|
//! \cond internal
|
||||||
|
namespace internal
|
||||||
|
{
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////
|
||||||
|
// Internal character operations
|
||||||
|
|
||||||
|
// Copy characters from given range to given output iterator
|
||||||
|
template<class OutIt, class Ch>
|
||||||
|
inline OutIt copy_chars(const Ch *begin, const Ch *end, OutIt out)
|
||||||
|
{
|
||||||
|
while (begin != end)
|
||||||
|
*out++ = *begin++;
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy characters from given range to given output iterator and expand
|
||||||
|
// characters into references (< > ' " &)
|
||||||
|
template<class OutIt, class Ch>
|
||||||
|
inline OutIt copy_and_expand_chars(const Ch *begin, const Ch *end, Ch noexpand, OutIt out)
|
||||||
|
{
|
||||||
|
while (begin != end)
|
||||||
|
{
|
||||||
|
if (*begin == noexpand)
|
||||||
|
{
|
||||||
|
*out++ = *begin; // No expansion, copy character
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
switch (*begin)
|
||||||
|
{
|
||||||
|
case Ch('<'):
|
||||||
|
*out++ = Ch('&'); *out++ = Ch('l'); *out++ = Ch('t'); *out++ = Ch(';');
|
||||||
|
break;
|
||||||
|
case Ch('>'):
|
||||||
|
*out++ = Ch('&'); *out++ = Ch('g'); *out++ = Ch('t'); *out++ = Ch(';');
|
||||||
|
break;
|
||||||
|
case Ch('\''):
|
||||||
|
*out++ = Ch('&'); *out++ = Ch('a'); *out++ = Ch('p'); *out++ = Ch('o'); *out++ = Ch('s'); *out++ = Ch(';');
|
||||||
|
break;
|
||||||
|
case Ch('"'):
|
||||||
|
*out++ = Ch('&'); *out++ = Ch('q'); *out++ = Ch('u'); *out++ = Ch('o'); *out++ = Ch('t'); *out++ = Ch(';');
|
||||||
|
break;
|
||||||
|
case Ch('&'):
|
||||||
|
*out++ = Ch('&'); *out++ = Ch('a'); *out++ = Ch('m'); *out++ = Ch('p'); *out++ = Ch(';');
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
*out++ = *begin; // No expansion, copy character
|
||||||
|
}
|
||||||
|
}
|
||||||
|
++begin; // Step to next character
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fill given output iterator with repetitions of the same character
|
||||||
|
template<class OutIt, class Ch>
|
||||||
|
inline OutIt fill_chars(OutIt out, int n, Ch ch)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < n; ++i)
|
||||||
|
*out++ = ch;
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find character
|
||||||
|
template<class Ch, Ch ch>
|
||||||
|
inline bool find_char(const Ch *begin, const Ch *end)
|
||||||
|
{
|
||||||
|
while (begin != end)
|
||||||
|
if (*begin++ == ch)
|
||||||
|
return true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////
|
||||||
|
// Internal printing operations
|
||||||
|
|
||||||
|
// Print node
|
||||||
|
template<class OutIt, class Ch>
|
||||||
|
inline OutIt print_node(OutIt out, const xml_node<Ch> *node, int flags, int indent)
|
||||||
|
{
|
||||||
|
// Print proper node type
|
||||||
|
switch (node->type())
|
||||||
|
{
|
||||||
|
|
||||||
|
// Document
|
||||||
|
case node_document:
|
||||||
|
out = print_children(out, node, flags, indent);
|
||||||
|
break;
|
||||||
|
|
||||||
|
// Element
|
||||||
|
case node_element:
|
||||||
|
out = print_element_node(out, node, flags, indent);
|
||||||
|
break;
|
||||||
|
|
||||||
|
// Data
|
||||||
|
case node_data:
|
||||||
|
out = print_data_node(out, node, flags, indent);
|
||||||
|
break;
|
||||||
|
|
||||||
|
// CDATA
|
||||||
|
case node_cdata:
|
||||||
|
out = print_cdata_node(out, node, flags, indent);
|
||||||
|
break;
|
||||||
|
|
||||||
|
// Declaration
|
||||||
|
case node_declaration:
|
||||||
|
out = print_declaration_node(out, node, flags, indent);
|
||||||
|
break;
|
||||||
|
|
||||||
|
// Comment
|
||||||
|
case node_comment:
|
||||||
|
out = print_comment_node(out, node, flags, indent);
|
||||||
|
break;
|
||||||
|
|
||||||
|
// Doctype
|
||||||
|
case node_doctype:
|
||||||
|
out = print_doctype_node(out, node, flags, indent);
|
||||||
|
break;
|
||||||
|
|
||||||
|
// Pi
|
||||||
|
case node_pi:
|
||||||
|
out = print_pi_node(out, node, flags, indent);
|
||||||
|
break;
|
||||||
|
|
||||||
|
// Unknown
|
||||||
|
default:
|
||||||
|
assert(0);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If indenting not disabled, add line break after node
|
||||||
|
if (!(flags & print_no_indenting))
|
||||||
|
*out = Ch('\n'), ++out;
|
||||||
|
|
||||||
|
// Return modified iterator
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Print children of the node
|
||||||
|
template<class OutIt, class Ch>
|
||||||
|
inline OutIt print_children(OutIt out, const xml_node<Ch> *node, int flags, int indent)
|
||||||
|
{
|
||||||
|
for (xml_node<Ch> *child = node->first_node(); child; child = child->next_sibling())
|
||||||
|
out = print_node(out, child, flags, indent);
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Print attributes of the node
|
||||||
|
template<class OutIt, class Ch>
|
||||||
|
inline OutIt print_attributes(OutIt out, const xml_node<Ch> *node, int flags)
|
||||||
|
{
|
||||||
|
for (xml_attribute<Ch> *attribute = node->first_attribute(); attribute; attribute = attribute->next_attribute())
|
||||||
|
{
|
||||||
|
if (attribute->name() && attribute->value())
|
||||||
|
{
|
||||||
|
// Print attribute name
|
||||||
|
*out = Ch(' '), ++out;
|
||||||
|
out = copy_chars(attribute->name(), attribute->name() + attribute->name_size(), out);
|
||||||
|
*out = Ch('='), ++out;
|
||||||
|
// Print attribute value using appropriate quote type
|
||||||
|
if (find_char<Ch, Ch('"')>(attribute->value(), attribute->value() + attribute->value_size()))
|
||||||
|
{
|
||||||
|
*out = Ch('\''), ++out;
|
||||||
|
out = copy_and_expand_chars(attribute->value(), attribute->value() + attribute->value_size(), Ch('"'), out);
|
||||||
|
*out = Ch('\''), ++out;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
*out = Ch('"'), ++out;
|
||||||
|
out = copy_and_expand_chars(attribute->value(), attribute->value() + attribute->value_size(), Ch('\''), out);
|
||||||
|
*out = Ch('"'), ++out;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Print data node
|
||||||
|
template<class OutIt, class Ch>
|
||||||
|
inline OutIt print_data_node(OutIt out, const xml_node<Ch> *node, int flags, int indent)
|
||||||
|
{
|
||||||
|
assert(node->type() == node_data);
|
||||||
|
if (!(flags & print_no_indenting))
|
||||||
|
out = fill_chars(out, indent, Ch('\t'));
|
||||||
|
out = copy_and_expand_chars(node->value(), node->value() + node->value_size(), Ch(0), out);
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Print data node
|
||||||
|
template<class OutIt, class Ch>
|
||||||
|
inline OutIt print_cdata_node(OutIt out, const xml_node<Ch> *node, int flags, int indent)
|
||||||
|
{
|
||||||
|
assert(node->type() == node_cdata);
|
||||||
|
if (!(flags & print_no_indenting))
|
||||||
|
out = fill_chars(out, indent, Ch('\t'));
|
||||||
|
*out = Ch('<'); ++out;
|
||||||
|
*out = Ch('!'); ++out;
|
||||||
|
*out = Ch('['); ++out;
|
||||||
|
*out = Ch('C'); ++out;
|
||||||
|
*out = Ch('D'); ++out;
|
||||||
|
*out = Ch('A'); ++out;
|
||||||
|
*out = Ch('T'); ++out;
|
||||||
|
*out = Ch('A'); ++out;
|
||||||
|
*out = Ch('['); ++out;
|
||||||
|
out = copy_chars(node->value(), node->value() + node->value_size(), out);
|
||||||
|
*out = Ch(']'); ++out;
|
||||||
|
*out = Ch(']'); ++out;
|
||||||
|
*out = Ch('>'); ++out;
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Print element node
|
||||||
|
template<class OutIt, class Ch>
|
||||||
|
inline OutIt print_element_node(OutIt out, const xml_node<Ch> *node, int flags, int indent)
|
||||||
|
{
|
||||||
|
assert(node->type() == node_element);
|
||||||
|
|
||||||
|
// Print element name and attributes, if any
|
||||||
|
if (!(flags & print_no_indenting))
|
||||||
|
out = fill_chars(out, indent, Ch('\t'));
|
||||||
|
*out = Ch('<'), ++out;
|
||||||
|
out = copy_chars(node->name(), node->name() + node->name_size(), out);
|
||||||
|
out = print_attributes(out, node, flags);
|
||||||
|
|
||||||
|
// If node is childless
|
||||||
|
if (node->value_size() == 0 && !node->first_node())
|
||||||
|
{
|
||||||
|
// Print childless node tag ending
|
||||||
|
*out = Ch('/'), ++out;
|
||||||
|
*out = Ch('>'), ++out;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Print normal node tag ending
|
||||||
|
*out = Ch('>'), ++out;
|
||||||
|
|
||||||
|
// Test if node contains a single data node only (and no other nodes)
|
||||||
|
xml_node<Ch> *child = node->first_node();
|
||||||
|
if (!child)
|
||||||
|
{
|
||||||
|
// If node has no children, only print its value without indenting
|
||||||
|
out = copy_and_expand_chars(node->value(), node->value() + node->value_size(), Ch(0), out);
|
||||||
|
}
|
||||||
|
else if (child->next_sibling() == 0 && child->type() == node_data)
|
||||||
|
{
|
||||||
|
// If node has a sole data child, only print its value without indenting
|
||||||
|
out = copy_and_expand_chars(child->value(), child->value() + child->value_size(), Ch(0), out);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Print all children with full indenting
|
||||||
|
if (!(flags & print_no_indenting))
|
||||||
|
*out = Ch('\n'), ++out;
|
||||||
|
out = print_children(out, node, flags, indent + 1);
|
||||||
|
if (!(flags & print_no_indenting))
|
||||||
|
out = fill_chars(out, indent, Ch('\t'));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Print node end
|
||||||
|
*out = Ch('<'), ++out;
|
||||||
|
*out = Ch('/'), ++out;
|
||||||
|
out = copy_chars(node->name(), node->name() + node->name_size(), out);
|
||||||
|
*out = Ch('>'), ++out;
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Print declaration node
|
||||||
|
template<class OutIt, class Ch>
|
||||||
|
inline OutIt print_declaration_node(OutIt out, const xml_node<Ch> *node, int flags, int indent)
|
||||||
|
{
|
||||||
|
// Print declaration start
|
||||||
|
if (!(flags & print_no_indenting))
|
||||||
|
out = fill_chars(out, indent, Ch('\t'));
|
||||||
|
*out = Ch('<'), ++out;
|
||||||
|
*out = Ch('?'), ++out;
|
||||||
|
*out = Ch('x'), ++out;
|
||||||
|
*out = Ch('m'), ++out;
|
||||||
|
*out = Ch('l'), ++out;
|
||||||
|
|
||||||
|
// Print attributes
|
||||||
|
out = print_attributes(out, node, flags);
|
||||||
|
|
||||||
|
// Print declaration end
|
||||||
|
*out = Ch('?'), ++out;
|
||||||
|
*out = Ch('>'), ++out;
|
||||||
|
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Print comment node
|
||||||
|
template<class OutIt, class Ch>
|
||||||
|
inline OutIt print_comment_node(OutIt out, const xml_node<Ch> *node, int flags, int indent)
|
||||||
|
{
|
||||||
|
assert(node->type() == node_comment);
|
||||||
|
if (!(flags & print_no_indenting))
|
||||||
|
out = fill_chars(out, indent, Ch('\t'));
|
||||||
|
*out = Ch('<'), ++out;
|
||||||
|
*out = Ch('!'), ++out;
|
||||||
|
*out = Ch('-'), ++out;
|
||||||
|
*out = Ch('-'), ++out;
|
||||||
|
out = copy_chars(node->value(), node->value() + node->value_size(), out);
|
||||||
|
*out = Ch('-'), ++out;
|
||||||
|
*out = Ch('-'), ++out;
|
||||||
|
*out = Ch('>'), ++out;
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Print doctype node
|
||||||
|
template<class OutIt, class Ch>
|
||||||
|
inline OutIt print_doctype_node(OutIt out, const xml_node<Ch> *node, int flags, int indent)
|
||||||
|
{
|
||||||
|
assert(node->type() == node_doctype);
|
||||||
|
if (!(flags & print_no_indenting))
|
||||||
|
out = fill_chars(out, indent, Ch('\t'));
|
||||||
|
*out = Ch('<'), ++out;
|
||||||
|
*out = Ch('!'), ++out;
|
||||||
|
*out = Ch('D'), ++out;
|
||||||
|
*out = Ch('O'), ++out;
|
||||||
|
*out = Ch('C'), ++out;
|
||||||
|
*out = Ch('T'), ++out;
|
||||||
|
*out = Ch('Y'), ++out;
|
||||||
|
*out = Ch('P'), ++out;
|
||||||
|
*out = Ch('E'), ++out;
|
||||||
|
*out = Ch(' '), ++out;
|
||||||
|
out = copy_chars(node->value(), node->value() + node->value_size(), out);
|
||||||
|
*out = Ch('>'), ++out;
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Print pi node
|
||||||
|
template<class OutIt, class Ch>
|
||||||
|
inline OutIt print_pi_node(OutIt out, const xml_node<Ch> *node, int flags, int indent)
|
||||||
|
{
|
||||||
|
assert(node->type() == node_pi);
|
||||||
|
if (!(flags & print_no_indenting))
|
||||||
|
out = fill_chars(out, indent, Ch('\t'));
|
||||||
|
*out = Ch('<'), ++out;
|
||||||
|
*out = Ch('?'), ++out;
|
||||||
|
out = copy_chars(node->name(), node->name() + node->name_size(), out);
|
||||||
|
*out = Ch(' '), ++out;
|
||||||
|
out = copy_chars(node->value(), node->value() + node->value_size(), out);
|
||||||
|
*out = Ch('?'), ++out;
|
||||||
|
*out = Ch('>'), ++out;
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
//! \endcond
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////
|
||||||
|
// Printing
|
||||||
|
|
||||||
|
//! Prints XML to given output iterator.
|
||||||
|
//! \param out Output iterator to print to.
|
||||||
|
//! \param node Node to be printed. Pass xml_document to print entire document.
|
||||||
|
//! \param flags Flags controlling how XML is printed.
|
||||||
|
//! \return Output iterator pointing to position immediately after last character of printed text.
|
||||||
|
template<class OutIt, class Ch>
|
||||||
|
inline OutIt print(OutIt out, const xml_node<Ch> &node, int flags = 0)
|
||||||
|
{
|
||||||
|
return internal::print_node(out, &node, flags, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifndef RAPIDXML_NO_STREAMS
|
||||||
|
|
||||||
|
//! Prints XML to given output stream.
|
||||||
|
//! \param out Output stream to print to.
|
||||||
|
//! \param node Node to be printed. Pass xml_document to print entire document.
|
||||||
|
//! \param flags Flags controlling how XML is printed.
|
||||||
|
//! \return Output stream.
|
||||||
|
template<class Ch>
|
||||||
|
inline std::basic_ostream<Ch> &print(std::basic_ostream<Ch> &out, const xml_node<Ch> &node, int flags = 0)
|
||||||
|
{
|
||||||
|
print(std::ostream_iterator<Ch>(out), node, flags);
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
//! Prints formatted XML to given output stream. Uses default printing flags. Use print() function to customize printing process.
|
||||||
|
//! \param out Output stream to print to.
|
||||||
|
//! \param node Node to be printed.
|
||||||
|
//! \return Output stream.
|
||||||
|
template<class Ch>
|
||||||
|
inline std::basic_ostream<Ch> &operator <<(std::basic_ostream<Ch> &out, const xml_node<Ch> &node)
|
||||||
|
{
|
||||||
|
return print(out, node);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
122
Frostbite2D/include/rapidxml/rapidxml_utils.hpp
Normal file
122
Frostbite2D/include/rapidxml/rapidxml_utils.hpp
Normal file
@@ -0,0 +1,122 @@
|
|||||||
|
#ifndef RAPIDXML_UTILS_HPP_INCLUDED
|
||||||
|
#define RAPIDXML_UTILS_HPP_INCLUDED
|
||||||
|
|
||||||
|
// Copyright (C) 2006, 2009 Marcin Kalicinski
|
||||||
|
// Version 1.13
|
||||||
|
// Revision $DateTime: 2009/05/13 01:46:17 $
|
||||||
|
//! \file rapidxml_utils.hpp This file contains high-level rapidxml utilities that can be useful
|
||||||
|
//! in certain simple scenarios. They should probably not be used if maximizing performance is the main objective.
|
||||||
|
|
||||||
|
#include "rapidxml.hpp"
|
||||||
|
#include <vector>
|
||||||
|
#include <string>
|
||||||
|
#include <fstream>
|
||||||
|
#include <stdexcept>
|
||||||
|
|
||||||
|
namespace rapidxml
|
||||||
|
{
|
||||||
|
|
||||||
|
//! Represents data loaded from a file
|
||||||
|
template<class Ch = char>
|
||||||
|
class file
|
||||||
|
{
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
//! Loads file into the memory. Data will be automatically destroyed by the destructor.
|
||||||
|
//! \param filename Filename to load.
|
||||||
|
file(const char *filename)
|
||||||
|
{
|
||||||
|
using namespace std;
|
||||||
|
|
||||||
|
// Open stream
|
||||||
|
basic_ifstream<Ch> stream(filename, ios::binary);
|
||||||
|
if (!stream)
|
||||||
|
throw runtime_error(string("cannot open file ") + filename);
|
||||||
|
stream.unsetf(ios::skipws);
|
||||||
|
|
||||||
|
// Determine stream size
|
||||||
|
stream.seekg(0, ios::end);
|
||||||
|
size_t size = stream.tellg();
|
||||||
|
stream.seekg(0);
|
||||||
|
|
||||||
|
// Load data and add terminating 0
|
||||||
|
m_data.resize(size + 1);
|
||||||
|
stream.read(&m_data.front(), static_cast<streamsize>(size));
|
||||||
|
m_data[size] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
//! Loads file into the memory. Data will be automatically destroyed by the destructor
|
||||||
|
//! \param stream Stream to load from
|
||||||
|
file(std::basic_istream<Ch> &stream)
|
||||||
|
{
|
||||||
|
using namespace std;
|
||||||
|
|
||||||
|
// Load data and add terminating 0
|
||||||
|
stream.unsetf(ios::skipws);
|
||||||
|
m_data.assign(istreambuf_iterator<Ch>(stream), istreambuf_iterator<Ch>());
|
||||||
|
if (stream.fail() || stream.bad())
|
||||||
|
throw runtime_error("error reading stream");
|
||||||
|
m_data.push_back(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
//! Gets file data.
|
||||||
|
//! \return Pointer to data of file.
|
||||||
|
Ch *data()
|
||||||
|
{
|
||||||
|
return &m_data.front();
|
||||||
|
}
|
||||||
|
|
||||||
|
//! Gets file data.
|
||||||
|
//! \return Pointer to data of file.
|
||||||
|
const Ch *data() const
|
||||||
|
{
|
||||||
|
return &m_data.front();
|
||||||
|
}
|
||||||
|
|
||||||
|
//! Gets file data size.
|
||||||
|
//! \return Size of file data, in characters.
|
||||||
|
std::size_t size() const
|
||||||
|
{
|
||||||
|
return m_data.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
std::vector<Ch> m_data; // File data
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
//! Counts children of node. Time complexity is O(n).
|
||||||
|
//! \return Number of children of node
|
||||||
|
template<class Ch>
|
||||||
|
inline std::size_t count_children(xml_node<Ch> *node)
|
||||||
|
{
|
||||||
|
xml_node<Ch> *child = node->first_node();
|
||||||
|
std::size_t count = 0;
|
||||||
|
while (child)
|
||||||
|
{
|
||||||
|
++count;
|
||||||
|
child = child->next_sibling();
|
||||||
|
}
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
//! Counts attributes of node. Time complexity is O(n).
|
||||||
|
//! \return Number of attributes of node
|
||||||
|
template<class Ch>
|
||||||
|
inline std::size_t count_attributes(xml_node<Ch> *node)
|
||||||
|
{
|
||||||
|
xml_attribute<Ch> *attr = node->first_attribute();
|
||||||
|
std::size_t count = 0;
|
||||||
|
while (attr)
|
||||||
|
{
|
||||||
|
++count;
|
||||||
|
attr = attr->next_attribute();
|
||||||
|
}
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
425
Frostbite2D/src/frostbite2D/resource/audio_database.cpp
Normal file
425
Frostbite2D/src/frostbite2D/resource/audio_database.cpp
Normal file
@@ -0,0 +1,425 @@
|
|||||||
|
#include <frostbite2D/resource/audio_database.h>
|
||||||
|
#include <frostbite2D/resource/asset.h>
|
||||||
|
#include <rapidxml/rapidxml.hpp>
|
||||||
|
#include <SDL.h>
|
||||||
|
#include <algorithm>
|
||||||
|
#include <chrono>
|
||||||
|
#include <cstring>
|
||||||
|
#include <ctime>
|
||||||
|
#include <numeric>
|
||||||
|
#include <random>
|
||||||
|
|
||||||
|
namespace frostbite2D {
|
||||||
|
|
||||||
|
AudioDatabase& AudioDatabase::get() {
|
||||||
|
static AudioDatabase instance;
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AudioDatabase::loadFromFile(const std::string& path) {
|
||||||
|
clear();
|
||||||
|
|
||||||
|
Asset& asset = Asset::get();
|
||||||
|
std::string content;
|
||||||
|
|
||||||
|
if (!asset.readTextFile(path, content)) {
|
||||||
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "AudioDatabase: 无法读取文件: %s", path.c_str());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return loadFromString(content);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AudioDatabase::loadFromString(const std::string& xmlContent) {
|
||||||
|
clear();
|
||||||
|
|
||||||
|
if (xmlContent.empty()) {
|
||||||
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "AudioDatabase: XML 内容为空");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string mutableContent = xmlContent;
|
||||||
|
|
||||||
|
rapidxml::xml_document<> doc;
|
||||||
|
try {
|
||||||
|
doc.parse<rapidxml::parse_default>(&mutableContent[0]);
|
||||||
|
} catch (const rapidxml::parse_error& e) {
|
||||||
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "AudioDatabase: XML 解析失败: %s", e.what());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
rapidxml::xml_node<>* root = doc.first_node("AudioTagDatabase");
|
||||||
|
if (!root) {
|
||||||
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "AudioDatabase: 未找到 AudioTagDatabase 根元素");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (rapidxml::xml_node<>* node = root->first_node(); node; node = node->next_sibling()) {
|
||||||
|
const char* name = node->name();
|
||||||
|
|
||||||
|
if (strcmp(name, "EFFECT") == 0) {
|
||||||
|
rapidxml::xml_attribute<>* idAttr = node->first_attribute("ID");
|
||||||
|
rapidxml::xml_attribute<>* fileAttr = node->first_attribute("FILE");
|
||||||
|
|
||||||
|
if (!idAttr || !fileAttr) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strlen(idAttr->value()) == 0) {
|
||||||
|
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, "AudioDatabase: 跳过无效的 EFFECT 条目(ID 为空)");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strlen(fileAttr->value()) == 0) {
|
||||||
|
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, "AudioDatabase: 跳过 EFFECT 条目(FILE 为空): %s", idAttr->value());
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
AudioEffect effect;
|
||||||
|
effect.id = idAttr->value();
|
||||||
|
effect.file = fileAttr->value();
|
||||||
|
|
||||||
|
rapidxml::xml_attribute<>* subgroupAttr = node->first_attribute("SUBGROUP");
|
||||||
|
if (subgroupAttr) {
|
||||||
|
effect.subgroup = subgroupAttr->value();
|
||||||
|
}
|
||||||
|
|
||||||
|
rapidxml::xml_attribute<>* loopDelayAttr = node->first_attribute("LOOP_DELAY");
|
||||||
|
if (loopDelayAttr) {
|
||||||
|
effect.loopDelay = std::stoi(loopDelayAttr->value());
|
||||||
|
}
|
||||||
|
|
||||||
|
rapidxml::xml_attribute<>* loopDelayRangeAttr = node->first_attribute("LOOP_DELAY_RANGE");
|
||||||
|
if (loopDelayRangeAttr) {
|
||||||
|
effect.loopDelayRange = std::stoi(loopDelayRangeAttr->value());
|
||||||
|
}
|
||||||
|
|
||||||
|
effectMap_[effect.id] = std::move(effect);
|
||||||
|
} else if (strcmp(name, "MUSIC") == 0) {
|
||||||
|
rapidxml::xml_attribute<>* idAttr = node->first_attribute("ID");
|
||||||
|
rapidxml::xml_attribute<>* fileAttr = node->first_attribute("FILE");
|
||||||
|
|
||||||
|
if (!idAttr || !fileAttr) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strlen(idAttr->value()) == 0) {
|
||||||
|
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, "AudioDatabase: 跳过无效的 MUSIC 条目(ID 为空)");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strlen(fileAttr->value()) == 0) {
|
||||||
|
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, "AudioDatabase: 跳过 MUSIC 条目(FILE 为空): %s", idAttr->value());
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
AudioMusic music;
|
||||||
|
music.id = idAttr->value();
|
||||||
|
music.file = fileAttr->value();
|
||||||
|
|
||||||
|
rapidxml::xml_attribute<>* loopDelayAttr = node->first_attribute("LOOP_DELAY");
|
||||||
|
if (loopDelayAttr) {
|
||||||
|
music.loopDelay = std::stoi(loopDelayAttr->value());
|
||||||
|
}
|
||||||
|
|
||||||
|
musicMap_[music.id] = std::move(music);
|
||||||
|
} else if (strcmp(name, "RANDOM") == 0) {
|
||||||
|
rapidxml::xml_attribute<>* idAttr = node->first_attribute("ID");
|
||||||
|
|
||||||
|
if (!idAttr) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strlen(idAttr->value()) == 0) {
|
||||||
|
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, "AudioDatabase: 跳过无效的 RANDOM 条目(ID 为空)");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
AudioRandom random;
|
||||||
|
random.id = idAttr->value();
|
||||||
|
|
||||||
|
for (rapidxml::xml_node<>* itemNode = node->first_node("ITEM"); itemNode; itemNode = itemNode->next_sibling("ITEM")) {
|
||||||
|
rapidxml::xml_attribute<>* tagAttr = itemNode->first_attribute("TAG");
|
||||||
|
rapidxml::xml_attribute<>* probAttr = itemNode->first_attribute("PROB");
|
||||||
|
|
||||||
|
if (!tagAttr || !probAttr) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strlen(tagAttr->value()) == 0) {
|
||||||
|
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, "AudioDatabase: 跳过 RANDOM 条目下的无效 ITEM(TAG 为空): %s", random.id.c_str());
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
RandomItem ri;
|
||||||
|
ri.tag = tagAttr->value();
|
||||||
|
ri.probability = std::stoi(probAttr->value());
|
||||||
|
random.items.push_back(std::move(ri));
|
||||||
|
}
|
||||||
|
|
||||||
|
randomMap_[random.id] = std::move(random);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
loaded_ = true;
|
||||||
|
|
||||||
|
SDL_Log("AudioDatabase: 加载完成 - %zu 个音效, %zu 个音乐, %zu 个随机组",
|
||||||
|
effectMap_.size(), musicMap_.size(), randomMap_.size());
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AudioDatabase::clear() {
|
||||||
|
effectMap_.clear();
|
||||||
|
musicMap_.clear();
|
||||||
|
randomMap_.clear();
|
||||||
|
loaded_ = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AudioDatabase::isLoaded() const {
|
||||||
|
return loaded_;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<AudioEntry> AudioDatabase::get(const std::string& id) {
|
||||||
|
if (!loaded_) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
AudioEntry entry;
|
||||||
|
|
||||||
|
auto effectIt = effectMap_.find(id);
|
||||||
|
if (effectIt != effectMap_.end()) {
|
||||||
|
entry.type = AudioEntryType::Effect;
|
||||||
|
entry.effect = &effectIt->second;
|
||||||
|
return entry;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto musicIt = musicMap_.find(id);
|
||||||
|
if (musicIt != musicMap_.end()) {
|
||||||
|
entry.type = AudioEntryType::Music;
|
||||||
|
entry.music = &musicIt->second;
|
||||||
|
return entry;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto randomIt = randomMap_.find(id);
|
||||||
|
if (randomIt != randomMap_.end()) {
|
||||||
|
entry.type = AudioEntryType::Random;
|
||||||
|
entry.random = &randomIt->second;
|
||||||
|
return entry;
|
||||||
|
}
|
||||||
|
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<std::string> AudioDatabase::getSound(const std::string& id) {
|
||||||
|
if (!loaded_) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto randomIt = randomMap_.find(id);
|
||||||
|
if (randomIt != randomMap_.end()) {
|
||||||
|
return selectFromRandom(randomIt->second);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (effectMap_.find(id) != effectMap_.end()) {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<std::string> AudioDatabase::getFilePath(const std::string& id) {
|
||||||
|
if (!loaded_) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto soundIdOpt = getSound(id);
|
||||||
|
if (!soundIdOpt) {
|
||||||
|
auto musicIt = musicMap_.find(id);
|
||||||
|
if (musicIt != musicMap_.end()) {
|
||||||
|
return musicIt->second.file;
|
||||||
|
}
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string& soundId = *soundIdOpt;
|
||||||
|
auto effectIt = effectMap_.find(soundId);
|
||||||
|
if (effectIt != effectMap_.end()) {
|
||||||
|
return effectIt->second.file;
|
||||||
|
}
|
||||||
|
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<const AudioEffect*> AudioDatabase::getEffect(const std::string& id) {
|
||||||
|
if (!loaded_) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto it = effectMap_.find(id);
|
||||||
|
if (it != effectMap_.end()) {
|
||||||
|
return &it->second;
|
||||||
|
}
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<const AudioMusic*> AudioDatabase::getMusic(const std::string& id) {
|
||||||
|
if (!loaded_) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto it = musicMap_.find(id);
|
||||||
|
if (it != musicMap_.end()) {
|
||||||
|
return &it->second;
|
||||||
|
}
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<const AudioRandom*> AudioDatabase::getRandom(const std::string& id) {
|
||||||
|
if (!loaded_) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto it = randomMap_.find(id);
|
||||||
|
if (it != randomMap_.end()) {
|
||||||
|
return &it->second;
|
||||||
|
}
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t AudioDatabase::effectCount() const {
|
||||||
|
return effectMap_.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t AudioDatabase::musicCount() const {
|
||||||
|
return musicMap_.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t AudioDatabase::randomCount() const {
|
||||||
|
return randomMap_.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
void AudioDatabase::initializeRNG() {
|
||||||
|
if (rngInitialized_) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::random_device rd;
|
||||||
|
if (rd.entropy() > 0) {
|
||||||
|
rng_.seed(rd());
|
||||||
|
} else {
|
||||||
|
auto now = std::chrono::high_resolution_clock::now();
|
||||||
|
auto duration = now.time_since_epoch();
|
||||||
|
auto millis = std::chrono::duration_cast<std::chrono::milliseconds>(duration).count();
|
||||||
|
rng_.seed(static_cast<unsigned int>(millis));
|
||||||
|
}
|
||||||
|
|
||||||
|
rngInitialized_ = true;
|
||||||
|
SDL_Log("AudioDatabase: 随机数生成器已初始化");
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string AudioDatabase::selectFromRandom(const AudioRandom& randomGroup) {
|
||||||
|
initializeRNG();
|
||||||
|
|
||||||
|
std::vector<const RandomItem*> validItems;
|
||||||
|
for (const auto& item : randomGroup.items) {
|
||||||
|
if (!item.tag.empty()) {
|
||||||
|
validItems.push_back(&item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (validItems.empty()) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (validItems.size() == 1) {
|
||||||
|
return validItems[0]->tag;
|
||||||
|
}
|
||||||
|
|
||||||
|
int32 totalWeight = 0;
|
||||||
|
for (const auto* item : validItems) {
|
||||||
|
totalWeight += item->probability;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (totalWeight <= 0) {
|
||||||
|
std::uniform_int_distribution<size_t> dist(0, validItems.size() - 1);
|
||||||
|
return validItems[dist(rng_)]->tag;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::uniform_int_distribution<int32> dist(1, totalWeight);
|
||||||
|
int32 randomValue = dist(rng_);
|
||||||
|
|
||||||
|
int32 currentWeight = 0;
|
||||||
|
for (const auto* item : validItems) {
|
||||||
|
currentWeight += item->probability;
|
||||||
|
if (randomValue <= currentWeight) {
|
||||||
|
return item->tag;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return validItems.back()->tag;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AudioDatabase::has(const std::string& id) {
|
||||||
|
if (!loaded_) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return effectMap_.count(id) > 0 || musicMap_.count(id) > 0 || randomMap_.count(id) > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string AudioDatabase::filePath(const std::string& id) {
|
||||||
|
auto opt = getFilePath(id);
|
||||||
|
return opt ? *opt : "";
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string AudioDatabase::soundId(const std::string& idOrRandomId) {
|
||||||
|
auto opt = getSound(idOrRandomId);
|
||||||
|
return opt ? *opt : "";
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string AudioDatabase::subgroup(const std::string& id) {
|
||||||
|
if (!loaded_) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
auto it = effectMap_.find(id);
|
||||||
|
if (it != effectMap_.end()) {
|
||||||
|
return it->second.subgroup;
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
AudioEntryType AudioDatabase::typeOf(const std::string& id) {
|
||||||
|
if (!loaded_) {
|
||||||
|
return AudioEntryType::None;
|
||||||
|
}
|
||||||
|
if (effectMap_.count(id) > 0) {
|
||||||
|
return AudioEntryType::Effect;
|
||||||
|
}
|
||||||
|
if (musicMap_.count(id) > 0) {
|
||||||
|
return AudioEntryType::Music;
|
||||||
|
}
|
||||||
|
if (randomMap_.count(id) > 0) {
|
||||||
|
return AudioEntryType::Random;
|
||||||
|
}
|
||||||
|
return AudioEntryType::None;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AudioDatabase::tryGetFilePath(const std::string& id, std::string& outPath) {
|
||||||
|
auto opt = getFilePath(id);
|
||||||
|
if (opt) {
|
||||||
|
outPath = *opt;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AudioDatabase::tryGetSoundId(const std::string& id, std::string& outSoundId) {
|
||||||
|
auto opt = getSound(id);
|
||||||
|
if (opt) {
|
||||||
|
outSoundId = *opt;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace frostbite2D
|
||||||
19186
Game/assets/audio.xml
Normal file
19186
Game/assets/audio.xml
Normal file
File diff suppressed because it is too large
Load Diff
@@ -18,6 +18,7 @@
|
|||||||
#include <frostbite2D/audio/sound.h>
|
#include <frostbite2D/audio/sound.h>
|
||||||
#include <frostbite2D/resource/npk_archive.h>
|
#include <frostbite2D/resource/npk_archive.h>
|
||||||
|
|
||||||
|
#include <frostbite2D/resource/audio_database.h>
|
||||||
#include <frostbite2D/resource/sound_pack_archive.h>
|
#include <frostbite2D/resource/sound_pack_archive.h>
|
||||||
|
|
||||||
using namespace frostbite2D;
|
using namespace frostbite2D;
|
||||||
@@ -143,6 +144,16 @@ int main(int argc, char **argv) {
|
|||||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to load music!");
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to load music!");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto &audioDB = AudioDatabase::get();
|
||||||
|
audioDB.loadFromFile("assets/audio.xml");
|
||||||
|
|
||||||
|
// ========== 方式 1: 最简单的 API(推荐) ==========
|
||||||
|
std::string path = audioDB.filePath("P_ICECANNON_SHOT");
|
||||||
|
if (!path.empty()) {
|
||||||
|
SDL_Log("=== 方式 1: 直接获取文件路径 ===");
|
||||||
|
SDL_Log("File: %s", path.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
app.run();
|
app.run();
|
||||||
|
|
||||||
app.shutdown();
|
app.shutdown();
|
||||||
|
|||||||
Reference in New Issue
Block a user