diff --git a/src/assets/assetload_funcs.cpp b/src/assets/assetload_funcs.cpp index 7c351cf3..698a94f5 100644 --- a/src/assets/assetload_funcs.cpp +++ b/src/assets/assetload_funcs.cpp @@ -162,7 +162,7 @@ bool assetload::sound( std::shared_ptr config ) { auto cfg = dynamic_cast(config.get()); - auto sound = audio::loadSound(paths->find(file), cfg->keepPCM); + auto sound = audio::load_sound(paths->find(file), cfg->keepPCM); if (sound == nullptr) { std::cerr << "failed to load sound '" << name << "' from '"; std::cerr << file << "'" << std::endl; @@ -172,11 +172,13 @@ bool assetload::sound( return true; } -bool assetload::animation(Assets* assets, - const ResPaths* paths, - const std::string directory, - const std::string name, - Atlas* dstAtlas) { +bool assetload::animation( + Assets* assets, + const ResPaths* paths, + const std::string directory, + const std::string name, + Atlas* dstAtlas +) { std::string animsDir = directory + "/animations"; std::string blocksDir = directory + "/blocks"; diff --git a/src/audio/AL/ALAudio.cpp b/src/audio/AL/ALAudio.cpp index 8cbf01e4..e4601d67 100644 --- a/src/audio/AL/ALAudio.cpp +++ b/src/audio/AL/ALAudio.cpp @@ -19,13 +19,13 @@ ALSound::~ALSound() { buffer = 0; } -Speaker* ALSound::newInstance(int priority) const { +Speaker* ALSound::newInstance(int priority, int channel) const { uint source = al->getFreeSource(); if (source == 0) { return nullptr; } AL_CHECK(alSourcei(source, AL_BUFFER, buffer)); - return new ALSpeaker(al, source, priority); + return new ALSpeaker(al, source, priority, channel); } ALStream::ALStream(ALAudio* al, std::shared_ptr source, bool keepSource) @@ -54,7 +54,7 @@ bool ALStream::preloadBuffer(uint buffer, bool loop) { return true; } -Speaker* ALStream::createSpeaker(bool loop) { +Speaker* ALStream::createSpeaker(bool loop, int channel) { this->loop = loop; uint source = al->getFreeSource(); if (source == 0) { @@ -67,7 +67,7 @@ Speaker* ALStream::createSpeaker(bool loop) { } AL_CHECK(alSourceQueueBuffers(source, 1, &buffer)); } - return new ALSpeaker(al, source, PRIORITY_HIGH); + return new ALSpeaker(al, source, PRIORITY_HIGH, channel); } @@ -111,7 +111,7 @@ void ALStream::update(double delta) { AL_CHECK(alSourceQueueBuffers(source, 1, &buffer)); } } - if (speaker->isStopped() && !speaker->isStoppedManually()) { + if (speaker->isStopped() && !alspeaker->stopped) { if (preloaded) { speaker->play(); } else { @@ -124,7 +124,8 @@ void ALStream::setTime(duration_t time) { // TODO: implement } -ALSpeaker::ALSpeaker(ALAudio* al, uint source, int priority) : al(al), priority(priority), source(source) { +ALSpeaker::ALSpeaker(ALAudio* al, uint source, int priority, int channel) +: al(al), priority(priority), channel(channel), source(source) { } ALSpeaker::~ALSpeaker() { @@ -133,6 +134,23 @@ ALSpeaker::~ALSpeaker() { } } +void ALSpeaker::update(const Channel* channel, float masterVolume) { + float gain = this->volume * channel->getVolume()*masterVolume; + AL_CHECK(alSourcef(source, AL_GAIN, gain)); + + if (!paused) { + if (isPaused() && !channel->isPaused()) { + play(); + } else if (isPlaying() && channel->isPaused()) { + AL_CHECK(alSourcePause(source)); + } + } +} + +int ALSpeaker::getChannel() const { + return channel; +} + State ALSpeaker::getState() const { int state = AL::getSourcei(source, AL_SOURCE_STATE, AL_STOPPED); switch (state) { @@ -147,7 +165,7 @@ float ALSpeaker::getVolume() const { } void ALSpeaker::setVolume(float volume) { - AL_CHECK(alSourcef(source, AL_GAIN, volume)); + this->volume = volume; } float ALSpeaker::getPitch() const { @@ -167,16 +185,18 @@ void ALSpeaker::setLoop(bool loop) { } void ALSpeaker::play() { - stoppedManually = false; + paused = false; + stopped = false; AL_CHECK(alSourcePlay(source)); } void ALSpeaker::pause() { + paused = true; AL_CHECK(alSourcePause(source)); } void ALSpeaker::stop() { - stoppedManually = true; + stopped = true; if (source) { AL_CHECK(alSourceStop(source)); al->freeSource(source); @@ -184,10 +204,6 @@ void ALSpeaker::stop() { } } -bool ALSpeaker::isStoppedManually() const { - return stoppedManually; -} - duration_t ALSpeaker::getTime() const { return static_cast(AL::getSourcef(source, AL_SEC_OFFSET)); } diff --git a/src/audio/AL/ALAudio.h b/src/audio/AL/ALAudio.h index e2afd03b..4ba42bd2 100644 --- a/src/audio/AL/ALAudio.h +++ b/src/audio/AL/ALAudio.h @@ -40,7 +40,7 @@ namespace audio { return pcm; } - Speaker* newInstance(int priority) const override; + Speaker* newInstance(int priority, int channel) const override; }; class ALStream : public Stream { @@ -61,7 +61,7 @@ namespace audio { std::shared_ptr getSource() const override; void bindSpeaker(speakerid_t speaker) override; - Speaker* createSpeaker(bool loop) override; + Speaker* createSpeaker(bool loop, int channel) override; speakerid_t getSpeaker() const override; void update(double delta) override; void setTime(duration_t time) override; @@ -73,13 +73,19 @@ namespace audio { class ALSpeaker : public Speaker { ALAudio* al; int priority; - bool stoppedManually = false; + int channel; + float volume = 0.0f; public: + bool stopped = true; + bool paused = false; uint source; - ALSpeaker(ALAudio* al, uint source, int priority); + ALSpeaker(ALAudio* al, uint source, int priority, int channel); ~ALSpeaker(); + void update(const Channel* channel, float masterVolume) override; + int getChannel() const override; + State getState() const override; float getVolume() const override; @@ -94,7 +100,6 @@ namespace audio { void play() override; void pause() override; void stop() override; - bool isStoppedManually() const override; duration_t getTime() const override; void setTime(duration_t time) override; diff --git a/src/audio/NoAudio.h b/src/audio/NoAudio.h index 8d91ebd3..37c72113 100644 --- a/src/audio/NoAudio.h +++ b/src/audio/NoAudio.h @@ -19,7 +19,7 @@ namespace audio { return pcm; } - Speaker* newInstance(int priority) const override { + Speaker* newInstance(int priority, int channel) const override { return nullptr; } }; @@ -42,7 +42,7 @@ namespace audio { void bindSpeaker(speakerid_t speaker) override { } - Speaker* createSpeaker(bool loop) override{ + Speaker* createSpeaker(bool loop, int channel) override{ return nullptr; } diff --git a/src/audio/audio.cpp b/src/audio/audio.cpp index ecb5db2d..47c3bcbc 100644 --- a/src/audio/audio.cpp +++ b/src/audio/audio.cpp @@ -14,20 +14,50 @@ namespace audio { static Backend* backend; static std::unordered_map> speakers; static std::unordered_map> streams; + static std::vector> channels; } using namespace audio; +Channel::Channel(std::string name) : name(name) { +} + +float Channel::getVolume() const { + return volume; +} + +void Channel::setVolume(float volume) { + this->volume = std::max(0.0f, std::min(volume, 1.0f)); +} + +const std::string& Channel::getName() const { + return name; +} + +// see pause logic in audio::update +void Channel::pause() { + paused = true; +} + +// see pause logic in audio::update +void Channel::resume() { + paused = false; +} + +bool Channel::isPaused() const { + return paused; +} + size_t PCMStream::readFully(char* buffer, size_t bufferSize, bool loop) { if (!isOpen()) { return 0; } - long bytes = 0; size_t size = 0; do { + size_t bytes = 0; do { bytes = read(buffer, bufferSize); - if (bytes < 0) { + if (bytes == PCMStream::ERROR) { return size; } size += bytes; @@ -126,9 +156,14 @@ void audio::initialize(bool enabled) { std::cerr << "could not to initialize audio" << std::endl; backend = NoAudio::create(); } + create_channel("master"); + create_channel("regular"); + create_channel("music"); + create_channel("ambient"); + create_channel("ui"); } -PCM* audio::loadPCM(const fs::path& file, bool headerOnly) { +PCM* audio::load_PCM(const fs::path& file, bool headerOnly) { if (!fs::exists(file)) { throw std::runtime_error("file not found '"+file.u8string()+"'"); } @@ -141,16 +176,16 @@ PCM* audio::loadPCM(const fs::path& file, bool headerOnly) { throw std::runtime_error("unsupported audio format"); } -Sound* audio::loadSound(const fs::path& file, bool keepPCM) { - std::shared_ptr pcm(loadPCM(file, !keepPCM && backend->isDummy())); - return createSound(pcm, keepPCM); +Sound* audio::load_sound(const fs::path& file, bool keepPCM) { + std::shared_ptr pcm(load_PCM(file, !keepPCM && backend->isDummy())); + return create_sound(pcm, keepPCM); } -Sound* audio::createSound(std::shared_ptr pcm, bool keepPCM) { +Sound* audio::create_sound(std::shared_ptr pcm, bool keepPCM) { return backend->createSound(pcm, keepPCM); } -PCMStream* audio::openPCMStream(const fs::path& file) { +PCMStream* audio::open_PCM_stream(const fs::path& file) { std::string ext = file.extension().u8string(); if (ext == ".wav" || ext == ".WAV") { return wav::create_stream(file); @@ -160,27 +195,27 @@ PCMStream* audio::openPCMStream(const fs::path& file) { throw std::runtime_error("unsupported audio stream format"); } -Stream* audio::openStream(const fs::path& file, bool keepSource) { +Stream* audio::open_stream(const fs::path& file, bool keepSource) { if (!keepSource && backend->isDummy()) { - auto header = loadPCM(file, true); + auto header = load_PCM(file, true); // using void source sized as audio instead of actual audio file - return openStream( + return open_stream( std::make_shared(header->totalSamples, header->sampleRate, header->seekable), keepSource ); } - return openStream( - std::shared_ptr(openPCMStream(file)), + return open_stream( + std::shared_ptr(open_PCM_stream(file)), keepSource ); } -Stream* audio::openStream(std::shared_ptr stream, bool keepSource) { +Stream* audio::open_stream(std::shared_ptr stream, bool keepSource) { return backend->openStream(stream, keepSource); } -void audio::setListener( +void audio::set_listener( glm::vec3 position, glm::vec3 velocity, glm::vec3 lookAt, @@ -215,12 +250,13 @@ speakerid_t audio::play( float volume, float pitch, bool loop, - int priority + int priority, + int channel ) { - Speaker* speaker = sound->newInstance(priority); + Speaker* speaker = sound->newInstance(priority, channel); if (speaker == nullptr) { remove_lower_priority_speaker(priority); - speaker = sound->newInstance(priority); + speaker = sound->newInstance(priority, channel); } if (speaker == nullptr) { return 0; @@ -242,12 +278,13 @@ speakerid_t audio::play( bool relative, float volume, float pitch, - bool loop + bool loop, + int channel ) { - Speaker* speaker = stream->createSpeaker(loop); + Speaker* speaker = stream->createSpeaker(loop, channel); if (speaker == nullptr) { remove_lower_priority_speaker(PRIORITY_HIGH); - speaker = stream->createSpeaker(loop); + speaker = stream->createSpeaker(loop, channel); } if (speaker == nullptr) { return 0; @@ -266,16 +303,17 @@ speakerid_t audio::play( return id; } -speakerid_t audio::playStream( +speakerid_t audio::play_stream( const fs::path& file, glm::vec3 position, bool relative, float volume, float pitch, - bool loop + bool loop, + int channel ) { - std::shared_ptr stream (openStream(file, false)); - return play(stream, position, relative, volume, pitch, loop); + std::shared_ptr stream (open_stream(file, false)); + return play(stream, position, relative, volume, pitch, loop, channel); } Speaker* audio::get(speakerid_t id) { @@ -286,6 +324,37 @@ Speaker* audio::get(speakerid_t id) { return found->second.get(); } +int audio::create_channel(const std::string& name) { + int index = get_channel_index(name); + if (index != -1) { + return index; + } + channels.emplace_back(new Channel(name)); + return channels.size()-1; +} + +int audio::get_channel_index(const std::string& name) { + int index = 0; + for (auto& channel : channels) { + if (channel->getName() == name) { + return index; + } + index++; + } + return -1; +} + +Channel* audio::get_channel(int index) { + if (index < 0 || index >= int(channels.size())) { + return nullptr; + } + return channels.at(index).get(); +} + +float audio::get_master_volume() { + return channels.at(0)->getVolume(); +} + void audio::update(double delta) { backend->update(delta); @@ -293,8 +362,15 @@ void audio::update(double delta) { entry.second->update(delta); } + float masterVolume = get_master_volume(); for (auto it = speakers.begin(); it != speakers.end();) { - if (it->second->isStoppedManually()) { + auto speaker = it->second.get(); + int speakerChannel = speaker->getChannel(); + auto channel = get_channel(speakerChannel); + if (channel != nullptr) { + speaker->update(channel, speakerChannel == 0 ? 1.0f : masterVolume); + } + if (speaker->isStopped()) { streams.erase(it->first); it = speakers.erase(it); } else { @@ -303,6 +379,22 @@ void audio::update(double delta) { } } +void audio::reset() { + for (auto& entry : speakers) { + entry.second->stop(); + } + for (auto& entry : streams) { + entry.second->update(0.0f); + } + for (auto& channel : channels) { + if (channel->isPaused()) { + channel->resume(); + } + } + streams.clear(); + speakers.clear(); +} + void audio::close() { speakers.clear(); delete backend; diff --git a/src/audio/audio.h b/src/audio/audio.h index bb15aaff..9a29b932 100644 --- a/src/audio/audio.h +++ b/src/audio/audio.h @@ -34,6 +34,37 @@ namespace audio { stopped }; + /// @brief Mixer channel controls speakers volume and effects + /// There is main channel 'master' and sub-channels like 'regular', 'music', 'ambient'... + class Channel { + /// @brief Channel name + std::string name; + /// @brief Channel volume setting + float volume = 1.0f; + bool paused = false; + public: + Channel(std::string name); + + /// @brief Get channel volume + float getVolume() const; + + /// @brief Set channel volume + /// @param volume value in range [0.0, 1.0] + void setVolume(float volume); + + /// @brief Get channel name + const std::string& getName() const; + + /// @brief Pause all speakers in channel + void pause(); + + /// @brief Unpause all speakers paused by this channel + void resume(); + + /// @brief Check if the channel is paused + bool isPaused() const; + }; + /// @brief Pulse-code modulation data struct PCM { /// @brief May contain 8 bit and 16 bit PCM data @@ -64,6 +95,8 @@ namespace audio { sampleRate(sampleRate), seekable(seekable) {} + /// @brief Get total audio duration + /// @return duration in seconds inline duration_t getDuration() const { return static_cast(totalSamples) / static_cast(sampleRate); @@ -115,6 +148,9 @@ namespace audio { /// @brief Move playhead to the selected sample number /// @param position selected sample number virtual void seek(size_t position) = 0; + + /// @brief Value returning by read(...) in case of error + static const size_t ERROR = -1; }; /// @brief Audio streaming interface @@ -130,8 +166,9 @@ namespace audio { /// @brief Create new speaker bound to the Stream /// and having high priority /// @param loop is stream looped (required for correct buffers preload) + /// @param channel channel index /// @return speaker id or 0 - virtual Speaker* createSpeaker(bool loop) = 0; + virtual Speaker* createSpeaker(bool loop, int channel) = 0; /// @brief Unbind previous speaker and bind new speaker to the stream /// @param speaker speaker id or 0 if all you need is unbind speaker @@ -168,16 +205,26 @@ namespace audio { /// @brief Create new sound instance /// @param priority instance priority. High priority instance can /// take out speaker from low priority instance + /// @param channel channel index /// @return new speaker with sound bound or nullptr /// if all speakers are in use - virtual Speaker* newInstance(int priority) const = 0; + virtual Speaker* newInstance(int priority, int channel) const = 0; }; - /// @brief Audio source controller interface + /// @brief Audio source controller interface. + /// @attention Speaker is not supposed to be reused class Speaker { public: virtual ~Speaker() {} + /// @brief Synchronize the speaker with channel settings + /// @param channel speaker channel + /// @param masterVolume volume of the master channel + virtual void update(const Channel* channel, float masterVolume) = 0; + + /// @brief Check speaker channel index + virtual int getChannel() const = 0; + /// @brief Get current speaker state /// @return speaker state virtual State getState() const = 0; @@ -214,9 +261,6 @@ namespace audio { /// @brief Stop and destroy speaker virtual void stop() = 0; - - /// @brief Check if the speaker has stopped by calling stop() - virtual bool isStoppedManually() const = 0; /// @brief Get current time position of playing audio /// @return time position in seconds @@ -297,45 +341,45 @@ namespace audio { /// @param headerOnly read header only /// @throws std::runtime_error if I/O error ocurred or format is unknown /// @return PCM audio data - extern PCM* loadPCM(const fs::path& file, bool headerOnly); + extern PCM* load_PCM(const fs::path& file, bool headerOnly); /// @brief Load sound from file /// @param file audio file path /// @param keepPCM store PCM data in sound to make it accessible with Sound::getPCM /// @throws std::runtime_error if I/O error ocurred or format is unknown /// @return new Sound instance - extern Sound* loadSound(const fs::path& file, bool keepPCM); + extern Sound* load_sound(const fs::path& file, bool keepPCM); /// @brief Create new sound from PCM data /// @param pcm PCM data /// @param keepPCM store PCM data in sound to make it accessible with Sound::getPCM /// @return new Sound instance - extern Sound* createSound(std::shared_ptr pcm, bool keepPCM); + extern Sound* create_sound(std::shared_ptr pcm, bool keepPCM); /// @brief Open new PCM stream from file /// @param file audio file path /// @throws std::runtime_error if I/O error ocurred or format is unknown /// @return new PCMStream instance - extern PCMStream* openPCMStream(const fs::path& file); + extern PCMStream* open_PCM_stream(const fs::path& file); /// @brief Open new audio stream from file /// @param file audio file path /// @param keepSource store PCMStream in stream to make it accessible with Stream::getSource /// @return new Stream instance - extern Stream* openStream(const fs::path& file, bool keepSource); + extern Stream* open_stream(const fs::path& file, bool keepSource); /// @brief Open new audio stream from source /// @param stream PCM data source /// @param keepSource store PCMStream in stream to make it accessible with Stream::getSource /// @return new Stream instance - extern Stream* openStream(std::shared_ptr stream, bool keepSource); + extern Stream* open_stream(std::shared_ptr stream, bool keepSource); /// @brief Configure 3D listener /// @param position listener position /// @param velocity listener velocity (used for Doppler effect) /// @param lookAt point the listener look at /// @param up camera up vector - extern void setListener( + extern void set_listener( glm::vec3 position, glm::vec3 velocity, glm::vec3 lookAt, @@ -351,6 +395,7 @@ namespace audio { /// @param loop loop sound /// @param priority sound priority /// (PRIORITY_LOW, PRIORITY_NORMAL, PRIORITY_HIGH) + /// @param channel channel index /// @return speaker id or 0 extern speakerid_t play( Sound* sound, @@ -359,7 +404,8 @@ namespace audio { float volume, float pitch, bool loop, - int priority + int priority, + int channel ); /// @brief Play stream @@ -369,6 +415,7 @@ namespace audio { /// @param volume stream volume [0.0-1.0] /// @param pitch stream pitch multiplier [0.0-...] /// @param loop loop stream + /// @param channel channel index /// @return speaker id or 0 extern speakerid_t play( std::shared_ptr stream, @@ -376,7 +423,8 @@ namespace audio { bool relative, float volume, float pitch, - bool loop + bool loop, + int channel ); /// @brief Play stream from file @@ -386,25 +434,48 @@ namespace audio { /// @param volume stream volume [0.0-1.0] /// @param pitch stream pitch multiplier [0.0-...] /// @param loop loop stream + /// @param channel channel index /// @return speaker id or 0 - /// @return - extern speakerid_t playStream( + extern speakerid_t play_stream( const fs::path& file, glm::vec3 position, bool relative, float volume, float pitch, - bool loop + bool loop, + int channel ); /// @brief Get speaker by id /// @param id speaker id /// @return speaker or nullptr extern Speaker* get(speakerid_t id); - + + /// @brief Create new channel. + /// All non-builtin channels will be destroyed on audio::reset() call + /// @param name channel name + /// @return new channel index + extern int create_channel(const std::string& name); + + /// @brief Get channel index by name + /// @param name channel name + /// @return channel index or -1 + extern int get_channel_index(const std::string& name); + + /// @brief Get channel by index. 0 - is master channel + /// @param name channel index + /// @return channel or nullptr + extern Channel* get_channel(int index); + + /// @brief Get volume of the master channel + extern float get_master_volume(); + /// @brief Update audio streams and sound instanced /// @param delta time elapsed since the last update (seconds) extern void update(double delta); + + /// @brief Stop all playing audio, destroy all non-builtin channels + extern void reset(); /// @brief Finalize audio system extern void close(); diff --git a/src/coders/ogg.cpp b/src/coders/ogg.cpp index 38e401b0..807401e8 100644 --- a/src/coders/ogg.cpp +++ b/src/coders/ogg.cpp @@ -91,7 +91,7 @@ public: long bytes = ov_read(&vf, buffer, bufferSize, 0, 2, true, &bitstream); if (bytes < 0) { std::cerr << "ogg::load_pcm: " << vorbis_error_message(bytes) << " " << bytes << std::endl; - return 0; + return PCMStream::ERROR; } return bytes; } diff --git a/src/frontend/screens.cpp b/src/frontend/screens.cpp index badf8bf9..be3535b8 100644 --- a/src/frontend/screens.cpp +++ b/src/frontend/screens.cpp @@ -153,7 +153,7 @@ void LevelScreen::update(float delta) { auto player = controller->getPlayer(); auto camera = player->camera; - audio::setListener( + audio::set_listener( camera->position, player->hitbox->velocity, camera->position+camera->dir,