G2: JUCE VST3 hosting + latency that qua FxLatReport - probe pureComp 2108
This commit is contained in:
@@ -0,0 +1,36 @@
|
|||||||
|
# G2 — JUCE VST3 hosting + latency thật (FxLatReport)
|
||||||
|
|
||||||
|
Trạng thái: **DONE** — đã verify qua probe, build pass.
|
||||||
|
|
||||||
|
## Thay đổi
|
||||||
|
|
||||||
|
- `native_bridge/juce_fx/JuceFxEngine.cpp` — `setChain(chainJson)`: parse mảng fx_chain
|
||||||
|
(`sheredom_json.h`), load VST3 qua `juce::VST3PluginFormat::findAllTypesForFile` +
|
||||||
|
`createInstanceFromDescription`; preset_b64 → `setStateFromVSTPresetFile` (deprecated,
|
||||||
|
vẫn dùng được); nối node nối tiếp in→p0→…→out; sau `prepareToPlay`:
|
||||||
|
`slotLatencies[i] = getLatencySamples()` từng plugin, `totalLatency = graph.getLatencySamples()`.
|
||||||
|
`entryLatencies()` trả `std::vector<uint32_t>` theo thứ tự slot (skip/bypass/fail = 0).
|
||||||
|
- `JuceFxEngine.h` — khai báo `struct Slot;` + `setChain`/`entryLatencies`.
|
||||||
|
- `JuceFxLoop.cpp` — đọc `chainJson` từ job, gọi `engine.setChain` trước prepare;
|
||||||
|
`reportLatencies` báo từng slot qua FxLatReport (giữ giao thức cũ).
|
||||||
|
- `juce_fx/CMakeLists.txt` — bật `JUCE_PLUGINHOST_VST3=1` (JUCE mặc định 0 → class
|
||||||
|
VST3PluginFormat bị ẩn, gây C3861/C2039).
|
||||||
|
- `src-tauri/binaries/juce_fx_bridge-x86_64-pc-windows-msvc.exe` — build mới (G2).
|
||||||
|
|
||||||
|
## Verify (debug/g0_latency_probe.py, exe mới)
|
||||||
|
|
||||||
|
| config | plugin_latency | drained | recv/sent | roundtrip mean |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| empty chain | `{}` | True | 10000/10000 | 21.57 ms |
|
||||||
|
| pureComp | `{0: 2108}` | True | 10000/10000 | 21.70 ms |
|
||||||
|
| Ozone 11 | `{0: 0}` | True | 10000/10000 | 21.69 ms |
|
||||||
|
|
||||||
|
- Latency thật đúng dự đoán: pureComp ~2108 samples @48k, Ozone 11 không delay.
|
||||||
|
- Không crackle, drained=True, recv=sent — benchmark G1 giữ nguyên.
|
||||||
|
|
||||||
|
## Ghi chú
|
||||||
|
|
||||||
|
- `setStateFromVSTPresetFile` là API deprecated nhưng public (bên trong `#ifndef DOXYGEN`),
|
||||||
|
compile được với `JUCE_PLUGINHOST_VST3=1`.
|
||||||
|
- Bỏ hằng `(7+8)*256` trong `fxRtApplyPdc` (app.jsx) là việc của G3 (dùng latencyTotal
|
||||||
|
thật từ WS — hiện `FX_RT_ROUNDTRIP_SAMPLES=3840` là ước lượng RTT pipeline, ponytail).
|
||||||
@@ -29,6 +29,10 @@ target_link_libraries(juce_fx_bridge PRIVATE
|
|||||||
juce::juce_audio_basics
|
juce::juce_audio_basics
|
||||||
juce::juce_audio_processors
|
juce::juce_audio_processors
|
||||||
)
|
)
|
||||||
|
# G2: VST3 hosting — JUCE mặc định JUCE_PLUGINHOST_VST3=0, bật để
|
||||||
|
# VST3PluginFormat (findAllTypesForFile/createInstanceFromDescription/...)
|
||||||
|
# được compile (juce_audio_processors tự bundle VST3_SDK).
|
||||||
|
target_compile_definitions(juce_fx_bridge PRIVATE JUCE_PLUGINHOST_VST3=1)
|
||||||
if(WIN32)
|
if(WIN32)
|
||||||
# timeBeginPeriod (winmm) — như fx_vst_bridge
|
# timeBeginPeriod (winmm) — như fx_vst_bridge
|
||||||
target_link_libraries(juce_fx_bridge PRIVATE winmm)
|
target_link_libraries(juce_fx_bridge PRIVATE winmm)
|
||||||
|
|||||||
@@ -1,19 +1,96 @@
|
|||||||
// native_bridge/juce_fx/JuceFxEngine.cpp
|
// native_bridge/juce_fx/JuceFxEngine.cpp
|
||||||
// Implementation: AudioProcessorGraph với input/output node — chain rỗng G1.
|
// Implementation: AudioProcessorGraph với input/output node — chain rỗng G1.
|
||||||
|
// G2: nạp VST3 theo fx_chain (path, preset_b64, bypass), chèn node giữa
|
||||||
|
// input/output; latency thật = graph.getLatencySamples() (PDC nội bộ graph).
|
||||||
#include "JuceFxEngine.h"
|
#include "JuceFxEngine.h"
|
||||||
|
|
||||||
#include <juce_audio_processors/juce_audio_processors.h>
|
#include <juce_audio_processors/juce_audio_processors.h>
|
||||||
|
#include "sheredom_json.h"
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <array>
|
#include <array>
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <cstring>
|
||||||
|
#include <iostream>
|
||||||
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
// --- base64 decode (RFC 4648) — như RenderFxJob.cpp -------------------------
|
||||||
|
int b64val(unsigned char c) {
|
||||||
|
if (c >= 'A' && c <= 'Z') return (int)(c - 'A');
|
||||||
|
if (c >= 'a' && c <= 'z') return (int)(c - 'a') + 26;
|
||||||
|
if (c >= '0' && c <= '9') return (int)(c - '0') + 52;
|
||||||
|
if (c == '+') return 62;
|
||||||
|
if (c == '/') return 63;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool base64Decode(const std::string& in, std::vector<uint8_t>& out) {
|
||||||
|
out.clear();
|
||||||
|
out.reserve((in.size() / 4) * 3);
|
||||||
|
uint32_t acc = 0;
|
||||||
|
int bits = 0;
|
||||||
|
for (unsigned char ch : in) {
|
||||||
|
if (ch == '=' || ch == '\n' || ch == '\r' || ch == ' ') continue;
|
||||||
|
int v = b64val(ch);
|
||||||
|
if (v < 0) return false;
|
||||||
|
acc = (acc << 6) | (uint32_t)v;
|
||||||
|
bits += 6;
|
||||||
|
if (bits >= 8) {
|
||||||
|
bits -= 8;
|
||||||
|
out.push_back((uint8_t)((acc >> bits) & 0xFF));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- sheredom helpers (giống RenderFxJob.cpp) -------------------------------
|
||||||
|
const json_object_element_s* jMember(const json_object_s* o, const char* key) {
|
||||||
|
for (const json_object_element_s* e = o ? o->start : nullptr; e; e = e->next)
|
||||||
|
if (e->name && e->name->string && std::strcmp(e->name->string, key) == 0)
|
||||||
|
return e;
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string jString(const json_object_s* o, const char* key, const std::string& def) {
|
||||||
|
const json_object_element_s* m = jMember(o, key);
|
||||||
|
if (m && m->value && m->value->type == json_type_string) {
|
||||||
|
const auto* s = static_cast<const json_string_s*>(m->value->payload);
|
||||||
|
return std::string(s->string, s->string_size);
|
||||||
|
}
|
||||||
|
return def;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool jBool(const json_object_s* o, const char* key, bool def) {
|
||||||
|
const json_object_element_s* m = jMember(o, key);
|
||||||
|
if (m && m->value) {
|
||||||
|
if (m->value->type == json_type_true) return true;
|
||||||
|
if (m->value->type == json_type_false) return false;
|
||||||
|
}
|
||||||
|
return def;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
struct JuceFxEngine::Slot {
|
||||||
|
std::string type;
|
||||||
|
std::string path;
|
||||||
|
std::string presetB64;
|
||||||
|
bool bypass = false;
|
||||||
|
};
|
||||||
|
|
||||||
struct JuceFxEngine::Impl {
|
struct JuceFxEngine::Impl {
|
||||||
juce::AudioProcessorGraph graph;
|
juce::AudioProcessorGraph graph;
|
||||||
juce::AudioBuffer<float> buf;
|
juce::AudioBuffer<float> buf;
|
||||||
uint32_t sampleRate = 0;
|
uint32_t sampleRate = 0;
|
||||||
uint32_t blockSize = 0;
|
uint32_t blockSize = 0;
|
||||||
bool prepared = false;
|
bool prepared = false;
|
||||||
|
std::vector<Slot> slots; // chain đã parse (thứ tự slot)
|
||||||
|
std::vector<juce::AudioProcessorGraph::Node::Ptr> pluginNodes; // node theo slot (null = bỏ qua)
|
||||||
|
std::vector<uint32_t> slotLatencies; // latency thật từng slot (0 = skip/bypass/fail)
|
||||||
|
uint32_t totalLatency = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
JuceFxEngine::JuceFxEngine() : impl_(std::make_unique<Impl>()) {}
|
JuceFxEngine::JuceFxEngine() : impl_(std::make_unique<Impl>()) {}
|
||||||
@@ -28,6 +105,30 @@ void JuceFxEngine::shutdown() {
|
|||||||
impl_->sampleRate = 0;
|
impl_->sampleRate = 0;
|
||||||
impl_->blockSize = 0;
|
impl_->blockSize = 0;
|
||||||
}
|
}
|
||||||
|
impl_->pluginNodes.clear();
|
||||||
|
impl_->slotLatencies.assign(impl_->slots.size(), 0);
|
||||||
|
impl_->totalLatency = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void JuceFxEngine::setChain(const std::string& chainJson) {
|
||||||
|
// Parse mảng JSON fx_chain: [ {type,path,preset_b64,bypass}, ... ].
|
||||||
|
impl_->slots.clear();
|
||||||
|
json_value_s* root = json_parse(chainJson.data(), chainJson.size());
|
||||||
|
if (root && root->type == json_type_array) {
|
||||||
|
const auto* arr = static_cast<const json_array_s*>(root->payload);
|
||||||
|
for (const json_array_element_s* el = arr->start; el; el = el->next) {
|
||||||
|
const json_value_s* v = el->value;
|
||||||
|
if (!v || v->type != json_type_object) continue;
|
||||||
|
const auto* o = static_cast<const json_object_s*>(v->payload);
|
||||||
|
Slot s;
|
||||||
|
s.type = jString(o, "type", "");
|
||||||
|
s.path = jString(o, "path", "");
|
||||||
|
s.presetB64 = jString(o, "preset_b64", "");
|
||||||
|
s.bypass = jBool(o, "bypass", false);
|
||||||
|
impl_->slots.push_back(std::move(s));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (root) free(root);
|
||||||
}
|
}
|
||||||
|
|
||||||
void JuceFxEngine::prepare(uint32_t sampleRate, uint32_t blockSize) {
|
void JuceFxEngine::prepare(uint32_t sampleRate, uint32_t blockSize) {
|
||||||
@@ -40,8 +141,6 @@ void JuceFxEngine::prepare(uint32_t sampleRate, uint32_t blockSize) {
|
|||||||
impl_->blockSize = blockSize;
|
impl_->blockSize = blockSize;
|
||||||
impl_->graph.setPlayConfigDetails(2, 2, (double)sampleRate, (int)blockSize);
|
impl_->graph.setPlayConfigDetails(2, 2, (double)sampleRate, (int)blockSize);
|
||||||
|
|
||||||
// Chain rỗng G1: audio input -> audio output (passthrough). G2: chèn
|
|
||||||
// plugin node giữa hai node này theo fx_chain.
|
|
||||||
auto& g = impl_->graph;
|
auto& g = impl_->graph;
|
||||||
const auto inNode = g.addNode(std::make_unique<
|
const auto inNode = g.addNode(std::make_unique<
|
||||||
juce::AudioProcessorGraph::AudioGraphIOProcessor>(
|
juce::AudioProcessorGraph::AudioGraphIOProcessor>(
|
||||||
@@ -51,14 +150,66 @@ void JuceFxEngine::prepare(uint32_t sampleRate, uint32_t blockSize) {
|
|||||||
juce::AudioProcessorGraph::AudioGraphIOProcessor::audioOutputNode));
|
juce::AudioProcessorGraph::AudioGraphIOProcessor::audioOutputNode));
|
||||||
using NCh = juce::AudioProcessorGraph::NodeAndChannel;
|
using NCh = juce::AudioProcessorGraph::NodeAndChannel;
|
||||||
using Conn = juce::AudioProcessorGraph::Connection;
|
using Conn = juce::AudioProcessorGraph::Connection;
|
||||||
|
|
||||||
|
// G2: nạp VST3 theo chain — nối nối tiếp in -> p0 -> p1 -> ... -> out.
|
||||||
|
// Slot bypass / type khác vst3 / load fail → passthrough (latency 0).
|
||||||
|
impl_->pluginNodes.assign(impl_->slots.size(), nullptr);
|
||||||
|
impl_->slotLatencies.assign(impl_->slots.size(), 0);
|
||||||
|
juce::AudioProcessorGraph::Node::Ptr prev = inNode;
|
||||||
|
for (size_t i = 0; i < impl_->slots.size(); ++i) {
|
||||||
|
const Slot& s = impl_->slots[i];
|
||||||
|
if (s.bypass || (s.type != "vst3" && s.type != "vst") || s.path.empty())
|
||||||
|
continue;
|
||||||
|
juce::VST3PluginFormat fmt;
|
||||||
|
juce::OwnedArray<juce::PluginDescription> descs;
|
||||||
|
fmt.findAllTypesForFile(descs, juce::File(s.path).getFullPathName());
|
||||||
|
if (descs.isEmpty()) {
|
||||||
|
std::cerr << "[JuceFxEngine] slot " << i << " no VST3 desc: "
|
||||||
|
<< s.path << std::endl;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
auto inst = fmt.createInstanceFromDescription(*descs.getFirst(),
|
||||||
|
(double)sampleRate,
|
||||||
|
(int)blockSize);
|
||||||
|
if (!inst) {
|
||||||
|
std::cerr << "[JuceFxEngine] slot " << i << " createInstance fail: "
|
||||||
|
<< s.path << std::endl;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!s.presetB64.empty()) {
|
||||||
|
std::vector<uint8_t> bytes;
|
||||||
|
if (base64Decode(s.presetB64, bytes) && !bytes.empty()) {
|
||||||
|
juce::MemoryBlock mb(bytes.data(), bytes.size());
|
||||||
|
if (juce::VST3PluginFormat::setStateFromVSTPresetFile(inst.get(), mb))
|
||||||
|
std::cerr << "[JuceFxEngine] slot " << i << " preset applied" << std::endl;
|
||||||
|
else
|
||||||
|
std::cerr << "[JuceFxEngine] slot " << i << " preset FAILED" << std::endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
auto node = g.addNode(std::unique_ptr<juce::AudioProcessor>(std::move(inst)));
|
||||||
|
impl_->pluginNodes[i] = node;
|
||||||
|
for (int ch = 0; ch < 2; ++ch) {
|
||||||
|
NCh src; src.nodeID = prev->nodeID; src.channelIndex = ch;
|
||||||
|
NCh dst; dst.nodeID = node->nodeID; dst.channelIndex = ch;
|
||||||
|
g.addConnection(Conn(src, dst));
|
||||||
|
}
|
||||||
|
prev = node;
|
||||||
|
}
|
||||||
for (int ch = 0; ch < 2; ++ch) {
|
for (int ch = 0; ch < 2; ++ch) {
|
||||||
NCh src; src.nodeID = inNode->nodeID; src.channelIndex = ch;
|
NCh src; src.nodeID = prev->nodeID; src.channelIndex = ch;
|
||||||
NCh dst; dst.nodeID = outNode->nodeID; dst.channelIndex = ch;
|
NCh dst; dst.nodeID = outNode->nodeID; dst.channelIndex = ch;
|
||||||
g.addConnection(Conn(src, dst));
|
g.addConnection(Conn(src, dst));
|
||||||
}
|
}
|
||||||
|
|
||||||
g.prepareToPlay((double)sampleRate, (int)(blockSize * 4)); // batch tối đa 4 block
|
g.prepareToPlay((double)sampleRate, (int)(blockSize * 4)); // batch tối đa 4 block
|
||||||
impl_->buf.setSize(2, (int)(blockSize * 4), false, false, true);
|
impl_->buf.setSize(2, (int)(blockSize * 4), false, false, true);
|
||||||
|
|
||||||
|
// Latency thật từng slot sau prepareToPlay (plugin set trong prepare).
|
||||||
|
for (size_t i = 0; i < impl_->pluginNodes.size(); ++i)
|
||||||
|
if (impl_->pluginNodes[i])
|
||||||
|
impl_->slotLatencies[i] =
|
||||||
|
(uint32_t)impl_->pluginNodes[i]->getProcessor()->getLatencySamples();
|
||||||
|
impl_->totalLatency = (uint32_t)g.getLatencySamples();
|
||||||
impl_->prepared = true;
|
impl_->prepared = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -70,7 +221,7 @@ void JuceFxEngine::process(float* inL, float* inR, uint32_t n) {
|
|||||||
b.copyFrom(1, 0, inR, (int)n);
|
b.copyFrom(1, 0, inR, (int)n);
|
||||||
juce::MidiBuffer midi;
|
juce::MidiBuffer midi;
|
||||||
impl_->graph.processBlock(b, midi);
|
impl_->graph.processBlock(b, midi);
|
||||||
// G1 passthrough: copy kết quả về buffer đầu vào (in-place như contract).
|
// Graph PDC nội bộ: output đã align dry/wet — copy về in-place như contract.
|
||||||
const auto* outL = b.getReadPointer(0);
|
const auto* outL = b.getReadPointer(0);
|
||||||
const auto* outR = b.getReadPointer(1);
|
const auto* outR = b.getReadPointer(1);
|
||||||
std::copy(outL, outL + n, inL);
|
std::copy(outL, outL + n, inL);
|
||||||
@@ -78,5 +229,9 @@ void JuceFxEngine::process(float* inL, float* inR, uint32_t n) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
uint32_t JuceFxEngine::latencySamples() const {
|
uint32_t JuceFxEngine::latencySamples() const {
|
||||||
return impl_->prepared ? (uint32_t)impl_->graph.getLatencySamples() : 0u;
|
return impl_->prepared ? impl_->totalLatency : 0u;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<uint32_t> JuceFxEngine::entryLatencies() const {
|
||||||
|
return impl_->slotLatencies;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,8 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
class JuceFxEngine {
|
class JuceFxEngine {
|
||||||
public:
|
public:
|
||||||
@@ -13,17 +15,23 @@ public:
|
|||||||
JuceFxEngine(const JuceFxEngine&) = delete;
|
JuceFxEngine(const JuceFxEngine&) = delete;
|
||||||
JuceFxEngine& operator=(const JuceFxEngine&) = delete;
|
JuceFxEngine& operator=(const JuceFxEngine&) = delete;
|
||||||
|
|
||||||
// Tạo graph mới (input node -> output node, passthrough), prepareToPlay.
|
// Tạo graph mới (input node -> [VST3 theo chain] -> output node).
|
||||||
// sampleRate/blockSize đổi → teardown graph cũ + prepare lại (validate
|
// sampleRate/blockSize đổi → teardown graph cũ + prepare lại (validate
|
||||||
// header G1). blockSize là kích thước mỗi SHM block (256).
|
// header G1). blockSize là kích thước mỗi SHM block (256).
|
||||||
void prepare(uint32_t sampleRate, uint32_t blockSize);
|
void prepare(uint32_t sampleRate, uint32_t blockSize);
|
||||||
|
// G2: đặt chain (mảng JSON fx_chain) trước prepare — các slot bypass /
|
||||||
|
// không phải vst3 / load fail chạy passthrough (latency 0).
|
||||||
|
void setChain(const std::string& chainJson);
|
||||||
// Audio thread: xử lý n mẫu stereo in-place (n <= blockSize * FXRT_IN_SLOTS).
|
// Audio thread: xử lý n mẫu stereo in-place (n <= blockSize * FXRT_IN_SLOTS).
|
||||||
void process(float* inL, float* inR, uint32_t n);
|
void process(float* inL, float* inR, uint32_t n);
|
||||||
// Graph latency samples (G1 chain rỗng = 0; G2 = getLatencySamples thật).
|
// Graph latency samples (G1 chain rỗng = 0; G2 = getLatencySamples thật).
|
||||||
uint32_t latencySamples() const;
|
uint32_t latencySamples() const;
|
||||||
|
// Latency thật từng slot theo thứ tự chain (0 = skip/bypass/fail).
|
||||||
|
std::vector<uint32_t> entryLatencies() const;
|
||||||
void shutdown();
|
void shutdown();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
struct Slot; // G2: một mục fx_chain (type/path/presetB64/bypass)
|
||||||
struct Impl;
|
struct Impl;
|
||||||
std::unique_ptr<Impl> impl_;
|
std::unique_ptr<Impl> impl_;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -62,6 +62,22 @@ double jsonNumber(const std::string& s, const std::string& key, double def) {
|
|||||||
try { return std::stod(s.substr(p, e - p)); } catch (...) { return def; }
|
try { return std::stod(s.substr(p, e - p)); } catch (...) { return def; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Cắt substring mảng JSON "fx_chain":[...] — như RealtimeFxLoop.cpp.
|
||||||
|
std::string extractChainJson(const std::string& s) {
|
||||||
|
std::string k = "\"fx_chain\"";
|
||||||
|
size_t p = s.find(k);
|
||||||
|
if (p == std::string::npos) return "";
|
||||||
|
p = s.find('[', p);
|
||||||
|
if (p == std::string::npos) return "";
|
||||||
|
size_t depth = 0, i = p;
|
||||||
|
for (; i < s.size(); ++i) {
|
||||||
|
if (s[i] == '[') ++depth;
|
||||||
|
else if (s[i] == ']') { --depth; if (depth == 0) break; }
|
||||||
|
}
|
||||||
|
if (i >= s.size()) return "";
|
||||||
|
return s.substr(p, i - p + 1);
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
int run_juce_fx_loop(const std::string& jobPath, const std::string& shmName,
|
int run_juce_fx_loop(const std::string& jobPath, const std::string& shmName,
|
||||||
@@ -77,7 +93,11 @@ int run_juce_fx_loop(const std::string& jobPath, const std::string& shmName,
|
|||||||
const uint32_t sampleRate = (uint32_t)srD;
|
const uint32_t sampleRate = (uint32_t)srD;
|
||||||
const double blkD = jsonNumber(job, "block_size", (double)FXRT_BLOCK);
|
const double blkD = jsonNumber(job, "block_size", (double)FXRT_BLOCK);
|
||||||
const uint32_t block = (uint32_t)std::max<double>(32.0, std::min<double>(blkD, (double)FXRT_BLOCK));
|
const uint32_t block = (uint32_t)std::max<double>(32.0, std::min<double>(blkD, (double)FXRT_BLOCK));
|
||||||
// G1: fx_chain bỏ qua (chain rỗng). G2: đọc fx_chain -> build graph.
|
// G2: đọc fx_chain -> build graph (VST3 theo chain). Rỗng = passthrough.
|
||||||
|
const std::string chainJson = extractChainJson(job);
|
||||||
|
if (chainJson.empty())
|
||||||
|
std::cerr << "[JuceFxLoop] no fx_chain in job (empty chain = passthrough)"
|
||||||
|
<< std::endl;
|
||||||
|
|
||||||
fxshm::ShmView* v = fxshm::openShm(shmName, sizeof(FxRealtimeIPC));
|
fxshm::ShmView* v = fxshm::openShm(shmName, sizeof(FxRealtimeIPC));
|
||||||
if (!v) {
|
if (!v) {
|
||||||
@@ -104,25 +124,39 @@ int run_juce_fx_loop(const std::string& jobPath, const std::string& shmName,
|
|||||||
SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_ABOVE_NORMAL);
|
SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_ABOVE_NORMAL);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Engine: prepare graph (chain rỗng). sr/block từ job; header là nguồn
|
// Engine: prepare graph (chain theo fx_chain). sr/block từ job; header là
|
||||||
// thật (engine ghi lúc tạo SHM) — nếu khác, lấy header.
|
// nguồn thật (engine ghi lúc tạo SHM) — nếu khác, lấy header.
|
||||||
uint32_t curSr = sampleRate;
|
uint32_t curSr = sampleRate;
|
||||||
uint32_t curBlock = block;
|
uint32_t curBlock = block;
|
||||||
JuceFxEngine engine;
|
JuceFxEngine engine;
|
||||||
|
engine.setChain(chainJson);
|
||||||
if (ipc->h.sampleRate) curSr = ipc->h.sampleRate;
|
if (ipc->h.sampleRate) curSr = ipc->h.sampleRate;
|
||||||
if (ipc->h.blockSize) curBlock = ipc->h.blockSize;
|
if (ipc->h.blockSize) curBlock = ipc->h.blockSize;
|
||||||
engine.prepare(curSr, curBlock);
|
engine.prepare(curSr, curBlock);
|
||||||
std::cerr << "[JuceFxLoop] prepared sr=" << curSr << " block=" << curBlock
|
std::cerr << "[JuceFxLoop] prepared sr=" << curSr << " block=" << curBlock
|
||||||
<< " latency=" << engine.latencySamples() << std::endl;
|
<< " latency=" << engine.latencySamples() << std::endl;
|
||||||
|
|
||||||
// Report latency ban đầu (slot 0 = tổng chain).
|
// Report latency ban đầu: từng slot (giữ giao thức FxLatReport cũ — engine
|
||||||
{
|
// Python sum → total). Chain rỗng → slot 0 = 0 (khớp G1).
|
||||||
const uint32_t ls = ipc->h.latWrite & (FXRT_LAT_SLOTS - 1);
|
auto reportLatencies = [&]() {
|
||||||
ipc->lat[ls].slot = 0;
|
const auto lats = engine.entryLatencies();
|
||||||
ipc->lat[ls].samples = engine.latencySamples();
|
if (lats.empty()) {
|
||||||
MemoryBarrier();
|
const uint32_t ls = ipc->h.latWrite & (FXRT_LAT_SLOTS - 1);
|
||||||
ipc->h.latWrite++;
|
ipc->lat[ls].slot = 0;
|
||||||
}
|
ipc->lat[ls].samples = 0;
|
||||||
|
MemoryBarrier();
|
||||||
|
ipc->h.latWrite++;
|
||||||
|
} else {
|
||||||
|
for (size_t i = 0; i < lats.size(); ++i) {
|
||||||
|
const uint32_t ls = ipc->h.latWrite & (FXRT_LAT_SLOTS - 1);
|
||||||
|
ipc->lat[ls].slot = (uint32_t)i;
|
||||||
|
ipc->lat[ls].samples = lats[i];
|
||||||
|
MemoryBarrier();
|
||||||
|
ipc->h.latWrite++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
reportLatencies();
|
||||||
ipc->h.state = FXRT_STATE_READY;
|
ipc->h.state = FXRT_STATE_READY;
|
||||||
std::cerr << "[JuceFxLoop] ready" << std::endl;
|
std::cerr << "[JuceFxLoop] ready" << std::endl;
|
||||||
|
|
||||||
@@ -159,11 +193,7 @@ int run_juce_fx_loop(const std::string& jobPath, const std::string& shmName,
|
|||||||
curSr = ipc->h.sampleRate;
|
curSr = ipc->h.sampleRate;
|
||||||
curBlock = ipc->h.blockSize;
|
curBlock = ipc->h.blockSize;
|
||||||
engine.prepare(curSr, curBlock);
|
engine.prepare(curSr, curBlock);
|
||||||
const uint32_t ls = ipc->h.latWrite & (FXRT_LAT_SLOTS - 1);
|
reportLatencies();
|
||||||
ipc->lat[ls].slot = 0;
|
|
||||||
ipc->lat[ls].samples = engine.latencySamples();
|
|
||||||
MemoryBarrier();
|
|
||||||
ipc->h.latWrite++;
|
|
||||||
std::cerr << "[JuceFxLoop] re-prepared sr=" << curSr
|
std::cerr << "[JuceFxLoop] re-prepared sr=" << curSr
|
||||||
<< " block=" << curBlock
|
<< " block=" << curBlock
|
||||||
<< " latency=" << engine.latencySamples() << std::endl;
|
<< " latency=" << engine.latencySamples() << std::endl;
|
||||||
|
|||||||
Binary file not shown.
Reference in New Issue
Block a user