#include "plugin_dialog.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include 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(data); d->accepted = true; d->hide(); } static void cancel(Fl_Widget *, void *data) { static_cast(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, bool instrument) { Dialog dialog(760,450,"Plugin descriptors"); 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 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 (!!spec.instrument != instrument) { ++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(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 + " | " + std::string(xtc_plugin_format_name(spec.format)) + " ID " + std::to_string(spec.unique_id) + " | descriptor " + std::to_string(spec.index) + " | identity " + spec.identifier + "\n" + spec.path + "\n" + details.copyright + "\nMetadata verified; Apply validates initialization. Adapter may not expose units or editable controls."; 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 inputs{}; for (unsigned int i = 0; i < spec.control_count; ++i) { int y = 150 + static_cast(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 *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(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(value); } if (reset.used) candidate.point_count = 0; edited = candidate; return true; }