载入中...
搜索中...
未找到
Sound.cpp
浏览该文件的文档.
1#include "Sound.h"
2#include "Decoder.h"
3#include "SoundData.h"
4
5#include "common/Exception.h"
6#include "common/config.h"
7#include "common/Resource.h"
10
11#include "medialoader/Exception.h"
12#include "medialoader/sound/WaveDecoder.h"
13#include "medialoader/sound/VorbisDecoder.h"
14#include "medialoader/sound/Mpg123Decoder.h"
15#include "medialoader/sound/FLACDecoder.h"
16#include "medialoader/sound/ModPlugDecoder.h"
17
18#include <simplesquirrel/simplesquirrel.hpp>
19
20#include <cctype>
21#include <cstring>
22#include <fstream>
23#include <string>
24
25#if defined(_WIN32)
26#include <stdlib.h>
27#else
28#include <cstdlib>
29#endif
30
31namespace eve {
32namespace sound {
33
35
36namespace {
37
38#if defined(EVENGINE_MACOSX) || defined(EVENGINE_LINUX) || defined(EVENGINE_WINDOWS)
39
40bool fileExists(const std::string &path) {
41 std::ifstream in(path, std::ios::binary);
42 return in.good();
43}
44
45std::string parentPath(std::string path) {
46 while (!path.empty() && (path.back() == '/' || path.back() == '\\'))
47 path.pop_back();
48 auto slash = path.find_last_of("/\\");
49 if (slash == std::string::npos)
50 return {};
51 if (slash == 0)
52 return path.substr(0, 1);
53 return path.substr(0, slash);
54}
55
56std::string joinPath(const std::string &a, const std::string &b) {
57 if (a.empty())
58 return b;
59 if (a.back() == '/' || a.back() == '\\')
60 return a + b;
61 return a + "/" + b;
62}
63
64// Prefer installed / staged share/eve/timidity next to the executable.
65// Does not override an existing MMPAT_PATH_TO_CFG (user trim / custom banks).
66void ensureDesktopTimidityShare() {
67 if (const char *existing = std::getenv("MMPAT_PATH_TO_CFG")) {
68 if (existing[0] != '\0')
69 return;
70 }
71
72 auto *fs = filesystem::Filesystem::create();
73 std::string exe = fs->getExecutablePath();
74 if (exe.empty())
75 return;
76
77 std::string dir = parentPath(exe);
78 const char *relatives[] = {
79 "share/eve/timidity",
80 "../share/eve/timidity",
81 "../../share/eve/timidity",
82 "../../../share/eve/timidity",
83 };
84
85 std::string found;
86 for (const char *rel : relatives) {
87 std::string cand = joinPath(dir, rel);
88 if (fileExists(joinPath(cand, "timidity.cfg"))) {
89 found = cand;
90 break;
91 }
92 }
93 if (found.empty())
94 return;
95
96#if defined(_WIN32)
97 _putenv_s("MMPAT_PATH_TO_CFG", found.c_str());
98#else
99 setenv("MMPAT_PATH_TO_CFG", found.c_str(), 0);
100#endif
101}
102
103#endif // desktop
104
105} // namespace
106
108#if defined(EVENGINE_MACOSX) || defined(EVENGINE_LINUX) || defined(EVENGINE_WINDOWS)
109 ensureDesktopTimidityShare();
110#endif
111}
112
113Sound::~Sound() = default;
114
115namespace {
116
117std::string lowerExt(std::string ext) {
118 for (char &c : ext)
119 c = static_cast<char>(std::tolower(static_cast<unsigned char>(c)));
120 return ext;
121}
122
123template <typename T>
124std::unique_ptr<medialoader::Decoder> tryMake(const char *data, size_t size, int bufferSize) {
125 try {
126 return std::unique_ptr<medialoader::Decoder>(new T(data, size, bufferSize));
127 } catch (const medialoader::Exception &) {
128 return nullptr;
129 } catch (...) {
130 return nullptr;
131 }
132}
133
134} // namespace
135
136Decoder *Sound::newDecoder(Data *data, int bufferSize) {
137 if (data == nullptr || data->getData() == nullptr || data->getSize() == 0)
138 throw eve::Exception("Cannot decode empty sound data");
139
140 std::vector<char> owned(static_cast<const char *>(data->getData()),
141 static_cast<const char *>(data->getData()) + data->getSize());
142
143 std::string ext;
144 if (auto *fd = dynamic_cast<filesystem::FileData *>(data))
145 ext = lowerExt(fd->getExtension());
146
147 const char *ptr = owned.data();
148 size_t size = owned.size();
149
150 auto tryByExt = [&](const std::string &e) -> std::unique_ptr<medialoader::Decoder> {
151 if (e.empty())
152 return nullptr;
153 if (medialoader::WaveDecoder::accepts(e))
154 return tryMake<medialoader::WaveDecoder>(ptr, size, bufferSize);
155 if (medialoader::VorbisDecoder::accepts(e))
156 return tryMake<medialoader::VorbisDecoder>(ptr, size, bufferSize);
157 if (medialoader::Mpg123Decoder::accepts(e))
158 return tryMake<medialoader::Mpg123Decoder>(ptr, size, bufferSize);
159 if (medialoader::FLACDecoder::accepts(e))
160 return tryMake<medialoader::FLACDecoder>(ptr, size, bufferSize);
161 if (medialoader::ModPlugDecoder::accepts(e))
162 return tryMake<medialoader::ModPlugDecoder>(ptr, size, bufferSize);
163 return nullptr;
164 };
165
166 auto impl = tryByExt(ext);
167 if (!impl) {
168 if (!impl) impl = tryMake<medialoader::WaveDecoder>(ptr, size, bufferSize);
169 if (!impl) impl = tryMake<medialoader::VorbisDecoder>(ptr, size, bufferSize);
170 if (!impl) impl = tryMake<medialoader::Mpg123Decoder>(ptr, size, bufferSize);
171 if (!impl) impl = tryMake<medialoader::FLACDecoder>(ptr, size, bufferSize);
172 if (!impl) impl = tryMake<medialoader::ModPlugDecoder>(ptr, size, bufferSize);
173 }
174
175 if (!impl)
176 throw eve::Exception("Could not decode sound data: unsupported format");
177
178 return new Decoder(std::move(impl), std::move(owned));
179}
180
182 if (decoder == nullptr)
183 throw eve::Exception("Decoder is null");
184
185 std::vector<uint8_t> pcm;
186 while (!decoder->isFinished()) {
187 int n = decoder->decode();
188 if (n <= 0)
189 break;
190 auto *buf = static_cast<const uint8_t *>(decoder->getBuffer());
191 pcm.insert(pcm.end(), buf, buf + n);
192 }
193
194 return new SoundData(std::move(pcm), decoder->getSampleRate(), decoder->getBitDepth(),
195 decoder->getChannelCount());
196}
197
199 Decoder *dec = newDecoder(data);
200 try {
202 delete dec;
203 return sd;
204 } catch (...) {
205 delete dec;
206 throw;
207 }
208}
209
211 if (path.empty())
212 throw eve::Exception("Sound::newSoundDataFromFile: empty path");
213
215 if (!resource)
216 throw eve::Exception("Could not load sound file: %s", path.c_str());
217 return static_cast<SoundData *>(resource);
218}
219
220SoundData *Sound::newSoundDataEmpty(int samples, int rate, int bitDepth, int channels) {
221 if (samples < 0)
222 throw eve::Exception("Invalid sample count");
223 size_t bytes = static_cast<size_t>(samples) * static_cast<size_t>(bitDepth / 8) * static_cast<size_t>(channels);
224 std::vector<uint8_t> pcm(bytes, 0);
225 return new SoundData(std::move(pcm), rate, bitDepth, channels);
226}
227
228void Sound::expose(ssq::Table& table) {
229 auto cls = table.addClass(name, Sound::create, false);
230 expose(cls);
231
232 auto dec = table.addClass<Decoder>(
233 "Decoder", std::function<Decoder *()>([]() -> Decoder * { return nullptr; }), true);
234 dec.addFunc("decode", &Decoder::decode);
235 dec.addFunc("getSize", &Decoder::getSize);
236 dec.addFunc("seek", &Decoder::seek);
237 dec.addFunc("rewind", &Decoder::rewind);
238 dec.addFunc("isSeekable", &Decoder::isSeekable);
239 dec.addFunc("isFinished", &Decoder::isFinished);
240 dec.addFunc("getChannelCount", &Decoder::getChannelCount);
241 dec.addFunc("getBitDepth", &Decoder::getBitDepth);
242 dec.addFunc("getSampleRate", &Decoder::getSampleRate);
243 dec.addFunc("getDuration", &Decoder::getDuration);
244
245 auto sd = table.addClass<SoundData>(
246 "SoundData", std::function<SoundData *()>([]() -> SoundData * { return nullptr; }), true);
247 sd.addFunc("getSampleCount", &SoundData::getSampleCount);
248 sd.addFunc("getSampleRate", &SoundData::getSampleRate);
249 sd.addFunc("getBitDepth", &SoundData::getBitDepth);
250 sd.addFunc("getChannelCount", &SoundData::getChannelCount);
251 sd.addFunc("getDuration", &SoundData::getDuration);
252 sd.addFunc("getSize", &SoundData::getSize);
253}
254
255void Sound::expose(ssq::Class& cls) {
256 cls.addFunc("getName", &Sound::getName);
257 cls.addFunc("newDecoder", &Sound::newDecoder);
258 cls.addFunc("newSoundData", &Sound::newSoundData);
259 cls.addFunc("newSoundDataFromDecoder", &Sound::newSoundDataFromDecoder);
260 cls.addFunc("newSoundDataFromFile", &Sound::newSoundDataFromFile);
261 cls.addFunc("newSoundDataEmpty", &Sound::newSoundDataEmpty);
262}
263
264} // namespace sound
265} // namespace eve
void * impl
HSQOBJECT cls
Definition ECS.cpp:21
glm::vec3 n
Definition Grass.cpp:64
void * ptr
uint32_t a
uint32_t b
uint32_t c
#define Module_IMPL(ModuleName, newExpr)
Definition Module.h:24
Light2D::Data * data
const char * name
Definition RockMesh.cpp:21
V3 dir
Definition TreeMesh.cpp:121
virtual std::string getName() const =0
Resource * get(std::string key)
Get the cached resource for key; on a miss, load it through the registered IAssetReloader providers a...
Definition Resource.cpp:56
static ResourceManager & getInstance()
Definition Resource.cpp:8
Resource is a game object that is managed by the ResourceManager. It can be loaded from a file or gen...
Definition Resource.h:35
Data buffer paired with a filename (used for type identification).
Definition FileData.h:12
Streaming audio decoder (medialoader-backed). Owns the raw encoded bytes and decodes incrementally vi...
Definition Decoder.h:20
void * getBuffer() const
Internal decoded buffer.
Definition Decoder.cpp:27
double getDuration()
Total duration in seconds.
Definition Decoder.cpp:35
bool isFinished()
True when the stream is fully decoded.
Definition Decoder.cpp:31
int getSampleRate() const
Definition Decoder.cpp:34
bool isSeekable()
True when seeking is supported.
Definition Decoder.cpp:30
int getChannelCount() const
Format metadata.
Definition Decoder.cpp:32
bool rewind()
Rewinds to the start; false when unsupported.
Definition Decoder.cpp:29
bool seek(double seconds)
Seeks to a time in seconds; false when unsupported.
Definition Decoder.cpp:28
int getSize() const
Bytes decoded so far into the internal buffer.
Definition Decoder.cpp:26
int getBitDepth() const
Definition Decoder.cpp:33
int decode()
Decodes the next chunk; returns bytes produced (0 = end/error).
Definition Decoder.cpp:25
Raw PCM audio buffer with format metadata (samples/rate/bit depth/channels).
Definition SoundData.h:13
double getDuration() const
Duration in seconds.
Definition SoundData.cpp:40
int getBitDepth() const
Definition SoundData.cpp:37
int getChannelCount() const
Definition SoundData.cpp:38
int getSampleRate() const
Definition SoundData.cpp:36
int getSampleCount() const
Format metadata.
Definition SoundData.cpp:30
size_t getSize() const
Definition SoundData.cpp:47
Sound module: decodes compressed audio and produces SoundData buffers. Script: sound <- eve....
Definition Sound.h:16
SoundData * newSoundDataFromDecoder(Decoder *decoder)
Fully decodes a Decoder into a SoundData buffer.
Definition Sound.cpp:181
SoundData * newSoundDataFromFile(std::string path)
Decodes a sound file from a VFS path through the unified resource cache. Repeated loads of one path s...
Definition Sound.cpp:210
SoundData * newSoundData(Data *data)
Fully decodes data into a SoundData buffer.
Definition Sound.cpp:198
~Sound() override
SoundData * newSoundDataEmpty(int samples, int rate, int bitDepth, int channels)
Creates an empty PCM buffer (e.g. for synthesis).
Definition Sound.cpp:220
Decoder * newDecoder(Data *data, int bufferSize=16384)
Creates a streaming decoder over raw encoded data.
Definition Sound.cpp:136
Definition Build.cpp:11