diff --git a/src/libs/audio.cpp b/src/libs/audio.cpp index 69f8d73c1..db8a25cc5 100644 --- a/src/libs/audio.cpp +++ b/src/libs/audio.cpp @@ -18,8 +18,10 @@ #include #include #include +#include #include #include +#include #include #include "libatrac9.h" @@ -1824,26 +1826,115 @@ enum class Ngs2RackType { 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 { - Ngs2Internal* ngs = nullptr; - Ngs2RackInternal* next = nullptr; - Ngs2RackType type = Ngs2RackType::Sampler; - Ngs2RackOptionUnion option; - Ngs2ContextBufferInfo buffer_info; - Ngs2BufferAllocator allocator; + Ngs2Internal* ngs = nullptr; + Ngs2RackInternal* next = nullptr; + Ngs2RackType type = Ngs2RackType::Sampler; + Ngs2RackOptionUnion option; + Ngs2ContextBufferInfo buffer_info; + Ngs2BufferAllocator allocator; + std::array, 24> common; + std::array fx {}; }; enum class Ngs2VoicePlayState { Empty, Playing, Paused, Stopped }; - enum class Ngs2VoicePlayEvent { None, Play, Pause, Resume, Stop, StopImm, Kill }; struct Ngs2VoiceInternal { - 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; + struct Port { + Ngs2VoiceInternal* dest = nullptr; + uint32_t input = 0; + float volume = 1.0f; + int32_t matrix = -1; + }; + struct Block { + const uint8_t* data; + Ngs2WaveformBlock info; + uint32_t cursor = 0; + }; + struct Module { + std::vector 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 blocks; + std::vector ports; + std::vector> matrices; + std::vector modules; + std::vector samples; + std::vector 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 { @@ -1931,6 +2022,9 @@ static Ngs2RackInternal* g_racks_list = nullptr; static std::atomic_uint32_t g_next_ngs_uid = 1; 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(Ngs2SystemInfo) == 184); static_assert(sizeof(Ngs2RackOption) == 176); @@ -2272,6 +2366,35 @@ int KYTY_SYSV_ABI Ngs2SystemDestroy(uintptr_t system_handle, Ngs2ContextBufferIn 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(&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(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, const Ngs2RackOption* option, 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(buffer_info->host_buffer_size)); auto* ngs = reinterpret_cast(system_handle); - auto* rack = static_cast(buffer_info->host_buffer); + auto* rack = new (buffer_info->host_buffer) Ngs2RackInternal {}; auto* voices = reinterpret_cast(rack + 1); 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++) { - voices[i].rack = rack; - voices[i].event = Ngs2VoicePlayEvent::None; - voices[i].state = Ngs2VoicePlayState::Empty; + auto& voice = *std::construct_at(voices + i); + voice.rack = rack; + 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(rack); @@ -2477,26 +2605,41 @@ int KYTY_SYSV_ABI Ngs2RackDestroy(uintptr_t rack_handle, Ngs2ContextBufferInfo* if (ngs == nullptr) { return ERROR_INVALID_RACK_HANDLE; } - Ngs2ContextBufferInfo context_buffer; Ngs2BufferAllocator allocator; { Common::LockGuard lock(ngs->mutex); - Common::LockGuard racks_lock(g_racks_mutex); - - auto** link = &g_racks_list; - while (*link != nullptr && *link != rack) { - link = &(*link)->next; + { + Common::LockGuard racks_lock(g_racks_mutex); + auto** link = &g_racks_list; + while (*link != nullptr && *link != rack) { + link = &(*link)->next; + } + if (*link == nullptr) { + return ERROR_INVALID_RACK_HANDLE; + } + *link = rack->next; } - if (*link == nullptr) { - return ERROR_INVALID_RACK_HANDLE; + auto* voices = reinterpret_cast(rack + 1); + 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; allocator = rack->allocator; - rack->ngs = nullptr; - rack->next = nullptr; + std::destroy_at(rack); } if (allocator.free_handler != nullptr) { @@ -2539,75 +2682,200 @@ int KYTY_SYSV_ABI Ngs2RackUnlock(uintptr_t rack_handle) { 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(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(&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(voice.callback)( + &info); + } + } + } + voice.has_samples = output != 0; +} + +static void Ngs2RenderVoice(Ngs2VoiceInternal& voice, const std::vector& 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 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, uint32_t num_buffer_info) { - static std::atomic_uint32_t render_log_count = 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); - + EXIT_NOT_IMPLEMENTED(buffer_info == nullptr || system_handle == 0 || num_buffer_info == 0); auto* ngs = reinterpret_cast(system_handle); - Common::LockGuard lock(ngs->mutex); - - for (uint32_t i = 0; i < num_buffer_info; i++) { + std::vector voices; + { + 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(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) { std::memset(buffer_info[i].buffer, 0, buffer_info[i].buffer_size); } } - - Common::LockGuard racks_lock(g_racks_mutex); - for (auto* rack = g_racks_list; rack != nullptr; rack = rack->next) { - if (rack->ngs == ngs) { - auto* voices = reinterpret_cast(rack + 1); - - for (uint32_t i = 0; i < rack->option.common.max_voices; i++) { - auto& voice = voices[i]; - switch (voice.event) { - case Ngs2VoicePlayEvent::None: - if (voice.state == Ngs2VoicePlayState::Playing || - voice.state == Ngs2VoicePlayState::Stopped) { - voice.state = Ngs2VoicePlayState::Empty; - } - 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; + const auto grain = ngs->option.num_grain_samples; + for (auto* voice: voices) { + Ngs2RenderVoice(*voice, voices, grain); + if (voice->rack->type != Ngs2RackType::Mastering || !voice->has_samples) { + continue; + } + EXIT_NOT_IMPLEMENTED(voice->output_id >= num_buffer_info); + const auto& output = buffer_info[voice->output_id]; + EXIT_NOT_IMPLEMENTED(output.buffer == nullptr); + EXIT_NOT_IMPLEMENTED(output.waveform_type != 0x18 || + output.num_channels != voice->channels); + EXIT_NOT_IMPLEMENTED(output.buffer_size < grain * output.num_channels * sizeof(float)); + auto* pcm = static_cast(output.buffer); + for (uint32_t dst = 0; dst < output.num_channels; ++dst) { + for (uint32_t src = 0; src < voice->channels; ++src) { + const float level = voice->output_matrix.empty() + ? (src == dst ? 1.0f : 0.0f) + : voice->output_matrix.at(src * output.num_channels + dst); + for (uint32_t i = 0; i < grain; ++i) { + pcm[i * output.num_channels + dst] += voice->samples[src * grain + i] * level; } - voice.event = Ngs2VoicePlayEvent::None; } } } - - ngs->render_count++; - + ++ngs->render_count; return OK; } @@ -2944,6 +3212,7 @@ int KYTY_SYSV_ABI Ngs2VoiceControl(uintptr_t voice_handle, const Ngs2VoiceParamH case 0x0001: { EXIT_NOT_IMPLEMENTED(param->size != sizeof(Ngs2VoiceMatrixLevelsParam)); const auto* ml = reinterpret_cast(param); + voice->SetMatrix(ml->matrix_id, ml->levels, ml->num_levels); LOGF("\t matrix_id = %u\n" "\t num_levels = %u\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)); const auto* volume = reinterpret_cast(param); + voice->SetVolume(volume->port, volume->level); LOGF("\t port = %u\n" "\t level = %f\n", volume->port, volume->level); @@ -2962,6 +3232,7 @@ int KYTY_SYSV_ABI Ngs2VoiceControl(uintptr_t voice_handle, const Ngs2VoiceParamH case 0x0003: { EXIT_NOT_IMPLEMENTED(param->size != sizeof(Ngs2VoicePortMatrixParam)); const auto* pm = reinterpret_cast(param); + voice->SetPortMatrix(pm->port, pm->matrix_id); LOGF("\t port = %u\n" "\t matrix_id = %d\n", pm->port, pm->matrix_id); @@ -2978,6 +3249,10 @@ int KYTY_SYSV_ABI Ngs2VoiceControl(uintptr_t voice_handle, const Ngs2VoiceParamH case 0x0005: { EXIT_NOT_IMPLEMENTED(param->size != sizeof(Ngs2VoicePatchParam)); const auto* patch = reinterpret_cast(param); + EXIT_NOT_IMPLEMENTED(patch->port >= voice->ports.size()); + voice->ports[patch->port].dest = + reinterpret_cast(patch->dest_handle); + voice->ports[patch->port].input = patch->dest_input_id; LOGF("\t connect->port = %u\n" "\t connect->dest_input_id = %u\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: { EXIT_NOT_IMPLEMENTED(param->size != sizeof(Ngs2VoiceEventParam)); const auto* event = reinterpret_cast(param); - switch (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); - } + voice->SetEvent(event->event_id); LOGF("\t event = %u\n", event->event_id); 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 0x2000: EXIT_NOT_IMPLEMENTED(voice->rack->type != Ngs2RackType::Submixer); break; case 0x2001: EXIT_NOT_IMPLEMENTED(voice->rack->type != Ngs2RackType::Reverb); break; - case 0x3000: EXIT_NOT_IMPLEMENTED(voice->rack->type != Ngs2RackType::Mastering); break; - case 0x4000: { - EXIT_NOT_IMPLEMENTED(!Ngs2RackIsCustom(voice->rack->type)); - auto cid = param->id & 0xffffu; - auto module_id = (cid >> 8u) & 0xffu; - auto ctl_id = (cid >> 5u) & 0x7u; - auto module_no = cid & 0x1fu; - LOGF("\t custom module_id = 0x%02" PRIx32 ", ctl_id = 0x%" PRIx32 - ", module_no = %" PRIu32 "\n", - module_id, ctl_id, module_no); + case 0x3000: { + EXIT_NOT_IMPLEMENTED(voice->rack->type != Ngs2RackType::Mastering); + const auto* values = reinterpret_cast(param + 1); + switch (param->id & 0xffffu) { + case 0: voice->channels = values[0]; break; + case 1: { + const auto* matrix = + reinterpret_cast(param); + EXIT_NOT_IMPLEMENTED(matrix->matrix_id != 5); + 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; } - 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(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); + switch (param->id & 0xffffu) { + case 0: { + const auto& format = *reinterpret_cast(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(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(param + 1) != 1.0f); + break; + default: EXIT("unsupported sampler control: 0x%08" PRIx32 "\n", param->id); + } 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(param + 1); + EXIT_NOT_IMPLEMENTED(channels[0] != channels[1]); + voice->channels = channels[0]; break; + } case 0x4003: EXIT_NOT_IMPLEMENTED(voice->rack->type != Ngs2RackType::CustomMastering); 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, - uint32_t num_commands, uint32_t flags) { - PRINT_NAME(); - - (void)voice_handle; - (void)commands; - (void)num_commands; - (void)flags; - + size_t num_commands) { + struct Command { + uint32_t id; + uint8_t flags, type; + uint16_t count; + union { + uint32_t u; + int32_t i; + float f; + const float* levels; + } value; + }; + auto* voice = reinterpret_cast(voice_handle); + Common::LockGuard lock(voice->rack->ngs->mutex); + const auto* params = static_cast(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; } diff --git a/src/libs/audio.h b/src/libs/audio.h index 9a97f557e..39a72b7cf 100644 --- a/src/libs/audio.h +++ b/src/libs/audio.h @@ -336,7 +336,7 @@ int KYTY_SYSV_ABI Ngs2RackGetVoiceHandle(uintptr_t rack_handle, uint32_t voice_i uintptr_t* handle); 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, - uint32_t num_commands, uint32_t flags); + size_t num_commands); int KYTY_SYSV_ABI Ngs2VoiceGetState(uintptr_t voice_handle, Ngs2VoiceState* state, size_t state_size); int KYTY_SYSV_ABI Ngs2VoiceGetStateFlags(uintptr_t voice_handle, uint32_t* state_flags);