diff --git a/CMakeLists.txt b/CMakeLists.txt index 80901d84e..d19efd5d1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -34,12 +34,16 @@ if(NOT DEFINED NOVELRT_TARGET) set(NOVELRT_TARGET "Win32" CACHE STRING "") elseif(APPLE) set(NOVELRT_TARGET "macOS" CACHE STRING "") + find_library(AVFOUNDATION_LIB AVFoundation) + find_library(FOUNDATION_LIB Foundation) + find_library(OBJC_LIB ObjC) elseif(UNIX) set(NOVELRT_TARGET "Linux" CACHE STRING "") else() set(NOVELRT_TARGET "Unknown" CACHE STRING "") endif() endif() +message("Using OS: ${NOVELRT_TARGET}") # # Prepend so that our FindVulkan gets picked up first when needed @@ -64,10 +68,11 @@ option(NOVELRT_BUILD_DEPS_WITH_MAX_CPU "Use all available CPU processing power w # set(NOVELRT_DOXYGEN_VERSION "1.8.17" CACHE STRING "Doxygen version") set(NOVELRT_FLAC_VERSION "1.3.4" CACHE STRING "FLAC version") +set(NOVELRT_FMT_VERSION "10.2.1" CACHE STRING "FMT version") set(NOVELRT_GLFW_VERSION "3.3.7" CACHE STRING "GLFW3 version") set(NOVELRT_GSL_VERSION "4.0.0" CACHE STRING "Microsoft.GSL version") set(NOVELRT_ONETBB_VERSION "2021.5.0" CACHE STRING "OneTBB version") -set(NOVELRT_OPENAL_VERSION "1.21.1" CACHE STRING "OpenAL version") +set(NOVELRT_OPENAL_VERSION "1.23.1" CACHE STRING "OpenAL version") set(NOVELRT_OGG_VERSION "1.3.5" CACHE STRING "Ogg version") set(NOVELRT_OPUS_VERSION "1.3.1" CACHE STRING "Opus version") set(NOVELRT_PNG_VERSION "1.6.35" CACHE STRING "PNG version") diff --git a/README.md b/README.md index 5a0cd9d06..8dcfff720 100644 --- a/README.md +++ b/README.md @@ -42,11 +42,12 @@ The dependencies that are handled by CMake that do not need to be manually insta - GLFW 3.3.7 - glm 0.9.9.9 - gtest/gmock 1.11.0 +- fmt 10.2.1 - libpng 1.6.35 - libsndfile 1.1.0 - Microsoft GSL 4.0.0 - OneTBB 2021.5.0 -- OpenAL 1.21.1 +- OpenAL 1.23.1 - spdlog 1.13.0 ### Build instructions @@ -144,7 +145,7 @@ cmake .. -DCMAKE_APPLE_SILICON_PROCESSOR="arm64" If Vulkan SDK is not installed in a system path and the `setup-env.sh` file did not properly add the required environment variables, you can specify the `VULKAN_SDK` environment variable to your local Vulkan SDK location as such: ``` -VULKAN_SDK=/Users/youruser/Vulkan SDK/1.3.231.1/macOS cmake .. +VULKAN_SDK=/Users/youruser/Vulkan SDK/1.3.231.1/macOS cmake .. ``` Please ensure that the path includes the macOS folder, otherwise finding the proper libraries will fail. diff --git a/audio/AVAudioEngine/AVAudioEngineAudioProvider.mm b/audio/AVAudioEngine/AVAudioEngineAudioProvider.mm new file mode 100644 index 000000000..b865810fc --- /dev/null +++ b/audio/AVAudioEngine/AVAudioEngineAudioProvider.mm @@ -0,0 +1,200 @@ +// Copyright © Matt Jones and Contributors. Licensed under the MIT Licence (MIT). See LICENCE.md in the repository root +// for more information. +#include +#include +#import +#import +#import +#import + +namespace NovelRT::Audio::AVAudioEngine +{ + AVAudioEngineAudioProvider::AVAudioEngineAudioProvider(): + _buffers(std::map()), + _sources(std::map()), + _sourceEQUnits(std::map()), + _sourceStates(std::map()), + _sourceContexts(std::map()), + _logger(spdlog::stdout_color_mt("AVAudioEngine")) + { + //Logger init + _logger->set_level(spdlog::level::debug); + + //Device and Context Init + @try + { + ::NSError* err; + _logger->debug("calling alloc and init"); + _impl = [::AVAudioEngine new]; + } + @catch(::NSException* ex) + { + std::string err = std::string([ex.reason UTF8String]); + _logger->error(err); + throw new Exceptions::InitialisationFailureException("Failed to initialise AVAudioEngine!", err); + } + } + + AVAudioEngineAudioProvider::~AVAudioEngineAudioProvider() + { + Dispose(); + [(::AVAudioEngine*)_impl stop]; + [(::AVAudioEngine*)_impl release]; + } + + void AVAudioEngineAudioProvider::Dispose() + { + for(auto [id, source] : _sources) + { + [(::AVAudioPlayerNode*)source stop]; + [(::AVAudioPlayerNode*)source release]; + } + _sources.clear(); + + for(auto [id, source] : _sourceEQUnits) + { + [(::AVAudioUnitEQ*)source release]; + } + _sourceEQUnits.clear(); + + for(auto [id, buffer] : _buffers) + { + [(::AVAudioPCMBuffer*)buffer release]; + } + _buffers.clear(); + + _sourceContexts.clear(); + } + + uint32_t AVAudioEngineAudioProvider::OpenSource(AudioSourceContext& context) + { + unused(context); + return _sourceCounter; + } + + uint32_t AVAudioEngineAudioProvider::OpenSourceInternal(AudioSourceContext& context, ::AVAudioPCMBuffer* buffer, ::AVAudioFormat* format) + { + uint32_t nextSource = ++_sourceCounter; + ::AVAudioPlayerNode* node = [::AVAudioPlayerNode new]; + [_impl attachNode:node]; + _sources.emplace(nextSource, node); + _sourceStates.emplace(nextSource, AudioSourceState::SOURCE_STOPPED); + _sourceContexts.emplace(nextSource, context); + + ::AVAudioUnitEQ* eq = [[::AVAudioUnitEQ alloc] initWithNumberOfBands: 1]; + [_impl attachNode:eq]; + _sourceEQUnits.emplace(nextSource, eq); + + _logger->debug("Connecting source {0} to EQ", nextSource); + [(::AVAudioEngine*)_impl connect:node to:eq format:nil]; + _logger->debug("Connecting source {0} EQ to mixer", nextSource); + [(::AVAudioEngine*)_impl connect:eq to:_impl.mainMixerNode format:nil]; + _buffers.emplace(nextSource, buffer); + + return nextSource; + } + + void AVAudioEngineAudioProvider::PlaySource(uint32_t sourceId) + { + if(!_impl.running) + { + _logger->debug("filling up gas tank..."); + [(::AVAudioEngine*)_impl prepare]; + ::NSError* err; + _logger->debug("starting engine.... VRROOOOOOOMMMMM...."); + [_impl startAndReturnError: &err]; + if(!_impl.running) + { + _logger->error("Could not start engine: {0}", std::string([err.localizedDescription UTF8String])); + } + } + + ::AVAudioPlayerNode* node = _sources.at(sourceId); + + if(!node.playing) + { + auto lambda = [this, sourceId = sourceId]() + { + this->UpdateSourceState(sourceId, AudioSourceState::SOURCE_STOPPED); + }; + if(_sourceContexts.at(sourceId).Loop) + { + _logger->debug("Looping source ID {0}", sourceId); + [node scheduleBuffer: _buffers.at(sourceId) atTime: nil options: AVAudioPlayerNodeBufferLoops completionHandler: nil]; + } + else + { + [node scheduleBuffer: _buffers.at(sourceId) completionHandler: lambda]; + } + + + + + [node play]; + } + + _sourceStates[sourceId] = AudioSourceState::SOURCE_PLAYING; + } + + void AVAudioEngineAudioProvider::UpdateSourceState(uint32_t sourceId, AudioSourceState state) + { + _sourceStates[sourceId] = state; + } + + void AVAudioEngineAudioProvider::StopSource(uint32_t sourceId) + { + ::AVAudioPlayerNode* node = _sources.at(sourceId); + if(node.playing) + { + [(::AVAudioPlayerNode*)node stop]; + } + _sourceStates[sourceId] = AudioSourceState::SOURCE_STOPPED; + } + + void AVAudioEngineAudioProvider::PauseSource(uint32_t sourceId) + { + ::AVAudioPlayerNode* node = _sources.at(sourceId); + if(node.playing) + { + [(::AVAudioPlayerNode*)node pause]; + } + _sourceStates[sourceId] = AudioSourceState::SOURCE_PAUSED; + } + + uint32_t AVAudioEngineAudioProvider::SubmitAudioBuffer(const NovelRT::Utilities::Misc::Span buffer, AudioSourceContext& context) + { + _logger->debug("Loading audio buffer - SampleRate: {0}, Channels: {1}", context.SampleRate, context.Channels); + + uint32_t frameCap = buffer.size() * sizeof(float); + ::AVAudioFormat* format = [[::AVAudioFormat alloc] initWithCommonFormat:AVAudioCommonFormat::AVAudioPCMFormatFloat32 sampleRate:44100 channels:context.Channels interleaved:true]; + ::AVAudioFormat* deformat = [[::AVAudioFormat alloc] initWithCommonFormat:AVAudioCommonFormat::AVAudioPCMFormatFloat32 sampleRate:44100 channels:context.Channels interleaved:false]; + AudioBufferList abl; + abl.mNumberBuffers = 1; + abl.mBuffers[0].mData = ((void *)new Byte[frameCap]); + abl.mBuffers[0].mNumberChannels = context.Channels; + abl.mBuffers[0].mDataByteSize = frameCap; + + std::memcpy((void*)abl.mBuffers[0].mData, reinterpret_cast(buffer.data()), frameCap); + + ::AVAudioPCMBuffer* pcmBuffer = [[::AVAudioPCMBuffer alloc] initWithPCMFormat:format bufferListNoCopy:&abl deallocator:NULL]; + + ::AVAudioConverter* convert = [[::AVAudioConverter alloc] initFromFormat:format toFormat:deformat]; + ::AVAudioPCMBuffer* deinterleavedBuffer = [[::AVAudioPCMBuffer alloc] initWithPCMFormat:deformat frameCapacity:pcmBuffer.frameCapacity]; + [convert convertToBuffer:deinterleavedBuffer fromBuffer:pcmBuffer error:nil]; + + return OpenSourceInternal(context, deinterleavedBuffer, format); + } + + void AVAudioEngineAudioProvider::SetSourceProperties(uint32_t sourceId, AudioSourceContext& context) + { + _sourceContexts[sourceId] = context; + + ::AVAudioUnitEQ* eq = _sourceEQUnits.at(sourceId); + eq.globalGain = context.Volume; + } + + AudioSourceState AVAudioEngineAudioProvider::GetSourceState(uint32_t sourceId) + { + return _sourceStates.at(sourceId); + } +} diff --git a/audio/AudioMixer.cpp b/audio/AudioMixer.cpp new file mode 100644 index 000000000..4c3a6d21c --- /dev/null +++ b/audio/AudioMixer.cpp @@ -0,0 +1,99 @@ +// Copyright © Matt Jones and Contributors. Licensed under the MIT Licence (MIT). See LICENCE.md in the repository root +// for more information. +#include + +//Conditional +#if defined(_WIN32) +#include +#elif __APPLE__ +#include +#else +#include +#endif +namespace NovelRT::Audio +{ + void AudioMixer::Initialise() + { + _sourceContextCache = std::map(); +#if defined(_WIN32) + _audioProvider = std::make_unique(); +#elif defined(__APPLE__) + _audioProvider = std::make_unique(); +#else + _audioProvider = std::make_unique(); +#endif + } + + uint32_t AudioMixer::SubmitAudioBuffer(const NovelRT::Utilities::Misc::Span buffer, int32_t channelCount, int32_t originalSampleRate) + { + auto newContext = AudioSourceContext{}; + newContext.Channels = channelCount; + newContext.SampleRate = originalSampleRate; + uint32_t sourceId = _audioProvider->SubmitAudioBuffer(buffer, newContext); + _sourceContextCache.emplace(sourceId, newContext); + return sourceId; + } + + AudioSourceState AudioMixer::GetSourceState(uint32_t id) + { + return _audioProvider->GetSourceState(id); + } + + void AudioMixer::PlaySource(uint32_t id) + { + _audioProvider->PlaySource(id); + } + + void AudioMixer::StopSource(uint32_t id) + { + _audioProvider->StopSource(id); + } + + void AudioMixer::PauseSource(uint32_t id) + { + _audioProvider->PauseSource(id); + } + + AudioSourceContext& AudioMixer::GetSourceContext(uint32_t id) + { + return _sourceContextCache.at(id); + } + + void AudioMixer::SetSourceContext(uint32_t id, AudioSourceContext& context) + { + _sourceContextCache.erase(id); + _sourceContextCache.emplace(id, context); + _audioProvider->SetSourceProperties(id, context); + } + + void AudioMixer::SetSourceVolume(uint32_t id, float volume) + { + auto& context = _sourceContextCache.at(id); + context.Volume = volume; + _audioProvider->SetSourceProperties(id, context); + } + + void AudioMixer::SetSourcePitch(uint32_t id, float pitch) + { + auto& context = _sourceContextCache.at(id); + context.Pitch = pitch; + _audioProvider->SetSourceProperties(id, context); + } + + void AudioMixer::SetSourceLoop(uint32_t id, bool isLooping) + { + auto& context = _sourceContextCache.at(id); + context.Loop = isLooping; + _audioProvider->SetSourceProperties(id, context); + } + + void AudioMixer::TearDown() + { + _audioProvider.reset(); + } + + AudioMixer::~AudioMixer() + { + TearDown(); + } +} diff --git a/audio/AudioService.cpp b/audio/AudioService.cpp deleted file mode 100644 index 9c02d0526..000000000 --- a/audio/AudioService.cpp +++ /dev/null @@ -1,484 +0,0 @@ -// Copyright © Matt Jones and Contributors. Licensed under the MIT Licence (MIT). See LICENCE.md in the repository root -// for more information. - -#include - -namespace NovelRT::Audio -{ - AudioService::AudioService() - : _device(Utilities::Lazy>( - std::function([this] { - auto device = alcOpenDevice((_deviceName.empty()) ? nullptr : _deviceName.c_str()); - if (!device) - { - std::string error = GetALError(); - _logger.logError("OpenAL device creation failed! {}", error); - throw Exceptions::InitialisationFailureException( - "OpenAL failed to create an audio device! Aborting...", error); - } - return device; - }), - [](auto x) { alcCloseDevice(x); })), - _context(Utilities::Lazy>( - std::function([this] { - auto context = alcCreateContext(_device.getActual(), nullptr); - alcMakeContextCurrent(context); - isInitialised = true; - _deviceName = alcGetString(_device.getActual(), ALC_DEVICE_SPECIFIER); - _logger.logInfo("OpenAL Initialized on device: {}", _deviceName); - return context; - }), - [](auto x) { - alcMakeContextCurrent(nullptr); - alcDestroyContext(x); - })), - _logger(Utilities::Misc::CONSOLE_LOG_AUDIO), - _manualLoad(false), - _musicSource(), - _musicSourceState(0), - _musicStopRequested(false), - _musicLoopAmount(0), - _soundLoopAmount(0), - _soundSourceState(0), - _soundStorage(), - _bufferStorage(), - isInitialised(false) - { - } - - bool AudioService::InitializeAudio() - { - _device.getActual(); - _context.getActual(); - alGenSources(1, &_musicSource); - alSourcef(_musicSource, AL_GAIN, 0.75f); - alSourcef(_musicSource, AL_PITCH, _pitch); - - return isInitialised; - } - - ALuint AudioService::BufferAudioFrameData(NovelRT::Utilities::Misc::Span audioFrameData, - int32_t channelCount, - int32_t sampleRate) - { - ALuint buffer; - alGenBuffers(1, &buffer); - - if (buffer == _noBuffer) - { - return _noBuffer; - } - - alBufferData(buffer, channelCount == 1 ? AL_FORMAT_MONO16 : AL_FORMAT_STEREO16, audioFrameData.data(), - static_cast(audioFrameData.size() * sizeof(int16_t)), sampleRate); - return buffer; - } - - /*Note: Due to the current design, this will currently block the thread it is being called on. - If it is called on the main thread, please do all loading of audio files at the start of - the engine (after NovelRunner has been created). - */ - std::vector::iterator AudioService::LoadMusic(NovelRT::Utilities::Misc::Span audioFrameData, - int32_t channelCount, - int32_t sampleRate) - { - if (!isInitialised) - { - _logger.logError("Cannot load new audio into memory while the service is uninitialised! Aborting..."); - throw NovelRT::Exceptions::NotInitialisedException( - "AudioService::load", "You cannot load new audio when the service is not initialised."); - } - - auto newBuffer = BufferAudioFrameData(audioFrameData, channelCount, sampleRate); - - // Sorry Matt, nullptr types are incompatible to ALuint according to VS. - if (newBuffer == _noBuffer) - { - _logger.logWarning( - "Could not buffer provided audio data. Please verify the resource was loaded correctly."); - return _music.end(); - } - - auto it = std::find(_music.begin(), _music.end(), newBuffer); - if (it != _music.end()) - { - alDeleteBuffers(1, &newBuffer); - return it; - } - else - { - _music.push_back(newBuffer); - _bufferStorage.push_back(newBuffer); - it = std::find(_music.begin(), _music.end(), newBuffer); - return it; - } - } - - void AudioService::SetSoundVolume(ALuint source, float value) - { - if (!isInitialised) - { - _logger.logError( - "Cannot change the volume of a nonexistent sound! the service is uninitialised! Aborting..."); - throw NovelRT::Exceptions::NotInitialisedException( - "AudioService::SetSoundVolume", - "You cannot modify a sound source when the AudioService is not initialised."); - } - - if (value > 1.0f) - { - alSourcef(source, AL_GAIN, 1.0f); - } - else if (value <= 0.0f) - { - alSourcef(source, AL_GAIN, 0.0f); - } - else - { - alSourcef(source, AL_GAIN, value); - } - } - - // Switched to using two floats - for some reason VS complained when trying to use Maths::GeoVector2 here... - // This also has no effect if the buffer is more than one channel (not Mono) - void AudioService::SetSoundPosition(ALuint source, float posX, float posY) - { - if (!isInitialised) - { - _logger.logError( - "Cannot move audio position on a nonexistent sound! The service is uninitialised! Aborting..."); - throw NovelRT::Exceptions::NotInitialisedException( - "AudioService::StopSound", "You cannot stop a sound when the AudioService is not initialised."); - } - - alSource3f(source, AL_POSITION, posX, posY, 0.0f); - } - - void AudioService::ResumeMusic() - { - if (!isInitialised) - { - _logger.logError( - "Cannot change the volume of a nonexistent sound! The service is uninitialised! Aborting..."); - throw NovelRT::Exceptions::NotInitialisedException( - "AudioService::SetSoundVolume", - "You cannot modify a sound source when the AudioService is not initialised."); - } - - alSourcePlay(_musicSource); - _musicStopRequested = false; - } - - void AudioService::PlayMusic(std::vector::iterator handle, int32_t loops) - { - if (!isInitialised) - { - _logger.logError("Cannot play audio while the service is uninitialised! Aborting..."); - throw NovelRT::Exceptions::NotInitialisedException( - "AudioService::playMusic", "You cannot play a sound when the AudioService is not initialised."); - } - - if (handle == _music.end()) - { - _logger.logWarning("Cannot play the requested sound - it may have been deleted or not loaded properly."); - return; - } - - alGetSourcei(_musicSource, AL_SOURCE_STATE, &_musicSourceState); - if (_musicSourceState == AL_PLAYING) - { - alSourceStop(_musicSource); - alGetSourcei(_musicSource, AL_SOURCE_STATE, &_musicSourceState); - } - alSourcei(_musicSource, AL_BUFFER, static_cast(*handle)); - if (loops == -1 || loops > 0) - { - _musicLoopAmount = loops; - alSourcei(_musicSource, AL_LOOPING, AL_TRUE); - } - else - { - alSourcei(_musicSource, AL_LOOPING, AL_FALSE); - } - alSourcePlay(_musicSource); - _musicStopRequested = false; - } - - void AudioService::PauseMusic() - { - if (!isInitialised) - { - _logger.logError("Cannot pause audio while the service is uninitialised! Aborting..."); - throw NovelRT::Exceptions::NotInitialisedException( - "AudioService::PauseMusic", "You cannot pause a sound when the AudioService is not initialised."); - } - - _musicStopRequested = true; - alSourcePause(_musicSource); - } - - void AudioService::StopMusic() - { - if (!isInitialised) - { - _logger.logError("Cannot stop audio while the service is uninitialised! Aborting..."); - throw NovelRT::Exceptions::NotInitialisedException( - "AudioService::StopMusic", "You cannot stop a sound when the AudioService is not initialised."); - } - - _musicStopRequested = true; - alSourceStop(_musicSource); - } - - void AudioService::SetMusicVolume(float value) - { - if (!isInitialised) - { - _logger.logError("Cannot modify audio while the service is uninitialised! Aborting..."); - throw NovelRT::Exceptions::NotInitialisedException( - "AudioService::SetMusicVolume", "You cannot modify a sound when the AudioService is not initialised."); - } - - if (value > 1.0f) - { - alSourcef(_musicSource, AL_GAIN, 1.0f); - } - else if (value <= 0.0f) - { - alSourcef(_musicSource, AL_GAIN, 0.0f); - } - else - { - alSourcef(_musicSource, AL_GAIN, value); - } - } - - void AudioService::CheckSources() - { - // Changing the init check as I don't want this to kill the Runner. - if (isInitialised) - { - - int32_t musicLoop = 0; - int32_t soundLoop = 0; - for (auto sound : _soundStorage) - { - alGetSourcei(sound, AL_LOOPING, &soundLoop); - if (soundLoop == AL_TRUE) - { - alGetSourcei(sound, AL_SOURCE_STATE, &_soundSourceState); - if (_soundLoopAmount > 0) - { - _soundLoopAmount--; - if (_soundLoopAmount == 0) - { - alSourcei(sound, AL_LOOPING, AL_FALSE); - } - } - } - } - - alGetSourcei(_musicSource, AL_LOOPING, &musicLoop); - - if (musicLoop == AL_TRUE) - { - alGetSourcei(_musicSource, AL_SOURCE_STATE, &_musicSourceState); - if (_musicLoopAmount > 0 && !_musicStopRequested) - { - _musicLoopAmount--; - if (_musicLoopAmount == 0) - { - alSourcei(_musicSource, AL_LOOPING, AL_FALSE); - } - } - } - } - } - - std::string AudioService::GetALError() - { - auto err = alGetError(); - switch (err) - { - case AL_INVALID_NAME: - { - return std::string("A bad ID or name was passed to the OpenAL function."); - } - case AL_INVALID_ENUM: - { - return std::string("An invalid enum was passed to an OpenAL function."); - } - case AL_INVALID_VALUE: - { - return std::string("An invalid value was passed to an OpenAL function."); - } - case AL_INVALID_OPERATION: - { - return std::string("The requested operation is not valid."); - } - case AL_OUT_OF_MEMORY: - { - return std::string("The requested operation resulted in OpenAL running out of memory."); - } - default: - { - return std::string(""); - } - } - } - - ALuint AudioService::LoadSound(NovelRT::Utilities::Misc::Span audioFrameData, - int32_t channelCount, - int32_t sampleRate) - { - if (!isInitialised) - { - _logger.logError("Cannot load new audio into memory while the service is yolo uninitialised! Aborting..."); - throw NovelRT::Exceptions::NotInitialisedException( - "AudioService::load", "You cannot load new audio when the service is not initialised."); - } - auto newBuffer = BufferAudioFrameData(audioFrameData, channelCount, sampleRate); - - if (newBuffer == _noBuffer) - { - _logger.logWarning("Cannot play the requested sound - it may have been deleted or not loaded properly."); - return _noBuffer; - } - - _manualLoad = true; - ALuint newSource = _noBuffer; - alGenSources(1, &newSource); - alSourcef(newSource, AL_GAIN, 0.75f); - alSourcef(newSource, AL_PITCH, _pitch); - alSourcei(newSource, AL_BUFFER, static_cast(newBuffer)); - - _soundStorage.push_back(newSource); - _bufferStorage.push_back(newBuffer); - - return newSource; - } - - void AudioService::Unload(ALuint source) - { - alSourcei(source, AL_BUFFER, 0); - } - - void AudioService::PlaySound(ALuint handle, int32_t loops) - { - if (!isInitialised) - { - _logger.logError("Cannot play audio while the service is uninitialised! Aborting..."); - throw NovelRT::Exceptions::NotInitialisedException( - "AudioService::playMusic", "You cannot play a sound when the AudioService is not initialised."); - } - - if (handle == _noBuffer) - { - _logger.logError("Cannot play the requested sound - it may have been deleted or not loaded properly."); - return; - } - - if (loops == -1 || loops > 0) - { - _soundLoopAmount = loops; - alSourcei(handle, AL_LOOPING, AL_TRUE); - } - else - { - alSourcei(handle, AL_LOOPING, AL_FALSE); - } - alSourcePlay(handle); - } - - void AudioService::StopSound(ALuint handle) - { - alSourceStop(handle); - } - - bool AudioService::IsLoaded(std::vector::iterator handle) - { - return (handle != _music.end()); - } - - bool AudioService::IsLoaded(ALuint handle) - { - return (handle != _noBuffer); - } - - void AudioService::TearDown() - { - if (!_context.isCreated()) - return; - - if (_manualLoad) - { - for (auto source : _soundStorage) - { - alDeleteSources(1, &source); - } - _soundStorage.clear(); - } - - alDeleteSources(1, &_musicSource); - if (!_music.empty()) - { - _music.clear(); - } - - for (auto buffer : _bufferStorage) - { - alDeleteBuffers(1, &buffer); - } - - // were deleting the objects explicitly here to ensure they're always deleted in the right order, lest you - // summon the kraken. - Ruby - _context.reset(); - _device.reset(); - } - - AudioService::~AudioService() - { - TearDown(); - } - - bool AudioService::IsMusicPlaying() - { - alGetSourcei(_musicSource, AL_SOURCE_STATE, &_musicSourceState); - return (_musicSourceState == AL_PLAYING || _musicSourceState == AL_PAUSED); - } - - bool AudioService::IsSoundPlaying(ALuint handle) - { - alGetSourcei(handle, AL_SOURCE_STATE, &_soundSourceState); - return (_soundSourceState == AL_PLAYING || _soundSourceState == AL_PAUSED); - } - - float AudioService::GetMusicVolume() - { - if (!isInitialised) - { - _logger.logError("Cannot modify audio while the service is uninitialised! Aborting..."); - throw NovelRT::Exceptions::NotInitialisedException( - "AudioService::SetMusicVolume", "You cannot modify a sound when the AudioService is not initialised."); - } - - float result = 0.0f; - alGetSourcef(_musicSource, AL_GAIN, &result); - return result; - } - - float AudioService::GetSoundVolume(ALuint handle) - { - if (!isInitialised) - { - _logger.logError("Cannot modify audio while the service is uninitialised! Aborting..."); - throw NovelRT::Exceptions::NotInitialisedException( - "AudioService::SetMusicVolume", "You cannot modify a sound when the AudioService is not initialised."); - } - - float result = 0.0f; - alGetSourcef(handle, AL_GAIN, &result); - return result; - } - -} // namespace NovelRT::Audio diff --git a/audio/CMakeLists.txt b/audio/CMakeLists.txt index 8fdb940a5..7710271bc 100644 --- a/audio/CMakeLists.txt +++ b/audio/CMakeLists.txt @@ -1,5 +1,8 @@ add_library(NovelRT-Audio STATIC - AudioService.cpp + AudioMixer.cpp + $<$>:XAudio2/XAudio2AudioProvider.cpp> + $<$>:AVAudioEngine/AVAudioEngineAudioProvider.mm> + $<$>:OpenAL/OpenALAudioProvider.cpp> ) target_sources(NovelRT-Audio @@ -8,8 +11,19 @@ target_sources(NovelRT-Audio TYPE HEADERS BASE_DIRS include FILES - include/NovelRT/Audio/Audio.hpp - include/NovelRT/Audio/AudioService.hpp + include/NovelRT/Audio/AudioMixer.hpp + include/NovelRT/Audio/IAudioProvider.hpp +) + +target_sources(NovelRT-Audio + PRIVATE + FILE_SET private_headers + TYPE HEADERS + BASE_DIRS include + FILES + $<$>:include/NovelRT/Audio/XAudio2/XAudio2AudioProvider.hpp> + $<$>:include/NovelRT/Audio/OpenAL/OpenALAudioProvider.hpp> + $<$>:include/NovelRT/Audio/AVAudioEngine/AVAudioEngineAudioProvider.hpp> ) set_target_properties(NovelRT-Audio @@ -47,9 +61,16 @@ target_include_directories(NovelRT-Audio PRIVATE "$>:OpenAL> ) +if(${NOVELRT_TARGET} STREQUAL "macOS") + target_link_libraries(NovelRT-Audio + PRIVATE + "-framework AVFoundation" + ) +endif() + install( TARGETS NovelRT-Audio EXPORT NovelRTConfig diff --git a/audio/OpenAL/OpenALAudioProvider.cpp b/audio/OpenAL/OpenALAudioProvider.cpp new file mode 100644 index 000000000..a1f12d464 --- /dev/null +++ b/audio/OpenAL/OpenALAudioProvider.cpp @@ -0,0 +1,245 @@ +// Copyright © Matt Jones and Contributors. Licensed under the MIT Licence (MIT). See LICENCE.md in the repository root +// for more information. +#include +#include +#include +#include + +namespace NovelRT::Audio::OpenAL +{ + typedef void (ALC_APIENTRY*LoggingCallback)(void*, char, const char*, int) noexcept; + typedef void (ALC_APIENTRY*CallbackProvider)(LoggingCallback, void*) noexcept; + + OpenALAudioProvider::OpenALAudioProvider(): + _sources(std::vector()), + _buffers(std::vector()), + _logger(spdlog::stdout_color_mt("OpenAL")) + { + //Logger init + _logger->set_level(spdlog::level::debug); + CallbackProvider setLogCallback = reinterpret_cast(alcGetProcAddress(nullptr, "alsoft_set_log_callback")); + if (setLogCallback != nullptr) + { + setLogCallback(static_cast([](void* ptr, char l, const char* m, int) noexcept + { + auto provider = reinterpret_cast(ptr); + provider->LogOpenALMessages(l, m); + }), this); + } + + + //Device and Context Init + _device = alcOpenDevice(nullptr); + if(!_device) + { + std::string error = GetALError(); + throw Exceptions::InitialisationFailureException( + "OpenAL failed to create an audio device!", error); + } + _context = alcCreateContext(_device, nullptr); + if(!_context) + { + std::string error = GetALError(); + throw Exceptions::InitialisationFailureException( + "OpenAL failed to create and attach a proper context!", error); + } + alcMakeContextCurrent(_context); + } + + OpenALAudioProvider::~OpenALAudioProvider() + { + Dispose(); + } + + void OpenALAudioProvider::Dispose() + { + alGetError(); + alSourceStopv(static_cast(_sources.size()), reinterpret_cast(_sources.data())); + GetALError(); + alDeleteSources(static_cast(_sources.size()), reinterpret_cast(_sources.data())); + GetALError(); + _sources.clear(); + alDeleteBuffers(static_cast(_buffers.size()), reinterpret_cast(_buffers.data())); + GetALError(); + _buffers.clear(); + alcMakeContextCurrent(NULL); + alcDestroyContext(_context); + alcCloseDevice(_device); + } + + uint32_t OpenALAudioProvider::OpenSource(AudioSourceContext& context) + { + uint32_t source = 0; + alGetError(); + alGenSources(1, &source); + GetALError(); + alSourcef(source, AL_GAIN, context.Volume); + GetALError(); + alSourcef(source, AL_PITCH, context.Pitch); + GetALError(); + alSourcei(source, AL_LOOPING, static_cast(context.Loop)); + GetALError(); + _sources.emplace_back(static_cast(source)); + return source; + } + + // void OpenALAudioProvider::CloseSource() + // { + // alDeleteSources(1, &_outputSource); + // } + + void OpenALAudioProvider::PlaySource(uint32_t sourceId) + { + alGetError(); + alSourcePlay(sourceId); + GetALError(); + } + + void OpenALAudioProvider::StopSource(uint32_t sourceId) + { + alGetError(); + alSourceStop(sourceId); + GetALError(); + } + + void OpenALAudioProvider::PauseSource(uint32_t sourceId) + { + alGetError(); + alSourcePause(sourceId); + GetALError(); + } + + std::string OpenALAudioProvider::GetALError() + { + auto err = alGetError(); + switch (err) + { + case AL_INVALID_NAME: + { + _logger->error("A bad ID or name was passed to the OpenAL function."); + return std::string("A bad ID or name was passed to the OpenAL function."); + } + case AL_INVALID_ENUM: + { + _logger->error("An invalid enum was passed to an OpenAL function."); + return std::string("An invalid enum was passed to an OpenAL function."); + } + case AL_INVALID_VALUE: + { + _logger->error("An invalid value was passed to an OpenAL function."); + return std::string("An invalid value was passed to an OpenAL function."); + } + case AL_INVALID_OPERATION: + { + _logger->error("The requested operation is not valid."); + return std::string("The requested operation is not valid."); + } + case AL_OUT_OF_MEMORY: + { + _logger->error("The requested operation resulted in OpenAL running out of memory."); + return std::string("The requested operation resulted in OpenAL running out of memory."); + } + case AL_NO_ERROR: + { + return std::string(); + } + default: + { + _logger->error("Unknown OpenAL Error - Code: {err}", err); + return std::string("Unknown OpenAL Error - Code: " + std::to_string(err)); + } + } + } + + uint32_t OpenALAudioProvider::SubmitAudioBuffer(const NovelRT::Utilities::Misc::Span buffer, AudioSourceContext& context) + { + ALuint alBuffer; + alGetError(); + alGenBuffers(1, &alBuffer); + GetALError(); + alBufferData(alBuffer, DetermineChannelFormat(context.Channels), buffer.data(), static_cast(buffer.size() * sizeof(float)), context.SampleRate); + GetALError(); + _buffers.emplace_back(static_cast(alBuffer)); + uint32_t sourceId = OpenSource(context); + alSourcei(sourceId, AL_BUFFER, alBuffer); + GetALError(); + return sourceId; + } + + void OpenALAudioProvider::SetSourceProperties(uint32_t sourceId, AudioSourceContext& context) + { + alGetError(); + alSourcef(sourceId, AL_GAIN, context.Volume); + GetALError(); + alSourcef(sourceId, AL_PITCH, context.Pitch); + GetALError(); + alSourcei(sourceId, AL_LOOPING, static_cast(context.Loop)); + GetALError(); + } + + AudioSourceState OpenALAudioProvider::ConvertToAudioSourceState(ALenum oALSourceState) + { + switch(oALSourceState) + { + case AL_PLAYING: + { + return AudioSourceState::SOURCE_PLAYING; + } + case AL_PAUSED: + { + return AudioSourceState::SOURCE_PAUSED; + } + case AL_STOPPED: + case AL_INITIAL: + default: + { + return AudioSourceState::SOURCE_STOPPED; + } + } + } + + AudioSourceState OpenALAudioProvider::GetSourceState(uint32_t sourceId) + { + ALenum state = 0x0; + alGetError(); + alGetSourcei(sourceId, AL_SOURCE_STATE, &state); + GetALError(); + return ConvertToAudioSourceState(state); + } + + ALenum OpenALAudioProvider::DetermineChannelFormat(int32_t numberOfChannels) + { + switch(numberOfChannels) + { + case 1: + return AL_FORMAT_MONO_FLOAT32; + case 5: + return AL_FORMAT_51CHN32; + case 7: + return AL_FORMAT_71CHN32; + case 2: + default: + return AL_FORMAT_STEREO_FLOAT32; + } + } + + void OpenALAudioProvider::LogOpenALMessages(char level, const char* message) + { + switch(level) + { + case 'W': + { + _logger->warn(message); + } + case 'E': + { + _logger->error(message); + } + case 'I': + default: + { + _logger->debug(message); + } + } + } +} diff --git a/audio/XAudio2/XAudio2AudioProvider.cpp b/audio/XAudio2/XAudio2AudioProvider.cpp new file mode 100644 index 000000000..a56570e6c --- /dev/null +++ b/audio/XAudio2/XAudio2AudioProvider.cpp @@ -0,0 +1,195 @@ +// Copyright © Matt Jones and Contributors. Licensed under the MIT Licence (MIT). See LICENCE.md in the repository root +// for more information. +#include +#include +#include + +namespace NovelRT::Audio::XAudio2 +{ + + XAudio2AudioProvider::XAudio2AudioProvider(): + _device(nullptr), + _masterVoice(nullptr), + _sources(std::map()), + _sourceCounter(0), + _logger(spdlog::stdout_color_mt("XAudio2")), + _buffers(std::map()), + _bufferCounter(0) + { + //Logger init + _logger->set_level(spdlog::level::debug); + + //Device and Context Init + _hr = CoInitializeEx( nullptr, COINIT_MULTITHREADED ); + if (FAILED(_hr)) + { + throw new Exceptions::InitialisationFailureException("Failed to initialise COM!", _hr); + } + + if (FAILED(_hr = XAudio2Create( &_device, 0, XAUDIO2_DEFAULT_PROCESSOR))) + { + throw new Exceptions::InitialisationFailureException("Failed to create an instance of the XAudio2 engine!", _hr); + } + + if (FAILED(_hr = _device->CreateMasteringVoice(&_masterVoice, XAUDIO2_DEFAULT_CHANNELS, XAUDIO2_DEFAULT_SAMPLERATE))) + { + throw new Exceptions::InitialisationFailureException("Failed to create an output voice!", _hr); + } + + _masterVoice->SetVolume(1.0f); + + XAUDIO2_DEBUG_CONFIGURATION debug{}; + debug.TraceMask = XAUDIO2_LOG_ERRORS; + _device->SetDebugConfiguration(&debug); + + _logger->info("XAudio2 initialised."); + } + + XAudio2AudioProvider::~XAudio2AudioProvider() + { + Dispose(); + } + + void XAudio2AudioProvider::Dispose() + { + for(auto [id, source] : _sources) + { + source->FlushSourceBuffers(); + source->DestroyVoice(); + } + _sources.clear(); + _buffers.clear(); + _masterVoice->DestroyVoice(); + _device->StopEngine(); + } + + uint32_t XAudio2AudioProvider::OpenSource(AudioSourceContext& context) + { + uint32_t nextSource = ++_sourceCounter; + WAVEFORMATEX waveFormatContainer{}; + waveFormatContainer.wFormatTag = WAVE_FORMAT_IEEE_FLOAT; + waveFormatContainer.cbSize = 0; + waveFormatContainer.nChannels = 2; + waveFormatContainer.wBitsPerSample = 32; + waveFormatContainer.nBlockAlign = 8; + waveFormatContainer.nSamplesPerSec = static_cast(context.SampleRate); + waveFormatContainer.nAvgBytesPerSec = static_cast(waveFormatContainer.nSamplesPerSec * waveFormatContainer.nBlockAlign); + + + IXAudio2SourceVoice* newVoice; + if(FAILED(_hr = _device->CreateSourceVoice(&newVoice, &waveFormatContainer))) + { + _logger->error("Could not create source voice - Code: {hr}", _hr); + } + newVoice->SetVolume(1.0f); + + _sources.emplace(nextSource, newVoice); + return nextSource; + } + + void XAudio2AudioProvider::PlaySource(uint32_t sourceId) + { + if(FAILED(_hr = _sources.at(sourceId)->Start(0))) + { + _logger->error("Error when attempting to play source {id} - Code: {hr}", sourceId, _hr); + } + } + + void XAudio2AudioProvider::StopSource(uint32_t sourceId) + { + if(FAILED(_hr = _sources.at(sourceId)->Stop(0))) + { + _logger->error("Error when stopping source {id} - Code: {hr}", sourceId, _hr); + } + } + + void XAudio2AudioProvider::PauseSource(uint32_t sourceId) + { + StopSource(sourceId); + } + + uint32_t XAudio2AudioProvider::SubmitAudioBuffer(const NovelRT::Utilities::Misc::Span buffer, AudioSourceContext& context) + { + //uint32_t nextBuffer = ++_bufferCounter; + XAUDIO2_BUFFER xABuffer = + { + XAUDIO2_END_OF_STREAM, // Flags + static_cast(buffer.size()*sizeof(float)), // AudioBytes + static_cast(new byte[buffer.size()*sizeof(float)]) //new buffer to copy float* to + }; + + //Because XAudio2 expects a BYTE*, we'll have to cast it up and copy the data from the provided span :( + std::memcpy((void*)(xABuffer.pAudioData), + reinterpret_cast(buffer.data()), buffer.size()*sizeof(float)); + if(context.Loop) + { + xABuffer.LoopCount = XAUDIO2_LOOP_INFINITE; + } + + uint32_t sourceId = OpenSource(context); + if(FAILED(_hr = _sources.at(sourceId)->SubmitSourceBuffer(&xABuffer))) + { + _logger->error("Failed to submit buffer to source {sourceId} - Code: {hr}", sourceId, std::to_string(_hr)); + } + + _buffers.emplace(sourceId, xABuffer); + return sourceId; + } + + void XAudio2AudioProvider::SetSourceProperties(uint32_t sourceId, AudioSourceContext& context) + { + //volume + if(FAILED(_hr = _sources.at(sourceId)->SetVolume(context.Volume))) + { + _logger->error("Error when setting volume for source {id} - Code: {hr}", sourceId, _hr); + } + //pitch + if(FAILED(_hr = _sources.at(sourceId)->SetFrequencyRatio(context.Pitch))) + { + _logger->error("Error when setting pitch for source {id} - Code: {hr}", sourceId, _hr); + } + } + + AudioSourceState XAudio2AudioProvider::GetSourceState(uint32_t sourceId) + { + XAUDIO2_VOICE_STATE voiceState; + _sources.at(sourceId)->GetState(&voiceState, 0); + AudioSourceState state = ConvertToAudioSourceState(voiceState); + if(state == AudioSourceState::SOURCE_STOPPED) + { + if(voiceState.pCurrentBufferContext == NULL && voiceState.BuffersQueued == 0 && voiceState.SamplesPlayed == 0) + { + auto source = _sources.at(sourceId); + source->Stop(0); + source->SubmitSourceBuffer(&_buffers.at(sourceId)); + } + } + return state; + } + + AudioSourceState XAudio2AudioProvider::ConvertToAudioSourceState(XAUDIO2_VOICE_STATE sourceState) + { + if(sourceState.BuffersQueued == 0) + { + if(sourceState.SamplesPlayed > 0 || (sourceState.pCurrentBufferContext == NULL && sourceState.SamplesPlayed == 0)) + { + return AudioSourceState::SOURCE_STOPPED; + } + else + { + return AudioSourceState::SOURCE_PLAYING; + } + } + else + { + if(sourceState.SamplesPlayed > 0) + { + return AudioSourceState::SOURCE_PLAYING; + } + else + { + return AudioSourceState::SOURCE_STOPPED; + } + } + } +} diff --git a/audio/include/NovelRT/Audio/AVAudioEngine/AVAudioEngineAudioProvider.hpp b/audio/include/NovelRT/Audio/AVAudioEngine/AVAudioEngineAudioProvider.hpp new file mode 100644 index 000000000..034ff85eb --- /dev/null +++ b/audio/include/NovelRT/Audio/AVAudioEngine/AVAudioEngineAudioProvider.hpp @@ -0,0 +1,61 @@ +// Copyright © Matt Jones and Contributors. Licensed under the MIT Licence (MIT). See LICENCE.md in the repository root +// for more information. +#pragma once + +#include +#include +#include +#include + + +#ifdef __OBJC__ + @class AVAudioEngine; + @class AVAudioPlayerNode; + @class AVAudioPCMBuffer; + @class AVAudioUnitEQ; + @class AVAudioFormat; + ::AVAudioEngine* _impl; // represents Obj-C object + ::AVAudioFormat* _mixerFormat; +#else + typedef struct objc_object AVAudioEngine; + typedef struct objc_object AVAudioPlayerNode; + typedef struct objc_object AVAudioPCMBuffer; + typedef struct objc_object AVAudioUnitEQ; + typedef struct objc_object AVAudioFormat; +#endif + + + +namespace NovelRT::Audio::AVAudioEngine +{ + class AVAudioEngineAudioProvider : public IAudioProvider + { + private: + + std::map _sources; + std::map _sourceEQUnits; + std::map _buffers; + std::map _sourceStates; + std::map _sourceContexts; + std::shared_ptr _logger; + uint32_t _sourceCounter = 0; + + uint32_t OpenSourceInternal(AudioSourceContext& context, ::AVAudioPCMBuffer* buffer, ::AVAudioFormat* format); + + protected: + void Dispose() final; + uint32_t OpenSource(AudioSourceContext& context) final; + void UpdateSourceState(uint32_t sourceId, AudioSourceState state); + + public: + AVAudioEngineAudioProvider(); + void PlaySource(uint32_t sourceId) final; + void StopSource(uint32_t sourceId) final; + void PauseSource(uint32_t sourceId) final; + void SetSourceProperties(uint32_t sourceId, AudioSourceContext& context) final; + uint32_t SubmitAudioBuffer(const NovelRT::Utilities::Misc::Span buffer, AudioSourceContext& context) final; + AudioSourceState GetSourceState(uint32_t id) final; + + ~AVAudioEngineAudioProvider() final; + }; +} diff --git a/audio/include/NovelRT/Audio/Audio.hpp b/audio/include/NovelRT/Audio/Audio.hpp deleted file mode 100644 index 79653a3aa..000000000 --- a/audio/include/NovelRT/Audio/Audio.hpp +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright © Matt Jones and Contributors. Licensed under the MIT Licence (MIT). See LICENCE.md in the repository root -// for more information. - -#ifndef NOVELRT_AUDIO_H -#define NOVELRT_AUDIO_H - -// Dependencies for Audio -#include -#include -#include -#include -#include -#include -#include -#include -#include - -/*** - * @brief Contains audio features, such as playing audio, and managing audio resources. - */ -namespace NovelRT::Audio -{ - typedef std::vector SoundBank; - typedef std::vector MusicBank; - typedef class AudioService AudioService; -} - -// Audio Types -#include - -#endif // NOVELRT_AUDIO_H diff --git a/audio/include/NovelRT/Audio/AudioMixer.hpp b/audio/include/NovelRT/Audio/AudioMixer.hpp new file mode 100644 index 000000000..ed569a948 --- /dev/null +++ b/audio/include/NovelRT/Audio/AudioMixer.hpp @@ -0,0 +1,37 @@ +// Copyright © Matt Jones and Contributors. Licensed under the MIT Licence (MIT). See LICENCE.md in the repository root +// for more information. +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +namespace NovelRT::Audio +{ + class AudioMixer + { + private: + std::unique_ptr _audioProvider; + std::map _sourceContextCache; + void TearDown(); + + public: + void Initialise(); + uint32_t SubmitAudioBuffer(const NovelRT::Utilities::Misc::Span buffer, int32_t channelCount, int32_t originalSampleRate); + void PlaySource(uint32_t id); + void StopSource(uint32_t id); + void PauseSource(uint32_t id); + AudioSourceContext& GetSourceContext(uint32_t id); + void SetSourceContext(uint32_t id, AudioSourceContext& context); + void SetSourceVolume(uint32_t id, float volume); + void SetSourcePitch(uint32_t id, float pitch); + void SetSourceLoop(uint32_t id, bool isLooping); + AudioSourceState GetSourceState(uint32_t id); + + ~AudioMixer(); + }; +} diff --git a/audio/include/NovelRT/Audio/AudioService.hpp b/audio/include/NovelRT/Audio/AudioService.hpp deleted file mode 100644 index 8e4689852..000000000 --- a/audio/include/NovelRT/Audio/AudioService.hpp +++ /dev/null @@ -1,75 +0,0 @@ -// Copyright © Matt Jones and Contributors. Licensed under the MIT Licence (MIT). See LICENCE.md in the repository root -// for more information. - -#ifndef NOVELRT_AUDIO_AUDIOSERVICE_H -#define NOVELRT_AUDIO_AUDIOSERVICE_H - -#ifndef NOVELRT_AUDIO_H -#error NovelRT does not support including types explicitly by default. Please include Audio.h instead for the Audio namespace subset. -#endif - -namespace NovelRT::Audio -{ - // TODO: This won't exist after Kenny's rewrite, not too bothered about this class. - class AudioService - { - private: - const ALuint _noBuffer = 0; - const ALfloat _pitch = 1.0f; - - Utilities::Lazy> _device; - Utilities::Lazy> _context; - std::string _deviceName; - LoggingService _logger; - bool _manualLoad; - MusicBank _music; - ALuint _musicSource; - ALint _musicSourceState; - bool _musicStopRequested; - ALint _musicLoopAmount; - ALint _soundLoopAmount; - ALint _soundSourceState; - SoundBank _soundStorage; - SoundBank _bufferStorage; - - ALuint BufferAudioFrameData(NovelRT::Utilities::Misc::Span audioFrameData, - int32_t channelCount, - int32_t sampleRate); - std::string GetALError(); - - public: - bool isInitialised; - - AudioService(); - ~AudioService(); - - bool InitializeAudio(); - std::vector::iterator LoadMusic(NovelRT::Utilities::Misc::Span audioFrameData, - int32_t channelCount, - int32_t sampleRate); - - void SetSoundVolume(ALuint source, float val); - void SetSoundPosition(ALuint source, float posX, float posY); - void ResumeMusic(); - void PlayMusic(std::vector::iterator handle, int32_t loops); - void PauseMusic(); - void StopMusic(); - void SetMusicVolume(float value); - void CheckSources(); - ALuint LoadSound(NovelRT::Utilities::Misc::Span audioFrameData, - int32_t channelCount, - int32_t sampleRate); - void Unload(ALuint handle); - void PlaySound(ALuint handle, int32_t loops); - void StopSound(ALuint handle); - void TearDown(); - [[nodiscard]] bool IsLoaded(std::vector::iterator handle); - [[nodiscard]] bool IsLoaded(ALuint handle); - [[nodiscard]] bool IsMusicPlaying(); - [[nodiscard]] bool IsSoundPlaying(ALuint handle); - [[nodiscard]] float GetMusicVolume(); - [[nodiscard]] float GetSoundVolume(ALuint handle); - }; -} - -#endif // NOVELRT_AUDIO_AUDIOSERVICE_H diff --git a/audio/include/NovelRT/Audio/AudioSourceContext.hpp b/audio/include/NovelRT/Audio/AudioSourceContext.hpp new file mode 100644 index 000000000..4fd275a93 --- /dev/null +++ b/audio/include/NovelRT/Audio/AudioSourceContext.hpp @@ -0,0 +1,18 @@ +// Copyright © Matt Jones and Contributors. Licensed under the MIT Licence (MIT). See LICENCE.md in the repository root +// for more information. +#pragma once + +#include + +namespace NovelRT::Audio +{ + struct AudioSourceContext + { + public: + float Volume = 0.75f; + float Pitch = 1.0f; + bool Loop = false; + int32_t Channels = 2; + int32_t SampleRate = 44100; + }; +} diff --git a/audio/include/NovelRT/Audio/AudioSourceState.hpp b/audio/include/NovelRT/Audio/AudioSourceState.hpp new file mode 100644 index 000000000..e683493a2 --- /dev/null +++ b/audio/include/NovelRT/Audio/AudioSourceState.hpp @@ -0,0 +1,15 @@ +// Copyright © Matt Jones and Contributors. Licensed under the MIT Licence (MIT). See LICENCE.md in the repository root +// for more information. +#pragma once + +#include + +namespace NovelRT::Audio +{ + enum class AudioSourceState: int32_t + { + SOURCE_STOPPED = 0, + SOURCE_PAUSED = 1, + SOURCE_PLAYING = 2 + }; +} diff --git a/audio/include/NovelRT/Audio/IAudioProvider.hpp b/audio/include/NovelRT/Audio/IAudioProvider.hpp new file mode 100644 index 000000000..99bb6b9f1 --- /dev/null +++ b/audio/include/NovelRT/Audio/IAudioProvider.hpp @@ -0,0 +1,28 @@ +// Copyright © Matt Jones and Contributors. Licensed under the MIT Licence (MIT). See LICENCE.md in the repository root +// for more information. +#pragma once + +#include +#include +#include + +namespace NovelRT::Audio +{ + class IAudioProvider + { + protected: + virtual void Dispose() = 0; + virtual uint32_t OpenSource(AudioSourceContext& context) = 0; + + public: + virtual uint32_t SubmitAudioBuffer(const NovelRT::Utilities::Misc::Span buffer, AudioSourceContext& context) = 0; + virtual void PlaySource(uint32_t sourceId) = 0; + virtual void StopSource(uint32_t sourceId) = 0; + virtual void PauseSource(uint32_t sourceId) = 0; + virtual void SetSourceProperties(uint32_t sourceId, AudioSourceContext& context) = 0; + virtual AudioSourceState GetSourceState(uint32_t id) = 0; + + virtual ~IAudioProvider() = default; + + }; +} diff --git a/audio/include/NovelRT/Audio/OpenAL/OpenALAudioProvider.hpp b/audio/include/NovelRT/Audio/OpenAL/OpenALAudioProvider.hpp new file mode 100644 index 000000000..399cbcb42 --- /dev/null +++ b/audio/include/NovelRT/Audio/OpenAL/OpenALAudioProvider.hpp @@ -0,0 +1,42 @@ +// Copyright © Matt Jones and Contributors. Licensed under the MIT Licence (MIT). See LICENCE.md in the repository root +// for more information. +#pragma once + +#include +#include +#include +#include +#include + +namespace NovelRT::Audio::OpenAL +{ + class OpenALAudioProvider final : public IAudioProvider + { + private: + ALCdevice* _device; + ALCcontext* _context; + std::vector _sources; + std::vector _buffers; + std::shared_ptr _logger; + + std::string GetALError(); + AudioSourceState ConvertToAudioSourceState(ALenum oalSourceState); + ALenum DetermineChannelFormat(int32_t numberOfChannels); + void LogOpenALMessages(char level, const char* message); + + protected: + void Dispose() final; + uint32_t OpenSource(AudioSourceContext& context) final; + + public: + OpenALAudioProvider(); + void PlaySource(uint32_t sourceId) final; + void StopSource(uint32_t sourceId) final; + void PauseSource(uint32_t sourceId) final; + void SetSourceProperties(uint32_t sourceId, AudioSourceContext& context) final; + uint32_t SubmitAudioBuffer(const NovelRT::Utilities::Misc::Span buffer, AudioSourceContext& context) final; + AudioSourceState GetSourceState(uint32_t id) final; + + ~OpenALAudioProvider() final; + }; +} diff --git a/audio/include/NovelRT/Audio/XAudio2/XAudio2AudioProvider.hpp b/audio/include/NovelRT/Audio/XAudio2/XAudio2AudioProvider.hpp new file mode 100644 index 000000000..dc38f2e6f --- /dev/null +++ b/audio/include/NovelRT/Audio/XAudio2/XAudio2AudioProvider.hpp @@ -0,0 +1,42 @@ +// Copyright © Matt Jones and Contributors. Licensed under the MIT Licence (MIT). See LICENCE.md in the repository root +// for more information. +#pragma once + +#include +#include +#include +#include +#include + +namespace NovelRT::Audio::XAudio2 +{ + class XAudio2AudioProvider : public IAudioProvider + { + private: + IXAudio2* _device; + IXAudio2MasteringVoice* _masterVoice; + std::map _sources; + uint32_t _sourceCounter; + std::shared_ptr _logger; + uint32_t _bufferCounter; + std::map _buffers; + HRESULT _hr; + + AudioSourceState ConvertToAudioSourceState(XAUDIO2_VOICE_STATE sourceState); + + protected: + void Dispose() final; + uint32_t OpenSource(AudioSourceContext& context) final; + + public: + XAudio2AudioProvider(); + void PlaySource(uint32_t sourceId) final; + void StopSource(uint32_t sourceId) final; + void PauseSource(uint32_t sourceId) final; + void SetSourceProperties(uint32_t sourceId, AudioSourceContext& context) final; + uint32_t SubmitAudioBuffer(const NovelRT::Utilities::Misc::Span buffer, AudioSourceContext& context) final; + AudioSourceState GetSourceState(uint32_t id) final; + + ~XAudio2AudioProvider() final; + }; +} diff --git a/include/NovelRT.Interop/Audio/NrtAudio.h b/include/NovelRT.Interop/Audio/NrtAudio.h deleted file mode 100644 index 28908c911..000000000 --- a/include/NovelRT.Interop/Audio/NrtAudio.h +++ /dev/null @@ -1,9 +0,0 @@ -// Copyright © Matt Jones and Contributors. Licensed under the MIT Licence (MIT). See LICENCE.md in the repository root -// for more information. - -#ifndef NOVELRT_INTEROP_AUDIO_H -#define NOVELRT_INTEROP_AUDIO_H - -#include "NrtAudioService.h" - -#endif // NOVELRT_INTEROP_AUDIO_H diff --git a/include/NovelRT.Interop/Audio/NrtAudioService.h b/include/NovelRT.Interop/Audio/NrtAudioService.h deleted file mode 100644 index eb1c64b93..000000000 --- a/include/NovelRT.Interop/Audio/NrtAudioService.h +++ /dev/null @@ -1,55 +0,0 @@ -// Copyright © Matt Jones and Contributors. Licensed under the MIT Licence (MIT). See LICENCE.md in the repository root -// for more information. - -#ifndef NOVELRT_INTEROP_AUDIO_AUDIOSERVICE_H -#define NOVELRT_INTEROP_AUDIO_AUDIOSERVICE_H - -#include "../NrtTypedefs.h" - -#ifdef __cplusplus -extern "C" -{ -#endif - - NrtAudioServiceHandle Nrt_AudioService_Create(); - NrtResult Nrt_AudioService_Destroy(NrtAudioServiceHandle service); - NrtBool Nrt_AudioService_InitialiseAudio(NrtAudioServiceHandle service); - NrtResult Nrt_AudioService_LoadMusic(NrtAudioServiceHandle service, - NrtInt16VectorHandle audioFrameData, - int32_t channelCount, - int32_t sampleRate, - NrtAudioServiceIteratorHandle* output); - NrtResult Nrt_AudioService_SetSoundVolume(NrtAudioServiceHandle service, unsigned int source, float val); - NrtResult Nrt_AudioService_SetSoundPosition(NrtAudioServiceHandle service, - unsigned int source, - float posX, - float posY); - NrtResult Nrt_AudioService_ResumeMusic(NrtAudioServiceHandle service); - NrtResult Nrt_AudioService_PlayMusic(NrtAudioServiceHandle service, - NrtAudioServiceIteratorHandle handle, - int32_t loops); - NrtResult Nrt_AudioService_PauseMusic(NrtAudioServiceHandle service); - NrtResult Nrt_AudioService_StopMusic(NrtAudioServiceHandle service); - NrtResult Nrt_AudioService_SetMusicVolume(NrtAudioServiceHandle service, float value); - NrtResult Nrt_AudioService_CheckSources(NrtAudioServiceHandle service); - NrtResult Nrt_AudioService_LoadSound(NrtAudioServiceHandle service, - NrtInt16VectorHandle audioFrameData, - int32_t channelCount, - int32_t sampleRate, - uint32_t* output); - NrtResult Nrt_AudioService_Unload(NrtAudioServiceHandle service, unsigned int handle); - NrtResult Nrt_AudioService_PlaySound(NrtAudioServiceHandle service, unsigned int handle, int loops); - NrtResult Nrt_AudioService_StopSound(NrtAudioServiceHandle service, unsigned int handle); - NrtResult Nrt_AudioService_TearDown(NrtAudioServiceHandle service); - NrtBool Nrt_AudioService_IsMusicLoaded(NrtAudioServiceHandle service, NrtAudioServiceIteratorHandle handle); - NrtBool Nrt_AudioService_IsSoundLoaded(NrtAudioServiceHandle service, unsigned int handle); - NrtBool Nrt_AudioService_IsMusicPlaying(NrtAudioServiceHandle service); - NrtBool Nrt_AudioService_IsSoundPlaying(NrtAudioServiceHandle service, unsigned int handle); - float Nrt_AudioService_GetMusicVolume(NrtAudioServiceHandle service); - float Nrt_AudioService_GetSoundVolume(NrtAudioServiceHandle service, unsigned int source); - -#ifdef __cplusplus -} -#endif - -#endif // NOVELRT_INTEROP_AUDIO_AUDIOSERVICE_H diff --git a/include/NovelRT.Interop/Audio/NrtAudioTypedefs.h b/include/NovelRT.Interop/Audio/NrtAudioTypedefs.h deleted file mode 100644 index 1323c8d03..000000000 --- a/include/NovelRT.Interop/Audio/NrtAudioTypedefs.h +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright © Matt Jones and Contributors. Licensed under the MIT License (MIT). See LICENCE.md in the repository root -// for more information. - -#ifndef NOVELRT_INTEROP_AUDIO_AUDIOTYPEDEFS_H -#define NOVELRT_INTEROP_AUDIO_AUDIOTYPEDEFS_H - -#ifdef __cplusplus -extern "C" -{ -#endif - - typedef struct NrtAudioService* NrtAudioServiceHandle; - typedef struct NrtAudioServiceIterator* NrtAudioServiceIteratorHandle; - -#ifdef __cplusplus -} -#endif - -#endif // NOVELRT_INTEROP_AUDIO_AUDIOTYPEDEFS_H diff --git a/include/NovelRT.Interop/NrtTypedefs.h b/include/NovelRT.Interop/NrtTypedefs.h index ce4ed8062..495216266 100644 --- a/include/NovelRT.Interop/NrtTypedefs.h +++ b/include/NovelRT.Interop/NrtTypedefs.h @@ -68,7 +68,6 @@ extern "C" #include "Maths/NrtMathsTypedefs.h" #include "Timing/NrtTimingTypedefs.h" #include "Utilities/NrtUtilitiesTypedefs.h" -#include "Audio/NrtAudioTypedefs.h" #include "Ecs/Audio/NrtEcsAudioTypedefs.h" #include "Ecs/Graphics/NrtEcsGraphicsTypedefs.h" #include "Ecs/NrtEcsTypedefs.h" diff --git a/include/NovelRT/Ecs/Audio/AudioSystem.h b/include/NovelRT/Ecs/Audio/AudioSystem.h index 78ed4e5e0..0b003f2f0 100644 --- a/include/NovelRT/Ecs/Audio/AudioSystem.h +++ b/include/NovelRT/Ecs/Audio/AudioSystem.h @@ -14,11 +14,10 @@ namespace NovelRT::Ecs::Audio { private: uint32_t _counter; - std::map> _fadeCache; LoggingService _logger; - std::map::iterator> _musicCache; - std::shared_ptr _service; - std::map _soundCache; + std::map _soundCache; + std::map> _fadeCache; + std::shared_ptr _mixer; NovelRT::Timing::Timestamp _systemTime; std::shared_ptr _resourceManagerPluginProvider; @@ -29,7 +28,7 @@ namespace NovelRT::Ecs::Audio ~AudioSystem() noexcept; void Update(Timing::Timestamp delta, Ecs::Catalogue catalogue) final; - uint32_t CreateAudio(std::string fileName, bool isMusic); + uint32_t RegisterSound(std::string fileName); }; } diff --git a/include/NovelRT/Ecs/Audio/Ecs.Audio.h b/include/NovelRT/Ecs/Audio/Ecs.Audio.h index dad3e5cbc..ed24e4167 100644 --- a/include/NovelRT/Ecs/Audio/Ecs.Audio.h +++ b/include/NovelRT/Ecs/Audio/Ecs.Audio.h @@ -8,9 +8,11 @@ #error NovelRT does not support including types explicitly by default. Please include Ecs.h instead for the Ecs namespace subset. #endif -#include "../../Timing/StepTimer.h" -#include "../../Timing/Timestamp.h" -#include +#include +#include +#include +#include + #include #include diff --git a/include/NovelRT/ResourceManagement/AudioMetadata.h b/include/NovelRT/ResourceManagement/AudioMetadata.h index f126f9480..ef0663cbb 100644 --- a/include/NovelRT/ResourceManagement/AudioMetadata.h +++ b/include/NovelRT/ResourceManagement/AudioMetadata.h @@ -4,15 +4,11 @@ #ifndef NOVELRT_RESOURCEMANAGEMENT_AUDIOMETADATA_H #define NOVELRT_RESOURCEMANAGEMENT_AUDIOMETADATA_H -#ifndef NOVELRT_RESOURCEMANAGEMENT_H -#error NovelRT does not support including types explicitly by default. Please include ResourceManagement.h instead for the ResourceManagement namespace subset. -#endif - namespace NovelRT::ResourceManagement { struct AudioMetadata { - std::vector processedAudioFrames; + std::vector processedAudioFrames; int32_t channelCount; int32_t sampleRate; uuids::uuid databaseHandle; diff --git a/include/NovelRT/Utilities/Event.h b/include/NovelRT/Utilities/Event.h index 045ec5734..6c4044e04 100644 --- a/include/NovelRT/Utilities/Event.h +++ b/include/NovelRT/Utilities/Event.h @@ -2,6 +2,7 @@ // for more information. #include "../Atom.h" +#include #include #include diff --git a/samples/AudioEcsSample/CMakeLists.txt b/samples/AudioEcsSample/CMakeLists.txt index f6c1a9d52..c5123e152 100644 --- a/samples/AudioEcsSample/CMakeLists.txt +++ b/samples/AudioEcsSample/CMakeLists.txt @@ -24,10 +24,14 @@ target_link_libraries(AudioEcsSample copy_build_products(AudioEcsSample DEPENDENCY Resources - TARGET_LOCATION $,$/../Resources,$/Resources> + TARGET_LOCATION $>,$/../Resources,$/Resources> ) -if(APPLE) +if(${NOVELRT_TARGET} STREQUAL "macOS") + target_link_libraries(AudioEcsSample + PRIVATE + "-framework AVFoundation" + ) copy_runtime_dependencies(AudioEcsSample DEPENDENCY Engine LIBRARY Vulkan::MoltenVK diff --git a/samples/AudioEcsSample/main.cpp b/samples/AudioEcsSample/main.cpp index 37eecd0d3..3fd111341 100644 --- a/samples/AudioEcsSample/main.cpp +++ b/samples/AudioEcsSample/main.cpp @@ -41,12 +41,12 @@ int main() // Create the sound components std::string uwu = (soundDir / "uwu.ogg").string(); - auto uwuHandle = audioSystem->CreateAudio(uwu, true); + auto uwuHandle = audioSystem->RegisterSound(uwu); AudioEmitterComponent uwuComponent = AudioEmitterComponent{uwuHandle, true, -1, 0.75f}; AudioEmitterStateComponent uwuState = AudioEmitterStateComponent{AudioEmitterState::ToFadeIn, 8.0f, 0.75f}; std::string ahh = (soundDir / "goat.wav").string(); - auto goatHandle = audioSystem->CreateAudio(ahh, false); + auto goatHandle = audioSystem->RegisterSound(ahh); AudioEmitterComponent goatComponent = AudioEmitterComponent{goatHandle, false, 0, 0.75f}; AudioEmitterStateComponent goatState = AudioEmitterStateComponent{AudioEmitterState::Stopped, 0, 0}; diff --git a/src/NovelRT.Interop/Audio/NrtAudioService.cpp b/src/NovelRT.Interop/Audio/NrtAudioService.cpp deleted file mode 100644 index 36cd7321a..000000000 --- a/src/NovelRT.Interop/Audio/NrtAudioService.cpp +++ /dev/null @@ -1,413 +0,0 @@ -// Copyright © Matt Jones and Contributors. Licensed under the MIT Licence (MIT). See LICENCE.md in the repository root -// for more information. - -#include -#include -#include - -#ifdef __cplusplus -using namespace NovelRT; - -extern "C" -{ -#endif - - NrtAudioServiceHandle Nrt_AudioService_Create() - { - return reinterpret_cast(new Audio::AudioService()); - } - - NrtResult Nrt_AudioService_Destroy(NrtAudioServiceHandle service) - { - if (service == nullptr) - { - Nrt_setErrMsgIsNullInstanceProvidedInternal(); - return NRT_FAILURE_NULL_INSTANCE_PROVIDED; - } - - auto serv = reinterpret_cast(service); - serv->~AudioService(); - return NRT_SUCCESS; - } - - NrtBool Nrt_AudioService_InitialiseAudio(NrtAudioServiceHandle service) - { - auto serv = reinterpret_cast(service); - - try - { - return serv->InitializeAudio() ? NRT_TRUE : NRT_FALSE; - } - catch (const Exceptions::InitialisationFailureException&) - { - Nrt_setErrMsgIsInitialisationFailureInternal(); - return NRT_FAILURE_INITIALISATION_FAILURE; - } - } - - NrtResult Nrt_AudioService_LoadMusic(NrtAudioServiceHandle service, - NrtInt16VectorHandle audioFrameData, - int32_t channelCount, - int32_t sampleRate, - NrtAudioServiceIteratorHandle* output) - { - if (service == nullptr) - { - Nrt_setErrMsgIsNullInstanceProvidedInternal(); - return NRT_FAILURE_NULL_INSTANCE_PROVIDED; - } - - auto serv = reinterpret_cast(service); - - NovelRT::Audio::SoundBank::iterator out; - try - { - out = serv->LoadMusic(*reinterpret_cast*>(audioFrameData), channelCount, sampleRate); - } - catch (const Exceptions::NotInitialisedException&) - { - Nrt_setErrMsgIsNotInitialisedInternal(); - return NRT_FAILURE_NOT_INITIALISED; - } - - *output = reinterpret_cast(out); - return NRT_SUCCESS; - } - - NrtResult Nrt_AudioService_SetSoundVolume(NrtAudioServiceHandle service, unsigned int source, float val) - { - if (service == nullptr) - { - Nrt_setErrMsgIsNullInstanceProvidedInternal(); - return NRT_FAILURE_NULL_INSTANCE_PROVIDED; - } - - auto serv = reinterpret_cast(service); - - try - { - serv->SetSoundVolume(source, val); - } - catch (const Exceptions::NotInitialisedException&) - { - Nrt_setErrMsgIsNotInitialisedInternal(); - return NRT_FAILURE_NOT_INITIALISED; - } - - return NRT_SUCCESS; - } - - NrtResult Nrt_AudioService_SetSoundPosition(NrtAudioServiceHandle service, - unsigned int source, - float posX, - float posY) - { - if (service == nullptr) - { - Nrt_setErrMsgIsNullInstanceProvidedInternal(); - return NRT_FAILURE_NULL_INSTANCE_PROVIDED; - } - - auto serv = reinterpret_cast(service); - - try - { - serv->SetSoundPosition(source, posX, posY); - } - catch (const Exceptions::NotInitialisedException&) - { - Nrt_setErrMsgIsNotInitialisedInternal(); - return NRT_FAILURE_NOT_INITIALISED; - } - - return NRT_SUCCESS; - } - - NrtResult Nrt_AudioService_ResumeMusic(NrtAudioServiceHandle service) - { - if (service == nullptr) - { - Nrt_setErrMsgIsNullInstanceProvidedInternal(); - return NRT_FAILURE_NULL_INSTANCE_PROVIDED; - } - - auto serv = reinterpret_cast(service); - - try - { - serv->ResumeMusic(); - } - catch (const Exceptions::NotInitialisedException&) - { - Nrt_setErrMsgIsNotInitialisedInternal(); - return NRT_FAILURE_NOT_INITIALISED; - } - - return NRT_SUCCESS; - } - - NrtResult Nrt_AudioService_PlayMusic(NrtAudioServiceHandle service, - NrtAudioServiceIteratorHandle handle, - int32_t loops) - { - if (service == nullptr) - { - Nrt_setErrMsgIsNullInstanceProvidedInternal(); - return NRT_FAILURE_NULL_INSTANCE_PROVIDED; - } - - auto serv = reinterpret_cast(service); - - try - { - serv->PlayMusic(reinterpret_cast(handle), loops); - } - catch (const Exceptions::NotInitialisedException&) - { - Nrt_setErrMsgIsNotInitialisedInternal(); - return NRT_FAILURE_NOT_INITIALISED; - } - - return NRT_SUCCESS; - } - - NrtResult Nrt_AudioService_PauseMusic(NrtAudioServiceHandle service) - { - if (service == nullptr) - { - Nrt_setErrMsgIsNullInstanceProvidedInternal(); - return NRT_FAILURE_NULL_INSTANCE_PROVIDED; - } - - auto serv = reinterpret_cast(service); - - try - { - serv->PauseMusic(); - } - catch (const Exceptions::NotInitialisedException&) - { - Nrt_setErrMsgIsNotInitialisedInternal(); - return NRT_FAILURE_NOT_INITIALISED; - } - - return NRT_SUCCESS; - } - - NrtResult Nrt_AudioService_StopMusic(NrtAudioServiceHandle service) - { - if (service == nullptr) - { - Nrt_setErrMsgIsNullInstanceProvidedInternal(); - return NRT_FAILURE_NULL_INSTANCE_PROVIDED; - } - - auto serv = reinterpret_cast(service); - - try - { - serv->StopMusic(); - } - catch (const Exceptions::NotInitialisedException&) - { - Nrt_setErrMsgIsNotInitialisedInternal(); - return NRT_FAILURE_NOT_INITIALISED; - } - - return NRT_SUCCESS; - } - - NrtResult Nrt_AudioService_SetMusicVolume(NrtAudioServiceHandle service, float value) - { - if (service == nullptr) - { - Nrt_setErrMsgIsNullInstanceProvidedInternal(); - return NRT_FAILURE_NULL_INSTANCE_PROVIDED; - } - - auto serv = reinterpret_cast(service); - - try - { - serv->SetMusicVolume(value); - } - catch (const Exceptions::NotInitialisedException&) - { - Nrt_setErrMsgIsNotInitialisedInternal(); - return NRT_FAILURE_NOT_INITIALISED; - } - - return NRT_SUCCESS; - } - - NrtResult Nrt_AudioService_CheckSources(NrtAudioServiceHandle service) - { - if (service == nullptr) - { - Nrt_setErrMsgIsNullInstanceProvidedInternal(); - return NRT_FAILURE_NULL_INSTANCE_PROVIDED; - } - - auto serv = reinterpret_cast(service); - serv->CheckSources(); - - return NRT_SUCCESS; - } - - NrtResult Nrt_AudioService_LoadSound(NrtAudioServiceHandle service, - NrtInt16VectorHandle audioFrameData, - int32_t channelCount, - int32_t sampleRate, - uint32_t* output) - { - if (service == nullptr) - { - Nrt_setErrMsgIsNullInstanceProvidedInternal(); - return NRT_FAILURE_NULL_INSTANCE_PROVIDED; - } - - auto serv = reinterpret_cast(service); - - try - { - *output = - serv->LoadSound(*reinterpret_cast*>(audioFrameData), channelCount, sampleRate); - } - catch (const Exceptions::NotInitialisedException&) - { - Nrt_setErrMsgIsNotInitialisedInternal(); - return NRT_FAILURE_NOT_INITIALISED; - } - - return NRT_SUCCESS; - } - - NrtResult Nrt_AudioService_Unload(NrtAudioServiceHandle service, unsigned int handle) - { - if (service == nullptr) - { - Nrt_setErrMsgIsNullInstanceProvidedInternal(); - return NRT_FAILURE_NULL_INSTANCE_PROVIDED; - } - - auto serv = reinterpret_cast(service); - serv->Unload(handle); - return NRT_SUCCESS; - } - - NrtResult Nrt_AudioService_PlaySound(NrtAudioServiceHandle service, unsigned int handle, int loops) - { - if (service == nullptr) - { - Nrt_setErrMsgIsNullInstanceProvidedInternal(); - return NRT_FAILURE_NULL_INSTANCE_PROVIDED; - } - - auto serv = reinterpret_cast(service); - - try - { - serv->PlaySound(handle, loops); - } - catch (const Exceptions::NotInitialisedException&) - { - Nrt_setErrMsgIsNotInitialisedInternal(); - return NRT_FAILURE_NOT_INITIALISED; - } - - return NRT_SUCCESS; - } - - NrtResult Nrt_AudioService_StopSound(NrtAudioServiceHandle service, unsigned int handle) - { - if (service == nullptr) - { - Nrt_setErrMsgIsNullInstanceProvidedInternal(); - return NRT_FAILURE_NULL_INSTANCE_PROVIDED; - } - - auto serv = reinterpret_cast(service); - serv->StopSound(handle); - return NRT_SUCCESS; - } - - NrtResult Nrt_AudioService_TearDown(NrtAudioServiceHandle service) - { - if (service == nullptr) - { - Nrt_setErrMsgIsNullInstanceProvidedInternal(); - return NRT_FAILURE_NULL_INSTANCE_PROVIDED; - } - - auto serv = reinterpret_cast(service); - serv->TearDown(); - return NRT_SUCCESS; - } - - NrtBool Nrt_AudioService_IsMusicLoaded(NrtAudioServiceHandle service, NrtAudioServiceIteratorHandle handle) - { - if (service == nullptr) - { - Nrt_setErrMsgIsNullInstanceProvidedInternal(); - return NRT_FAILURE_NULL_INSTANCE_PROVIDED; - } - - auto serv = reinterpret_cast(service); - - return serv->IsLoaded(reinterpret_cast(handle)) ? NRT_TRUE : NRT_FALSE; - } - - NrtBool Nrt_AudioService_IsSoundLoaded(NrtAudioServiceHandle service, unsigned int handle) - { - if (service == nullptr) - { - Nrt_setErrMsgIsNullInstanceProvidedInternal(); - return NRT_FAILURE_NULL_INSTANCE_PROVIDED; - } - - auto serv = reinterpret_cast(service); - - return serv->IsLoaded(handle) ? NRT_TRUE : NRT_FALSE; - } - - NrtBool Nrt_AudioService_IsMusicPlaying(NrtAudioServiceHandle service) - { - if (service == nullptr) - { - Nrt_setErrMsgIsNullInstanceProvidedInternal(); - return NRT_FAILURE_NULL_INSTANCE_PROVIDED; - } - - auto serv = reinterpret_cast(service); - - return serv->IsMusicPlaying() ? NRT_TRUE : NRT_FALSE; - } - - NrtBool Nrt_AudioService_IsSoundPlaying(NrtAudioServiceHandle service, unsigned int handle) - { - if (service == nullptr) - { - Nrt_setErrMsgIsNullInstanceProvidedInternal(); - return NRT_FAILURE_NULL_INSTANCE_PROVIDED; - } - - auto serv = reinterpret_cast(service); - - return serv->IsSoundPlaying(handle) ? NRT_TRUE : NRT_FALSE; - } - - float Nrt_AudioService_GetMusicVolume(NrtAudioServiceHandle service) - { - auto serv = reinterpret_cast(service); - return serv->GetMusicVolume(); - } - - float Nrt_AudioService_GetSoundVolume(NrtAudioServiceHandle service, unsigned int source) - { - auto serv = reinterpret_cast(service); - return serv->GetSoundVolume(source); - } - -#ifdef __cplusplus -} -#endif diff --git a/src/NovelRT.Interop/CMakeLists.txt b/src/NovelRT.Interop/CMakeLists.txt index 88c599611..fa077d418 100644 --- a/src/NovelRT.Interop/CMakeLists.txt +++ b/src/NovelRT.Interop/CMakeLists.txt @@ -2,8 +2,6 @@ set(INTEROP_SOURCES NrtInteropErrorHandling.cpp NrtLoggingService.cpp - Audio/NrtAudioService.cpp - Ecs/NrtCatalogue.cpp Ecs/NrtConfigurator.cpp Ecs/NrtComponentBufferMemoryContainer.cpp @@ -14,10 +12,6 @@ set(INTEROP_SOURCES Ecs/NrtSystemScheduler.cpp Ecs/NrtUnsafeComponentView.cpp - Ecs/Audio/NrtAudioSystem.cpp - Ecs/Audio/NrtAudioEmitterComponent.cpp - Ecs/Audio/NrtAudioEmitterStateComponent.cpp - Ecs/Graphics/NrtDefaultRenderingSystem.cpp Graphics/NrtRGBAColour.cpp diff --git a/src/NovelRT.Interop/Ecs/Audio/NrtAudioSystem.cpp b/src/NovelRT.Interop/Ecs/Audio/NrtAudioSystem.cpp index 0f2ff1408..a0e125ab4 100644 --- a/src/NovelRT.Interop/Ecs/Audio/NrtAudioSystem.cpp +++ b/src/NovelRT.Interop/Ecs/Audio/NrtAudioSystem.cpp @@ -1,7 +1,6 @@ // Copyright © Matt Jones and Contributors. Licensed under the MIT Licence (MIT). See LICENCE.md in the repository root // for more information. -#include #include #include diff --git a/src/NovelRT/CMakeLists.txt b/src/NovelRT/CMakeLists.txt index 615f865d2..756dab9fe 100644 --- a/src/NovelRT/CMakeLists.txt +++ b/src/NovelRT/CMakeLists.txt @@ -68,8 +68,8 @@ target_link_libraries(Engine PUBLIC runtime tbb - OpenAL sndfile + samplerate glfw png spdlog @@ -80,11 +80,19 @@ target_link_libraries(Engine FLAC opus ogg + $<$>:OpenAL> NovelRT-Graphics NovelRT-Audio NovelRT-Input ) +if(${NOVELRT_TARGET} STREQUAL "macOS") +target_link_libraries(Engine + PRIVATE + "-framework AVFoundation" +) +endif() + if(NOVELRT_INSTALL) install( TARGETS Engine diff --git a/src/NovelRT/Ecs/Audio/AudioSystem.cpp b/src/NovelRT/Ecs/Audio/AudioSystem.cpp index f2ac17b1a..47d07c7e8 100644 --- a/src/NovelRT/Ecs/Audio/AudioSystem.cpp +++ b/src/NovelRT/Ecs/Audio/AudioSystem.cpp @@ -8,15 +8,14 @@ namespace NovelRT::Ecs::Audio AudioSystem::AudioSystem( std::shared_ptr resourceManagerPluginProvider) : _counter(1), - _fadeCache(std::map>()), _logger(Utilities::Misc::CONSOLE_LOG_AUDIO), - _musicCache(std::map::iterator>()), - _service(std::make_shared()), - _soundCache(std::map()), + _mixer(std::make_shared()), + _soundCache(std::map()), + _fadeCache(std::map>()), _systemTime(Timing::Timestamp::zero()), _resourceManagerPluginProvider(std::move(resourceManagerPluginProvider)) { - _service->InitializeAudio(); + _mixer->Initialise(); } void AudioSystem::Update(Timing::Timestamp delta, Ecs::Catalogue catalogue) @@ -32,14 +31,8 @@ namespace NovelRT::Ecs::Audio { case AudioEmitterState::ToPlay: { - if (emitter.isMusic) - { - _service->PlayMusic(_musicCache.at(emitter.handle), emitter.numberOfLoops); - } - else - { - _service->PlaySound(_soundCache.at(emitter.handle), emitter.numberOfLoops); - } + _mixer->SetSourceLoop(emitter.handle, emitter.numberOfLoops > 0); + _mixer->PlaySource(emitter.handle); _logger.logDebug("Entity ID {} - EmitterState ToPlay -> Playing", entity); states.PushComponentUpdateInstruction( entity, AudioEmitterStateComponent{AudioEmitterState::Playing, emitterState.fadeDuration}); @@ -47,14 +40,7 @@ namespace NovelRT::Ecs::Audio } case AudioEmitterState::ToStop: { - if (emitter.isMusic) - { - _service->StopMusic(); - } - else - { - _service->StopSound(_soundCache.at(emitter.handle)); - } + _mixer->StopSource(emitter.handle); _logger.logDebug("Entity ID {} - EmitterState ToStop -> Stopped", entity); states.PushComponentUpdateInstruction( entity, AudioEmitterStateComponent{AudioEmitterState::Stopped, emitterState.fadeDuration}); @@ -62,35 +48,23 @@ namespace NovelRT::Ecs::Audio } case AudioEmitterState::ToPause: { - if (emitter.isMusic) - { - _service->PauseMusic(); - _logger.logDebug("Entity ID {} - EmitterState ToPause -> Paused", entity); - states.PushComponentUpdateInstruction( - entity, AudioEmitterStateComponent{AudioEmitterState::Paused, emitterState.fadeDuration}); - } + _mixer->PauseSource(emitter.handle); + _logger.logDebug("Entity ID {} - EmitterState ToPause -> Paused", entity); + states.PushComponentUpdateInstruction( + entity, AudioEmitterStateComponent{AudioEmitterState::Paused, emitterState.fadeDuration}); break; } case AudioEmitterState::ToResume: { - if (emitter.isMusic) - { - _service->ResumeMusic(); - _logger.logDebug("Entity ID {} - EmitterState ToResume -> Playing", entity); - states.PushComponentUpdateInstruction( - entity, AudioEmitterStateComponent{AudioEmitterState::Playing, emitterState.fadeDuration}); - } + _mixer->PlaySource(emitter.handle); + _logger.logDebug("Entity ID {} - EmitterState ToResume -> Playing", entity); + states.PushComponentUpdateInstruction( + entity, AudioEmitterStateComponent{AudioEmitterState::Playing, emitterState.fadeDuration}); break; } case AudioEmitterState::ToFadeOut: { - if (emitter.isMusic && !_service->IsMusicPlaying()) - { - states.PushComponentUpdateInstruction( - entity, AudioEmitterStateComponent{AudioEmitterState::Stopped, 0.0f, 0.0f}); - break; - } - else if (!emitter.isMusic && !_service->IsSoundPlaying(_soundCache.at(emitter.handle))) + if (_mixer->GetSourceState(emitter.handle) == NovelRT::Audio::AudioSourceState::SOURCE_STOPPED) { states.PushComponentUpdateInstruction( entity, AudioEmitterStateComponent{AudioEmitterState::Stopped, 0.0f, 0.0f}); @@ -116,7 +90,8 @@ namespace NovelRT::Ecs::Audio { float slope = std::get<1>(_fadeCache.at(entity)); float newVolume = emitter.volume + (slope * delta.getSecondsFloat()); - ChangeAudioVolume(emitter, newVolume); + _mixer->SetSourceVolume(emitter.handle, newVolume); + states.PushComponentUpdateInstruction( entity, AudioEmitterStateComponent{AudioEmitterState::FadingOut, remainingDuration, emitterState.fadeExpectedVolume}); @@ -130,15 +105,8 @@ namespace NovelRT::Ecs::Audio } else { - ChangeAudioVolume(emitter, 0.0f); - if (emitter.isMusic) - { - _service->StopMusic(); - } - else - { - _service->StopSound(_soundCache.at(emitter.handle)); - } + _mixer->SetSourceVolume(emitter.handle, 0.0f); + _mixer->StopSource(emitter.handle); emitters.PushComponentUpdateInstruction( entity, AudioEmitterComponent{emitter.handle, emitter.isMusic, emitter.numberOfLoops, @@ -154,16 +122,10 @@ namespace NovelRT::Ecs::Audio } case AudioEmitterState::ToFadeIn: { - if (emitter.isMusic && !_service->IsMusicPlaying()) - { - _service->SetMusicVolume(0.0f); - _service->PlayMusic(_musicCache.at(emitter.handle), emitter.numberOfLoops); - } - else if (!emitter.isMusic && !_service->IsSoundPlaying(emitter.handle)) + if (_mixer->GetSourceState(emitter.handle) == NovelRT::Audio::AudioSourceState::SOURCE_STOPPED) { - auto sound = _soundCache.at(emitter.handle); - _service->SetSoundVolume(sound, 0.0f); - _service->PlaySound(sound, emitter.numberOfLoops); + _mixer->SetSourceVolume(emitter.handle, 0.0f); + _mixer->PlaySource(emitter.handle); } else { @@ -193,7 +155,7 @@ namespace NovelRT::Ecs::Audio { float slope = std::get<1>(_fadeCache.at(entity)); float newVolume = emitter.volume + (slope * delta.getSecondsFloat()); - ChangeAudioVolume(emitter, newVolume); + _mixer->SetSourceVolume(emitter.handle, newVolume); states.PushComponentUpdateInstruction( entity, AudioEmitterStateComponent{AudioEmitterState::FadingIn, remainingDuration, emitterState.fadeExpectedVolume}); @@ -209,7 +171,7 @@ namespace NovelRT::Ecs::Audio { if (emitter.volume < emitterState.fadeExpectedVolume) { - ChangeAudioVolume(emitter, emitterState.fadeExpectedVolume); + _mixer->SetSourceVolume(emitter.handle, emitterState.fadeExpectedVolume); } emitters.PushComponentUpdateInstruction( entity, AudioEmitterComponent{emitter.handle, emitter.isMusic, emitter.numberOfLoops, @@ -223,36 +185,17 @@ namespace NovelRT::Ecs::Audio } case AudioEmitterState::Playing: { - float currentVolume = 0; - if (emitter.isMusic) + auto soundContext = _mixer->GetSourceContext(emitter.handle); + if (soundContext.Volume != emitter.volume) { - currentVolume = _service->GetMusicVolume(); - if (currentVolume != emitter.volume) - { - _service->SetMusicVolume(emitter.volume); - _logger.logDebug("Entity ID {} - Emitter Volume {} -> {}", entity, currentVolume, - emitter.volume); - } - if (!_service->IsMusicPlaying()) - { - states.PushComponentUpdateInstruction( - entity, AudioEmitterStateComponent{AudioEmitterState::ToStop, 0.0f, 0.0f}); - } + _mixer->SetSourceVolume(emitter.handle, emitter.volume); + _logger.logDebug("Entity ID {} - Emitter Volume {} -> {}", entity, soundContext.Volume, + emitter.volume); } - else + if (_mixer->GetSourceState(emitter.handle) != NovelRT::Audio::AudioSourceState::SOURCE_PLAYING) { - currentVolume = _service->GetSoundVolume(_soundCache.at(emitter.handle)); - if (currentVolume != emitter.volume) - { - _service->SetSoundVolume(_soundCache.at(emitter.handle), emitter.volume); - _logger.logDebug("Entity ID {} - Emitter Volume {} -> {}", entity, currentVolume, - emitter.volume); - } - if (!_service->IsSoundPlaying(_soundCache.at(emitter.handle))) - { - states.PushComponentUpdateInstruction( - entity, AudioEmitterStateComponent{AudioEmitterState::ToStop, 0.0f, 0.0f}); - } + states.PushComponentUpdateInstruction( + entity, AudioEmitterStateComponent{AudioEmitterState::ToStop, 0.0f, 0.0f}); } break; } @@ -264,64 +207,31 @@ namespace NovelRT::Ecs::Audio } } } - - _service->CheckSources(); } - uint32_t AudioSystem::CreateAudio(std::string fileName, bool isMusic) + uint32_t AudioSystem::RegisterSound(std::string fileName) { if (_counter == UINT32_MAX) { - // add logging here return 0U; } - // 0 is not valid here since we're working with only positive numbers... not that anyone should be loading - // thousands of sounds. - uint32_t value = 0U; - if (isMusic) - { - auto asset = _resourceManagerPluginProvider->GetResourceLoader()->LoadAudioFrameData(fileName); - auto handle = _service->LoadMusic(asset.processedAudioFrames, asset.channelCount, asset.sampleRate); - if (_service->IsLoaded(handle)) - { - _musicCache.insert({_counter, handle}); - value = _counter; - _counter++; - } - } - else - { - auto asset = _resourceManagerPluginProvider->GetResourceLoader()->LoadAudioFrameData(fileName); - auto handle = _service->LoadSound(asset.processedAudioFrames, asset.channelCount, asset.sampleRate); - if (_service->IsLoaded(handle)) - { - _soundCache.insert({_counter, handle}); - value = _counter; - _counter++; - } - } - - return value; + auto asset = _resourceManagerPluginProvider->GetResourceLoader()->LoadAudioFrameData(fileName); + auto handle = _mixer->SubmitAudioBuffer( + NovelRT::Utilities::Misc::Span(asset.processedAudioFrames.data(), asset.processedAudioFrames.size()), + asset.channelCount, asset.sampleRate); + _soundCache.emplace(handle, asset); + _counter++; + return handle; } void AudioSystem::ChangeAudioVolume(AudioEmitterComponent emitter, float desiredVolume) { - if (emitter.isMusic) - { - _service->SetMusicVolume(desiredVolume); - } - else - { - _service->SetSoundVolume(_soundCache.at(emitter.handle), desiredVolume); - } + _mixer->SetSourceVolume(emitter.handle, desiredVolume); } AudioSystem::~AudioSystem() noexcept { unused(_soundCache.empty()); - unused(_musicCache.empty()); - unused(_fadeCache.empty()); - _service->TearDown(); } } diff --git a/src/NovelRT/ResourceManagement/Desktop/DesktopResourceLoader.cpp b/src/NovelRT/ResourceManagement/Desktop/DesktopResourceLoader.cpp index 6bc2cab1c..86a7a12ec 100644 --- a/src/NovelRT/ResourceManagement/Desktop/DesktopResourceLoader.cpp +++ b/src/NovelRT/ResourceManagement/Desktop/DesktopResourceLoader.cpp @@ -3,6 +3,7 @@ #include #include +#include namespace NovelRT::ResourceManagement::Desktop { @@ -389,6 +390,7 @@ namespace NovelRT::ResourceManagement::Desktop AudioMetadata DesktopResourceLoader::LoadAudioFrameData(std::filesystem::path filePath) { constexpr size_t _bufferSize = 2048; + constexpr int32_t _sampleRate = 44100; if (filePath.is_relative()) { @@ -405,15 +407,13 @@ namespace NovelRT::ResourceManagement::Desktop throw NovelRT::Exceptions::IOException(filePath.string(), std::string(sf_strerror(file))); } - std::vector data; - std::vector readBuffer; + std::vector data; + std::vector readBuffer; readBuffer.resize(_bufferSize); - sf_command(file, SFC_SET_SCALE_FLOAT_INT_READ, nullptr, SF_TRUE); - sf_count_t readSize = 0; - while ((readSize = sf_read_short(file, readBuffer.data(), static_cast(readBuffer.size()))) != 0) + while ((readSize = sf_read_float(file, readBuffer.data(), static_cast(readBuffer.size()))) != 0) { data.insert(data.end(), readBuffer.begin(), readBuffer.begin() + readSize); } @@ -423,6 +423,30 @@ namespace NovelRT::ResourceManagement::Desktop auto relativePathForAssetDatabase = std::filesystem::relative(filePath, _resourcesRootDirectory); uuids::uuid databaseHandle = RegisterAsset(relativePathForAssetDatabase); + if (info.samplerate != _sampleRate) + { + _logger.logDebug("Detected sample rate of {0}", info.samplerate); + info.samplerate > 44100 ? _logger.logDebug("Downscaling...") : _logger.logDebug("Upscaling..."); + std::vector resampledData = std::vector(data.size()); + SRC_DATA conversionInfo = SRC_DATA{}; + conversionInfo.data_in = data.data(); + conversionInfo.data_out = resampledData.data(); + conversionInfo.input_frames = info.channels == 1 ? data.size() : data.size() / info.channels; + conversionInfo.output_frames = conversionInfo.input_frames; + double rate = 44100.0 / static_cast(info.samplerate); + _logger.logDebug("Scaling by ratio of {0:f}", rate); + conversionInfo.src_ratio = rate; + int result = src_simple(&conversionInfo, SRC_SINC_MEDIUM_QUALITY, info.channels); + if (result != 0) + { + std::string err = src_strerror(result); + _logger.logErrorLine(err); + throw new NovelRT::Exceptions::InvalidOperationException(err); + } + + return AudioMetadata{resampledData, info.channels, _sampleRate, databaseHandle}; + } + return AudioMetadata{data, info.channels, info.samplerate, databaseHandle}; } diff --git a/thirdparty/CMakeLists.txt b/thirdparty/CMakeLists.txt index fb65e2eda..8f4203805 100644 --- a/thirdparty/CMakeLists.txt +++ b/thirdparty/CMakeLists.txt @@ -43,12 +43,14 @@ thirdparty_module(GSL URL_HASH SHA512=710ab8b06b24dbca2cd1a8abb2eca2282548644224519784d04b1c7b70e3639f3b3e8d8f993f7f8c3dce3fcfe9ff0f3757b8d2625b676df36c84caa6f684133e ) thirdparty_module(GTest - URL https://github.com/google/googletest/archive/refs/tags/release-1.12.1.zip - URL_HASH SHA512=1479ea2f3172c622c0ca305f5b2bc45a42941221ec0ac7865e6d6d020ec4d008d952fc64e01a4c5138d7bed4148cf75596f25bb9e9044a98bbbf5662053ea11c + URL https://github.com/google/googletest/archive/refs/tags/v1.14.0.zip + URL_HASH SHA512=16cc8f5b03b862e2937d1b926ca6dc291234ac5670050dc4fc2752ec98497330ebcd46f292317cd2bbb111ce33bbdcfd088ddf6a7fe273e2442963908732d589 + OVERRIDE_FIND_PACKAGE ) thirdparty_module(nlohmann_json - URL https://github.com/nlohmann/json/releases/download/v3.10.5/json.tar.xz - URL_HASH SHA512=feb7dbdd1f1b6318131821f55d1411f0435262601c65eca368ccf3418750fd6348a37a3cd9d0694b31ccacfc91d02fbbecf94af52429005f8898490a5233c37d + URL https://github.com/nlohmann/json/releases/download/v3.11.3/json.tar.xz + URL_HASH SHA512=1aa94cdc3378a1fe0e3048ee73293e34bfa5ed9b46c6db0993e58e289ef818f7b7a472c0dc9c920114312e2e3ae1ff346ca797407ff48143744592adfd0a41ad + OVERRIDE_FIND_PACKAGE ) thirdparty_module(Ogg URL https://github.com/xiph/ogg/releases/download/v1.3.5/libogg-1.3.5.tar.gz @@ -56,8 +58,8 @@ thirdparty_module(Ogg OVERRIDE_FIND_PACKAGE ) thirdparty_module(OpenAL - URL https://github.com/kcat/openal-soft/archive/refs/tags/1.21.1.zip - URL_HASH SHA512=bc17d628548e59d0db3996917d207a4af0bbbf615ba3ba10ae8e99b28213e845e967a0510c8aad74e551aa66ddfb11499fe26082725af82160ac5d2db4e7e69d + URL https://github.com/kcat/openal-soft/archive/ef44737658313cc0a6394eed7878ad28d36fdca5.zip + URL_HASH SHA512=66b1e5a373ab6f5414220b7b22c46a0e0eba7d83e004088be75bb5ec808e4d4e2152fc14989ba182475c4d1d020332bbd9b3deb1b26f8506d40112f995e02206 OVERRIDE_FIND_PACKAGE ) thirdparty_module(Opus @@ -70,6 +72,12 @@ thirdparty_module(PNG URL_HASH SHA512=65df0c2befa06d0b9ec0c6395a73987fb071754610d328b94ce01a6b8f7161c71ce97ddf0e0a8e28c2e50019a103368c4b4f0b63bd8cd75e168b32f940922d96 OVERRIDE_FIND_PACKAGE ) + +thirdparty_module(samplerate + URL https://github.com/libsndfile/libsamplerate/archive/refs/tags/0.2.2.zip + URL_HASH SHA512=dedd072bc83ccebfe1a33e8a3fb3f72559bfd36282b958b1d5463b786f2325f684137580db38d91f0f4f5345a1da3e9e1c4a1695e303182e57d542c085db829f +) + thirdparty_module(SndFile URL https://github.com/novelrt/libsndfile/archive/f76d63813c810c2ed09bdb3821bb807498a7558e.zip URL_HASH SHA512=7e650af94068277246e4ccaf3b5dc20d0f93d2a2e0ecdf0f24f0be79196f879c21ec692ad48d39454f22dd01d9a4864d21458daa8d7b8f5ea4568c9551b345c1 @@ -103,12 +111,14 @@ thirdparty_module(ZLIB # N.B. Order matters here, for dependency searching! foreach(module - nlohmann_json Fabulist + nlohmann_json + Fabulist glfw3 glm GSL GTest - Opus Ogg FLAC Vorbis SndFile OpenAL + Opus Ogg FLAC Vorbis SndFile samplerate + OpenAL ZLIB PNG fmt spdlog stduuid diff --git a/thirdparty/OpenAL/CMakeLists.txt b/thirdparty/OpenAL/CMakeLists.txt index 6edf185c6..fbf96edf2 100644 --- a/thirdparty/OpenAL/CMakeLists.txt +++ b/thirdparty/OpenAL/CMakeLists.txt @@ -1,40 +1,43 @@ -include(FetchContent) +if(${NOVELRT_TARGET} STREQUAL Linux) + include(FetchContent) -set(CMAKE_POLICY_DEFAULT_CMP0077 NEW) + set(CMAKE_POLICY_DEFAULT_CMP0077 NEW) -set(ALSOFT_UTILS OFF) -set(ALSOFT_NO_CONFIG_UTIL ON) -set(ALSOFT_EXAMPLES OFF) -set(ALSOFT_INSTALL_EXAMPLES OFF) -set(ALSOFT_INSTALL_UTILS OFF) -set(ALSOFT_TESTS OFF) + set(ALSOFT_UTILS OFF) + set(ALSOFT_NO_CONFIG_UTIL ON) + set(ALSOFT_EXAMPLES OFF) + set(ALSOFT_INSTALL_EXAMPLES OFF) + set(ALSOFT_INSTALL_UTILS OFF) + set(ALSOFT_TESTS OFF) -set(BUILD_SHARED_LIBS ON) -set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS OFF) + set(BUILD_SHARED_LIBS ON) + set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS OFF) -FetchContent_MakeAvailable(OpenAL) -# These warnings are outside our scope as we do not manage the libraries themselves. -# We'll silence the warnings so that output is a little cleaner. -target_compile_options(OpenAL - PRIVATE - $<$:/wd4127> - $<$:/wd4834> + FetchContent_MakeAvailable(OpenAL) - $<$:-Wno-deprecated-copy> + # These warnings are outside our scope as we do not manage the libraries themselves. + # We'll silence the warnings so that output is a little cleaner. + target_compile_options(OpenAL + PRIVATE + $<$:/wd4127> + $<$:/wd4834> - $<$:-Wno-deprecated-copy> + $<$:-Wno-deprecated-copy> - $<$:-Wno-deprecated-copy> -) -target_compile_options(common - PRIVATE - $<$:/wd4127> - $<$:/wd4834> + $<$:-Wno-deprecated-copy> - $<$:-Wno-deprecated-copy> + $<$:-Wno-deprecated-copy> + ) + target_compile_options(alcommon + PRIVATE + $<$:/wd4127> + $<$:/wd4834> - $<$:-Wno-deprecated-copy> + $<$:-Wno-deprecated-copy> - $<$:-Wno-deprecated-copy> -) + $<$:-Wno-deprecated-copy> + + $<$:-Wno-deprecated-copy> + ) +endif() diff --git a/thirdparty/fmt/CMakeLists.txt b/thirdparty/fmt/CMakeLists.txt index f88fd462a..7d8099c01 100644 --- a/thirdparty/fmt/CMakeLists.txt +++ b/thirdparty/fmt/CMakeLists.txt @@ -3,4 +3,3 @@ include(FetchContent) FetchContent_MakeAvailable(fmt) set_property(TARGET fmt PROPERTY POSITION_INDEPENDENT_CODE ON) - diff --git a/thirdparty/samplerate/CMakeLists.txt b/thirdparty/samplerate/CMakeLists.txt new file mode 100644 index 000000000..203ae7f5d --- /dev/null +++ b/thirdparty/samplerate/CMakeLists.txt @@ -0,0 +1,8 @@ +include(FetchContent) + +set(CMAKE_POLICY_DEFAULT_CMP0077 NEW) + +set(BUILD_SHARED_LIBS ON) +set(LIBSAMPLERATE_INSTALL ON) + +FetchContent_MakeAvailable(samplerate)