V45: D17 seamless loop wrap + D18 stop solo note-off (Qin) + D18b/c guard + engine VST load fixes
- D17: wrap_info/wrap-skip, fx wrap xfade, playhead-derived scheduling - D18: solo stop -> NOTE_OFF all sounding timeline pitches + CC123/CC120 - D18b: track audible gain guard, tracksRef fallback - D18c: timelinePitchesRef per-track Set, kill at applyAllTrackMuteSolo - bridge ring _MAX 16, index v=202608290928 - plugins.py/vst_engine.py/fx_realtime.py/JuceFxEngine.cpp: VSTi load + Qin fixes (uncommitted 12:18)
This commit is contained in:
@@ -9,6 +9,7 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <iostream>
|
||||
@@ -72,6 +73,103 @@ bool jBool(const json_object_s* o, const char* key, bool def) {
|
||||
return def;
|
||||
}
|
||||
|
||||
int64_t jInt64(const json_object_s* o, const char* key, int64_t def) {
|
||||
const json_object_element_s* m = jMember(o, key);
|
||||
if (m && m->value && m->value->type == json_type_number) {
|
||||
const auto* n = static_cast<const json_number_s*>(m->value->payload);
|
||||
return (int64_t)std::strtoll(n->number, nullptr, 10);
|
||||
}
|
||||
return def;
|
||||
}
|
||||
|
||||
uint32_t jUInt32(const json_object_s* o, const char* key, uint32_t def) {
|
||||
const json_object_element_s* m = jMember(o, key);
|
||||
if (m && m->value && m->value->type == json_type_number) {
|
||||
const auto* n = static_cast<const json_number_s*>(m->value->payload);
|
||||
return (uint32_t)std::strtoul(n->number, nullptr, 10);
|
||||
}
|
||||
return def;
|
||||
}
|
||||
|
||||
std::string jsonEscape(const std::string& s) {
|
||||
std::string out;
|
||||
out.reserve(s.size());
|
||||
for (char c : s) {
|
||||
if (c == '\\' || c == '"') { out += '\\'; out += c; }
|
||||
else out += c;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
// --- VST3 description cache -------------------------------------------------
|
||||
// findAllTypesForFile() slow path load DLL + quét factory + instantiate
|
||||
// component (hàng trăm ms/plugin mỗi session). createInstanceFromDescription
|
||||
// chỉ cần {fileOrIdentifier, name, uniqueId, deprecatedUid} —
|
||||
// findClassMatchingDescription so name+uid trên class info, KHÔNG instantiate.
|
||||
// Cache JSON theo path+mtime: hit → dựng desc trực tiếp, bỏ qua scan.
|
||||
struct DescCacheEntry {
|
||||
std::string path;
|
||||
int64_t mtime = 0;
|
||||
std::string name;
|
||||
uint32_t uniqueId = 0;
|
||||
uint32_t deprecatedUid = 0;
|
||||
};
|
||||
|
||||
std::string descCachePath() {
|
||||
return juce::File::getSpecialLocation(juce::File::userApplicationDataDirectory)
|
||||
.getChildFile("SonicForgeDAW")
|
||||
.getChildFile("juce_vst3_desc_cache.json")
|
||||
.getFullPathName()
|
||||
.toStdString();
|
||||
}
|
||||
|
||||
std::vector<DescCacheEntry> loadDescCache() {
|
||||
std::vector<DescCacheEntry> out;
|
||||
const juce::File f(descCachePath());
|
||||
if (!f.existsAsFile()) return out;
|
||||
const juce::String content = f.loadFileAsString();
|
||||
json_value_s* root = json_parse(content.toRawUTF8(), content.getNumBytesAsUTF8());
|
||||
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);
|
||||
DescCacheEntry e;
|
||||
e.path = jString(o, "path", "");
|
||||
e.mtime = jInt64(o, "mtime", 0);
|
||||
e.name = jString(o, "name", "");
|
||||
e.uniqueId = jUInt32(o, "unique_id", 0);
|
||||
e.deprecatedUid = jUInt32(o, "deprecated_uid", 0);
|
||||
if (!e.path.empty() && !e.name.empty())
|
||||
out.push_back(std::move(e));
|
||||
}
|
||||
}
|
||||
if (root) free(root);
|
||||
return out;
|
||||
}
|
||||
|
||||
void saveDescCache(const std::vector<DescCacheEntry>& cache) {
|
||||
std::string json = "[\n";
|
||||
for (size_t i = 0; i < cache.size(); ++i) {
|
||||
const auto& e = cache[i];
|
||||
if (i) json += ",\n";
|
||||
json += " {\"path\":\"" + jsonEscape(e.path) +
|
||||
"\",\"mtime\":" + std::to_string(e.mtime) +
|
||||
",\"name\":\"" + jsonEscape(e.name) +
|
||||
"\",\"unique_id\":" + std::to_string(e.uniqueId) +
|
||||
",\"deprecated_uid\":" + std::to_string(e.deprecatedUid) + "}";
|
||||
}
|
||||
json += "\n]\n";
|
||||
try {
|
||||
const juce::File f(descCachePath());
|
||||
f.getParentDirectory().createDirectory();
|
||||
f.replaceWithText(juce::String(json));
|
||||
} catch (...) {
|
||||
// Cache lỗi không được làm hỏng prepare() — lần sau scan lại.
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
struct JuceFxEngine::Slot {
|
||||
@@ -155,6 +253,8 @@ void JuceFxEngine::prepare(uint32_t sampleRate, uint32_t blockSize) {
|
||||
// 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);
|
||||
static std::vector<DescCacheEntry> descCache = loadDescCache();
|
||||
bool cacheDirty = false;
|
||||
juce::AudioProcessorGraph::Node::Ptr prev = inNode;
|
||||
for (size_t i = 0; i < impl_->slots.size(); ++i) {
|
||||
const Slot& s = impl_->slots[i];
|
||||
@@ -162,7 +262,35 @@ void JuceFxEngine::prepare(uint32_t sampleRate, uint32_t blockSize) {
|
||||
continue;
|
||||
juce::VST3PluginFormat fmt;
|
||||
juce::OwnedArray<juce::PluginDescription> descs;
|
||||
fmt.findAllTypesForFile(descs, juce::File(s.path).getFullPathName());
|
||||
const juce::File pluginFile(s.path);
|
||||
const int64_t mtime = pluginFile.getLastModificationTime().toMilliseconds();
|
||||
const DescCacheEntry* hit = nullptr;
|
||||
for (const auto& e : descCache)
|
||||
if (e.path == s.path && e.mtime == mtime) { hit = &e; break; }
|
||||
if (hit) {
|
||||
// Cache hit: dựng desc (name+uid) — không scan factory, không
|
||||
// instantiate 2 lần như findAllTypesForFile slow path.
|
||||
auto* d = new juce::PluginDescription();
|
||||
d->fileOrIdentifier = juce::String(hit->path);
|
||||
d->name = juce::String(hit->name);
|
||||
d->uniqueId = hit->uniqueId;
|
||||
d->deprecatedUid = hit->deprecatedUid;
|
||||
d->pluginFormatName = "VST3";
|
||||
descs.add(d);
|
||||
} else {
|
||||
fmt.findAllTypesForFile(descs, pluginFile.getFullPathName());
|
||||
if (!descs.isEmpty()) {
|
||||
const auto* d = descs.getFirst();
|
||||
DescCacheEntry e;
|
||||
e.path = s.path;
|
||||
e.mtime = mtime;
|
||||
e.name = d->name.toStdString();
|
||||
e.uniqueId = d->uniqueId;
|
||||
e.deprecatedUid = d->deprecatedUid;
|
||||
descCache.push_back(std::move(e));
|
||||
cacheDirty = true;
|
||||
}
|
||||
}
|
||||
if (descs.isEmpty()) {
|
||||
std::cerr << "[JuceFxEngine] slot " << i << " no VST3 desc: "
|
||||
<< s.path << std::endl;
|
||||
@@ -195,6 +323,7 @@ void JuceFxEngine::prepare(uint32_t sampleRate, uint32_t blockSize) {
|
||||
}
|
||||
prev = node;
|
||||
}
|
||||
if (cacheDirty) saveDescCache(descCache);
|
||||
for (int ch = 0; ch < 2; ++ch) {
|
||||
NCh src; src.nodeID = prev->nodeID; src.channelIndex = ch;
|
||||
NCh dst; dst.nodeID = outNode->nodeID; dst.channelIndex = ch;
|
||||
|
||||
Reference in New Issue
Block a user