Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Platform-specific Audio #599

Merged
merged 43 commits into from
Jul 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
df0e764
Progress commit: Compiling, untested - now submitting individual buff…
capnkenny Nov 6, 2023
9c006f6
Merge branch 'main' into feature/audio-system
capnkenny Nov 7, 2023
a705703
Fix spdlog to use external fmt
capnkenny Feb 17, 2024
bcc2261
OpenAL partially working
capnkenny Feb 17, 2024
412c5bf
OpenAL working again with proper shutdown and playback
capnkenny Feb 18, 2024
cfe09c2
Update OpenAL and hook logging
capnkenny Feb 18, 2024
14f9b2f
Partially working XAudio2 for Windows
capnkenny Feb 18, 2024
66719ea
XAudio2 now conforming to AudioSystem
capnkenny Feb 18, 2024
da30f47
[UNTESTED] Obj-C++ wrapper for AVAudioEngine compiles
capnkenny Feb 21, 2024
f6bf98f
50% success - usually runs with AVAudioEngine, sometimes crashes due …
capnkenny Feb 21, 2024
71a3726
[WIP, WORKING] Switched to 32-bit float audio and static samplerate.
capnkenny Mar 10, 2024
592d870
[IN PROGRESS COMMIT] Save audio changes
capnkenny Apr 14, 2024
c285fed
Fix XAudio2 to work with floating point audio
capnkenny Jul 2, 2024
ca62d66
Progress commit: Compiling, untested - now submitting individual buff…
capnkenny Nov 6, 2023
0db4194
Fix spdlog to use external fmt
capnkenny Feb 17, 2024
d02bb39
OpenAL partially working
capnkenny Feb 17, 2024
bc8222d
OpenAL working again with proper shutdown and playback
capnkenny Feb 18, 2024
cd5c187
Update OpenAL and hook logging
capnkenny Feb 18, 2024
26f0cf4
Partially working XAudio2 for Windows
capnkenny Feb 18, 2024
09d64f1
XAudio2 now conforming to AudioSystem
capnkenny Feb 18, 2024
2fdc941
[UNTESTED] Obj-C++ wrapper for AVAudioEngine compiles
capnkenny Feb 21, 2024
06d98fb
50% success - usually runs with AVAudioEngine, sometimes crashes due …
capnkenny Feb 21, 2024
d59e67a
[WIP, WORKING] Switched to 32-bit float audio and static samplerate.
capnkenny Mar 10, 2024
55cf573
[IN PROGRESS COMMIT] Save audio changes
capnkenny Apr 14, 2024
e3220de
Fix XAudio2 to work with floating point audio
capnkenny Jul 2, 2024
f3cd3de
Move OpenAL to supporting floating audio - needs bugfix for unsupport…
capnkenny Jul 2, 2024
b78a72c
Fix bug when destroying audio provider
capnkenny Jul 2, 2024
d517243
Fix issue where GTest in system libs was being preffered
capnkenny Jul 4, 2024
6c6147b
Update nlohmann json
capnkenny Jul 4, 2024
c5c353f
Remove testing mistake in thirdparty
capnkenny Jul 4, 2024
6307007
Clang-format
capnkenny Jul 4, 2024
f7d4881
Remove NrtAudioService, and interop references to legacy audio system
capnkenny Jul 4, 2024
ca7a665
Fix interop building issues for clang
capnkenny Jul 4, 2024
1c5155e
Pin OpenAL to new version that fixes compiler issues with internal fu…
capnkenny Jul 4, 2024
92afb65
Revert OpenAL-Soft to not as new version for clang 10 compat
capnkenny Jul 7, 2024
daa8768
Clang-format and reverting OpenAL again
capnkenny Jul 7, 2024
55f9f4d
Revert change made to support Clang 17
capnkenny Jul 7, 2024
0696f00
Merge branch 'main' into feature/audio-system
capnkenny Jul 12, 2024
71475be
Merge linux changes back into branch
capnkenny Jul 29, 2024
d85d52c
Fix missing change volume function
capnkenny Jul 29, 2024
ff50ad2
Fix issues caused by merge
capnkenny Jul 29, 2024
fb8aeee
Restore clang-10 supported OpenAL
capnkenny Jul 29, 2024
fa7b475
Revert openal updates
capnkenny Jul 29, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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")
Expand Down
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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.

Expand Down
200 changes: 200 additions & 0 deletions audio/AVAudioEngine/AVAudioEngineAudioProvider.mm
Original file line number Diff line number Diff line change
@@ -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 <NovelRT/Exceptions/Exceptions.h>
#include <spdlog/sinks/stdout_color_sinks.h>
#import <NovelRT/Audio/AVAudioEngine/AVAudioEngineAudioProvider.hpp>
#import <Foundation/Foundation.h>
#import <AVFoundation/AVFoundation.h>
#import <AVFAudio/AVAudioEngine.h>

namespace NovelRT::Audio::AVAudioEngine
{
AVAudioEngineAudioProvider::AVAudioEngineAudioProvider():
_buffers(std::map<uint32_t, ::AVAudioPCMBuffer*>()),
_sources(std::map<uint32_t, ::AVAudioPlayerNode*>()),
_sourceEQUnits(std::map<uint32_t, ::AVAudioUnitEQ*>()),
_sourceStates(std::map<uint32_t, AudioSourceState>()),
_sourceContexts(std::map<uint32_t, AudioSourceContext>()),
_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<float> 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<void*>(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);
}
}
99 changes: 99 additions & 0 deletions audio/AudioMixer.cpp
Original file line number Diff line number Diff line change
@@ -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 <NovelRT/Audio/AudioMixer.hpp>

//Conditional
#if defined(_WIN32)
#include <NovelRT/Audio/XAudio2/XAudio2AudioProvider.hpp>
#elif __APPLE__
#include <NovelRT/Audio/AVAudioEngine/AVAudioEngineAudioProvider.hpp>
#else
#include <NovelRT/Audio/OpenAL/OpenALAudioProvider.hpp>
#endif
namespace NovelRT::Audio
{
void AudioMixer::Initialise()
{
_sourceContextCache = std::map<uint32_t, AudioSourceContext>();
#if defined(_WIN32)
_audioProvider = std::make_unique<XAudio2::XAudio2AudioProvider>();
#elif defined(__APPLE__)
_audioProvider = std::make_unique<AVAudioEngine::AVAudioEngineAudioProvider>();
#else
_audioProvider = std::make_unique<OpenAL::OpenALAudioProvider>();
#endif
}

uint32_t AudioMixer::SubmitAudioBuffer(const NovelRT::Utilities::Misc::Span<float> 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();
}
}
Loading
Loading