mirror of
https://github.com/KytyPS5/KytyPS5.git
synced 2026-10-28 22:06:59 +01:00
audio: play NGS2 PCM buffers
This commit is contained in:
+477
-116
@@ -18,8 +18,10 @@
|
|||||||
#include <atomic>
|
#include <atomic>
|
||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
|
#include <deque>
|
||||||
#include <limits>
|
#include <limits>
|
||||||
#include <magic_enum.hpp>
|
#include <magic_enum.hpp>
|
||||||
|
#include <memory>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "libatrac9.h"
|
#include "libatrac9.h"
|
||||||
@@ -1824,26 +1826,115 @@ enum class Ngs2RackType {
|
|||||||
CustomSampler,
|
CustomSampler,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct Ngs2UserFxContext {
|
||||||
|
void* common;
|
||||||
|
void* param;
|
||||||
|
void* work;
|
||||||
|
uintptr_t user_data;
|
||||||
|
uint32_t max_voices;
|
||||||
|
uint32_t voice_index;
|
||||||
|
uint64_t reserved[4] {};
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Ngs2UserFxProcessContext {
|
||||||
|
float** channels;
|
||||||
|
void* common;
|
||||||
|
const void* param;
|
||||||
|
void* work;
|
||||||
|
void* state;
|
||||||
|
uintptr_t user_data;
|
||||||
|
uint32_t flags;
|
||||||
|
uint32_t input_channels;
|
||||||
|
uint32_t output_channels;
|
||||||
|
uint32_t grain_samples;
|
||||||
|
uint32_t sample_rate;
|
||||||
|
uint32_t reserved {};
|
||||||
|
uint64_t reserved2[4] {};
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Ngs2UserFxOption {
|
||||||
|
Ngs2CustomModuleOption header;
|
||||||
|
int KYTY_SYSV_ABI (*setup)(Ngs2UserFxContext*);
|
||||||
|
int KYTY_SYSV_ABI (*cleanup)(Ngs2UserFxContext*);
|
||||||
|
uintptr_t control;
|
||||||
|
int KYTY_SYSV_ABI (*process)(Ngs2UserFxProcessContext*);
|
||||||
|
size_t common_size;
|
||||||
|
size_t param_size;
|
||||||
|
size_t work_size;
|
||||||
|
uintptr_t user_data;
|
||||||
|
};
|
||||||
|
|
||||||
struct Ngs2RackInternal {
|
struct Ngs2RackInternal {
|
||||||
Ngs2Internal* ngs = nullptr;
|
Ngs2Internal* ngs = nullptr;
|
||||||
Ngs2RackInternal* next = nullptr;
|
Ngs2RackInternal* next = nullptr;
|
||||||
Ngs2RackType type = Ngs2RackType::Sampler;
|
Ngs2RackType type = Ngs2RackType::Sampler;
|
||||||
Ngs2RackOptionUnion option;
|
Ngs2RackOptionUnion option;
|
||||||
Ngs2ContextBufferInfo buffer_info;
|
Ngs2ContextBufferInfo buffer_info;
|
||||||
Ngs2BufferAllocator allocator;
|
Ngs2BufferAllocator allocator;
|
||||||
|
std::array<std::vector<uint8_t>, 24> common;
|
||||||
|
std::array<Ngs2UserFxOption, 24> fx {};
|
||||||
};
|
};
|
||||||
|
|
||||||
enum class Ngs2VoicePlayState { Empty, Playing, Paused, Stopped };
|
enum class Ngs2VoicePlayState { Empty, Playing, Paused, Stopped };
|
||||||
|
|
||||||
enum class Ngs2VoicePlayEvent { None, Play, Pause, Resume, Stop, StopImm, Kill };
|
enum class Ngs2VoicePlayEvent { None, Play, Pause, Resume, Stop, StopImm, Kill };
|
||||||
|
|
||||||
struct Ngs2VoiceInternal {
|
struct Ngs2VoiceInternal {
|
||||||
Ngs2VoicePlayEvent event = Ngs2VoicePlayEvent::None;
|
struct Port {
|
||||||
Ngs2VoicePlayState state = Ngs2VoicePlayState::Empty;
|
Ngs2VoiceInternal* dest = nullptr;
|
||||||
Ngs2RackInternal* rack = nullptr;
|
uint32_t input = 0;
|
||||||
uintptr_t callback = 0;
|
float volume = 1.0f;
|
||||||
uintptr_t callback_data = 0;
|
int32_t matrix = -1;
|
||||||
uint32_t callback_flags = 0;
|
};
|
||||||
|
struct Block {
|
||||||
|
const uint8_t* data;
|
||||||
|
Ngs2WaveformBlock info;
|
||||||
|
uint32_t cursor = 0;
|
||||||
|
};
|
||||||
|
struct Module {
|
||||||
|
std::vector<uint8_t> param, work, state;
|
||||||
|
uint32_t flags = 1;
|
||||||
|
};
|
||||||
|
Ngs2VoicePlayEvent event = Ngs2VoicePlayEvent::None;
|
||||||
|
Ngs2VoicePlayState state = Ngs2VoicePlayState::Empty;
|
||||||
|
Ngs2RackInternal* rack = nullptr;
|
||||||
|
uintptr_t callback = 0;
|
||||||
|
uintptr_t callback_data = 0;
|
||||||
|
uint32_t callback_flags = 0;
|
||||||
|
std::deque<Block> blocks;
|
||||||
|
std::vector<Port> ports;
|
||||||
|
std::vector<std::vector<float>> matrices;
|
||||||
|
std::vector<Module> modules;
|
||||||
|
std::vector<float> samples;
|
||||||
|
std::vector<float> output_matrix;
|
||||||
|
uint32_t channels = 0;
|
||||||
|
uint32_t output_id = 0;
|
||||||
|
bool rendering = false;
|
||||||
|
bool rendered = false;
|
||||||
|
bool has_samples = false;
|
||||||
|
|
||||||
|
void SetMatrix(uint32_t index, const float* levels, uint32_t count) {
|
||||||
|
EXIT_NOT_IMPLEMENTED(index >= matrices.size());
|
||||||
|
matrices[index].assign(levels, levels + count);
|
||||||
|
}
|
||||||
|
void SetVolume(uint32_t index, float volume) {
|
||||||
|
EXIT_NOT_IMPLEMENTED(index >= ports.size());
|
||||||
|
ports[index].volume = volume;
|
||||||
|
}
|
||||||
|
void SetPortMatrix(uint32_t index, int32_t matrix) {
|
||||||
|
EXIT_NOT_IMPLEMENTED(index >= ports.size());
|
||||||
|
ports[index].matrix = matrix;
|
||||||
|
}
|
||||||
|
void SetEvent(uint32_t id) {
|
||||||
|
switch (id) {
|
||||||
|
case 1: event = Ngs2VoicePlayEvent::Play; break;
|
||||||
|
case 2: event = Ngs2VoicePlayEvent::Stop; break;
|
||||||
|
case 4: event = Ngs2VoicePlayEvent::StopImm; break;
|
||||||
|
case 8: event = Ngs2VoicePlayEvent::Kill; break;
|
||||||
|
case 16: event = Ngs2VoicePlayEvent::Pause; break;
|
||||||
|
case 32: event = Ngs2VoicePlayEvent::Resume; break;
|
||||||
|
default: EXIT("unknown event_id: 0x%08" PRIx32 "\n", id);
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
struct Ngs2VoiceParamHeader {
|
struct Ngs2VoiceParamHeader {
|
||||||
@@ -1931,6 +2022,9 @@ static Ngs2RackInternal* g_racks_list = nullptr;
|
|||||||
static std::atomic_uint32_t g_next_ngs_uid = 1;
|
static std::atomic_uint32_t g_next_ngs_uid = 1;
|
||||||
static Common::Mutex g_racks_mutex;
|
static Common::Mutex g_racks_mutex;
|
||||||
|
|
||||||
|
static_assert(sizeof(Ngs2UserFxContext) == 72);
|
||||||
|
static_assert(sizeof(Ngs2UserFxProcessContext) == 104);
|
||||||
|
static_assert(sizeof(Ngs2UserFxOption) == 72);
|
||||||
static_assert(sizeof(Ngs2SystemOption) == 144);
|
static_assert(sizeof(Ngs2SystemOption) == 144);
|
||||||
static_assert(sizeof(Ngs2SystemInfo) == 184);
|
static_assert(sizeof(Ngs2SystemInfo) == 184);
|
||||||
static_assert(sizeof(Ngs2RackOption) == 176);
|
static_assert(sizeof(Ngs2RackOption) == 176);
|
||||||
@@ -2272,6 +2366,35 @@ int KYTY_SYSV_ABI Ngs2SystemDestroy(uintptr_t system_handle, Ngs2ContextBufferIn
|
|||||||
return OK;
|
return OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void Ngs2InitModules(Ngs2RackInternal& rack) {
|
||||||
|
if (!Ngs2RackIsCustom(rack.type)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const auto& custom = rack.option.custom_sampler.custom_rack_option;
|
||||||
|
EXIT_NOT_IMPLEMENTED(custom.num_buffers != 1);
|
||||||
|
auto* voices = reinterpret_cast<Ngs2VoiceInternal*>(&rack + 1);
|
||||||
|
for (uint32_t m = 0; m < custom.num_modules; ++m) {
|
||||||
|
if (custom.module[m].module_id != 0x1f) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
auto& fx = rack.fx[m];
|
||||||
|
fx = *reinterpret_cast<const Ngs2UserFxOption*>(custom.module[m].option);
|
||||||
|
rack.common[m].resize(fx.common_size);
|
||||||
|
for (uint32_t i = 0; i < rack.option.common.max_voices; ++i) {
|
||||||
|
auto& module = voices[i].modules[m];
|
||||||
|
module.param.resize(fx.param_size);
|
||||||
|
module.work.resize(fx.work_size);
|
||||||
|
module.state.resize(custom.module[m].state_size);
|
||||||
|
if (fx.setup != nullptr) {
|
||||||
|
Ngs2UserFxContext context {
|
||||||
|
rack.common[m].data(), module.param.data(), module.work.data(),
|
||||||
|
fx.user_data, rack.option.common.max_voices, i};
|
||||||
|
EXIT_NOT_IMPLEMENTED(fx.setup(&context) != OK);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
int KYTY_SYSV_ABI Ngs2RackCreate(uintptr_t system_handle, uint32_t rack_id,
|
int KYTY_SYSV_ABI Ngs2RackCreate(uintptr_t system_handle, uint32_t rack_id,
|
||||||
const Ngs2RackOption* option,
|
const Ngs2RackOption* option,
|
||||||
const Ngs2ContextBufferInfo* buffer_info, uintptr_t* handle) {
|
const Ngs2ContextBufferInfo* buffer_info, uintptr_t* handle) {
|
||||||
@@ -2313,7 +2436,7 @@ int KYTY_SYSV_ABI Ngs2RackCreate(uintptr_t system_handle, uint32_t rack_id,
|
|||||||
static_cast<uint64_t>(buffer_info->host_buffer_size));
|
static_cast<uint64_t>(buffer_info->host_buffer_size));
|
||||||
|
|
||||||
auto* ngs = reinterpret_cast<Ngs2Internal*>(system_handle);
|
auto* ngs = reinterpret_cast<Ngs2Internal*>(system_handle);
|
||||||
auto* rack = static_cast<Ngs2RackInternal*>(buffer_info->host_buffer);
|
auto* rack = new (buffer_info->host_buffer) Ngs2RackInternal {};
|
||||||
auto* voices = reinterpret_cast<Ngs2VoiceInternal*>(rack + 1);
|
auto* voices = reinterpret_cast<Ngs2VoiceInternal*>(rack + 1);
|
||||||
|
|
||||||
Common::LockGuard lock(ngs->mutex);
|
Common::LockGuard lock(ngs->mutex);
|
||||||
@@ -2373,10 +2496,15 @@ int KYTY_SYSV_ABI Ngs2RackCreate(uintptr_t system_handle, uint32_t rack_id,
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (uint32_t i = 0; i < option->max_voices; i++) {
|
for (uint32_t i = 0; i < option->max_voices; i++) {
|
||||||
voices[i].rack = rack;
|
auto& voice = *std::construct_at(voices + i);
|
||||||
voices[i].event = Ngs2VoicePlayEvent::None;
|
voice.rack = rack;
|
||||||
voices[i].state = Ngs2VoicePlayState::Empty;
|
voice.ports.resize(option->max_ports);
|
||||||
|
voice.matrices.resize(option->max_matrices);
|
||||||
|
if (Ngs2RackIsCustom(rack->type)) {
|
||||||
|
voice.modules.resize(rack->option.custom_sampler.custom_rack_option.num_modules);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Ngs2InitModules(*rack);
|
||||||
|
|
||||||
*handle = reinterpret_cast<uintptr_t>(rack);
|
*handle = reinterpret_cast<uintptr_t>(rack);
|
||||||
|
|
||||||
@@ -2477,26 +2605,41 @@ int KYTY_SYSV_ABI Ngs2RackDestroy(uintptr_t rack_handle, Ngs2ContextBufferInfo*
|
|||||||
if (ngs == nullptr) {
|
if (ngs == nullptr) {
|
||||||
return ERROR_INVALID_RACK_HANDLE;
|
return ERROR_INVALID_RACK_HANDLE;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ngs2ContextBufferInfo context_buffer;
|
Ngs2ContextBufferInfo context_buffer;
|
||||||
Ngs2BufferAllocator allocator;
|
Ngs2BufferAllocator allocator;
|
||||||
{
|
{
|
||||||
Common::LockGuard lock(ngs->mutex);
|
Common::LockGuard lock(ngs->mutex);
|
||||||
Common::LockGuard racks_lock(g_racks_mutex);
|
{
|
||||||
|
Common::LockGuard racks_lock(g_racks_mutex);
|
||||||
auto** link = &g_racks_list;
|
auto** link = &g_racks_list;
|
||||||
while (*link != nullptr && *link != rack) {
|
while (*link != nullptr && *link != rack) {
|
||||||
link = &(*link)->next;
|
link = &(*link)->next;
|
||||||
|
}
|
||||||
|
if (*link == nullptr) {
|
||||||
|
return ERROR_INVALID_RACK_HANDLE;
|
||||||
|
}
|
||||||
|
*link = rack->next;
|
||||||
}
|
}
|
||||||
if (*link == nullptr) {
|
auto* voices = reinterpret_cast<Ngs2VoiceInternal*>(rack + 1);
|
||||||
return ERROR_INVALID_RACK_HANDLE;
|
for (uint32_t i = 0; i < rack->option.common.max_voices; ++i) {
|
||||||
|
for (size_t m = 0; m < voices[i].modules.size(); ++m) {
|
||||||
|
const auto& fx = rack->fx[m];
|
||||||
|
if (fx.cleanup != nullptr) {
|
||||||
|
auto& module = voices[i].modules[m];
|
||||||
|
Ngs2UserFxContext context {rack->common[m].data(),
|
||||||
|
module.param.data(),
|
||||||
|
module.work.data(),
|
||||||
|
fx.user_data,
|
||||||
|
rack->option.common.max_voices,
|
||||||
|
i};
|
||||||
|
EXIT_NOT_IMPLEMENTED(fx.cleanup(&context) != OK);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
std::destroy_at(voices + i);
|
||||||
}
|
}
|
||||||
|
|
||||||
*link = rack->next;
|
|
||||||
context_buffer = rack->buffer_info;
|
context_buffer = rack->buffer_info;
|
||||||
allocator = rack->allocator;
|
allocator = rack->allocator;
|
||||||
rack->ngs = nullptr;
|
std::destroy_at(rack);
|
||||||
rack->next = nullptr;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (allocator.free_handler != nullptr) {
|
if (allocator.free_handler != nullptr) {
|
||||||
@@ -2539,75 +2682,200 @@ int KYTY_SYSV_ABI Ngs2RackUnlock(uintptr_t rack_handle) {
|
|||||||
return OK;
|
return OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void Ngs2ApplyEvent(Ngs2VoiceInternal& voice) {
|
||||||
|
switch (voice.event) {
|
||||||
|
case Ngs2VoicePlayEvent::None: break;
|
||||||
|
case Ngs2VoicePlayEvent::Play:
|
||||||
|
if (voice.state == Ngs2VoicePlayState::Empty) {
|
||||||
|
voice.state = Ngs2VoicePlayState::Playing;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case Ngs2VoicePlayEvent::Pause:
|
||||||
|
if (voice.state == Ngs2VoicePlayState::Playing) {
|
||||||
|
voice.state = Ngs2VoicePlayState::Paused;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case Ngs2VoicePlayEvent::Resume:
|
||||||
|
if (voice.state == Ngs2VoicePlayState::Paused) {
|
||||||
|
voice.state = Ngs2VoicePlayState::Playing;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case Ngs2VoicePlayEvent::Stop:
|
||||||
|
if (voice.state == Ngs2VoicePlayState::Playing) {
|
||||||
|
voice.state = Ngs2VoicePlayState::Stopped;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case Ngs2VoicePlayEvent::StopImm:
|
||||||
|
case Ngs2VoicePlayEvent::Kill: voice.state = Ngs2VoicePlayState::Empty; break;
|
||||||
|
}
|
||||||
|
voice.event = Ngs2VoicePlayEvent::None;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void Ngs2ConsumePcm(Ngs2VoiceInternal& voice, uint32_t grain) {
|
||||||
|
uint32_t output = 0;
|
||||||
|
while (output < grain && !voice.blocks.empty()) {
|
||||||
|
auto& block = voice.blocks.front();
|
||||||
|
const auto count = std::min(grain - output, block.info.num_samples - block.cursor);
|
||||||
|
const auto* pcm = reinterpret_cast<const int16_t*>(block.data) +
|
||||||
|
(block.info.num_skip_samples + block.cursor) * voice.channels;
|
||||||
|
for (uint32_t c = 0; c < voice.channels; ++c) {
|
||||||
|
for (uint32_t i = 0; i < count; ++i) {
|
||||||
|
voice.samples[c * grain + output + i] = pcm[i * voice.channels + c] / 32768.0f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
output += count;
|
||||||
|
block.cursor += count;
|
||||||
|
if (block.cursor == block.info.num_samples) {
|
||||||
|
struct CallbackInfo {
|
||||||
|
uintptr_t data, voice;
|
||||||
|
uint32_t flag, reserved;
|
||||||
|
uintptr_t user;
|
||||||
|
const void* block_data;
|
||||||
|
size_t size;
|
||||||
|
uint32_t repeats, attributes;
|
||||||
|
} info {voice.callback_data,
|
||||||
|
reinterpret_cast<uintptr_t>(&voice),
|
||||||
|
1,
|
||||||
|
0,
|
||||||
|
block.info.user_data,
|
||||||
|
block.data,
|
||||||
|
block.info.data_size,
|
||||||
|
0,
|
||||||
|
0};
|
||||||
|
static_assert(sizeof(CallbackInfo) == 56);
|
||||||
|
voice.blocks.pop_front();
|
||||||
|
if (voice.callback != 0 && (voice.callback_flags & 1u) != 0) {
|
||||||
|
reinterpret_cast<void KYTY_SYSV_ABI (*)(const CallbackInfo*)>(voice.callback)(
|
||||||
|
&info);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
voice.has_samples = output != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void Ngs2RenderVoice(Ngs2VoiceInternal& voice, const std::vector<Ngs2VoiceInternal*>& voices,
|
||||||
|
uint32_t grain) {
|
||||||
|
if (voice.rendered) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
EXIT_NOT_IMPLEMENTED(voice.rendering);
|
||||||
|
voice.rendering = true;
|
||||||
|
voice.samples.assign(grain * voice.channels, 0.0f);
|
||||||
|
voice.has_samples = false;
|
||||||
|
if (voice.state == Ngs2VoicePlayState::Playing) {
|
||||||
|
if (voice.rack->type == Ngs2RackType::CustomSampler) {
|
||||||
|
Ngs2ConsumePcm(voice, grain);
|
||||||
|
}
|
||||||
|
for (auto* source: voices) {
|
||||||
|
for (const auto& port: source->ports) {
|
||||||
|
if (port.dest != &voice) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
Ngs2RenderVoice(*source, voices, grain);
|
||||||
|
if (!source->has_samples) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
EXIT_NOT_IMPLEMENTED(port.input != 0);
|
||||||
|
const auto* matrix = port.matrix < 0 ? nullptr : &source->matrices.at(port.matrix);
|
||||||
|
for (uint32_t dst = 0; dst < voice.channels; ++dst) {
|
||||||
|
for (uint32_t src = 0; src < source->channels; ++src) {
|
||||||
|
const float level =
|
||||||
|
port.volume * (matrix == nullptr
|
||||||
|
? (src == dst ? 1.0f : 0.0f)
|
||||||
|
: matrix->at(dst * source->channels + src));
|
||||||
|
for (uint32_t i = 0; i < grain; ++i) {
|
||||||
|
voice.samples[dst * grain + i] +=
|
||||||
|
source->samples[src * grain + i] * level;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
voice.has_samples = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (voice.has_samples) {
|
||||||
|
const auto& custom = voice.rack->option.custom_sampler.custom_rack_option;
|
||||||
|
for (size_t m = 0; m < voice.modules.size(); ++m) {
|
||||||
|
EXIT_NOT_IMPLEMENTED(custom.module[m].module_id != 0x1f);
|
||||||
|
EXIT_NOT_IMPLEMENTED(custom.module[m].source_buffer_id != 0 ||
|
||||||
|
custom.module[m].dest_buffer_id != 0);
|
||||||
|
auto& module = voice.modules[m];
|
||||||
|
const auto& fx = voice.rack->fx[m];
|
||||||
|
std::vector<float*> channels(voice.channels);
|
||||||
|
for (uint32_t c = 0; c < voice.channels; ++c) {
|
||||||
|
channels[c] = voice.samples.data() + c * grain;
|
||||||
|
}
|
||||||
|
Ngs2UserFxProcessContext context {channels.data(),
|
||||||
|
voice.rack->common[m].data(),
|
||||||
|
module.param.data(),
|
||||||
|
module.work.data(),
|
||||||
|
module.state.data(),
|
||||||
|
fx.user_data,
|
||||||
|
module.flags,
|
||||||
|
voice.channels,
|
||||||
|
voice.channels,
|
||||||
|
grain,
|
||||||
|
voice.rack->ngs->option.sample_rate};
|
||||||
|
EXIT_NOT_IMPLEMENTED(fx.process(&context) != OK);
|
||||||
|
module.flags = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
voice.rendering = false;
|
||||||
|
voice.rendered = true;
|
||||||
|
}
|
||||||
|
|
||||||
int KYTY_SYSV_ABI Ngs2SystemRender(uintptr_t system_handle, const Ngs2RenderBufferInfo* buffer_info,
|
int KYTY_SYSV_ABI Ngs2SystemRender(uintptr_t system_handle, const Ngs2RenderBufferInfo* buffer_info,
|
||||||
uint32_t num_buffer_info) {
|
uint32_t num_buffer_info) {
|
||||||
static std::atomic_uint32_t render_log_count = 0;
|
EXIT_NOT_IMPLEMENTED(buffer_info == nullptr || system_handle == 0 || num_buffer_info == 0);
|
||||||
const auto log_index = render_log_count.fetch_add(1, std::memory_order_relaxed);
|
|
||||||
const bool log_this_call = (log_index < 16 || (log_index % 600) == 0);
|
|
||||||
|
|
||||||
if (log_this_call) {
|
|
||||||
PRINT_NAME();
|
|
||||||
LOGF("\t call_count = %" PRIu32 "\n", log_index + 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
EXIT_NOT_IMPLEMENTED(buffer_info == nullptr);
|
|
||||||
EXIT_NOT_IMPLEMENTED(system_handle == 0);
|
|
||||||
EXIT_NOT_IMPLEMENTED(num_buffer_info == 0);
|
|
||||||
|
|
||||||
auto* ngs = reinterpret_cast<Ngs2Internal*>(system_handle);
|
auto* ngs = reinterpret_cast<Ngs2Internal*>(system_handle);
|
||||||
|
|
||||||
Common::LockGuard lock(ngs->mutex);
|
Common::LockGuard lock(ngs->mutex);
|
||||||
|
std::vector<Ngs2VoiceInternal*> voices;
|
||||||
for (uint32_t i = 0; i < num_buffer_info; i++) {
|
{
|
||||||
|
Common::LockGuard racks_lock(g_racks_mutex);
|
||||||
|
for (auto* rack = g_racks_list; rack != nullptr; rack = rack->next) {
|
||||||
|
if (rack->ngs != ngs) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
auto* items = reinterpret_cast<Ngs2VoiceInternal*>(rack + 1);
|
||||||
|
for (uint32_t i = 0; i < rack->option.common.max_voices; ++i) {
|
||||||
|
Ngs2ApplyEvent(items[i]);
|
||||||
|
items[i].rendered = false;
|
||||||
|
if (items[i].channels != 0) {
|
||||||
|
voices.push_back(items + i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (uint32_t i = 0; i < num_buffer_info; ++i) {
|
||||||
if (buffer_info[i].buffer != nullptr && buffer_info[i].buffer_size != 0) {
|
if (buffer_info[i].buffer != nullptr && buffer_info[i].buffer_size != 0) {
|
||||||
std::memset(buffer_info[i].buffer, 0, buffer_info[i].buffer_size);
|
std::memset(buffer_info[i].buffer, 0, buffer_info[i].buffer_size);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
const auto grain = ngs->option.num_grain_samples;
|
||||||
Common::LockGuard racks_lock(g_racks_mutex);
|
for (auto* voice: voices) {
|
||||||
for (auto* rack = g_racks_list; rack != nullptr; rack = rack->next) {
|
Ngs2RenderVoice(*voice, voices, grain);
|
||||||
if (rack->ngs == ngs) {
|
if (voice->rack->type != Ngs2RackType::Mastering || !voice->has_samples) {
|
||||||
auto* voices = reinterpret_cast<Ngs2VoiceInternal*>(rack + 1);
|
continue;
|
||||||
|
}
|
||||||
for (uint32_t i = 0; i < rack->option.common.max_voices; i++) {
|
EXIT_NOT_IMPLEMENTED(voice->output_id >= num_buffer_info);
|
||||||
auto& voice = voices[i];
|
const auto& output = buffer_info[voice->output_id];
|
||||||
switch (voice.event) {
|
EXIT_NOT_IMPLEMENTED(output.buffer == nullptr);
|
||||||
case Ngs2VoicePlayEvent::None:
|
EXIT_NOT_IMPLEMENTED(output.waveform_type != 0x18 ||
|
||||||
if (voice.state == Ngs2VoicePlayState::Playing ||
|
output.num_channels != voice->channels);
|
||||||
voice.state == Ngs2VoicePlayState::Stopped) {
|
EXIT_NOT_IMPLEMENTED(output.buffer_size < grain * output.num_channels * sizeof(float));
|
||||||
voice.state = Ngs2VoicePlayState::Empty;
|
auto* pcm = static_cast<float*>(output.buffer);
|
||||||
}
|
for (uint32_t dst = 0; dst < output.num_channels; ++dst) {
|
||||||
break;
|
for (uint32_t src = 0; src < voice->channels; ++src) {
|
||||||
case Ngs2VoicePlayEvent::Play:
|
const float level = voice->output_matrix.empty()
|
||||||
if (voice.state == Ngs2VoicePlayState::Empty) {
|
? (src == dst ? 1.0f : 0.0f)
|
||||||
voice.state = Ngs2VoicePlayState::Playing;
|
: voice->output_matrix.at(src * output.num_channels + dst);
|
||||||
}
|
for (uint32_t i = 0; i < grain; ++i) {
|
||||||
break;
|
pcm[i * output.num_channels + dst] += voice->samples[src * grain + i] * level;
|
||||||
case Ngs2VoicePlayEvent::Pause:
|
|
||||||
if (voice.state == Ngs2VoicePlayState::Playing) {
|
|
||||||
voice.state = Ngs2VoicePlayState::Paused;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case Ngs2VoicePlayEvent::Resume:
|
|
||||||
if (voice.state == Ngs2VoicePlayState::Paused) {
|
|
||||||
voice.state = Ngs2VoicePlayState::Playing;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case Ngs2VoicePlayEvent::Stop:
|
|
||||||
if (voice.state == Ngs2VoicePlayState::Playing) {
|
|
||||||
voice.state = Ngs2VoicePlayState::Stopped;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case Ngs2VoicePlayEvent::StopImm:
|
|
||||||
case Ngs2VoicePlayEvent::Kill: voice.state = Ngs2VoicePlayState::Empty; break;
|
|
||||||
}
|
}
|
||||||
voice.event = Ngs2VoicePlayEvent::None;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
++ngs->render_count;
|
||||||
ngs->render_count++;
|
|
||||||
|
|
||||||
return OK;
|
return OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2944,6 +3212,7 @@ int KYTY_SYSV_ABI Ngs2VoiceControl(uintptr_t voice_handle, const Ngs2VoiceParamH
|
|||||||
case 0x0001: {
|
case 0x0001: {
|
||||||
EXIT_NOT_IMPLEMENTED(param->size != sizeof(Ngs2VoiceMatrixLevelsParam));
|
EXIT_NOT_IMPLEMENTED(param->size != sizeof(Ngs2VoiceMatrixLevelsParam));
|
||||||
const auto* ml = reinterpret_cast<const Ngs2VoiceMatrixLevelsParam*>(param);
|
const auto* ml = reinterpret_cast<const Ngs2VoiceMatrixLevelsParam*>(param);
|
||||||
|
voice->SetMatrix(ml->matrix_id, ml->levels, ml->num_levels);
|
||||||
LOGF("\t matrix_id = %u\n"
|
LOGF("\t matrix_id = %u\n"
|
||||||
"\t num_levels = %u\n"
|
"\t num_levels = %u\n"
|
||||||
"\t levels = 0x%016" PRIx64 "\n",
|
"\t levels = 0x%016" PRIx64 "\n",
|
||||||
@@ -2954,6 +3223,7 @@ int KYTY_SYSV_ABI Ngs2VoiceControl(uintptr_t voice_handle, const Ngs2VoiceParamH
|
|||||||
EXIT_NOT_IMPLEMENTED(param->size != sizeof(Ngs2VoicePortVolumeParam));
|
EXIT_NOT_IMPLEMENTED(param->size != sizeof(Ngs2VoicePortVolumeParam));
|
||||||
const auto* volume =
|
const auto* volume =
|
||||||
reinterpret_cast<const Ngs2VoicePortVolumeParam*>(param);
|
reinterpret_cast<const Ngs2VoicePortVolumeParam*>(param);
|
||||||
|
voice->SetVolume(volume->port, volume->level);
|
||||||
LOGF("\t port = %u\n"
|
LOGF("\t port = %u\n"
|
||||||
"\t level = %f\n",
|
"\t level = %f\n",
|
||||||
volume->port, volume->level);
|
volume->port, volume->level);
|
||||||
@@ -2962,6 +3232,7 @@ int KYTY_SYSV_ABI Ngs2VoiceControl(uintptr_t voice_handle, const Ngs2VoiceParamH
|
|||||||
case 0x0003: {
|
case 0x0003: {
|
||||||
EXIT_NOT_IMPLEMENTED(param->size != sizeof(Ngs2VoicePortMatrixParam));
|
EXIT_NOT_IMPLEMENTED(param->size != sizeof(Ngs2VoicePortMatrixParam));
|
||||||
const auto* pm = reinterpret_cast<const Ngs2VoicePortMatrixParam*>(param);
|
const auto* pm = reinterpret_cast<const Ngs2VoicePortMatrixParam*>(param);
|
||||||
|
voice->SetPortMatrix(pm->port, pm->matrix_id);
|
||||||
LOGF("\t port = %u\n"
|
LOGF("\t port = %u\n"
|
||||||
"\t matrix_id = %d\n",
|
"\t matrix_id = %d\n",
|
||||||
pm->port, pm->matrix_id);
|
pm->port, pm->matrix_id);
|
||||||
@@ -2978,6 +3249,10 @@ int KYTY_SYSV_ABI Ngs2VoiceControl(uintptr_t voice_handle, const Ngs2VoiceParamH
|
|||||||
case 0x0005: {
|
case 0x0005: {
|
||||||
EXIT_NOT_IMPLEMENTED(param->size != sizeof(Ngs2VoicePatchParam));
|
EXIT_NOT_IMPLEMENTED(param->size != sizeof(Ngs2VoicePatchParam));
|
||||||
const auto* patch = reinterpret_cast<const Ngs2VoicePatchParam*>(param);
|
const auto* patch = reinterpret_cast<const Ngs2VoicePatchParam*>(param);
|
||||||
|
EXIT_NOT_IMPLEMENTED(patch->port >= voice->ports.size());
|
||||||
|
voice->ports[patch->port].dest =
|
||||||
|
reinterpret_cast<Ngs2VoiceInternal*>(patch->dest_handle);
|
||||||
|
voice->ports[patch->port].input = patch->dest_input_id;
|
||||||
LOGF("\t connect->port = %u\n"
|
LOGF("\t connect->port = %u\n"
|
||||||
"\t connect->dest_input_id = %u\n"
|
"\t connect->dest_input_id = %u\n"
|
||||||
"\t connect->dest_handle = 0x%016" PRIx64 "\n",
|
"\t connect->dest_handle = 0x%016" PRIx64 "\n",
|
||||||
@@ -2987,15 +3262,7 @@ int KYTY_SYSV_ABI Ngs2VoiceControl(uintptr_t voice_handle, const Ngs2VoiceParamH
|
|||||||
case 0x0006: {
|
case 0x0006: {
|
||||||
EXIT_NOT_IMPLEMENTED(param->size != sizeof(Ngs2VoiceEventParam));
|
EXIT_NOT_IMPLEMENTED(param->size != sizeof(Ngs2VoiceEventParam));
|
||||||
const auto* event = reinterpret_cast<const Ngs2VoiceEventParam*>(param);
|
const auto* event = reinterpret_cast<const Ngs2VoiceEventParam*>(param);
|
||||||
switch (event->event_id) {
|
voice->SetEvent(event->event_id);
|
||||||
case 0x0001: voice->event = Ngs2VoicePlayEvent::Play; break;
|
|
||||||
case 0x0002: voice->event = Ngs2VoicePlayEvent::Stop; break;
|
|
||||||
case 0x0004: voice->event = Ngs2VoicePlayEvent::StopImm; break;
|
|
||||||
case 0x0008: voice->event = Ngs2VoicePlayEvent::Kill; break;
|
|
||||||
case 0x0010: voice->event = Ngs2VoicePlayEvent::Pause; break;
|
|
||||||
case 0x0020: voice->event = Ngs2VoicePlayEvent::Resume; break;
|
|
||||||
default: EXIT("unknown event_id: 0x%08" PRIx32 "\n", event->event_id);
|
|
||||||
}
|
|
||||||
LOGF("\t event = %u\n", event->event_id);
|
LOGF("\t event = %u\n", event->event_id);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -3020,24 +3287,88 @@ int KYTY_SYSV_ABI Ngs2VoiceControl(uintptr_t voice_handle, const Ngs2VoiceParamH
|
|||||||
case 0x1000: EXIT_NOT_IMPLEMENTED(voice->rack->type != Ngs2RackType::Sampler); break;
|
case 0x1000: EXIT_NOT_IMPLEMENTED(voice->rack->type != Ngs2RackType::Sampler); break;
|
||||||
case 0x2000: EXIT_NOT_IMPLEMENTED(voice->rack->type != Ngs2RackType::Submixer); break;
|
case 0x2000: EXIT_NOT_IMPLEMENTED(voice->rack->type != Ngs2RackType::Submixer); break;
|
||||||
case 0x2001: EXIT_NOT_IMPLEMENTED(voice->rack->type != Ngs2RackType::Reverb); break;
|
case 0x2001: EXIT_NOT_IMPLEMENTED(voice->rack->type != Ngs2RackType::Reverb); break;
|
||||||
case 0x3000: EXIT_NOT_IMPLEMENTED(voice->rack->type != Ngs2RackType::Mastering); break;
|
case 0x3000: {
|
||||||
case 0x4000: {
|
EXIT_NOT_IMPLEMENTED(voice->rack->type != Ngs2RackType::Mastering);
|
||||||
EXIT_NOT_IMPLEMENTED(!Ngs2RackIsCustom(voice->rack->type));
|
const auto* values = reinterpret_cast<const uint32_t*>(param + 1);
|
||||||
auto cid = param->id & 0xffffu;
|
switch (param->id & 0xffffu) {
|
||||||
auto module_id = (cid >> 8u) & 0xffu;
|
case 0: voice->channels = values[0]; break;
|
||||||
auto ctl_id = (cid >> 5u) & 0x7u;
|
case 1: {
|
||||||
auto module_no = cid & 0x1fu;
|
const auto* matrix =
|
||||||
LOGF("\t custom module_id = 0x%02" PRIx32 ", ctl_id = 0x%" PRIx32
|
reinterpret_cast<const Ngs2VoiceMatrixLevelsParam*>(param);
|
||||||
", module_no = %" PRIu32 "\n",
|
EXIT_NOT_IMPLEMENTED(matrix->matrix_id != 5);
|
||||||
module_id, ctl_id, module_no);
|
voice->output_matrix.assign(matrix->levels,
|
||||||
|
matrix->levels + matrix->num_levels);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 5: voice->output_id = values[0]; break;
|
||||||
|
default: EXIT("unsupported mastering control: 0x%08" PRIx32 "\n", param->id);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 0x4001:
|
case 0x4000: {
|
||||||
|
EXIT_NOT_IMPLEMENTED(!Ngs2RackIsCustom(voice->rack->type));
|
||||||
|
const auto index = param->id & 0x1fu;
|
||||||
|
EXIT_NOT_IMPLEMENTED((param->id & 0xffffffe0u) != 0x40001f00u ||
|
||||||
|
index >= voice->modules.size());
|
||||||
|
struct FxParam {
|
||||||
|
Ngs2VoiceParamHeader header;
|
||||||
|
const void* data;
|
||||||
|
size_t size;
|
||||||
|
};
|
||||||
|
const auto& fx = *reinterpret_cast<const FxParam*>(param);
|
||||||
|
auto& module = voice->modules[index];
|
||||||
|
EXIT_NOT_IMPLEMENTED(voice->rack->fx[index].control != 0 ||
|
||||||
|
fx.size != module.param.size());
|
||||||
|
std::memcpy(module.param.data(), fx.data, fx.size);
|
||||||
|
module.flags |= 2;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 0x4001: {
|
||||||
EXIT_NOT_IMPLEMENTED(voice->rack->type != Ngs2RackType::CustomSampler);
|
EXIT_NOT_IMPLEMENTED(voice->rack->type != Ngs2RackType::CustomSampler);
|
||||||
|
switch (param->id & 0xffffu) {
|
||||||
|
case 0: {
|
||||||
|
const auto& format = *reinterpret_cast<const Ngs2WaveformFormat*>(param + 1);
|
||||||
|
EXIT_NOT_IMPLEMENTED(format.waveform_type != 0x12 ||
|
||||||
|
format.sample_rate != voice->rack->ngs->option.sample_rate);
|
||||||
|
voice->channels = format.num_channels;
|
||||||
|
voice->blocks.clear();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 1: {
|
||||||
|
struct BlocksParam {
|
||||||
|
Ngs2VoiceParamHeader header;
|
||||||
|
const uint8_t* data;
|
||||||
|
uint32_t flags, count;
|
||||||
|
const Ngs2WaveformBlock* blocks;
|
||||||
|
};
|
||||||
|
const auto& blocks = *reinterpret_cast<const BlocksParam*>(param);
|
||||||
|
EXIT_NOT_IMPLEMENTED(blocks.flags != 0x11);
|
||||||
|
for (uint32_t i = 0; i < blocks.count; ++i) {
|
||||||
|
const auto& block = blocks.blocks[i];
|
||||||
|
EXIT_NOT_IMPLEMENTED(
|
||||||
|
block.num_repeats != 0 || block.num_samples == 0 ||
|
||||||
|
(uint64_t(block.num_skip_samples) + block.num_samples) *
|
||||||
|
voice->channels * 2 >
|
||||||
|
block.data_size);
|
||||||
|
voice->blocks.push_back({blocks.data + block.data_offset, block});
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 5:
|
||||||
|
EXIT_NOT_IMPLEMENTED(*reinterpret_cast<const float*>(param + 1) != 1.0f);
|
||||||
|
break;
|
||||||
|
default: EXIT("unsupported sampler control: 0x%08" PRIx32 "\n", param->id);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case 0x4002:
|
}
|
||||||
EXIT_NOT_IMPLEMENTED(voice->rack->type != Ngs2RackType::CustomSubmixer);
|
case 0x4002: {
|
||||||
|
EXIT_NOT_IMPLEMENTED(voice->rack->type != Ngs2RackType::CustomSubmixer ||
|
||||||
|
(param->id & 0xffffu) != 0);
|
||||||
|
const auto* channels = reinterpret_cast<const uint32_t*>(param + 1);
|
||||||
|
EXIT_NOT_IMPLEMENTED(channels[0] != channels[1]);
|
||||||
|
voice->channels = channels[0];
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
case 0x4003:
|
case 0x4003:
|
||||||
EXIT_NOT_IMPLEMENTED(voice->rack->type != Ngs2RackType::CustomMastering);
|
EXIT_NOT_IMPLEMENTED(voice->rack->type != Ngs2RackType::CustomMastering);
|
||||||
break;
|
break;
|
||||||
@@ -3055,14 +3386,44 @@ int KYTY_SYSV_ABI Ngs2VoiceControl(uintptr_t voice_handle, const Ngs2VoiceParamH
|
|||||||
}
|
}
|
||||||
|
|
||||||
int KYTY_SYSV_ABI Ngs2VoiceRunCommands(uintptr_t voice_handle, const void* commands,
|
int KYTY_SYSV_ABI Ngs2VoiceRunCommands(uintptr_t voice_handle, const void* commands,
|
||||||
uint32_t num_commands, uint32_t flags) {
|
size_t num_commands) {
|
||||||
PRINT_NAME();
|
struct Command {
|
||||||
|
uint32_t id;
|
||||||
(void)voice_handle;
|
uint8_t flags, type;
|
||||||
(void)commands;
|
uint16_t count;
|
||||||
(void)num_commands;
|
union {
|
||||||
(void)flags;
|
uint32_t u;
|
||||||
|
int32_t i;
|
||||||
|
float f;
|
||||||
|
const float* levels;
|
||||||
|
} value;
|
||||||
|
};
|
||||||
|
auto* voice = reinterpret_cast<Ngs2VoiceInternal*>(voice_handle);
|
||||||
|
Common::LockGuard lock(voice->rack->ngs->mutex);
|
||||||
|
const auto* params = static_cast<const Command*>(commands);
|
||||||
|
for (size_t i = 0; i < num_commands; ++i) {
|
||||||
|
const auto& p = params[i];
|
||||||
|
const auto index = p.id >> 24;
|
||||||
|
switch (p.id & 0xffffffu) {
|
||||||
|
case 2:
|
||||||
|
EXIT_NOT_IMPLEMENTED(p.type != 4);
|
||||||
|
voice->SetEvent(p.value.u);
|
||||||
|
break;
|
||||||
|
case 5:
|
||||||
|
EXIT_NOT_IMPLEMENTED(p.type != 0x11);
|
||||||
|
voice->SetMatrix(index, p.value.levels, p.count);
|
||||||
|
break;
|
||||||
|
case 6:
|
||||||
|
EXIT_NOT_IMPLEMENTED(p.type != 1);
|
||||||
|
voice->SetVolume(index, p.value.f);
|
||||||
|
break;
|
||||||
|
case 7:
|
||||||
|
EXIT_NOT_IMPLEMENTED(p.type != 3);
|
||||||
|
voice->SetPortMatrix(index, p.value.i);
|
||||||
|
break;
|
||||||
|
default: EXIT("unknown command: 0x%08" PRIx32 "\n", p.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
return OK;
|
return OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -336,7 +336,7 @@ int KYTY_SYSV_ABI Ngs2RackGetVoiceHandle(uintptr_t rack_handle, uint32_t voice_i
|
|||||||
uintptr_t* handle);
|
uintptr_t* handle);
|
||||||
int KYTY_SYSV_ABI Ngs2VoiceControl(uintptr_t voice_handle, const Ngs2VoiceParamHeader* param_list);
|
int KYTY_SYSV_ABI Ngs2VoiceControl(uintptr_t voice_handle, const Ngs2VoiceParamHeader* param_list);
|
||||||
int KYTY_SYSV_ABI Ngs2VoiceRunCommands(uintptr_t voice_handle, const void* commands,
|
int KYTY_SYSV_ABI Ngs2VoiceRunCommands(uintptr_t voice_handle, const void* commands,
|
||||||
uint32_t num_commands, uint32_t flags);
|
size_t num_commands);
|
||||||
int KYTY_SYSV_ABI Ngs2VoiceGetState(uintptr_t voice_handle, Ngs2VoiceState* state,
|
int KYTY_SYSV_ABI Ngs2VoiceGetState(uintptr_t voice_handle, Ngs2VoiceState* state,
|
||||||
size_t state_size);
|
size_t state_size);
|
||||||
int KYTY_SYSV_ABI Ngs2VoiceGetStateFlags(uintptr_t voice_handle, uint32_t* state_flags);
|
int KYTY_SYSV_ABI Ngs2VoiceGetStateFlags(uintptr_t voice_handle, uint32_t* state_flags);
|
||||||
|
|||||||
Reference in New Issue
Block a user