diff --git a/src/audio/AL/ALAudio.cpp b/src/audio/AL/ALAudio.cpp index d9e2c402..72627dae 100644 --- a/src/audio/AL/ALAudio.cpp +++ b/src/audio/AL/ALAudio.cpp @@ -25,7 +25,10 @@ Speaker* ALSound::newInstance(int priority, int channel) const { return nullptr; } AL_CHECK(alSourcei(source, AL_BUFFER, buffer)); - return new ALSpeaker(al, source, priority, channel); + + auto speaker = new ALSpeaker(al, source, priority, channel); + speaker->duration = duration; + return speaker; } ALStream::ALStream(ALAudio* al, std::shared_ptr source, bool keepSource) @@ -77,12 +80,51 @@ void ALStream::bindSpeaker(speakerid_t speaker) { sp->stop(); } this->speaker = speaker; + sp = audio::get_speaker(speaker); + if (sp) { + auto alspeaker = dynamic_cast(sp); + alspeaker->stream = this; + alspeaker->duration = source->getTotalDuration(); + } } speakerid_t ALStream::getSpeaker() const { return speaker; } +void ALStream::unqueueBuffers(uint alsource) { + uint processed = AL::getSourcei(alsource, AL_BUFFERS_PROCESSED); + + while (processed--) { + uint buffer; + AL_CHECK(alSourceUnqueueBuffers(alsource, 1, &buffer)); + unusedBuffers.push(buffer); + + uint bps = source->getBitsPerSample()/8; + uint channels = source->getChannels(); + + ALint bufferSize; + alGetBufferi(buffer, AL_SIZE, &bufferSize); + totalPlayedSamples += bufferSize / bps / channels; + if (source->isSeekable()) { + totalPlayedSamples %= source->getTotalSamples(); + } + } +} + +uint ALStream::enqueueBuffers(uint alsource) { + uint preloaded = 0; + if (!unusedBuffers.empty()) { + uint buffer = unusedBuffers.front(); + if (preloadBuffer(buffer, loop)) { + preloaded++; + unusedBuffers.pop(); + AL_CHECK(alSourceQueueBuffers(alsource, 1, &buffer)); + } + } + return preloaded; +} + void ALStream::update(double delta) { if (this->speaker == 0) { return; @@ -93,24 +135,11 @@ void ALStream::update(double delta) { return; } ALSpeaker* alspeaker = dynamic_cast(speaker); - uint source = alspeaker->source; - uint processed = AL::getSourcei(source, AL_BUFFERS_PROCESSED); - - while (processed--) { - uint buffer; - AL_CHECK(alSourceUnqueueBuffers(source, 1, &buffer)); - unusedBuffers.push(buffer); - } - - uint preloaded = 0; - if (!unusedBuffers.empty()) { - uint buffer = unusedBuffers.front(); - if (preloadBuffer(buffer, loop)) { - preloaded++; - unusedBuffers.pop(); - AL_CHECK(alSourceQueueBuffers(source, 1, &buffer)); - } - } + uint alsource = alspeaker->source; + + unqueueBuffers(alsource); + uint preloaded = enqueueBuffers(alsource); + if (speaker->isStopped() && !alspeaker->stopped) { if (preloaded) { speaker->play(); @@ -120,8 +149,38 @@ void ALStream::update(double delta) { } } +duration_t ALStream::getTime() const { + uint total = totalPlayedSamples; + auto alspeaker = dynamic_cast(audio::get_speaker(this->speaker)); + if (alspeaker) { + uint alsource = alspeaker->source; + total += static_cast(AL::getSourcef(alsource, AL_SAMPLE_OFFSET)); + if (source->isSeekable()) { + total %= source->getTotalSamples(); + } + } + return total / static_cast(source->getSampleRate()); +} + void ALStream::setTime(duration_t time) { - // TODO: implement + if (!source->isSeekable()) + return; + uint sample = time * source->getSampleRate(); + source->seek(sample); + auto alspeaker = dynamic_cast(audio::get_speaker(this->speaker)); + if (alspeaker) { + bool paused = alspeaker->isPaused(); + AL_CHECK(alSourceStop(alspeaker->source)); + unqueueBuffers(alspeaker->source); + totalPlayedSamples = sample; + enqueueBuffers(alspeaker->source); + AL_CHECK(alSourcePlay(alspeaker->source)); + if (paused) { + AL_CHECK(alSourcePause(alspeaker->source)); + } + } else { + totalPlayedSamples = sample; + } } ALSpeaker::ALSpeaker(ALAudio* al, uint source, int priority, int channel) @@ -199,15 +258,32 @@ void ALSpeaker::stop() { stopped = true; if (source) { AL_CHECK(alSourceStop(source)); + + uint processed = AL::getSourcei(source, AL_BUFFERS_PROCESSED); + while (processed--) { + uint buffer; + AL_CHECK(alSourceUnqueueBuffers(source, 1, &buffer)); + al->freeBuffer(buffer); + } al->freeSource(source); } } duration_t ALSpeaker::getTime() const { + if (stream) { + return stream->getTime(); + } return static_cast(AL::getSourcef(source, AL_SEC_OFFSET)); } +duration_t ALSpeaker::getDuration() const { + return duration; +} + void ALSpeaker::setTime(duration_t time) { + if (stream) { + return stream->setTime(time); + } AL_CHECK(alSourcef(source, AL_SEC_OFFSET, static_cast(time))); } diff --git a/src/audio/AL/ALAudio.h b/src/audio/AL/ALAudio.h index 4ba42bd2..9c97f19f 100644 --- a/src/audio/AL/ALAudio.h +++ b/src/audio/AL/ALAudio.h @@ -55,7 +55,11 @@ namespace audio { bool loop = false; bool preloadBuffer(uint buffer, bool loop); + void unqueueBuffers(uint alsource); + uint enqueueBuffers(uint alsource); public: + size_t totalPlayedSamples = 0; + ALStream(ALAudio* al, std::shared_ptr source, bool keepSource); ~ALStream(); @@ -64,6 +68,8 @@ namespace audio { Speaker* createSpeaker(bool loop, int channel) override; speakerid_t getSpeaker() const override; void update(double delta) override; + + duration_t getTime() const override; void setTime(duration_t time) override; static inline constexpr uint STREAM_BUFFERS = 3; @@ -76,9 +82,11 @@ namespace audio { int channel; float volume = 0.0f; public: + ALStream* stream = nullptr; bool stopped = true; bool paused = false; uint source; + duration_t duration = 0.0f; ALSpeaker(ALAudio* al, uint source, int priority, int channel); ~ALSpeaker(); @@ -102,6 +110,7 @@ namespace audio { void stop() override; duration_t getTime() const override; + duration_t getDuration() const override; void setTime(duration_t time) override; void setPosition(glm::vec3 pos) override; diff --git a/src/audio/NoAudio.h b/src/audio/NoAudio.h index 37c72113..3029adea 100644 --- a/src/audio/NoAudio.h +++ b/src/audio/NoAudio.h @@ -53,6 +53,10 @@ namespace audio { void update(double delta) override { } + duration_t getTime() const override { + return 0.0; + } + void setTime(duration_t time) override { } }; diff --git a/src/audio/audio.cpp b/src/audio/audio.cpp index af852f2a..b5e61b15 100644 --- a/src/audio/audio.cpp +++ b/src/audio/audio.cpp @@ -347,6 +347,14 @@ Channel* audio::get_channel(int index) { return channels.at(index).get(); } +std::shared_ptr audio::get_associated_stream(speakerid_t id) { + auto found = streams.find(id); + if (found != streams.end()) { + return found->second; + } + return nullptr; +} + size_t audio::count_speakers() { return speakers.size(); } diff --git a/src/audio/audio.h b/src/audio/audio.h index 1e1e6c0e..61609b41 100644 --- a/src/audio/audio.h +++ b/src/audio/audio.h @@ -182,6 +182,9 @@ namespace audio { /// @param delta time elapsed since the last update virtual void update(double delta) = 0; + /// @brief Get current stream time + virtual duration_t getTime() const = 0; + /// @brief Set playhead to the selected time /// @param time selected time virtual void setTime(duration_t time) = 0; @@ -269,6 +272,9 @@ namespace audio { /// @return time position in seconds virtual duration_t getTime() const = 0; + /// @brief Get playing audio total duration + virtual duration_t getDuration() const = 0; + /// @brief Set playing audio time position /// @param time time position in seconds virtual void setTime(duration_t time) = 0; @@ -470,6 +476,11 @@ namespace audio { /// @return channel or nullptr extern Channel* get_channel(int index); + /// @brief Get stream associated with speaker + /// @param id speaker id + /// @return stream or nullptr + extern std::shared_ptr get_associated_stream(speakerid_t id); + /// @brief Get alive speakers number (including paused) extern size_t count_speakers(); diff --git a/src/coders/ogg.cpp b/src/coders/ogg.cpp index 807401e8..a1091ed5 100644 --- a/src/coders/ogg.cpp +++ b/src/coders/ogg.cpp @@ -134,7 +134,7 @@ public: void seek(size_t position) override { if (!closed && seekable) { - ov_raw_seek(&vf, position); + ov_pcm_seek(&vf, position); } } }; diff --git a/src/engine.cpp b/src/engine.cpp index d7dad0c9..ee6accc5 100644 --- a/src/engine.cpp +++ b/src/engine.cpp @@ -276,6 +276,10 @@ EnginePaths* Engine::getPaths() { return paths; } +ResPaths* Engine::getResPaths() { + return resPaths.get(); +} + std::shared_ptr Engine::getScreen() { return screen; } diff --git a/src/engine.h b/src/engine.h index 6e986e85..aec0df72 100644 --- a/src/engine.h +++ b/src/engine.h @@ -106,6 +106,9 @@ public: /** Get engine filesystem paths source */ EnginePaths* getPaths(); + /** Get engine resource paths controller */ + ResPaths* getResPaths(); + /** Get current Content instance */ const Content* getContent() const; diff --git a/src/logic/scripting/lua/LuaState.cpp b/src/logic/scripting/lua/LuaState.cpp index 0a49fa89..e7988dd5 100644 --- a/src/logic/scripting/lua/LuaState.cpp +++ b/src/logic/scripting/lua/LuaState.cpp @@ -122,6 +122,7 @@ void lua::LuaState::createLibs() { openlib("time", timelib, 0); openlib("file", filelib, 0); openlib("gui", guilib, 0); + openlib("audio", audiolib, 0); addfunc("print", lua_wrap_errors); } diff --git a/src/logic/scripting/lua/api_lua.h b/src/logic/scripting/lua/api_lua.h index 5cc5a6a7..10639e45 100644 --- a/src/logic/scripting/lua/api_lua.h +++ b/src/logic/scripting/lua/api_lua.h @@ -15,6 +15,7 @@ extern const luaL_Reg playerlib []; extern const luaL_Reg inventorylib []; extern const luaL_Reg guilib []; extern const luaL_Reg hudlib []; +extern const luaL_Reg audiolib []; // Lua Overrides diff --git a/src/logic/scripting/lua/libaudio.cpp b/src/logic/scripting/lua/libaudio.cpp new file mode 100644 index 00000000..45fcd99f --- /dev/null +++ b/src/logic/scripting/lua/libaudio.cpp @@ -0,0 +1,418 @@ +#include "api_lua.h" +#include "lua_commons.h" +#include "lua_util.h" + +#include "../../../audio/audio.h" +#include "../../../engine.h" +#include "../scripting.h" + +inline const char* DEFAULT_CHANNEL = "regular"; + +inline int extract_channel_index(lua_State* L, int idx) { + const char* channel = DEFAULT_CHANNEL; + if (!lua_isnoneornil(L, idx)) { + channel = lua_tostring(L, idx); + } + return audio::get_channel_index(channel); +} + +inline audio::speakerid_t play_sound( + const char* name, + bool relative, + lua::luanumber x, + lua::luanumber y, + lua::luanumber z, + lua::luanumber volume, + lua::luanumber pitch, + bool loop, + int channel +) { + if (channel == -1) + return 0; + auto assets = scripting::engine->getAssets(); + auto sound = assets->getSound(name); + if (sound == nullptr) { + return 0; + } + + return audio::play( + sound, + glm::vec3( + static_cast(x), + static_cast(y), + static_cast(z) + ), + false, + volume, + pitch, + false, + audio::PRIORITY_NORMAL, + channel + ); +} + +inline audio::speakerid_t play_stream( + const char* filename, + bool relative, + lua::luanumber x, + lua::luanumber y, + lua::luanumber z, + lua::luanumber volume, + lua::luanumber pitch, + bool loop, + int channel +) { + if (channel == -1) + return 0; + auto paths = scripting::engine->getResPaths(); + fs::path file = paths->find(fs::path(filename)); + return audio::play_stream( + file, + glm::vec3( + static_cast(x), + static_cast(y), + static_cast(z) + ), + relative, + volume, + pitch, + loop, + channel + ); +} + +/// @brief audio.play_stream( +/// name: string, +/// x: number, +/// y: number, +/// z: number, +/// volume: number, +/// pitch: number, +/// channel: string = "regular", +/// loop: bool = false) +static int l_audio_play_stream(lua_State* L) { + lua_pushinteger(L, static_cast( + play_stream( + lua_tostring(L, 1), + false, + lua_tonumber(L, 2), + lua_tonumber(L, 3), + lua_tonumber(L, 4), + lua_tonumber(L, 5), + lua_tonumber(L, 6), + lua_toboolean(L, 8), + extract_channel_index(L, 7) + ) + )); + return 1; +} + +/// @brief audio.play_stream_2d( +/// name: string, +/// volume: number, +/// pitch: number, +/// channel: string = "regular", +/// loop: bool = false) +static int l_audio_play_stream_2d(lua_State* L) { + lua_pushinteger(L, static_cast( + play_stream( + lua_tostring(L, 1), + true, + 0.0, 0.0, 0.0, + lua_tonumber(L, 2), + lua_tonumber(L, 3), + lua_toboolean(L, 5), + extract_channel_index(L, 4) + ) + )); + return 1; +} + +/// @brief audio.play_sound( +/// name: string, +/// x: number, +/// y: number, +/// z: number, +/// volume: number, +/// pitch: number, +/// channel: string = "regular", +/// loop: bool = false) +static int l_audio_play_sound(lua_State* L) { + lua_pushinteger(L, static_cast( + play_sound( + lua_tostring(L, 1), + false, + lua_tonumber(L, 2), + lua_tonumber(L, 3), + lua_tonumber(L, 4), + lua_tonumber(L, 5), + lua_tonumber(L, 6), + lua_toboolean(L, 8), + extract_channel_index(L, 7) + ) + )); + return 1; +} + +/// @brief audio.play_sound_2d( +/// name: string, +/// volume: number, +/// pitch: number, +/// channel: string = "regular", +/// loop: bool = false) +static int l_audio_play_sound_2d(lua_State* L) { + lua_pushinteger(L, static_cast( + play_sound( + lua_tostring(L, 1), + true, + 0.0, 0.0, 0.0, + lua_tonumber(L, 2), + lua_tonumber(L, 3), + lua_toboolean(L, 5), + extract_channel_index(L, 4) + ) + )); + return 1; +} + +/// @brief audio.stop(speakerid: integer) -> nil +static int l_audio_stop(lua_State* L) { + lua::luaint id = lua_tonumber(L, 1); + auto speaker = audio::get_speaker(id); + if (speaker != nullptr) { + speaker->stop(); + } + return 0; +} + +/// @brief audio.pause(speakerid: integer) -> nil +static int l_audio_pause(lua_State* L) { + lua::luaint id = lua_tonumber(L, 1); + auto speaker = audio::get_speaker(id); + if (speaker != nullptr) { + speaker->pause(); + } + return 0; +} + +/// @brief audio.resume(speakerid: integer) -> nil +static int l_audio_resume(lua_State* L) { + lua::luaint id = lua_tonumber(L, 1); + auto speaker = audio::get_speaker(id); + if (speaker != nullptr && speaker->isPaused()) { + speaker->play(); + } + return 0; +} + +/// @brief audio.set_loop(speakerid: integer, value: bool) -> nil +static int l_audio_set_loop(lua_State* L) { + lua::luaint id = lua_tonumber(L, 1); + auto speaker = audio::get_speaker(id); + if (speaker != nullptr) { + bool value = lua_toboolean(L, 2); + speaker->setLoop(value); + } + return 0; +} + +/// @brief audio.set_volume(speakerid: integer, value: number) -> nil +static int l_audio_set_volume(lua_State* L) { + lua::luaint id = lua_tonumber(L, 1); + auto speaker = audio::get_speaker(id); + if (speaker != nullptr) { + lua::luanumber value = lua_tonumber(L, 2); + speaker->setVolume(static_cast(value)); + } + return 0; +} + +/// @brief audio.set_pitch(speakerid: integer, value: number) -> nil +static int l_audio_set_pitch(lua_State* L) { + lua::luaint id = lua_tonumber(L, 1); + auto speaker = audio::get_speaker(id); + if (speaker != nullptr) { + lua::luanumber value = lua_tonumber(L, 2); + speaker->setPitch(static_cast(value)); + } + return 0; +} + +/// @brief audio.set_time(speakerid: integer, value: number) -> nil +static int l_audio_set_time(lua_State* L) { + lua::luaint id = lua_tonumber(L, 1); + auto speaker = audio::get_speaker(id); + if (speaker != nullptr) { + lua::luanumber value = lua_tonumber(L, 2); + speaker->setTime(static_cast(value)); + } + return 0; +} + +/// @brief audio.set_position(speakerid: integer, x: number, y: number, z: number) -> nil +static int l_audio_set_position(lua_State* L) { + lua::luaint id = lua_tonumber(L, 1); + auto speaker = audio::get_speaker(id); + if (speaker != nullptr) { + lua::luanumber x = lua_tonumber(L, 2); + lua::luanumber y = lua_tonumber(L, 3); + lua::luanumber z = lua_tonumber(L, 4); + speaker->setPosition(glm::vec3( + static_cast(x), + static_cast(y), + static_cast(z) + )); + } + return 0; +} + +/// @brief audio.set_velocity(speakerid: integer, x: number, y: number, z: number) -> nil +static int l_audio_set_velocity(lua_State* L) { + lua::luaint id = lua_tonumber(L, 1); + auto speaker = audio::get_speaker(id); + if (speaker != nullptr) { + lua::luanumber x = lua_tonumber(L, 2); + lua::luanumber y = lua_tonumber(L, 3); + lua::luanumber z = lua_tonumber(L, 4); + speaker->setVelocity(glm::vec3( + static_cast(x), + static_cast(y), + static_cast(z) + )); + } + return 0; +} + +/// @brief audio.is_playing(speakerid: integer) -> bool +static int l_audio_is_playing(lua_State* L) { + lua::luaint id = lua_tonumber(L, 1); + auto speaker = audio::get_speaker(id); + if (speaker != nullptr) { + lua_pushboolean(L, speaker->isPlaying()); + return 1; + } + lua_pushboolean(L, false); + return 1; +} + +/// @brief audio.is_paused(speakerid: integer) -> bool +static int l_audio_is_paused(lua_State* L) { + lua::luaint id = lua_tonumber(L, 1); + auto speaker = audio::get_speaker(id); + if (speaker != nullptr) { + lua_pushboolean(L, speaker->isPaused()); + return 1; + } + lua_pushboolean(L, false); + return 1; +} + +/// @brief audio.is_loop(speakerid: integer) -> bool +static int l_audio_is_loop(lua_State* L) { + lua::luaint id = lua_tonumber(L, 1); + auto speaker = audio::get_speaker(id); + if (speaker != nullptr) { + lua_pushboolean(L, speaker->isLoop()); + return 1; + } + lua_pushboolean(L, false); + return 1; +} + +/// @brief audio.get_volume(speakerid: integer) -> number +static int l_audio_get_volume(lua_State* L) { + lua::luaint id = lua_tonumber(L, 1); + auto speaker = audio::get_speaker(id); + if (speaker != nullptr) { + lua_pushnumber(L, speaker->getVolume()); + return 1; + } + lua_pushnumber(L, 0.0); + return 1; +} + +/// @brief audio.get_pitch(speakerid: integer) -> number +static int l_audio_get_pitch(lua_State* L) { + lua::luaint id = lua_tonumber(L, 1); + auto speaker = audio::get_speaker(id); + if (speaker != nullptr) { + lua_pushnumber(L, speaker->getPitch()); + return 1; + } + lua_pushnumber(L, 1.0); + return 1; +} + +/// @brief audio.get_time(speakerid: integer) -> number +static int l_audio_get_time(lua_State* L) { + lua::luaint id = lua_tonumber(L, 1); + auto speaker = audio::get_speaker(id); + if (speaker != nullptr) { + lua_pushnumber(L, speaker->getTime()); + return 1; + } + lua_pushnumber(L, 0.0); + return 1; +} + +/// @brief audio.get_duration(speakerid: integer) -> number +static int l_audio_get_duration(lua_State* L) { + lua::luaint id = lua_tonumber(L, 1); + auto speaker = audio::get_speaker(id); + if (speaker != nullptr) { + lua_pushnumber(L, speaker->getDuration()); + return 1; + } + lua_pushnumber(L, 0.0); + return 1; +} + +/// @brief audio.get_position(speakerid: integer) -> number, number, number +static int l_audio_get_position(lua_State* L) { + lua::luaint id = lua_tonumber(L, 1); + auto speaker = audio::get_speaker(id); + if (speaker != nullptr) { + auto vec = speaker->getPosition(); + lua::pushvec3(L, vec); + return 1; + } + return 0; +} + +/// @brief audio.get_velocity(speakerid: integer) -> number, number, number +static int l_audio_get_velocity(lua_State* L) { + lua::luaint id = lua_tonumber(L, 1); + auto speaker = audio::get_speaker(id); + if (speaker != nullptr) { + auto vec = speaker->getVelocity(); + lua::pushvec3(L, vec); + return 1; + } + return 0; +} + +const luaL_Reg audiolib [] = { + {"play_sound", lua_wrap_errors}, + {"play_sound_2d", lua_wrap_errors}, + {"play_stream", lua_wrap_errors}, + {"play_stream_2d", lua_wrap_errors}, + {"stop", lua_wrap_errors}, + {"pause", lua_wrap_errors}, + {"resume", lua_wrap_errors}, + {"set_loop", lua_wrap_errors}, + {"set_volume", lua_wrap_errors}, + {"set_pitch", lua_wrap_errors}, + {"set_time", lua_wrap_errors}, + {"set_position", lua_wrap_errors}, + {"set_velocity", lua_wrap_errors}, + {"is_playing", lua_wrap_errors}, + {"is_paused", lua_wrap_errors}, + {"is_loop", lua_wrap_errors}, + {"get_volume", lua_wrap_errors}, + {"get_pitch", lua_wrap_errors}, + {"get_time", lua_wrap_errors}, + {"get_duration", lua_wrap_errors}, + {"get_position", lua_wrap_errors}, + {"get_velocity", lua_wrap_errors}, + {NULL, NULL} +};