176 lines
6.9 KiB
C++
176 lines
6.9 KiB
C++
#include <chrono>
|
|
#include <cstdint>
|
|
#include <cstddef>
|
|
#include <cstring>
|
|
#include <deque>
|
|
#include <mutex>
|
|
#include <stdexcept>
|
|
#include <string>
|
|
#include <thread>
|
|
#include "SceTypes.hpp"
|
|
#include "prx/libc/include/General.hpp"
|
|
|
|
namespace {
|
|
|
|
constexpr int AUDIO3D_ERROR_INVALID_PORT = static_cast<int>(0x80EA0002);
|
|
constexpr int AUDIO3D_ERROR_INVALID_PARAMETER = static_cast<int>(0x80EA0004);
|
|
constexpr int AUDIO3D_ERROR_OUT_OF_RESOURCES = static_cast<int>(0x80EA0006);
|
|
constexpr int AUDIO3D_ERROR_NOT_READY = static_cast<int>(0x80EA0007);
|
|
|
|
constexpr int AUDIO3D_USER_ID_SYSTEM = 0xFF;
|
|
constexpr std::uint32_t AUDIO3D_PORT_ID = 0;
|
|
constexpr std::uint32_t AUDIO3D_RATE_48000 = 0;
|
|
constexpr std::uint32_t AUDIO3D_SAMPLE_RATE = 48000;
|
|
constexpr std::uint32_t AUDIO3D_BUFFER_ADVANCE_AND_PUSH = 2;
|
|
constexpr std::uint32_t AUDIO3D_BLOCKING_ASYNC = 0;
|
|
constexpr std::uint32_t AUDIO3D_BLOCKING_SYNC = 1;
|
|
constexpr std::uint32_t AUDIO3D_ATTRIBUTE_LATE_REVERB_LEVEL = 0x10001;
|
|
constexpr std::uint32_t AUDIO3D_ATTRIBUTE_DOWNMIX_SPREAD_RADIUS = 0x10002;
|
|
constexpr std::uint32_t AUDIO3D_ATTRIBUTE_DOWNMIX_SPREAD_HEIGHT_AWARE = 0x10003;
|
|
|
|
using Clock = std::chrono::steady_clock;
|
|
|
|
struct Audio3dPort {
|
|
bool open = false;
|
|
std::uint32_t queue_depth = 0;
|
|
Clock::duration frame_duration{};
|
|
std::uint32_t advanced = 0;
|
|
std::deque<Clock::time_point> playing;
|
|
};
|
|
|
|
std::mutex g_mutex;
|
|
bool g_initialized = false;
|
|
Audio3dPort g_port;
|
|
|
|
[[noreturn]] void Unsupported(const char* function, const std::string& what) {
|
|
throw std::runtime_error(std::string(function) + ": " + what);
|
|
}
|
|
|
|
bool PortIsOpen(std::uint32_t port_id) {
|
|
return port_id == AUDIO3D_PORT_ID && g_port.open;
|
|
}
|
|
|
|
std::uint32_t QueueLevel(Clock::time_point now) {
|
|
while (!g_port.playing.empty() && g_port.playing.front() <= now) g_port.playing.pop_front();
|
|
return g_port.advanced + static_cast<std::uint32_t>(g_port.playing.size());
|
|
}
|
|
|
|
}
|
|
|
|
extern "C" {
|
|
|
|
void APS5_VABI sceAudio3dGetDefaultOpenParameters(Audio3dOpenParameters* p) {
|
|
if (p == nullptr) APS5_INVALID_ARG_EX;
|
|
constexpr Audio3dOpenParameters defaults{0x20, 256, 0, 512, 2, 2, 0, 0};
|
|
static_assert(offsetof(Audio3dOpenParameters, num_beds) == 0x20);
|
|
std::memcpy(p, &defaults, defaults.size_this);
|
|
}
|
|
|
|
int APS5_VABI sceAudio3dInitialize(int64_t reserved) {
|
|
if (reserved != 0) return AUDIO3D_ERROR_INVALID_PARAMETER;
|
|
std::lock_guard lock(g_mutex);
|
|
if (g_initialized) Unsupported(__func__, "repeated initialization");
|
|
g_initialized = true;
|
|
return 0;
|
|
}
|
|
|
|
int APS5_VABI sceAudio3dPortAdvance(uint32_t port_id) {
|
|
std::lock_guard lock(g_mutex);
|
|
if (!PortIsOpen(port_id)) return AUDIO3D_ERROR_INVALID_PORT;
|
|
if (QueueLevel(Clock::now()) >= g_port.queue_depth) Unsupported(__func__, "queue is full");
|
|
++g_port.advanced;
|
|
return 0;
|
|
}
|
|
|
|
int APS5_VABI sceAudio3dPortClose(uint32_t port_id) {
|
|
std::lock_guard lock(g_mutex);
|
|
if (!PortIsOpen(port_id)) return AUDIO3D_ERROR_INVALID_PORT;
|
|
g_port = Audio3dPort{};
|
|
return 0;
|
|
}
|
|
|
|
int APS5_VABI sceAudio3dPortGetQueueLevel(uint32_t port_id, uint32_t* queue_level, uint32_t* queue_available) {
|
|
std::lock_guard lock(g_mutex);
|
|
if (!PortIsOpen(port_id)) return AUDIO3D_ERROR_INVALID_PORT;
|
|
if (queue_level == nullptr && queue_available == nullptr) return AUDIO3D_ERROR_INVALID_PARAMETER;
|
|
const std::uint32_t level = QueueLevel(Clock::now());
|
|
if (queue_level != nullptr) *queue_level = level;
|
|
if (queue_available != nullptr) *queue_available = g_port.queue_depth - level;
|
|
return 0;
|
|
}
|
|
|
|
int APS5_VABI sceAudio3dPortOpen(int user_id, const Audio3dOpenParameters* parameters, uint32_t* id) {
|
|
std::lock_guard lock(g_mutex);
|
|
if (!g_initialized) return AUDIO3D_ERROR_NOT_READY;
|
|
if (user_id != AUDIO3D_USER_ID_SYSTEM || parameters == nullptr || id == nullptr) return AUDIO3D_ERROR_INVALID_PARAMETER;
|
|
std::uint32_t num_beds = 2;
|
|
switch (parameters->size_this) {
|
|
case 0x10:
|
|
case 0x18:
|
|
Unsupported(__func__, "parameter size " + std::to_string(parameters->size_this) + " selects an unsupported buffer mode");
|
|
case 0x20:
|
|
break;
|
|
case 0x28:
|
|
num_beds = parameters->num_beds;
|
|
break;
|
|
default:
|
|
return AUDIO3D_ERROR_INVALID_PARAMETER;
|
|
}
|
|
if (parameters->rate != AUDIO3D_RATE_48000) return AUDIO3D_ERROR_INVALID_PARAMETER;
|
|
if (parameters->granularity < 0x100 || (parameters->granularity & 0xFF) != 0) return AUDIO3D_ERROR_INVALID_PARAMETER;
|
|
if (parameters->max_objects == 0 || parameters->queue_depth == 0) return AUDIO3D_ERROR_INVALID_PARAMETER;
|
|
if (parameters->buffer_mode > AUDIO3D_BUFFER_ADVANCE_AND_PUSH) return AUDIO3D_ERROR_INVALID_PARAMETER;
|
|
if (num_beds != 2 && num_beds != 3) return AUDIO3D_ERROR_INVALID_PARAMETER;
|
|
if (parameters->buffer_mode != AUDIO3D_BUFFER_ADVANCE_AND_PUSH) {
|
|
Unsupported(__func__, "buffer mode " + std::to_string(parameters->buffer_mode));
|
|
}
|
|
if (num_beds != 2) Unsupported(__func__, "3 beds");
|
|
if (g_port.open) return AUDIO3D_ERROR_OUT_OF_RESOURCES;
|
|
g_port = Audio3dPort{};
|
|
g_port.open = true;
|
|
g_port.queue_depth = parameters->queue_depth;
|
|
g_port.frame_duration = std::chrono::duration_cast<Clock::duration>(
|
|
std::chrono::nanoseconds(std::uint64_t{parameters->granularity} * 1000000000u / AUDIO3D_SAMPLE_RATE));
|
|
*id = AUDIO3D_PORT_ID;
|
|
return 0;
|
|
}
|
|
|
|
int APS5_VABI sceAudio3dPortPush(uint32_t port_id, uint32_t blocking) {
|
|
Clock::time_point wait_until;
|
|
{
|
|
std::lock_guard lock(g_mutex);
|
|
if (!PortIsOpen(port_id)) return AUDIO3D_ERROR_INVALID_PORT;
|
|
if (blocking != AUDIO3D_BLOCKING_ASYNC && blocking != AUDIO3D_BLOCKING_SYNC) {
|
|
Unsupported(__func__, "blocking mode " + std::to_string(blocking));
|
|
}
|
|
const Clock::time_point now = Clock::now();
|
|
const std::uint32_t level = QueueLevel(now);
|
|
Clock::time_point end = g_port.playing.empty() ? now : g_port.playing.back();
|
|
for (; g_port.advanced != 0; --g_port.advanced) {
|
|
end += g_port.frame_duration;
|
|
g_port.playing.push_back(end);
|
|
}
|
|
if (blocking == AUDIO3D_BLOCKING_ASYNC || level < g_port.queue_depth) return 0;
|
|
wait_until = g_port.playing[level - g_port.queue_depth];
|
|
}
|
|
std::this_thread::sleep_until(wait_until);
|
|
return 0;
|
|
}
|
|
|
|
int APS5_VABI sceAudio3dPortSetAttribute(uint32_t port_id, uint32_t attribute_id, const void* attribute, size_t attribute_size) {
|
|
std::lock_guard lock(g_mutex);
|
|
if (!PortIsOpen(port_id)) return AUDIO3D_ERROR_INVALID_PORT;
|
|
if (attribute == nullptr) return AUDIO3D_ERROR_INVALID_PARAMETER;
|
|
if (attribute_size != 4) Unsupported(__func__, "attribute size " + std::to_string(attribute_size));
|
|
switch (attribute_id) {
|
|
case AUDIO3D_ATTRIBUTE_LATE_REVERB_LEVEL:
|
|
case AUDIO3D_ATTRIBUTE_DOWNMIX_SPREAD_RADIUS:
|
|
case AUDIO3D_ATTRIBUTE_DOWNMIX_SPREAD_HEIGHT_AWARE:
|
|
return 0;
|
|
default:
|
|
Unsupported(__func__, "attribute " + std::to_string(attribute_id));
|
|
}
|
|
}
|
|
|
|
}
|