mirror of
https://github.com/SFML/SFML.git
synced 2024-11-24 20:31:05 +08:00
Added support for retrieving a list of audio playback devices, getting the default audio playback device and changing the audio playback device during runtime.
This commit is contained in:
parent
4b1751321a
commit
123270f7ad
@ -1089,14 +1089,25 @@ int main()
|
|||||||
|
|
||||||
// Create the description text
|
// Create the description text
|
||||||
sf::Text description(font, "Current effect: " + effects[current]->getName(), 20);
|
sf::Text description(font, "Current effect: " + effects[current]->getName(), 20);
|
||||||
description.setPosition({10.f, 530.f});
|
description.setPosition({10.f, 522.f});
|
||||||
description.setFillColor(sf::Color(80, 80, 80));
|
description.setFillColor(sf::Color(80, 80, 80));
|
||||||
|
|
||||||
// Create the instructions text
|
// Create the instructions text
|
||||||
sf::Text instructions(font, "Press left and right arrows to change the current effect", 20);
|
sf::Text instructions(font, "Press left and right arrows to change the current effect", 20);
|
||||||
instructions.setPosition({280.f, 555.f});
|
instructions.setPosition({280.f, 544.f});
|
||||||
instructions.setFillColor(sf::Color(80, 80, 80));
|
instructions.setFillColor(sf::Color(80, 80, 80));
|
||||||
|
|
||||||
|
// Create the playback device text
|
||||||
|
auto playbackDeviceName = sf::PlaybackDevice::getDevice();
|
||||||
|
sf::Text playbackDevice(font, "Current playback device: " + playbackDeviceName.value_or("None"), 20);
|
||||||
|
playbackDevice.setPosition({10.f, 566.f});
|
||||||
|
playbackDevice.setFillColor(sf::Color(80, 80, 80));
|
||||||
|
|
||||||
|
// Create the playback device instructions text
|
||||||
|
sf::Text playbackDeviceInstructions(font, "Press F1 to change device", 20);
|
||||||
|
playbackDeviceInstructions.setPosition({565.f, 566.f});
|
||||||
|
playbackDeviceInstructions.setFillColor(sf::Color(80, 80, 80));
|
||||||
|
|
||||||
// Start the game loop
|
// Start the game loop
|
||||||
const sf::Clock clock;
|
const sf::Clock clock;
|
||||||
while (window.isOpen())
|
while (window.isOpen())
|
||||||
@ -1139,6 +1150,37 @@ int main()
|
|||||||
description.setString("Current effect: " + effects[current]->getName());
|
description.setString("Current effect: " + effects[current]->getName());
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
// F1 key: change playback device
|
||||||
|
case sf::Keyboard::Key::F1:
|
||||||
|
{
|
||||||
|
// We need to query the list every time we want to change
|
||||||
|
// since new devices could have been added in the mean time
|
||||||
|
const auto devices = sf::PlaybackDevice::getAvailableDevices();
|
||||||
|
const auto currentDevice = sf::PlaybackDevice::getDevice();
|
||||||
|
auto next = currentDevice;
|
||||||
|
|
||||||
|
for (auto iter = devices.begin(); iter != devices.end(); ++iter)
|
||||||
|
{
|
||||||
|
if (*iter == currentDevice)
|
||||||
|
{
|
||||||
|
const auto nextIter = std::next(iter);
|
||||||
|
next = (nextIter == devices.end()) ? devices.front() : *nextIter;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (next)
|
||||||
|
{
|
||||||
|
if (!sf::PlaybackDevice::setDevice(*next))
|
||||||
|
std::cerr << "Failed to set the playback device to: " << *next << std::endl;
|
||||||
|
|
||||||
|
playbackDeviceName = sf::PlaybackDevice::getDevice();
|
||||||
|
playbackDevice.setString("Current playback device: " + playbackDeviceName.value_or("None"));
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
effects[current]->handleKey(keyPressed->code);
|
effects[current]->handleKey(keyPressed->code);
|
||||||
break;
|
break;
|
||||||
@ -1160,6 +1202,8 @@ int main()
|
|||||||
window.draw(textBackground);
|
window.draw(textBackground);
|
||||||
window.draw(instructions);
|
window.draw(instructions);
|
||||||
window.draw(description);
|
window.draw(description);
|
||||||
|
window.draw(playbackDevice);
|
||||||
|
window.draw(playbackDeviceInstructions);
|
||||||
|
|
||||||
// Finally, display the rendered frame on screen
|
// Finally, display the rendered frame on screen
|
||||||
window.display();
|
window.display();
|
||||||
|
@ -32,6 +32,7 @@
|
|||||||
#include <SFML/Audio/Listener.hpp>
|
#include <SFML/Audio/Listener.hpp>
|
||||||
#include <SFML/Audio/Music.hpp>
|
#include <SFML/Audio/Music.hpp>
|
||||||
#include <SFML/Audio/OutputSoundFile.hpp>
|
#include <SFML/Audio/OutputSoundFile.hpp>
|
||||||
|
#include <SFML/Audio/PlaybackDevice.hpp>
|
||||||
#include <SFML/Audio/Sound.hpp>
|
#include <SFML/Audio/Sound.hpp>
|
||||||
#include <SFML/Audio/SoundBuffer.hpp>
|
#include <SFML/Audio/SoundBuffer.hpp>
|
||||||
#include <SFML/Audio/SoundBufferRecorder.hpp>
|
#include <SFML/Audio/SoundBufferRecorder.hpp>
|
||||||
|
110
include/SFML/Audio/PlaybackDevice.hpp
Normal file
110
include/SFML/Audio/PlaybackDevice.hpp
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
////////////////////////////////////////////////////////////
|
||||||
|
//
|
||||||
|
// SFML - Simple and Fast Multimedia Library
|
||||||
|
// Copyright (C) 2007-2024 Laurent Gomila (laurent@sfml-dev.org)
|
||||||
|
//
|
||||||
|
// This software is provided 'as-is', without any express or implied warranty.
|
||||||
|
// In no event will the authors be held liable for any damages arising from the use of this software.
|
||||||
|
//
|
||||||
|
// Permission is granted to anyone to use this software for any purpose,
|
||||||
|
// including commercial applications, and to alter it and redistribute it freely,
|
||||||
|
// subject to the following restrictions:
|
||||||
|
//
|
||||||
|
// 1. The origin of this software must not be misrepresented;
|
||||||
|
// you must not claim that you wrote the original software.
|
||||||
|
// If you use this software in a product, an acknowledgment
|
||||||
|
// in the product documentation would be appreciated but is not required.
|
||||||
|
//
|
||||||
|
// 2. Altered source versions must be plainly marked as such,
|
||||||
|
// and must not be misrepresented as being the original software.
|
||||||
|
//
|
||||||
|
// 3. This notice may not be removed or altered from any source distribution.
|
||||||
|
//
|
||||||
|
////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////
|
||||||
|
// Headers
|
||||||
|
////////////////////////////////////////////////////////////
|
||||||
|
#include <SFML/Audio/Export.hpp>
|
||||||
|
|
||||||
|
#include <optional>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
|
||||||
|
namespace sf::PlaybackDevice
|
||||||
|
{
|
||||||
|
////////////////////////////////////////////////////////////
|
||||||
|
/// \brief Get a list of the names of all available audio playback devices
|
||||||
|
///
|
||||||
|
/// This function returns a vector of strings containing
|
||||||
|
/// the names of all available audio playback devices.
|
||||||
|
///
|
||||||
|
/// If the operating system reports multiple devices with
|
||||||
|
/// the same name, a number will be appended to the name
|
||||||
|
/// of all subsequent devices to distinguish them from each
|
||||||
|
/// other. This guarantees that every entry returned by this
|
||||||
|
/// function will represent a unique device.
|
||||||
|
///
|
||||||
|
/// For example, if the operating system reports multiple
|
||||||
|
/// devices with the name "Sound Card", the entries returned
|
||||||
|
/// would be:
|
||||||
|
/// - Sound Card
|
||||||
|
/// - Sound Card 2
|
||||||
|
/// - Sound Card 3
|
||||||
|
/// - ...
|
||||||
|
///
|
||||||
|
/// The default device, if one is marked as such, will be
|
||||||
|
/// placed at the beginning of the vector.
|
||||||
|
///
|
||||||
|
/// If no devices are available, this function will return
|
||||||
|
/// an empty vector.
|
||||||
|
///
|
||||||
|
/// \return A vector of strings containing the device names or an empty vector if no devices are available
|
||||||
|
///
|
||||||
|
////////////////////////////////////////////////////////////
|
||||||
|
[[nodiscard]] SFML_AUDIO_API std::vector<std::string> getAvailableDevices();
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////
|
||||||
|
/// \brief Get the name of the default audio playback device
|
||||||
|
///
|
||||||
|
/// This function returns the name of the default audio
|
||||||
|
/// playback device. If none is available, an empty string
|
||||||
|
/// is returned.
|
||||||
|
///
|
||||||
|
/// \return The name of the default audio playback device
|
||||||
|
///
|
||||||
|
////////////////////////////////////////////////////////////
|
||||||
|
[[nodiscard]] SFML_AUDIO_API std::optional<std::string> getDefaultDevice();
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////
|
||||||
|
/// \brief Set the audio playback device
|
||||||
|
///
|
||||||
|
/// This function sets the audio playback device to the device
|
||||||
|
/// with the given \a name. It can be called on the fly (i.e:
|
||||||
|
/// while sounds are playing).
|
||||||
|
///
|
||||||
|
/// If there are sounds playing when the audio playback
|
||||||
|
/// device is switched, the sounds will continue playing
|
||||||
|
/// uninterrupted on the new audio playback device.
|
||||||
|
///
|
||||||
|
/// \param name The name of the audio playback device
|
||||||
|
///
|
||||||
|
/// \return True, if it was able to set the requested device
|
||||||
|
///
|
||||||
|
/// \see getAvailableDevices, getDefaultDevice
|
||||||
|
///
|
||||||
|
////////////////////////////////////////////////////////////
|
||||||
|
[[nodiscard]] SFML_AUDIO_API bool setDevice(const std::string& name);
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////
|
||||||
|
/// \brief Get the name of the current audio playback device
|
||||||
|
///
|
||||||
|
/// \return The name of the current audio playback device or std::nullopt if there is none
|
||||||
|
///
|
||||||
|
////////////////////////////////////////////////////////////
|
||||||
|
[[nodiscard]] SFML_AUDIO_API std::optional<std::string> getDevice();
|
||||||
|
|
||||||
|
} // namespace sf::PlaybackDevice
|
@ -26,16 +26,36 @@
|
|||||||
// Headers
|
// Headers
|
||||||
////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////
|
||||||
#include <SFML/Audio/AudioDevice.hpp>
|
#include <SFML/Audio/AudioDevice.hpp>
|
||||||
|
#include <SFML/Audio/PlaybackDevice.hpp>
|
||||||
|
|
||||||
#include <SFML/System/Err.hpp>
|
#include <SFML/System/Err.hpp>
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <array>
|
#include <array>
|
||||||
#include <ostream>
|
#include <ostream>
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
|
#include <cassert>
|
||||||
|
|
||||||
|
|
||||||
namespace sf::priv
|
namespace sf::priv
|
||||||
{
|
{
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
// Instead of a variable in an anonymous namespace,
|
||||||
|
// we use a function that returns a reference to a static
|
||||||
|
// variable to delay initialization of the variable as long
|
||||||
|
// as possible, i.e. until it is requested by someone.
|
||||||
|
// This also avoids static initialization order races in the
|
||||||
|
// event some other static object gets/sets the current device.
|
||||||
|
std::optional<std::string>& getCurrentDevice()
|
||||||
|
{
|
||||||
|
static std::optional<std::string> currentDevice;
|
||||||
|
return currentDevice;
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////
|
||||||
AudioDevice::AudioDevice()
|
AudioDevice::AudioDevice()
|
||||||
{
|
{
|
||||||
@ -115,72 +135,8 @@ AudioDevice::AudioDevice()
|
|||||||
if (m_context->backend == ma_backend_null)
|
if (m_context->backend == ma_backend_null)
|
||||||
err() << "Using NULL audio backend for playback" << std::endl;
|
err() << "Using NULL audio backend for playback" << std::endl;
|
||||||
|
|
||||||
// Create the playback device
|
if (!initialize())
|
||||||
m_playbackDevice.emplace();
|
err() << "Failed to initialize audio device or engine" << std::endl;
|
||||||
|
|
||||||
auto playbackDeviceConfig = ma_device_config_init(ma_device_type_playback);
|
|
||||||
playbackDeviceConfig.dataCallback = [](ma_device* device, void* output, const void*, ma_uint32 frameCount)
|
|
||||||
{
|
|
||||||
auto& audioDevice = *static_cast<AudioDevice*>(device->pUserData);
|
|
||||||
|
|
||||||
if (audioDevice.m_engine)
|
|
||||||
{
|
|
||||||
if (const auto result = ma_engine_read_pcm_frames(&*audioDevice.m_engine, output, frameCount, nullptr);
|
|
||||||
result != MA_SUCCESS)
|
|
||||||
err() << "Failed to read PCM frames from audio engine: " << ma_result_description(result) << std::endl;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
playbackDeviceConfig.pUserData = this;
|
|
||||||
playbackDeviceConfig.playback.format = ma_format_f32;
|
|
||||||
|
|
||||||
if (const auto result = ma_device_init(&*m_context, &playbackDeviceConfig, &*m_playbackDevice); result != MA_SUCCESS)
|
|
||||||
{
|
|
||||||
m_playbackDevice.reset();
|
|
||||||
err() << "Failed to initialize the audio playback device: " << ma_result_description(result) << std::endl;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create the engine
|
|
||||||
auto engineConfig = ma_engine_config_init();
|
|
||||||
engineConfig.pContext = &*m_context;
|
|
||||||
engineConfig.pDevice = &*m_playbackDevice;
|
|
||||||
engineConfig.listenerCount = 1;
|
|
||||||
|
|
||||||
m_engine.emplace();
|
|
||||||
|
|
||||||
if (const auto result = ma_engine_init(&engineConfig, &*m_engine); result != MA_SUCCESS)
|
|
||||||
{
|
|
||||||
m_engine.reset();
|
|
||||||
err() << "Failed to initialize the audio engine: " << ma_result_description(result) << std::endl;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set master volume, position, velocity, cone and world up vector
|
|
||||||
if (const auto result = ma_device_set_master_volume(ma_engine_get_device(&*m_engine),
|
|
||||||
getListenerProperties().volume * 0.01f);
|
|
||||||
result != MA_SUCCESS)
|
|
||||||
err() << "Failed to set audio device master volume: " << ma_result_description(result) << std::endl;
|
|
||||||
|
|
||||||
ma_engine_listener_set_position(&*m_engine,
|
|
||||||
0,
|
|
||||||
getListenerProperties().position.x,
|
|
||||||
getListenerProperties().position.y,
|
|
||||||
getListenerProperties().position.z);
|
|
||||||
ma_engine_listener_set_velocity(&*m_engine,
|
|
||||||
0,
|
|
||||||
getListenerProperties().velocity.x,
|
|
||||||
getListenerProperties().velocity.y,
|
|
||||||
getListenerProperties().velocity.z);
|
|
||||||
ma_engine_listener_set_cone(&*m_engine,
|
|
||||||
0,
|
|
||||||
getListenerProperties().cone.innerAngle.asRadians(),
|
|
||||||
getListenerProperties().cone.outerAngle.asRadians(),
|
|
||||||
getListenerProperties().cone.outerGain);
|
|
||||||
ma_engine_listener_set_world_up(&*m_engine,
|
|
||||||
0,
|
|
||||||
getListenerProperties().upVector.x,
|
|
||||||
getListenerProperties().upVector.y,
|
|
||||||
getListenerProperties().upVector.z);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -221,6 +177,143 @@ ma_engine* AudioDevice::getEngine()
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////
|
||||||
|
bool AudioDevice::reinitialize()
|
||||||
|
{
|
||||||
|
auto* instance = getInstance();
|
||||||
|
|
||||||
|
// We don't have to do anything if an instance doesn't exist yet
|
||||||
|
if (!instance)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
const std::lock_guard lock(instance->m_resourcesMutex);
|
||||||
|
|
||||||
|
// Deinitialize all audio resources
|
||||||
|
for (const auto& entry : instance->m_resources)
|
||||||
|
entry.deinitializeFunc(entry.resource);
|
||||||
|
|
||||||
|
// Destroy the old engine
|
||||||
|
if (instance->m_engine)
|
||||||
|
ma_engine_uninit(&*instance->m_engine);
|
||||||
|
|
||||||
|
// Destroy the old playback device
|
||||||
|
if (instance->m_playbackDevice)
|
||||||
|
ma_device_uninit(&*instance->m_playbackDevice);
|
||||||
|
|
||||||
|
// Create the new objects
|
||||||
|
const auto result = instance->initialize();
|
||||||
|
|
||||||
|
// Reinitialize all audio resources
|
||||||
|
for (const auto& entry : instance->m_resources)
|
||||||
|
entry.reinitializeFunc(entry.resource);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////
|
||||||
|
std::vector<AudioDevice::DeviceEntry> AudioDevice::getAvailableDevices()
|
||||||
|
{
|
||||||
|
const auto getDevices = [](auto& context)
|
||||||
|
{
|
||||||
|
ma_device_info* deviceInfos{};
|
||||||
|
ma_uint32 deviceCount{};
|
||||||
|
|
||||||
|
// Get the playback devices
|
||||||
|
if (const auto result = ma_context_get_devices(&context, &deviceInfos, &deviceCount, nullptr, nullptr);
|
||||||
|
result != MA_SUCCESS)
|
||||||
|
{
|
||||||
|
err() << "Failed to get audio playback devices: " << ma_result_description(result) << std::endl;
|
||||||
|
return std::vector<DeviceEntry>{};
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<DeviceEntry> deviceList;
|
||||||
|
deviceList.reserve(deviceCount);
|
||||||
|
|
||||||
|
// In order to report devices with identical names and still allow
|
||||||
|
// the user to differentiate between them when selecting, we append
|
||||||
|
// an index (number) to their name starting from the second entry
|
||||||
|
std::unordered_map<std::string, int> deviceIndices;
|
||||||
|
deviceIndices.reserve(deviceCount);
|
||||||
|
|
||||||
|
for (auto i = 0u; i < deviceCount; ++i)
|
||||||
|
{
|
||||||
|
auto name = std::string(deviceInfos[i].name);
|
||||||
|
auto& index = deviceIndices[name];
|
||||||
|
|
||||||
|
++index;
|
||||||
|
|
||||||
|
if (index > 1)
|
||||||
|
name += ' ' + std::to_string(index);
|
||||||
|
|
||||||
|
// Make sure the default device is always placed at the front
|
||||||
|
deviceList.emplace(deviceInfos[i].isDefault ? deviceList.begin() : deviceList.end(),
|
||||||
|
DeviceEntry{name, deviceInfos[i].id, deviceInfos[i].isDefault == MA_TRUE});
|
||||||
|
}
|
||||||
|
|
||||||
|
return deviceList;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Use an existing instance's context if one exists
|
||||||
|
auto* instance = getInstance();
|
||||||
|
|
||||||
|
if (instance && instance->m_context)
|
||||||
|
return getDevices(*instance->m_context);
|
||||||
|
|
||||||
|
// Otherwise, construct a temporary context
|
||||||
|
ma_context context{};
|
||||||
|
|
||||||
|
if (const auto result = ma_context_init(nullptr, 0, nullptr, &context); result != MA_SUCCESS)
|
||||||
|
{
|
||||||
|
err() << "Failed to initialize the audio playback context: " << ma_result_description(result) << std::endl;
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
auto deviceList = getDevices(context);
|
||||||
|
ma_context_uninit(&context);
|
||||||
|
return deviceList;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////
|
||||||
|
bool AudioDevice::setDevice(const std::string& name)
|
||||||
|
{
|
||||||
|
getCurrentDevice() = name;
|
||||||
|
return reinitialize();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////
|
||||||
|
std::optional<std::string> AudioDevice::getDevice()
|
||||||
|
{
|
||||||
|
return getCurrentDevice();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////
|
||||||
|
AudioDevice::ResourceEntryIter AudioDevice::registerResource(void* resource,
|
||||||
|
ResourceEntry::Func deinitializeFunc,
|
||||||
|
ResourceEntry::Func reinitializeFunc)
|
||||||
|
{
|
||||||
|
// There should always be an AudioDevice instance when registerResource is called
|
||||||
|
auto* instance = getInstance();
|
||||||
|
assert(instance && "AudioDevice instance should exist when calling AudioDevice::registerResource");
|
||||||
|
const std::lock_guard lock(instance->m_resourcesMutex);
|
||||||
|
return instance->m_resources.insert(instance->m_resources.end(), {resource, deinitializeFunc, reinitializeFunc});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////
|
||||||
|
void AudioDevice::unregisterResource(AudioDevice::ResourceEntryIter resourceEntry)
|
||||||
|
{
|
||||||
|
// There should always be an AudioDevice instance when unregisterResource is called
|
||||||
|
auto* instance = getInstance();
|
||||||
|
assert(instance && "AudioDevice instance should exist when calling AudioDevice::unregisterResource");
|
||||||
|
const std::lock_guard lock(instance->m_resourcesMutex);
|
||||||
|
instance->m_resources.erase(resourceEntry);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////
|
||||||
void AudioDevice::setGlobalVolume(float volume)
|
void AudioDevice::setGlobalVolume(float volume)
|
||||||
{
|
{
|
||||||
@ -359,6 +452,126 @@ Vector3f AudioDevice::getUpVector()
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////
|
||||||
|
std::optional<ma_device_id> AudioDevice::getSelectedDeviceId() const
|
||||||
|
{
|
||||||
|
const auto devices = getAvailableDevices();
|
||||||
|
auto deviceName = getDevice();
|
||||||
|
|
||||||
|
// If no device has been selected by the user yet, use the default device
|
||||||
|
if (!deviceName)
|
||||||
|
deviceName = PlaybackDevice::getDefaultDevice();
|
||||||
|
|
||||||
|
auto iter = std::find_if(devices.begin(),
|
||||||
|
devices.end(),
|
||||||
|
[&](const auto& device) { return device.name == deviceName; });
|
||||||
|
|
||||||
|
if (iter != devices.end())
|
||||||
|
return iter->id;
|
||||||
|
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////
|
||||||
|
bool AudioDevice::initialize()
|
||||||
|
{
|
||||||
|
const auto deviceId = getSelectedDeviceId();
|
||||||
|
|
||||||
|
// Create the playback device
|
||||||
|
m_playbackDevice.emplace();
|
||||||
|
|
||||||
|
auto playbackDeviceConfig = ma_device_config_init(ma_device_type_playback);
|
||||||
|
playbackDeviceConfig.dataCallback = [](ma_device* device, void* output, const void*, ma_uint32 frameCount)
|
||||||
|
{
|
||||||
|
auto& audioDevice = *static_cast<AudioDevice*>(device->pUserData);
|
||||||
|
|
||||||
|
if (audioDevice.m_engine)
|
||||||
|
{
|
||||||
|
if (const auto result = ma_engine_read_pcm_frames(&*audioDevice.m_engine, output, frameCount, nullptr);
|
||||||
|
result != MA_SUCCESS)
|
||||||
|
err() << "Failed to read PCM frames from audio engine: " << ma_result_description(result) << std::endl;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
playbackDeviceConfig.pUserData = this;
|
||||||
|
playbackDeviceConfig.playback.format = ma_format_f32;
|
||||||
|
playbackDeviceConfig.playback.pDeviceID = deviceId ? &*deviceId : nullptr;
|
||||||
|
|
||||||
|
if (const auto result = ma_device_init(&*m_context, &playbackDeviceConfig, &*m_playbackDevice); result != MA_SUCCESS)
|
||||||
|
{
|
||||||
|
m_playbackDevice.reset();
|
||||||
|
getCurrentDevice() = std::nullopt;
|
||||||
|
err() << "Failed to initialize the audio playback device: " << ma_result_description(result) << std::endl;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the current device string from the the device we just initialized
|
||||||
|
{
|
||||||
|
std::array<char, MA_MAX_DEVICE_NAME_LENGTH + 1> deviceName{};
|
||||||
|
size_t deviceNameLength{};
|
||||||
|
|
||||||
|
if (const auto result = ma_device_get_name(&*m_playbackDevice,
|
||||||
|
ma_device_type_playback,
|
||||||
|
deviceName.data(),
|
||||||
|
deviceName.size(),
|
||||||
|
&deviceNameLength);
|
||||||
|
result != MA_SUCCESS)
|
||||||
|
{
|
||||||
|
err() << "Failed to get name of audio playback device: " << ma_result_description(result) << std::endl;
|
||||||
|
getCurrentDevice() = std::nullopt;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
getCurrentDevice() = std::string(deviceName.data(), deviceNameLength);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the engine
|
||||||
|
auto engineConfig = ma_engine_config_init();
|
||||||
|
engineConfig.pContext = &*m_context;
|
||||||
|
engineConfig.pDevice = &*m_playbackDevice;
|
||||||
|
engineConfig.listenerCount = 1;
|
||||||
|
|
||||||
|
m_engine.emplace();
|
||||||
|
|
||||||
|
if (const auto result = ma_engine_init(&engineConfig, &*m_engine); result != MA_SUCCESS)
|
||||||
|
{
|
||||||
|
m_engine.reset();
|
||||||
|
err() << "Failed to initialize the audio engine: " << ma_result_description(result) << std::endl;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set master volume, position, velocity, cone and world up vector
|
||||||
|
if (const auto result = ma_device_set_master_volume(ma_engine_get_device(&*m_engine),
|
||||||
|
getListenerProperties().volume * 0.01f);
|
||||||
|
result != MA_SUCCESS)
|
||||||
|
err() << "Failed to set audio device master volume: " << ma_result_description(result) << std::endl;
|
||||||
|
|
||||||
|
ma_engine_listener_set_position(&*m_engine,
|
||||||
|
0,
|
||||||
|
getListenerProperties().position.x,
|
||||||
|
getListenerProperties().position.y,
|
||||||
|
getListenerProperties().position.z);
|
||||||
|
ma_engine_listener_set_velocity(&*m_engine,
|
||||||
|
0,
|
||||||
|
getListenerProperties().velocity.x,
|
||||||
|
getListenerProperties().velocity.y,
|
||||||
|
getListenerProperties().velocity.z);
|
||||||
|
ma_engine_listener_set_cone(&*m_engine,
|
||||||
|
0,
|
||||||
|
getListenerProperties().cone.innerAngle.asRadians(),
|
||||||
|
getListenerProperties().cone.outerAngle.asRadians(),
|
||||||
|
getListenerProperties().cone.outerGain);
|
||||||
|
ma_engine_listener_set_world_up(&*m_engine,
|
||||||
|
0,
|
||||||
|
getListenerProperties().upVector.x,
|
||||||
|
getListenerProperties().upVector.y,
|
||||||
|
getListenerProperties().upVector.z);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////
|
||||||
AudioDevice*& AudioDevice::getInstance()
|
AudioDevice*& AudioDevice::getInstance()
|
||||||
{
|
{
|
||||||
|
@ -33,7 +33,11 @@
|
|||||||
|
|
||||||
#include <miniaudio.h>
|
#include <miniaudio.h>
|
||||||
|
|
||||||
|
#include <list>
|
||||||
|
#include <mutex>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
|
||||||
namespace sf::priv
|
namespace sf::priv
|
||||||
@ -71,6 +75,110 @@ public:
|
|||||||
////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////
|
||||||
static ma_engine* getEngine();
|
static ma_engine* getEngine();
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////
|
||||||
|
/// \brief Reinitialize the audio engine and device
|
||||||
|
///
|
||||||
|
/// Calling this function will reinitialize the audio engine
|
||||||
|
/// and device using the currently selected device name as
|
||||||
|
/// returned by sf::PlaybackDevice::getDevice.
|
||||||
|
///
|
||||||
|
/// \return True if reinitialization was successful, false otherwise
|
||||||
|
///
|
||||||
|
////////////////////////////////////////////////////////////
|
||||||
|
[[nodiscard]] static bool reinitialize();
|
||||||
|
|
||||||
|
struct DeviceEntry
|
||||||
|
{
|
||||||
|
std::string name;
|
||||||
|
ma_device_id id{};
|
||||||
|
bool isDefault{};
|
||||||
|
};
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////
|
||||||
|
/// \brief Get a list of all available audio playback devices
|
||||||
|
///
|
||||||
|
/// This function returns a vector of device entries,
|
||||||
|
/// containing the names and IDs of all available audio
|
||||||
|
/// playback devices. Additionally, if applicable, one entry
|
||||||
|
/// will be marked as the default device as reported by the
|
||||||
|
/// operating system.
|
||||||
|
///
|
||||||
|
/// \return A vector of device entries containing the names and IDs of all available audio playback devices
|
||||||
|
///
|
||||||
|
////////////////////////////////////////////////////////////
|
||||||
|
static std::vector<DeviceEntry> getAvailableDevices();
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////
|
||||||
|
/// \brief Set the audio playback device
|
||||||
|
///
|
||||||
|
/// This function sets the audio playback device to the device
|
||||||
|
/// with the given \a name. It can be called on the fly (i.e:
|
||||||
|
/// while sounds are playing).
|
||||||
|
///
|
||||||
|
/// If there are sounds playing when the audio playback
|
||||||
|
/// device is switched, the sounds will continue playing
|
||||||
|
/// uninterrupted on the new audio playback device.
|
||||||
|
///
|
||||||
|
/// \param name The name of the audio playback device
|
||||||
|
///
|
||||||
|
/// \return True, if it was able to set the requested device
|
||||||
|
///
|
||||||
|
/// \see getAvailableDevices, getDefaultDevice
|
||||||
|
///
|
||||||
|
////////////////////////////////////////////////////////////
|
||||||
|
[[nodiscard]] static bool setDevice(const std::string& name);
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////
|
||||||
|
/// \brief Get the name of the current audio playback device
|
||||||
|
///
|
||||||
|
/// \return The name of the current audio playback device or `std::nullopt` if there is none
|
||||||
|
///
|
||||||
|
////////////////////////////////////////////////////////////
|
||||||
|
[[nodiscard]] static std::optional<std::string> getDevice();
|
||||||
|
|
||||||
|
struct ResourceEntry
|
||||||
|
{
|
||||||
|
using Func = void (*)(void*);
|
||||||
|
void* resource{};
|
||||||
|
Func deinitializeFunc{};
|
||||||
|
Func reinitializeFunc{};
|
||||||
|
};
|
||||||
|
|
||||||
|
using ResourceEntryList = std::list<ResourceEntry>;
|
||||||
|
using ResourceEntryIter = ResourceEntryList::const_iterator;
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////
|
||||||
|
/// \brief Register an audio resource
|
||||||
|
///
|
||||||
|
/// In order to support switching audio devices during
|
||||||
|
/// runtime, all audio resources will have to be
|
||||||
|
/// deinitialized using the old engine and device and then
|
||||||
|
/// reinitialized using the new engine and device. In order
|
||||||
|
/// for the AudioDevice to know which resources have to be
|
||||||
|
/// notified, they need to register themselves with the
|
||||||
|
/// AudioDevice using this function
|
||||||
|
///
|
||||||
|
/// \param resource A pointer uniquely identifying the object
|
||||||
|
/// \param deinitializeFunc The function to call to deinitialize the object
|
||||||
|
/// \param reinitializeFunc The function to call to reinitialize the object
|
||||||
|
///
|
||||||
|
/// \see unregisterResource
|
||||||
|
///
|
||||||
|
////////////////////////////////////////////////////////////
|
||||||
|
[[nodiscard]] static ResourceEntryIter registerResource(void* resource,
|
||||||
|
ResourceEntry::Func deinitializeFunc,
|
||||||
|
ResourceEntry::Func reinitializeFunc);
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////
|
||||||
|
/// \brief Unregister an audio resource
|
||||||
|
///
|
||||||
|
/// \param resourceEntry The iterator returned when registering the resource
|
||||||
|
///
|
||||||
|
/// \see registerResource
|
||||||
|
///
|
||||||
|
////////////////////////////////////////////////////////////
|
||||||
|
static void unregisterResource(ResourceEntryIter resourceEntry);
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////
|
||||||
/// \brief Change the global volume of all the sounds and musics
|
/// \brief Change the global volume of all the sounds and musics
|
||||||
///
|
///
|
||||||
@ -217,6 +325,22 @@ public:
|
|||||||
static Vector3f getUpVector();
|
static Vector3f getUpVector();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
////////////////////////////////////////////////////////////
|
||||||
|
/// \brief Get the device ID of the currently selected device
|
||||||
|
///
|
||||||
|
/// \return The device ID of the currently selected device or `std::nullopt` if none could be found
|
||||||
|
///
|
||||||
|
////////////////////////////////////////////////////////////
|
||||||
|
std::optional<ma_device_id> getSelectedDeviceId() const;
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////
|
||||||
|
/// \brief Initialize the audio device and engine
|
||||||
|
///
|
||||||
|
/// \return True if initialization was successful, false if it failed
|
||||||
|
///
|
||||||
|
////////////////////////////////////////////////////////////
|
||||||
|
[[nodiscard]] bool initialize();
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////
|
||||||
/// \brief This function makes sure the instance pointer is initialized before using it
|
/// \brief This function makes sure the instance pointer is initialized before using it
|
||||||
///
|
///
|
||||||
@ -250,6 +374,8 @@ private:
|
|||||||
std::optional<ma_context> m_context; //!< The miniaudio context
|
std::optional<ma_context> m_context; //!< The miniaudio context
|
||||||
std::optional<ma_device> m_playbackDevice; //!< The miniaudio playback device
|
std::optional<ma_device> m_playbackDevice; //!< The miniaudio playback device
|
||||||
std::optional<ma_engine> m_engine; //!< The miniaudio engine (used for effects and spatialisation)
|
std::optional<ma_engine> m_engine; //!< The miniaudio engine (used for effects and spatialisation)
|
||||||
|
ResourceEntryList m_resources; //!< Registered resources
|
||||||
|
std::mutex m_resourcesMutex; //!< The mutex guarding the registered resources
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace sf::priv
|
} // namespace sf::priv
|
||||||
|
@ -15,6 +15,8 @@ set(SRC
|
|||||||
${SRCROOT}/MiniaudioUtils.cpp
|
${SRCROOT}/MiniaudioUtils.cpp
|
||||||
${SRCROOT}/Music.cpp
|
${SRCROOT}/Music.cpp
|
||||||
${INCROOT}/Music.hpp
|
${INCROOT}/Music.hpp
|
||||||
|
${SRCROOT}/PlaybackDevice.cpp
|
||||||
|
${INCROOT}/PlaybackDevice.hpp
|
||||||
${SRCROOT}/Sound.cpp
|
${SRCROOT}/Sound.cpp
|
||||||
${INCROOT}/Sound.hpp
|
${INCROOT}/Sound.hpp
|
||||||
${SRCROOT}/SoundBuffer.cpp
|
${SRCROOT}/SoundBuffer.cpp
|
||||||
|
@ -25,59 +25,33 @@
|
|||||||
////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////
|
||||||
// Headers
|
// Headers
|
||||||
////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////
|
||||||
|
#include <SFML/Audio/AudioDevice.hpp>
|
||||||
#include <SFML/Audio/MiniaudioUtils.hpp>
|
#include <SFML/Audio/MiniaudioUtils.hpp>
|
||||||
#include <SFML/Audio/SoundChannel.hpp>
|
#include <SFML/Audio/SoundChannel.hpp>
|
||||||
|
|
||||||
#include <SFML/System/Angle.hpp>
|
|
||||||
#include <SFML/System/Err.hpp>
|
#include <SFML/System/Err.hpp>
|
||||||
#include <SFML/System/Time.hpp>
|
#include <SFML/System/Time.hpp>
|
||||||
|
|
||||||
#include <miniaudio.h>
|
#include <miniaudio.h>
|
||||||
|
|
||||||
#include <functional>
|
|
||||||
#include <limits>
|
|
||||||
#include <ostream>
|
#include <ostream>
|
||||||
|
|
||||||
#include <cassert>
|
#include <cassert>
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
|
|
||||||
namespace sf::priv
|
|
||||||
{
|
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////
|
||||||
struct SavedSettings
|
sf::priv::MiniaudioUtils::SavedSettings saveSettings(const ma_sound& sound)
|
||||||
{
|
|
||||||
float pitch{1.f};
|
|
||||||
float pan{0.f};
|
|
||||||
float volume{1.f};
|
|
||||||
ma_bool32 spatializationEnabled{MA_TRUE};
|
|
||||||
ma_vec3f position{0.f, 0.f, 0.f};
|
|
||||||
ma_vec3f direction{0.f, 0.f, -1.f};
|
|
||||||
float directionalAttenuationFactor{1.f};
|
|
||||||
ma_vec3f velocity{0.f, 0.f, 0.f};
|
|
||||||
float dopplerFactor{1.f};
|
|
||||||
ma_positioning positioning{ma_positioning_absolute};
|
|
||||||
float minDistance{1.f};
|
|
||||||
float maxDistance{std::numeric_limits<float>::max()};
|
|
||||||
float minGain{0.f};
|
|
||||||
float maxGain{1.f};
|
|
||||||
float rollOff{1.f};
|
|
||||||
float innerAngle{degrees(360.f).asRadians()};
|
|
||||||
float outerAngle{degrees(360.f).asRadians()};
|
|
||||||
float outerGain{0.f};
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////
|
|
||||||
SavedSettings saveSettings(const ma_sound& sound)
|
|
||||||
{
|
{
|
||||||
float innerAngle = 0;
|
float innerAngle = 0;
|
||||||
float outerAngle = 0;
|
float outerAngle = 0;
|
||||||
float outerGain = 0;
|
float outerGain = 0;
|
||||||
ma_sound_get_cone(&sound, &innerAngle, &outerAngle, &outerGain);
|
ma_sound_get_cone(&sound, &innerAngle, &outerAngle, &outerGain);
|
||||||
|
|
||||||
return SavedSettings{ma_sound_get_pitch(&sound),
|
return sf::priv::MiniaudioUtils::
|
||||||
|
SavedSettings{ma_sound_get_pitch(&sound),
|
||||||
ma_sound_get_pan(&sound),
|
ma_sound_get_pan(&sound),
|
||||||
ma_sound_get_volume(&sound),
|
ma_sound_get_volume(&sound),
|
||||||
ma_sound_is_spatialization_enabled(&sound),
|
ma_sound_is_spatialization_enabled(&sound),
|
||||||
@ -92,6 +66,8 @@ SavedSettings saveSettings(const ma_sound& sound)
|
|||||||
ma_sound_get_min_gain(&sound),
|
ma_sound_get_min_gain(&sound),
|
||||||
ma_sound_get_max_gain(&sound),
|
ma_sound_get_max_gain(&sound),
|
||||||
ma_sound_get_rolloff(&sound),
|
ma_sound_get_rolloff(&sound),
|
||||||
|
ma_sound_is_playing(&sound),
|
||||||
|
ma_sound_is_looping(&sound),
|
||||||
innerAngle,
|
innerAngle,
|
||||||
outerAngle,
|
outerAngle,
|
||||||
outerGain};
|
outerGain};
|
||||||
@ -99,7 +75,7 @@ SavedSettings saveSettings(const ma_sound& sound)
|
|||||||
|
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////
|
||||||
void applySettings(ma_sound& sound, const SavedSettings& savedSettings)
|
void applySettings(ma_sound& sound, const sf::priv::MiniaudioUtils::SavedSettings& savedSettings)
|
||||||
{
|
{
|
||||||
ma_sound_set_pitch(&sound, savedSettings.pitch);
|
ma_sound_set_pitch(&sound, savedSettings.pitch);
|
||||||
ma_sound_set_pan(&sound, savedSettings.pan);
|
ma_sound_set_pan(&sound, savedSettings.pan);
|
||||||
@ -116,22 +92,188 @@ void applySettings(ma_sound& sound, const SavedSettings& savedSettings)
|
|||||||
ma_sound_set_min_gain(&sound, savedSettings.minGain);
|
ma_sound_set_min_gain(&sound, savedSettings.minGain);
|
||||||
ma_sound_set_max_gain(&sound, savedSettings.maxGain);
|
ma_sound_set_max_gain(&sound, savedSettings.maxGain);
|
||||||
ma_sound_set_rolloff(&sound, savedSettings.rollOff);
|
ma_sound_set_rolloff(&sound, savedSettings.rollOff);
|
||||||
|
ma_sound_set_looping(&sound, savedSettings.looping);
|
||||||
|
|
||||||
ma_sound_set_cone(&sound, savedSettings.innerAngle, savedSettings.outerAngle, savedSettings.outerGain);
|
ma_sound_set_cone(&sound, savedSettings.innerAngle, savedSettings.outerAngle, savedSettings.outerGain);
|
||||||
|
|
||||||
|
if (savedSettings.playing)
|
||||||
|
{
|
||||||
|
ma_sound_start(&sound);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ma_sound_stop(&sound);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
|
||||||
|
namespace sf::priv
|
||||||
|
{
|
||||||
|
////////////////////////////////////////////////////////////
|
||||||
|
MiniaudioUtils::SoundBase::SoundBase(const ma_data_source_vtable& dataSourceVTable,
|
||||||
|
AudioDevice::ResourceEntry::Func reinitializeFunc)
|
||||||
|
{
|
||||||
|
// Set this object up as a miniaudio data source
|
||||||
|
ma_data_source_config config = ma_data_source_config_init();
|
||||||
|
config.vtable = &dataSourceVTable;
|
||||||
|
|
||||||
|
if (const ma_result result = ma_data_source_init(&config, &dataSourceBase); result != MA_SUCCESS)
|
||||||
|
err() << "Failed to initialize audio data source: " << ma_result_description(result) << std::endl;
|
||||||
|
|
||||||
|
resourceEntryIter = priv::AudioDevice::registerResource(
|
||||||
|
this,
|
||||||
|
[](void* ptr) { static_cast<SoundBase*>(ptr)->deinitialize(); },
|
||||||
|
reinitializeFunc);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////
|
||||||
void initializeDataSource(ma_data_source_base& dataSourceBase, const ma_data_source_vtable& vtable)
|
MiniaudioUtils::SoundBase::~SoundBase()
|
||||||
{
|
{
|
||||||
// Set this object up as a miniaudio data source
|
priv::AudioDevice::unregisterResource(resourceEntryIter);
|
||||||
ma_data_source_config config = ma_data_source_config_init();
|
ma_sound_uninit(&sound);
|
||||||
config.vtable = &vtable;
|
ma_node_uninit(&effectNode, nullptr);
|
||||||
|
ma_data_source_uninit(&dataSourceBase);
|
||||||
if (const ma_result result = ma_data_source_init(&config, &dataSourceBase); result != MA_SUCCESS)
|
}
|
||||||
err() << "Failed to initialize audio data source: " << ma_result_description(result) << std::endl;
|
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////
|
||||||
|
void MiniaudioUtils::SoundBase::initialize(ma_sound_end_proc endCallback)
|
||||||
|
{
|
||||||
|
// Initialize the sound
|
||||||
|
auto* engine = priv::AudioDevice::getEngine();
|
||||||
|
|
||||||
|
if (engine == nullptr)
|
||||||
|
{
|
||||||
|
err() << "Failed to initialize sound: No engine available" << std::endl;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ma_sound_config soundConfig;
|
||||||
|
|
||||||
|
soundConfig = ma_sound_config_init();
|
||||||
|
soundConfig.pDataSource = this;
|
||||||
|
soundConfig.pEndCallbackUserData = this;
|
||||||
|
soundConfig.endCallback = endCallback;
|
||||||
|
|
||||||
|
if (const ma_result result = ma_sound_init_ex(engine, &soundConfig, &sound); result != MA_SUCCESS)
|
||||||
|
{
|
||||||
|
err() << "Failed to initialize sound: " << ma_result_description(result) << std::endl;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize the custom effect node
|
||||||
|
effectNodeVTable.onProcess =
|
||||||
|
[](ma_node* node, const float** framesIn, ma_uint32* frameCountIn, float** framesOut, ma_uint32* frameCountOut)
|
||||||
|
{ static_cast<EffectNode*>(node)->impl->processEffect(framesIn, *frameCountIn, framesOut, *frameCountOut); };
|
||||||
|
effectNodeVTable.onGetRequiredInputFrameCount = nullptr;
|
||||||
|
effectNodeVTable.inputBusCount = 1;
|
||||||
|
effectNodeVTable.outputBusCount = 1;
|
||||||
|
effectNodeVTable.flags = MA_NODE_FLAG_CONTINUOUS_PROCESSING | MA_NODE_FLAG_ALLOW_NULL_INPUT;
|
||||||
|
|
||||||
|
const auto nodeChannelCount = ma_engine_get_channels(engine);
|
||||||
|
ma_node_config nodeConfig = ma_node_config_init();
|
||||||
|
nodeConfig.vtable = &effectNodeVTable;
|
||||||
|
nodeConfig.pInputChannels = &nodeChannelCount;
|
||||||
|
nodeConfig.pOutputChannels = &nodeChannelCount;
|
||||||
|
|
||||||
|
if (const ma_result result = ma_node_init(ma_engine_get_node_graph(engine), &nodeConfig, nullptr, &effectNode);
|
||||||
|
result != MA_SUCCESS)
|
||||||
|
{
|
||||||
|
err() << "Failed to initialize effect node: " << ma_result_description(result) << std::endl;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
effectNode.impl = this;
|
||||||
|
effectNode.channelCount = nodeChannelCount;
|
||||||
|
|
||||||
|
// Route the sound through the effect node depending on whether an effect processor is set
|
||||||
|
connectEffect(bool{effectProcessor});
|
||||||
|
|
||||||
|
applySettings(sound, savedSettings);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////
|
||||||
|
void MiniaudioUtils::SoundBase::deinitialize()
|
||||||
|
{
|
||||||
|
savedSettings = saveSettings(sound);
|
||||||
|
ma_sound_uninit(&sound);
|
||||||
|
ma_node_uninit(&effectNode, nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////
|
||||||
|
void MiniaudioUtils::SoundBase::processEffect(const float** framesIn,
|
||||||
|
ma_uint32& frameCountIn,
|
||||||
|
float** framesOut,
|
||||||
|
ma_uint32& frameCountOut) const
|
||||||
|
{
|
||||||
|
// If a processor is set, call it
|
||||||
|
if (effectProcessor)
|
||||||
|
{
|
||||||
|
if (!framesIn)
|
||||||
|
frameCountIn = 0;
|
||||||
|
|
||||||
|
effectProcessor(framesIn ? framesIn[0] : nullptr, frameCountIn, framesOut[0], frameCountOut, effectNode.channelCount);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise just pass the data through 1:1
|
||||||
|
if (framesIn == nullptr)
|
||||||
|
{
|
||||||
|
frameCountIn = 0;
|
||||||
|
frameCountOut = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto toProcess = std::min(frameCountIn, frameCountOut);
|
||||||
|
std::memcpy(framesOut[0], framesIn[0], toProcess * effectNode.channelCount * sizeof(float));
|
||||||
|
frameCountIn = toProcess;
|
||||||
|
frameCountOut = toProcess;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////
|
||||||
|
void MiniaudioUtils::SoundBase::connectEffect(bool connect)
|
||||||
|
{
|
||||||
|
auto* engine = AudioDevice::getEngine();
|
||||||
|
|
||||||
|
if (engine == nullptr)
|
||||||
|
{
|
||||||
|
err() << "Failed to connect effect: No engine available" << std::endl;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (connect)
|
||||||
|
{
|
||||||
|
// Attach the custom effect node output to our engine endpoint
|
||||||
|
if (const ma_result result = ma_node_attach_output_bus(&effectNode, 0, ma_engine_get_endpoint(engine), 0);
|
||||||
|
result != MA_SUCCESS)
|
||||||
|
{
|
||||||
|
err() << "Failed to attach effect node output to endpoint: " << ma_result_description(result) << std::endl;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Detach the custom effect node output from our engine endpoint
|
||||||
|
if (const ma_result result = ma_node_detach_output_bus(&effectNode, 0); result != MA_SUCCESS)
|
||||||
|
{
|
||||||
|
err() << "Failed to detach effect node output from endpoint: " << ma_result_description(result) << std::endl;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attach the sound output to the custom effect node or the engine endpoint
|
||||||
|
if (const ma_result result = ma_node_attach_output_bus(&sound, 0, connect ? &effectNode : ma_engine_get_endpoint(engine), 0);
|
||||||
|
result != MA_SUCCESS)
|
||||||
|
{
|
||||||
|
err() << "Failed to attach sound node output to effect node: " << ma_result_description(result) << std::endl;
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} // namespace
|
|
||||||
|
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////
|
||||||
@ -266,30 +408,4 @@ ma_uint64 MiniaudioUtils::getFrameIndex(ma_sound& sound, Time timeOffset)
|
|||||||
return frameIndex;
|
return frameIndex;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////
|
|
||||||
void MiniaudioUtils::reinitializeSound(ma_sound& sound, const std::function<void()>& initializeFn)
|
|
||||||
{
|
|
||||||
const SavedSettings savedSettings = saveSettings(sound);
|
|
||||||
ma_sound_uninit(&sound);
|
|
||||||
|
|
||||||
initializeFn();
|
|
||||||
|
|
||||||
applySettings(sound, savedSettings);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////
|
|
||||||
void MiniaudioUtils::initializeSound(const ma_data_source_vtable& vtable,
|
|
||||||
ma_data_source_base& dataSourceBase,
|
|
||||||
ma_sound& sound,
|
|
||||||
const std::function<void()>& initializeFn)
|
|
||||||
{
|
|
||||||
initializeDataSource(dataSourceBase, vtable);
|
|
||||||
|
|
||||||
// Initialize sound structure and set default settings
|
|
||||||
initializeFn();
|
|
||||||
applySettings(sound, SavedSettings{});
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace sf::priv
|
} // namespace sf::priv
|
||||||
|
@ -27,11 +27,15 @@
|
|||||||
////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////
|
||||||
// Headers
|
// Headers
|
||||||
////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////
|
||||||
|
#include <SFML/Audio/AudioDevice.hpp>
|
||||||
#include <SFML/Audio/SoundChannel.hpp>
|
#include <SFML/Audio/SoundChannel.hpp>
|
||||||
|
#include <SFML/Audio/SoundSource.hpp>
|
||||||
|
|
||||||
|
#include <SFML/System/Angle.hpp>
|
||||||
|
|
||||||
#include <miniaudio.h>
|
#include <miniaudio.h>
|
||||||
|
|
||||||
#include <functional>
|
#include <limits>
|
||||||
|
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////
|
||||||
@ -44,15 +48,64 @@ class Time;
|
|||||||
|
|
||||||
namespace priv::MiniaudioUtils
|
namespace priv::MiniaudioUtils
|
||||||
{
|
{
|
||||||
|
struct SavedSettings
|
||||||
|
{
|
||||||
|
float pitch{1.f};
|
||||||
|
float pan{0.f};
|
||||||
|
float volume{1.f};
|
||||||
|
ma_bool32 spatializationEnabled{MA_TRUE};
|
||||||
|
ma_vec3f position{0.f, 0.f, 0.f};
|
||||||
|
ma_vec3f direction{0.f, 0.f, -1.f};
|
||||||
|
float directionalAttenuationFactor{1.f};
|
||||||
|
ma_vec3f velocity{0.f, 0.f, 0.f};
|
||||||
|
float dopplerFactor{1.f};
|
||||||
|
ma_positioning positioning{ma_positioning_absolute};
|
||||||
|
float minDistance{1.f};
|
||||||
|
float maxDistance{std::numeric_limits<float>::max()};
|
||||||
|
float minGain{0.f};
|
||||||
|
float maxGain{1.f};
|
||||||
|
float rollOff{1.f};
|
||||||
|
ma_bool32 playing{MA_FALSE};
|
||||||
|
ma_bool32 looping{MA_FALSE};
|
||||||
|
float innerAngle{degrees(360.f).asRadians()};
|
||||||
|
float outerAngle{degrees(360.f).asRadians()};
|
||||||
|
float outerGain{0.f};
|
||||||
|
};
|
||||||
|
|
||||||
|
struct SoundBase
|
||||||
|
{
|
||||||
|
SoundBase(const ma_data_source_vtable& dataSourceVTable, AudioDevice::ResourceEntry::Func reinitializeFunc);
|
||||||
|
~SoundBase();
|
||||||
|
void initialize(ma_sound_end_proc endCallback);
|
||||||
|
void deinitialize();
|
||||||
|
void processEffect(const float** framesIn, ma_uint32& frameCountIn, float** framesOut, ma_uint32& frameCountOut) const;
|
||||||
|
void connectEffect(bool connect);
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////
|
||||||
|
// Member data
|
||||||
|
////////////////////////////////////////////////////////////
|
||||||
|
struct EffectNode
|
||||||
|
{
|
||||||
|
ma_node_base base{};
|
||||||
|
SoundBase* impl{};
|
||||||
|
ma_uint32 channelCount{};
|
||||||
|
};
|
||||||
|
|
||||||
|
ma_data_source_base dataSourceBase{}; //!< The struct that makes this object a miniaudio data source (must be first member)
|
||||||
|
ma_node_vtable effectNodeVTable{}; //!< Vtable of the effect node
|
||||||
|
EffectNode effectNode; //!< The engine node that performs effect processing
|
||||||
|
std::vector<ma_channel> soundChannelMap; //!< The map of position in sample frame to sound channel (miniaudio channels)
|
||||||
|
ma_sound sound{}; //!< The sound
|
||||||
|
SoundSource::Status status{SoundSource::Status::Stopped}; //!< The status
|
||||||
|
SoundSource::EffectProcessor effectProcessor; //!< The effect processor
|
||||||
|
priv::AudioDevice::ResourceEntryIter resourceEntryIter; //!< Iterator to the resource entry registered with the AudioDevice
|
||||||
|
priv::MiniaudioUtils::SavedSettings savedSettings; //!< Saved settings used to restore ma_sound state in case we need to recreate it
|
||||||
|
};
|
||||||
|
|
||||||
[[nodiscard]] ma_channel soundChannelToMiniaudioChannel(SoundChannel soundChannel);
|
[[nodiscard]] ma_channel soundChannelToMiniaudioChannel(SoundChannel soundChannel);
|
||||||
[[nodiscard]] SoundChannel miniaudioChannelToSoundChannel(ma_channel soundChannel);
|
[[nodiscard]] SoundChannel miniaudioChannelToSoundChannel(ma_channel soundChannel);
|
||||||
[[nodiscard]] Time getPlayingOffset(ma_sound& sound);
|
[[nodiscard]] Time getPlayingOffset(ma_sound& sound);
|
||||||
[[nodiscard]] ma_uint64 getFrameIndex(ma_sound& sound, Time timeOffset);
|
[[nodiscard]] ma_uint64 getFrameIndex(ma_sound& sound, Time timeOffset);
|
||||||
|
|
||||||
void reinitializeSound(ma_sound& sound, const std::function<void()>& initializeFn);
|
|
||||||
void initializeSound(const ma_data_source_vtable& vtable,
|
|
||||||
ma_data_source_base& dataSourceBase,
|
|
||||||
ma_sound& sound,
|
|
||||||
const std::function<void()>& initializeFn);
|
|
||||||
} // namespace priv::MiniaudioUtils
|
} // namespace priv::MiniaudioUtils
|
||||||
} // namespace sf
|
} // namespace sf
|
||||||
|
83
src/SFML/Audio/PlaybackDevice.cpp
Normal file
83
src/SFML/Audio/PlaybackDevice.cpp
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
////////////////////////////////////////////////////////////
|
||||||
|
//
|
||||||
|
// SFML - Simple and Fast Multimedia Library
|
||||||
|
// Copyright (C) 2007-2024 Laurent Gomila (laurent@sfml-dev.org)
|
||||||
|
//
|
||||||
|
// This software is provided 'as-is', without any express or implied warranty.
|
||||||
|
// In no event will the authors be held liable for any damages arising from the use of this software.
|
||||||
|
//
|
||||||
|
// Permission is granted to anyone to use this software for any purpose,
|
||||||
|
// including commercial applications, and to alter it and redistribute it freely,
|
||||||
|
// subject to the following restrictions:
|
||||||
|
//
|
||||||
|
// 1. The origin of this software must not be misrepresented;
|
||||||
|
// you must not claim that you wrote the original software.
|
||||||
|
// If you use this software in a product, an acknowledgment
|
||||||
|
// in the product documentation would be appreciated but is not required.
|
||||||
|
//
|
||||||
|
// 2. Altered source versions must be plainly marked as such,
|
||||||
|
// and must not be misrepresented as being the original software.
|
||||||
|
//
|
||||||
|
// 3. This notice may not be removed or altered from any source distribution.
|
||||||
|
//
|
||||||
|
////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////
|
||||||
|
// Headers
|
||||||
|
////////////////////////////////////////////////////////////
|
||||||
|
#include <SFML/Audio/AudioDevice.hpp>
|
||||||
|
#include <SFML/Audio/PlaybackDevice.hpp>
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
|
|
||||||
|
namespace sf::PlaybackDevice
|
||||||
|
{
|
||||||
|
////////////////////////////////////////////////////////////
|
||||||
|
std::vector<std::string> getAvailableDevices()
|
||||||
|
{
|
||||||
|
const auto devices = priv::AudioDevice::getAvailableDevices();
|
||||||
|
|
||||||
|
std::vector<std::string> deviceNameList;
|
||||||
|
deviceNameList.reserve(devices.size());
|
||||||
|
|
||||||
|
for (const auto& device : devices)
|
||||||
|
deviceNameList.emplace_back(device.name);
|
||||||
|
|
||||||
|
return deviceNameList;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////
|
||||||
|
std::optional<std::string> getDefaultDevice()
|
||||||
|
{
|
||||||
|
for (const auto& device : priv::AudioDevice::getAvailableDevices())
|
||||||
|
{
|
||||||
|
if (device.isDefault)
|
||||||
|
return device.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////
|
||||||
|
bool setDevice(const std::string& name)
|
||||||
|
{
|
||||||
|
// Perform a sanity check to make sure the user isn't passing us a non-existant device name
|
||||||
|
const auto devices = priv::AudioDevice::getAvailableDevices();
|
||||||
|
if (auto iter = std::find_if(devices.begin(), devices.end(), [&](const auto& device) { return device.name == name; });
|
||||||
|
iter == devices.end())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return priv::AudioDevice::setDevice(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////
|
||||||
|
std::optional<std::string> getDevice()
|
||||||
|
{
|
||||||
|
return priv::AudioDevice::getDevice();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace sf::PlaybackDevice
|
@ -25,7 +25,6 @@
|
|||||||
////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////
|
||||||
// Headers
|
// Headers
|
||||||
////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////
|
||||||
#include <SFML/Audio/AudioDevice.hpp>
|
|
||||||
#include <SFML/Audio/MiniaudioUtils.hpp>
|
#include <SFML/Audio/MiniaudioUtils.hpp>
|
||||||
#include <SFML/Audio/Sound.hpp>
|
#include <SFML/Audio/Sound.hpp>
|
||||||
#include <SFML/Audio/SoundBuffer.hpp>
|
#include <SFML/Audio/SoundBuffer.hpp>
|
||||||
@ -44,80 +43,17 @@
|
|||||||
|
|
||||||
namespace sf
|
namespace sf
|
||||||
{
|
{
|
||||||
struct Sound::Impl
|
struct Sound::Impl : priv::MiniaudioUtils::SoundBase
|
||||||
{
|
{
|
||||||
Impl()
|
Impl() : SoundBase(vtable, [](void* ptr) { static_cast<Impl*>(ptr)->initialize(); })
|
||||||
{
|
{
|
||||||
static constexpr ma_data_source_vtable vtable{read, seek, getFormat, getCursor, getLength, setLooping, 0};
|
// Initialize sound structure and set default settings
|
||||||
priv::MiniaudioUtils::initializeSound(vtable, dataSourceBase, sound, [this] { initialize(); });
|
initialize();
|
||||||
}
|
|
||||||
|
|
||||||
~Impl()
|
|
||||||
{
|
|
||||||
ma_sound_uninit(&sound);
|
|
||||||
ma_node_uninit(&effectNode, nullptr);
|
|
||||||
ma_data_source_uninit(&dataSourceBase);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void initialize()
|
void initialize()
|
||||||
{
|
{
|
||||||
// Initialize the sound
|
SoundBase::initialize(onEnd);
|
||||||
auto* engine = priv::AudioDevice::getEngine();
|
|
||||||
|
|
||||||
if (engine == nullptr)
|
|
||||||
{
|
|
||||||
err() << "Failed to initialize sound: No engine available" << std::endl;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
ma_sound_config soundConfig;
|
|
||||||
|
|
||||||
soundConfig = ma_sound_config_init();
|
|
||||||
soundConfig.pDataSource = this;
|
|
||||||
soundConfig.pEndCallbackUserData = this;
|
|
||||||
soundConfig.endCallback = [](void* userData, ma_sound* soundPtr)
|
|
||||||
{
|
|
||||||
auto& impl = *static_cast<Impl*>(userData);
|
|
||||||
impl.status = Status::Stopped;
|
|
||||||
|
|
||||||
// Seek back to the start of the sound when it finishes playing
|
|
||||||
if (const ma_result result = ma_sound_seek_to_pcm_frame(soundPtr, 0); result != MA_SUCCESS)
|
|
||||||
err() << "Failed to seek sound to frame 0: " << ma_result_description(result) << std::endl;
|
|
||||||
};
|
|
||||||
|
|
||||||
if (const ma_result result = ma_sound_init_ex(engine, &soundConfig, &sound); result != MA_SUCCESS)
|
|
||||||
{
|
|
||||||
err() << "Failed to initialize sound: " << ma_result_description(result) << std::endl;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialize the custom effect node
|
|
||||||
effectNodeVTable.onProcess =
|
|
||||||
[](ma_node* node, const float** framesIn, ma_uint32* frameCountIn, float** framesOut, ma_uint32* frameCountOut)
|
|
||||||
{ static_cast<EffectNode*>(node)->impl->processEffect(framesIn, *frameCountIn, framesOut, *frameCountOut); };
|
|
||||||
effectNodeVTable.onGetRequiredInputFrameCount = nullptr;
|
|
||||||
effectNodeVTable.inputBusCount = 1;
|
|
||||||
effectNodeVTable.outputBusCount = 1;
|
|
||||||
effectNodeVTable.flags = MA_NODE_FLAG_CONTINUOUS_PROCESSING | MA_NODE_FLAG_ALLOW_NULL_INPUT;
|
|
||||||
|
|
||||||
const auto nodeChannelCount = ma_engine_get_channels(engine);
|
|
||||||
ma_node_config nodeConfig = ma_node_config_init();
|
|
||||||
nodeConfig.vtable = &effectNodeVTable;
|
|
||||||
nodeConfig.pInputChannels = &nodeChannelCount;
|
|
||||||
nodeConfig.pOutputChannels = &nodeChannelCount;
|
|
||||||
|
|
||||||
if (const ma_result result = ma_node_init(ma_engine_get_node_graph(engine), &nodeConfig, nullptr, &effectNode);
|
|
||||||
result != MA_SUCCESS)
|
|
||||||
{
|
|
||||||
err() << "Failed to initialize effect node: " << ma_result_description(result) << std::endl;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
effectNode.impl = this;
|
|
||||||
effectNode.channelCount = nodeChannelCount;
|
|
||||||
|
|
||||||
// Route the sound through the effect node depending on whether an effect processor is set
|
|
||||||
connectEffect(bool{effectProcessor});
|
|
||||||
|
|
||||||
// Because we are providing a custom data source, we have to provide the channel map ourselves
|
// Because we are providing a custom data source, we have to provide the channel map ourselves
|
||||||
if (buffer && !buffer->getChannelMap().empty())
|
if (buffer && !buffer->getChannelMap().empty())
|
||||||
@ -137,82 +73,14 @@ struct Sound::Impl
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void reinitialize()
|
static void onEnd(void* userData, ma_sound* soundPtr)
|
||||||
{
|
{
|
||||||
priv::MiniaudioUtils::reinitializeSound(sound,
|
auto& impl = *static_cast<Impl*>(userData);
|
||||||
[this]
|
impl.status = Status::Stopped;
|
||||||
{
|
|
||||||
ma_node_uninit(&effectNode, nullptr);
|
|
||||||
initialize();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void processEffect(const float** framesIn, ma_uint32& frameCountIn, float** framesOut, ma_uint32& frameCountOut) const
|
// Seek back to the start of the sound when it finishes playing
|
||||||
{
|
if (const ma_result result = ma_sound_seek_to_pcm_frame(soundPtr, 0); result != MA_SUCCESS)
|
||||||
// If a processor is set, call it
|
err() << "Failed to seek sound to frame 0: " << ma_result_description(result) << std::endl;
|
||||||
if (effectProcessor)
|
|
||||||
{
|
|
||||||
if (!framesIn)
|
|
||||||
frameCountIn = 0;
|
|
||||||
|
|
||||||
effectProcessor(framesIn ? framesIn[0] : nullptr, frameCountIn, framesOut[0], frameCountOut, effectNode.channelCount);
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Otherwise just pass the data through 1:1
|
|
||||||
if (framesIn == nullptr)
|
|
||||||
{
|
|
||||||
frameCountIn = 0;
|
|
||||||
frameCountOut = 0;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto toProcess = std::min(frameCountIn, frameCountOut);
|
|
||||||
std::memcpy(framesOut[0], framesIn[0], toProcess * effectNode.channelCount * sizeof(float));
|
|
||||||
frameCountIn = toProcess;
|
|
||||||
frameCountOut = toProcess;
|
|
||||||
}
|
|
||||||
|
|
||||||
void connectEffect(bool connect)
|
|
||||||
{
|
|
||||||
auto* engine = priv::AudioDevice::getEngine();
|
|
||||||
|
|
||||||
if (engine == nullptr)
|
|
||||||
{
|
|
||||||
err() << "Failed to connect effect: No engine available" << std::endl;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (connect)
|
|
||||||
{
|
|
||||||
// Attach the custom effect node output to our engine endpoint
|
|
||||||
if (const ma_result result = ma_node_attach_output_bus(&effectNode, 0, ma_engine_get_endpoint(engine), 0);
|
|
||||||
result != MA_SUCCESS)
|
|
||||||
{
|
|
||||||
err() << "Failed to attach effect node output to endpoint: " << ma_result_description(result) << std::endl;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Detach the custom effect node output from our engine endpoint
|
|
||||||
if (const ma_result result = ma_node_detach_output_bus(&effectNode, 0); result != MA_SUCCESS)
|
|
||||||
{
|
|
||||||
err() << "Failed to detach effect node output from endpoint: " << ma_result_description(result)
|
|
||||||
<< std::endl;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Attach the sound output to the custom effect node or the engine endpoint
|
|
||||||
if (const ma_result
|
|
||||||
result = ma_node_attach_output_bus(&sound, 0, connect ? &effectNode : ma_engine_get_endpoint(engine), 0);
|
|
||||||
result != MA_SUCCESS)
|
|
||||||
{
|
|
||||||
err() << "Failed to attach sound node output to effect node: " << ma_result_description(result) << std::endl;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static ma_result read(ma_data_source* dataSource, void* framesOut, ma_uint64 frameCount, ma_uint64* framesRead)
|
static ma_result read(ma_data_source* dataSource, void* framesOut, ma_uint64 frameCount, ma_uint64* framesRead)
|
||||||
@ -310,23 +178,10 @@ struct Sound::Impl
|
|||||||
////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////
|
||||||
// Member data
|
// Member data
|
||||||
////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////
|
||||||
struct EffectNode
|
static constexpr ma_data_source_vtable vtable{read, seek, getFormat, getCursor, getLength, setLooping, 0};
|
||||||
{
|
|
||||||
ma_node_base base{};
|
|
||||||
Impl* impl{};
|
|
||||||
ma_uint32 channelCount{};
|
|
||||||
};
|
|
||||||
|
|
||||||
ma_data_source_base dataSourceBase{}; //!< The struct that makes this object a miniaudio data source (must be first member)
|
|
||||||
ma_node_vtable effectNodeVTable{}; //!< Vtable of the effect node
|
|
||||||
EffectNode effectNode; //!< The engine node that performs effect processing
|
|
||||||
std::vector<ma_channel> soundChannelMap; //!< The map of position in sample frame to sound channel (miniaudio channels)
|
|
||||||
ma_sound sound{}; //!< The sound
|
|
||||||
std::size_t cursor{}; //!< The current playing position
|
std::size_t cursor{}; //!< The current playing position
|
||||||
bool looping{}; //!< True if we are looping the sound
|
bool looping{}; //!< True if we are looping the sound
|
||||||
const SoundBuffer* buffer{}; //!< Sound buffer bound to the source
|
const SoundBuffer* buffer{}; //!< Sound buffer bound to the source
|
||||||
Status status{Status::Stopped}; //!< The status
|
|
||||||
EffectProcessor effectProcessor; //!< The effect processor
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
@ -422,7 +277,8 @@ void Sound::setBuffer(const SoundBuffer& buffer)
|
|||||||
m_impl->buffer = &buffer;
|
m_impl->buffer = &buffer;
|
||||||
m_impl->buffer->attachSound(this);
|
m_impl->buffer->attachSound(this);
|
||||||
|
|
||||||
m_impl->reinitialize();
|
m_impl->deinitialize();
|
||||||
|
m_impl->initialize();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -25,7 +25,6 @@
|
|||||||
////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////
|
||||||
// Headers
|
// Headers
|
||||||
////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////
|
||||||
#include <SFML/Audio/AudioDevice.hpp>
|
|
||||||
#include <SFML/Audio/MiniaudioUtils.hpp>
|
#include <SFML/Audio/MiniaudioUtils.hpp>
|
||||||
#include <SFML/Audio/SoundStream.hpp>
|
#include <SFML/Audio/SoundStream.hpp>
|
||||||
|
|
||||||
@ -44,81 +43,19 @@
|
|||||||
|
|
||||||
namespace sf
|
namespace sf
|
||||||
{
|
{
|
||||||
struct SoundStream::Impl
|
struct SoundStream::Impl : priv::MiniaudioUtils::SoundBase
|
||||||
{
|
{
|
||||||
Impl(SoundStream* ownerPtr) : owner(ownerPtr)
|
Impl(SoundStream* ownerPtr) :
|
||||||
|
SoundBase(vtable, [](void* ptr) { static_cast<Impl*>(ptr)->initialize(); }),
|
||||||
|
owner(ownerPtr)
|
||||||
{
|
{
|
||||||
static constexpr ma_data_source_vtable vtable{read, seek, getFormat, getCursor, getLength, setLooping, /* flags */ 0};
|
// Initialize sound structure and set default settings
|
||||||
priv::MiniaudioUtils::initializeSound(vtable, dataSourceBase, sound, [this] { initialize(); });
|
initialize();
|
||||||
}
|
|
||||||
|
|
||||||
~Impl()
|
|
||||||
{
|
|
||||||
ma_sound_uninit(&sound);
|
|
||||||
ma_node_uninit(&effectNode, nullptr);
|
|
||||||
ma_data_source_uninit(&dataSourceBase);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void initialize()
|
void initialize()
|
||||||
{
|
{
|
||||||
// Initialize the sound
|
SoundBase::initialize(onEnd);
|
||||||
auto* engine = priv::AudioDevice::getEngine();
|
|
||||||
|
|
||||||
if (engine == nullptr)
|
|
||||||
{
|
|
||||||
err() << "Failed to initialize sound: No engine available" << std::endl;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
ma_sound_config soundConfig;
|
|
||||||
|
|
||||||
soundConfig = ma_sound_config_init();
|
|
||||||
soundConfig.pDataSource = this;
|
|
||||||
soundConfig.pEndCallbackUserData = this;
|
|
||||||
soundConfig.endCallback = [](void* userData, ma_sound* soundPtr)
|
|
||||||
{
|
|
||||||
// Seek back to the start of the sound when it finishes playing
|
|
||||||
auto& impl = *static_cast<Impl*>(userData);
|
|
||||||
impl.streaming = true;
|
|
||||||
impl.status = Status::Stopped;
|
|
||||||
|
|
||||||
if (const ma_result result = ma_sound_seek_to_pcm_frame(soundPtr, 0); result != MA_SUCCESS)
|
|
||||||
err() << "Failed to seek sound to frame 0: " << ma_result_description(result) << std::endl;
|
|
||||||
};
|
|
||||||
|
|
||||||
if (const ma_result result = ma_sound_init_ex(engine, &soundConfig, &sound); result != MA_SUCCESS)
|
|
||||||
{
|
|
||||||
err() << "Failed to initialize sound: " << ma_result_description(result) << std::endl;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialize the custom effect node
|
|
||||||
effectNodeVTable.onProcess =
|
|
||||||
[](ma_node* node, const float** framesIn, ma_uint32* frameCountIn, float** framesOut, ma_uint32* frameCountOut)
|
|
||||||
{ static_cast<EffectNode*>(node)->impl->processEffect(framesIn, *frameCountIn, framesOut, *frameCountOut); };
|
|
||||||
effectNodeVTable.onGetRequiredInputFrameCount = nullptr;
|
|
||||||
effectNodeVTable.inputBusCount = 1;
|
|
||||||
effectNodeVTable.outputBusCount = 1;
|
|
||||||
effectNodeVTable.flags = MA_NODE_FLAG_CONTINUOUS_PROCESSING | MA_NODE_FLAG_ALLOW_NULL_INPUT;
|
|
||||||
|
|
||||||
const auto nodeChannelCount = ma_engine_get_channels(engine);
|
|
||||||
ma_node_config nodeConfig = ma_node_config_init();
|
|
||||||
nodeConfig.vtable = &effectNodeVTable;
|
|
||||||
nodeConfig.pInputChannels = &nodeChannelCount;
|
|
||||||
nodeConfig.pOutputChannels = &nodeChannelCount;
|
|
||||||
|
|
||||||
if (const ma_result result = ma_node_init(ma_engine_get_node_graph(engine), &nodeConfig, nullptr, &effectNode);
|
|
||||||
result != MA_SUCCESS)
|
|
||||||
{
|
|
||||||
err() << "Failed to initialize effect node: " << ma_result_description(result) << std::endl;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
effectNode.impl = this;
|
|
||||||
effectNode.channelCount = nodeChannelCount;
|
|
||||||
|
|
||||||
// Route the sound through the effect node depending on whether an effect processor is set
|
|
||||||
connectEffect(bool{effectProcessor});
|
|
||||||
|
|
||||||
// Because we are providing a custom data source, we have to provide the channel map ourselves
|
// Because we are providing a custom data source, we have to provide the channel map ourselves
|
||||||
if (!channelMap.empty())
|
if (!channelMap.empty())
|
||||||
@ -138,81 +75,15 @@ struct SoundStream::Impl
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void reinitialize()
|
static void onEnd(void* userData, ma_sound* soundPtr)
|
||||||
{
|
{
|
||||||
priv::MiniaudioUtils::reinitializeSound(sound,
|
// Seek back to the start of the sound when it finishes playing
|
||||||
[this]
|
auto& impl = *static_cast<Impl*>(userData);
|
||||||
{
|
impl.streaming = true;
|
||||||
ma_node_uninit(&effectNode, nullptr);
|
impl.status = Status::Stopped;
|
||||||
initialize();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void processEffect(const float** framesIn, ma_uint32& frameCountIn, float** framesOut, ma_uint32& frameCountOut) const
|
if (const ma_result result = ma_sound_seek_to_pcm_frame(soundPtr, 0); result != MA_SUCCESS)
|
||||||
{
|
err() << "Failed to seek sound to frame 0: " << ma_result_description(result) << std::endl;
|
||||||
// If a processor is set, call it
|
|
||||||
if (effectProcessor)
|
|
||||||
{
|
|
||||||
if (!framesIn)
|
|
||||||
frameCountIn = 0;
|
|
||||||
|
|
||||||
effectProcessor(framesIn ? framesIn[0] : nullptr, frameCountIn, framesOut[0], frameCountOut, effectNode.channelCount);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Otherwise just pass the data through 1:1
|
|
||||||
if (framesIn == nullptr)
|
|
||||||
{
|
|
||||||
frameCountIn = 0;
|
|
||||||
frameCountOut = 0;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto toProcess = std::min(frameCountIn, frameCountOut);
|
|
||||||
std::memcpy(framesOut[0], framesIn[0], toProcess * effectNode.channelCount * sizeof(float));
|
|
||||||
frameCountIn = toProcess;
|
|
||||||
frameCountOut = toProcess;
|
|
||||||
}
|
|
||||||
|
|
||||||
void connectEffect(bool connect)
|
|
||||||
{
|
|
||||||
auto* engine = priv::AudioDevice::getEngine();
|
|
||||||
|
|
||||||
if (engine == nullptr)
|
|
||||||
{
|
|
||||||
err() << "Failed to connect effect: No engine available" << std::endl;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (connect)
|
|
||||||
{
|
|
||||||
// Attach the custom effect node output to our engine endpoint
|
|
||||||
if (const ma_result result = ma_node_attach_output_bus(&effectNode, 0, ma_engine_get_endpoint(engine), 0);
|
|
||||||
result != MA_SUCCESS)
|
|
||||||
{
|
|
||||||
err() << "Failed to attach effect node output to endpoint: " << ma_result_description(result) << std::endl;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Detach the custom effect node output from our engine endpoint
|
|
||||||
if (const ma_result result = ma_node_detach_output_bus(&effectNode, 0); result != MA_SUCCESS)
|
|
||||||
{
|
|
||||||
err() << "Failed to detach effect node output from endpoint: " << ma_result_description(result)
|
|
||||||
<< std::endl;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Attach the sound output to the custom effect node or the engine endpoint
|
|
||||||
if (const ma_result
|
|
||||||
result = ma_node_attach_output_bus(&sound, 0, connect ? &effectNode : ma_engine_get_endpoint(engine), 0);
|
|
||||||
result != MA_SUCCESS)
|
|
||||||
{
|
|
||||||
err() << "Failed to attach sound node output to effect node: " << ma_result_description(result) << std::endl;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static ma_result read(ma_data_source* dataSource, void* framesOut, ma_uint64 frameCount, ma_uint64* framesRead)
|
static ma_result read(ma_data_source* dataSource, void* framesOut, ma_uint64 frameCount, ma_uint64* framesRead)
|
||||||
@ -339,19 +210,8 @@ struct SoundStream::Impl
|
|||||||
////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////
|
||||||
// Member data
|
// Member data
|
||||||
////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////
|
||||||
struct EffectNode
|
static constexpr ma_data_source_vtable vtable{read, seek, getFormat, getCursor, getLength, setLooping, /* flags */ 0};
|
||||||
{
|
|
||||||
ma_node_base base{};
|
|
||||||
Impl* impl{};
|
|
||||||
ma_uint32 channelCount{};
|
|
||||||
};
|
|
||||||
|
|
||||||
ma_data_source_base dataSourceBase{}; //!< The struct that makes this object a miniaudio data source (must be first member)
|
|
||||||
SoundStream* const owner; //!< Owning SoundStream object
|
SoundStream* const owner; //!< Owning SoundStream object
|
||||||
ma_node_vtable effectNodeVTable{}; //!< Vtable of the effect node
|
|
||||||
EffectNode effectNode; //!< The engine node that performs effect processing
|
|
||||||
std::vector<ma_channel> soundChannelMap; //!< The map of position in sample frame to sound channel (miniaudio channels)
|
|
||||||
ma_sound sound{}; //!< The sound
|
|
||||||
std::vector<std::int16_t> sampleBuffer; //!< Our temporary sample buffer
|
std::vector<std::int16_t> sampleBuffer; //!< Our temporary sample buffer
|
||||||
std::size_t sampleBufferCursor{}; //!< The current read position in the temporary sample buffer
|
std::size_t sampleBufferCursor{}; //!< The current read position in the temporary sample buffer
|
||||||
std::uint64_t samplesProcessed{}; //!< Number of samples processed since beginning of the stream
|
std::uint64_t samplesProcessed{}; //!< Number of samples processed since beginning of the stream
|
||||||
@ -360,8 +220,6 @@ struct SoundStream::Impl
|
|||||||
std::vector<SoundChannel> channelMap{}; //!< The map of position in sample frame to sound channel
|
std::vector<SoundChannel> channelMap{}; //!< The map of position in sample frame to sound channel
|
||||||
bool loop{}; //!< Loop flag (true to loop, false to play once)
|
bool loop{}; //!< Loop flag (true to loop, false to play once)
|
||||||
bool streaming{true}; //!< True if we are still streaming samples from the source
|
bool streaming{true}; //!< True if we are still streaming samples from the source
|
||||||
Status status{Status::Stopped}; //!< The status
|
|
||||||
EffectProcessor effectProcessor; //!< The effect processor
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
@ -383,7 +241,8 @@ void SoundStream::initialize(unsigned int channelCount, unsigned int sampleRate,
|
|||||||
m_impl->channelMap = channelMap;
|
m_impl->channelMap = channelMap;
|
||||||
m_impl->samplesProcessed = 0;
|
m_impl->samplesProcessed = 0;
|
||||||
|
|
||||||
m_impl->reinitialize();
|
m_impl->deinitialize();
|
||||||
|
m_impl->initialize();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user