2026-10-27 16:24:21 +01:00
|
|
|
#include "plugin_dialog.hpp"
|
|
|
|
|
#include <FL/Fl.H>
|
|
|
|
|
#include <FL/Fl_Double_Window.H>
|
|
|
|
|
#include <FL/Fl_Hold_Browser.H>
|
|
|
|
|
#include <FL/Fl_Box.H>
|
|
|
|
|
#include <FL/Fl_Button.H>
|
|
|
|
|
#include <FL/Fl_Return_Button.H>
|
|
|
|
|
#include <FL/Fl_Value_Input.H>
|
|
|
|
|
#include <FL/Fl_Scroll.H>
|
|
|
|
|
#include <FL/fl_ask.H>
|
|
|
|
|
#include <array>
|
|
|
|
|
#include <vector>
|
|
|
|
|
#include <string>
|
|
|
|
|
#include <cmath>
|
|
|
|
|
|
|
|
|
|
namespace {
|
|
|
|
|
struct Dialog : Fl_Double_Window {
|
|
|
|
|
bool accepted = false;
|
|
|
|
|
Dialog(int width, int height, const char *title) : Fl_Double_Window(width,height,title) { set_modal(); }
|
|
|
|
|
static void accept(Fl_Widget *, void *data) { auto *d = static_cast<Dialog *>(data); d->accepted = true; d->hide(); }
|
|
|
|
|
static void cancel(Fl_Widget *, void *data) { static_cast<Dialog *>(data)->hide(); }
|
|
|
|
|
bool run() { callback(cancel,this); end(); show(); while (shown()) Fl::wait(); return accepted; }
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
bool xtc_choose_plugin(const char *path, double rate, unsigned int &index)
|
|
|
|
|
{
|
2026-10-27 17:34:46 +01:00
|
|
|
Dialog dialog(760,450,"Plugin descriptors");
|
2026-10-27 16:24:21 +01:00
|
|
|
auto *heading = new Fl_Box(15,10,730,40);
|
|
|
|
|
heading->copy_label(path); heading->align(FL_ALIGN_LEFT|FL_ALIGN_INSIDE|FL_ALIGN_CLIP);
|
|
|
|
|
auto *list = new Fl_Hold_Browser(15,55,730,325);
|
|
|
|
|
list->format_char(0); // Plugin names are data, never browser formatting.
|
|
|
|
|
std::vector<unsigned int> indices;
|
|
|
|
|
size_t rejected = 0;
|
|
|
|
|
for (unsigned int i = 0; i < 1024; ++i) {
|
|
|
|
|
xtc_plugin_spec spec{}; xtc_plugin_details details{};
|
|
|
|
|
xtc_result r = xtc_plugin_probe(path,i,rate,&spec);
|
|
|
|
|
if (r == XTC_ERR_NOT_FOUND) break;
|
|
|
|
|
if (r != XTC_OK) { ++rejected; continue; }
|
|
|
|
|
if (xtc_plugin_describe(&spec,rate,&details) != XTC_OK) { ++rejected; continue; }
|
|
|
|
|
std::string row = std::to_string(i) + ": " + details.name + " (" + spec.label + ") — " + details.maker +
|
|
|
|
|
(spec.inputs ? " [effect]" : " [source]");
|
|
|
|
|
list->add(row.c_str()); indices.push_back(i);
|
|
|
|
|
}
|
|
|
|
|
auto *status = new Fl_Box(15,385,520,50);
|
|
|
|
|
std::string summary = std::to_string(indices.size()) + " compatible; " + std::to_string(rejected) +
|
|
|
|
|
" unsupported/unavailable. Select a descriptor.";
|
|
|
|
|
status->copy_label(summary.c_str()); status->align(FL_ALIGN_LEFT|FL_ALIGN_INSIDE|FL_ALIGN_WRAP);
|
|
|
|
|
auto *cancel = new Fl_Button(540,400,90,30,"Cancel"); cancel->callback(Dialog::cancel,&dialog);
|
|
|
|
|
auto *choose = new Fl_Return_Button(645,400,100,30,"Choose"); choose->callback(Dialog::accept,&dialog);
|
|
|
|
|
if (indices.empty()) choose->deactivate(); else list->select(1);
|
|
|
|
|
if (!dialog.run() || list->value() < 1) return false;
|
|
|
|
|
index = indices[static_cast<size_t>(list->value()-1)]; return true;
|
|
|
|
|
}
|
|
|
|
|
bool xtc_edit_plugin(const xtc_plugin_spec &spec, const xtc_plugin_details &details, xtc_plugin_spec &edited)
|
|
|
|
|
{
|
|
|
|
|
Dialog dialog(800,600,"Plugin identity and controls");
|
|
|
|
|
auto *identity = new Fl_Box(15,10,770,125);
|
|
|
|
|
std::string text = std::string(details.name) + " — " + details.maker + "\n" + spec.label +
|
2026-10-27 17:34:46 +01:00
|
|
|
" | " + std::string(xtc_plugin_format_name(spec.format)) + " ID " + std::to_string(spec.unique_id) + " | descriptor " + std::to_string(spec.index) + " | identity " + spec.identifier +
|
2026-10-27 16:24:21 +01:00
|
|
|
"\n" + spec.path + "\n" + details.copyright +
|
2026-10-27 17:34:46 +01:00
|
|
|
"\nMetadata verified; Apply validates initialization. Adapter may not expose units or editable controls.";
|
2026-10-27 16:24:21 +01:00
|
|
|
identity->copy_label(text.c_str()); identity->align(FL_ALIGN_LEFT|FL_ALIGN_INSIDE|FL_ALIGN_WRAP);
|
|
|
|
|
auto *scroll = new Fl_Scroll(10,140,780,370);
|
|
|
|
|
std::array<Fl_Value_Input *,XTC_PLUGIN_CONTROLS> inputs{};
|
|
|
|
|
for (unsigned int i = 0; i < spec.control_count; ++i) {
|
|
|
|
|
int y = 150 + static_cast<int>(i)*65;
|
|
|
|
|
const auto &p = details.parameters[i];
|
|
|
|
|
std::string name = std::to_string(p.id) + ": " + p.name;
|
|
|
|
|
auto *label = new Fl_Box(25,y,425,25); label->copy_label(name.c_str()); label->align(FL_ALIGN_LEFT|FL_ALIGN_INSIDE);
|
|
|
|
|
inputs[i] = new Fl_Value_Input(460,y,150,25); inputs[i]->value(spec.values[i]);
|
|
|
|
|
inputs[i]->step((details.flags[i] & (XTC_PLUGIN_INTEGER|XTC_PLUGIN_TOGGLED)) ? 1 : 0.001);
|
|
|
|
|
std::string hint = "Range " + std::string(details.flags[i] & XTC_PLUGIN_BOUNDED_BELOW ? std::to_string(p.minimum) : "unbounded") +
|
|
|
|
|
" .. " + (details.flags[i] & XTC_PLUGIN_BOUNDED_ABOVE ? std::to_string(p.maximum) : "unbounded") +
|
|
|
|
|
"; default " + std::to_string(p.default_value) +
|
|
|
|
|
((details.flags[i] & XTC_PLUGIN_TOGGLED) ? "; toggle 0/1" : (details.flags[i] & XTC_PLUGIN_INTEGER) ? "; integer" : "; continuous") +
|
|
|
|
|
((details.flags[i] & XTC_PLUGIN_LOGARITHMIC) ? "; logarithmic hint" : "");
|
|
|
|
|
auto *range = new Fl_Box(25,y+27,715,25); range->copy_label(hint.c_str()); range->align(FL_ALIGN_LEFT|FL_ALIGN_INSIDE);
|
|
|
|
|
}
|
|
|
|
|
scroll->end();
|
|
|
|
|
auto *note = new Fl_Box(15,515,770,30,"Automation overrides static controls. Reset defaults also clears all plugin automation.");
|
|
|
|
|
note->labelsize(12);
|
|
|
|
|
struct Reset { const xtc_plugin_details *details; std::array<Fl_Value_Input *,XTC_PLUGIN_CONTROLS> *inputs; unsigned int count; bool used; } reset{&details,&inputs,spec.control_count,false};
|
|
|
|
|
auto *defaults = new Fl_Button(20,555,170,30,"Reset defaults");
|
|
|
|
|
defaults->callback([](Fl_Widget *, void *data) {
|
|
|
|
|
auto *r = static_cast<Reset *>(data); r->used = true;
|
|
|
|
|
for (unsigned int i = 0; i < r->count; ++i) (*r->inputs)[i]->value(r->details->parameters[i].default_value);
|
|
|
|
|
},&reset);
|
|
|
|
|
auto *cancel = new Fl_Button(570,555,90,30,"Cancel"); cancel->callback(Dialog::cancel,&dialog);
|
|
|
|
|
auto *apply = new Fl_Return_Button(675,555,105,30,"Apply"); apply->callback(Dialog::accept,&dialog);
|
|
|
|
|
if (!dialog.run()) return false;
|
|
|
|
|
xtc_plugin_spec candidate = spec;
|
|
|
|
|
for (unsigned int i = 0; i < spec.control_count; ++i) {
|
|
|
|
|
double value = inputs[i]->value(); const auto &p = details.parameters[i]; unsigned int flags = details.flags[i];
|
|
|
|
|
if (!std::isfinite(value) || value < p.minimum || value > p.maximum ||
|
|
|
|
|
((flags & XTC_PLUGIN_INTEGER) && value != std::floor(value)) ||
|
|
|
|
|
((flags & XTC_PLUGIN_TOGGLED) && value != 0 && value != 1)) {
|
|
|
|
|
fl_alert("Invalid value for port %u (%s); no controls were changed.",p.id,p.name); return false;
|
|
|
|
|
}
|
|
|
|
|
candidate.values[i] = static_cast<float>(value);
|
|
|
|
|
}
|
|
|
|
|
if (reset.used) candidate.point_count = 0;
|
|
|
|
|
edited = candidate; return true;
|
|
|
|
|
}
|