View Issue Details
| ID | Project | Category | View Status | Date Submitted | Last Update |
|---|---|---|---|---|---|
| 0002482 | ardour | features | public | 2008-12-05 14:19 | 2012-05-23 16:45 |
| Reporter | colinf | Assigned To | paul | ||
| Priority | normal | Severity | tweak | Reproducibility | always |
| Status | closed | Resolution | fixed | ||
| Product Version | SVN/2.0-ongoing | ||||
| Target Version | 3.0-beta1 | ||||
| Summary | 0002482: LADSPA plugin windows are sometimes confusingly arranged. | ||||
| Description | Many LADSPA plugins have parameters that could be considered in groups, e.g. the bands of multi-band parametric equalisers. Especially when a plugin has many parameters, it's sometimes confusing when parameters which belong together are split across columns, and there's no visual cue as to which parameters are related. It's possible to make a guess as to which parameters should be considered as groups based on their names, in order to (a) try to avoid splitting groups between columns and (b) make some visual distinction of which controls are grouped. | ||||
| Additional Information | I've hacked together a patch which does this, based on whether it finds a numeric sub-string of the same value as, or a first or last word in common with the previous parameter. It is indeed pretty hacky & horrible, in both concept and implementation, but actually works surprisingly well on pretty much all the LADSPA plugins I've tried it with. If anyone thinks the idea is worthwhile, I can maybe have a go at implementing it a bit more nicely. | ||||
| Tags | No tags attached. | ||||
|
2008-12-05 14:19
|
generic-plugin-sections.patch (5,119 bytes)
Index: gtk2_ardour/generic_pluginui.cc
===================================================================
--- gtk2_ardour/generic_pluginui.cc (revision 4281)
+++ gtk2_ardour/generic_pluginui.cc (working copy)
@@ -121,9 +121,119 @@
}
}
+
+static int numeric_value(string label, int pos) {
+int value = -1;
+
+ stringstream s;
+ s << label.substr(pos);
+ s >> value;
+
+ return value;
+
+}
+
+static void split_label(string label, string &first, string &last, int &num) {
+static const char *digits = "0123456789";
+static const char *whitespace = " \t\r\n";
+
+
+ unsigned int first_digit_pos = label.find_first_of(digits);
+
+
+ if (first_digit_pos != string::npos) {
+ // found some digits
+ num = numeric_value(label, first_digit_pos);
+
+ if (first_digit_pos > 0) {
+ // some text preceding number
+ first = label.substr(0, first_digit_pos);
+
+ } else {
+ first = "";
+
+ }
+
+ unsigned int last_digit_pos = label.find_last_of(digits);
+ if (last_digit_pos < label.length()) {
+ // some text following number
+ last = label.substr(last_digit_pos + 1);
+
+ } else {
+ last = "";
+ }
+ } else {
+ // no digits found
+ num = -1;
+ unsigned int first_whitespace_pos = label.find_first_of(whitespace);
+ if (first_whitespace_pos != string::npos) {
+ // found some whitespace
+ if (first_whitespace_pos > 0) {
+ // some text before whitespace
+ first = label.substr(0, first_whitespace_pos);
+ } else {
+ // whitespace at start of label: what to do?
+ first = "";
+ }
+
+ unsigned int last_whitespace_pos = label.find_last_of(whitespace);
+ if (last_whitespace_pos < label.length()) {
+ // some text following whitespace
+ last = label.substr(last_whitespace_pos + 1);
+ } else {
+ // whitespace at end of label: what to do?
+ last = "";
+ }
+ } else {
+ // no whitespace
+ first = label;
+ last = "";
+ }
+
+ }
+}
+
+
+static bool possible_split(string label, string previous) {
+/* See how similar label is to previous.
+ *
+ * If they are sufficiently different, (according to a hand-wavy definition of
+ * "sufficiently"), return true to indicate that we might wish to start a new
+ * column, or display some visual demarcation.
+ *
+ * Otherwise, return false to indicate that this label and the previous one
+ * should probably be grouped together.
+ */
+
+
+ int section_number, previous_section_number;
+
+ string first_word, last_word,
+ previous_first_word, previous_last_word;
+
+ if (previous == "")
+ return false;
+
+ split_label(label, first_word, last_word, section_number);
+ split_label(previous, previous_first_word, previous_last_word, previous_section_number);
+
+ // cerr << "label: " << label << ", num: " << section_number << endl;
+
+ if (section_number == previous_section_number) {
+ return false;
+ } else {
+ if (first_word == previous_first_word &&
+ last_word == previous_last_word ) {
+ return false;
+ }
+ }
+ return true;
+
+}
+
+
void
GenericPluginUI::build ()
-
{
guint32 i = 0;
guint32 x = 0;
@@ -173,8 +283,10 @@
frame->set_label (_("Controls"));
frame->add (*box);
hpacker.pack_start(*frame, true, true);
-
+
/* find all ports. build control elements for all appropriate control ports */
+
+ string previous_label = "";
for (i = 0; i < plugin->parameter_count(); ++i) {
@@ -188,24 +300,7 @@
ControlUI* cui;
- /* if we are scrollable, just use one long column */
- if (!is_scrollable) {
- if (x++ > 20){
- frame = manage (new Frame);
- frame->set_name ("BaseFrame");
- box = manage (new VBox);
-
- box->set_border_width (5);
- box->set_spacing (1);
-
- frame->add (*box);
- hpacker.pack_start(*frame,true,true);
-
- x = 1;
- }
- }
-
if ((cui = build_control_ui (i, plugin->get_nth_control (i))) == 0) {
error << string_compose(_("Plugin Editor: could not build control element for port %1"), i) << endmsg;
continue;
@@ -213,10 +308,34 @@
if (cui->control || cui->clickbox || cui->combo) {
+ /* if we are scrollable, just use one long column */
+ if (!is_scrollable) {
+ x++;
+ if (possible_split(cui->label.get_text(), previous_label) || x > 20) {
+ if (x > 16) {
+ frame = manage (new Frame);
+ frame->set_name ("BaseFrame");
+ frame->set_label (_("Controls"));
+ box = manage (new VBox);
+
+ box->set_border_width (5);
+ box->set_spacing (1);
+
+ frame->add (*box);
+ hpacker.pack_start(*frame,true,true);
+
+ x = 0;
+ } else {
+ Label *split = new Label(" ");
+ box->pack_start(*split, false, false, 0);
+ }
+ }
+ }
+
box->pack_start (*cui, false, false);
-
+ previous_label = cui->label.get_text();
} else if (cui->button) {
-
+
if (button_row == button_rows) {
button_row = 0;
if (++button_col == button_cols) {
@@ -227,6 +346,7 @@
button_table.attach (*cui, button_col, button_col + 1, button_row, button_row+1,
FILL|EXPAND, FILL);
+
button_row++;
} else if (cui->display) {
|
|
|
Well I certainly agree that grouping certain parameters is useful, I tend to think of this more as a limitation of LADSPA than Ardour myself, and it will be limited in how useful it can be by the default LADSPA GUI I suppose. But I love to be proven wrong as well;) Seablade |
|
2009-04-12 22:57
|
|
|
2009-04-12 22:58
|
|
|
2009-04-12 22:58
|
|
|
2009-04-12 22:59
|
|
|
|
Well, I finally made some before-and-after screenshots. I certainly find the 'after' layouts easier to use, and I think it goes a little way towards addressing the criticism that LADSPA plugin UIs look 'plain', but without adding unnecessary bling. I might send a separate patch for the unnecessary bling later, if I get round to it :-) |
|
2009-11-10 18:44
|
|
|
|
OK, unnecessary bling, as promised. I know that the future is made of LV2 plugins, and very nice they look too, but just for a laugh in an idle moment the other day I rounded up a load of textural xpms I found about the place, and hacked generic_plugin_ui.cc & my ardour2_ui.rc theme file to sort-of-randomly assign them to the bg_pixmap of LADSPA plugin editor windows, according to a rudimentary "hash" of the 'creator' property of the plugin. Attached a screenshot: ardour-plugins-aargh-my-eyes.png. I can't decide whether this is a completely ridiculous idea or not. Thoughts, anyone? Also, seriously, any interest in the concept of the original patch here? |
|
|
.... mmmm, I don't know about the pink marble background ... :lol: but the idea is fun :) |
|
2010-04-22 13:40
|
generic-plugin-sections-v2.patch (7,109 bytes)
Index: gtk2_ardour/generic_pluginui.cc
===================================================================
--- gtk2_ardour/generic_pluginui.cc (revision 6960)
+++ gtk2_ardour/generic_pluginui.cc (working copy)
@@ -121,6 +121,61 @@
}
}
+// Some functions useful for calculation the 'similarity' of two plugin control
+// labels. Maybe there are standard library functions that do these things, but
+// I don't know what they are...
+
+static int get_number(string label) {
+static const char *digits = "0123456789";
+int value = -1;
+
+ unsigned int first_digit_pos = label.find_first_of(digits);
+ if (first_digit_pos != string::npos) {
+ // found some digits: there's a number in there somewhere
+ stringstream s;
+ s << label.substr(first_digit_pos);
+ s >> value;
+ }
+ return value;
+}
+
+
+static int match_or_digit(char c1, char c2) {
+ return c1 == c2 || (isdigit(c1) && isdigit(c2));
+}
+
+static int matching_chars_at_head(const string s1, const string s2) {
+int n = 0;
+int length;
+
+ length = min(s1.length(), s2.length());
+ while (n < length) {
+ if (!match_or_digit(s1[n], s2[n]))
+ break;
+ n++;
+ }
+ return n;
+}
+
+static int matching_chars_at_tail(const string s1, const string s2) {
+int n = 0;
+int s1pos, s2pos;
+
+ s1pos = s1.length();
+ s2pos = s2.length();
+ while (s1pos-- > 0 && s2pos-- > 0) {
+ if (!match_or_digit(s1[s1pos], s2[s2pos]) )
+ break;
+ n++;
+ }
+ return n;
+}
+
+// #include <iomanip>
+
+static const guint32 min_controls_per_column = 17, max_controls_per_column = 24;
+static const float default_similarity_threshold = 0.3;
+
void
GenericPluginUI::build ()
@@ -135,6 +190,7 @@
int output_rows, output_cols;
int button_rows, button_cols;
guint32 n_ins=0, n_outs = 0;
+ int tallest_column = 0;
prefheight = 30;
hpacker.set_spacing (10);
@@ -161,6 +217,7 @@
bt_frame = manage (new Frame);
bt_frame->set_name ("BaseFrame");
+ bt_frame->set_label (_("Switches"));
bt_frame->add (button_table);
hpacker.pack_start(*bt_frame, true, true);
@@ -175,6 +232,7 @@
hpacker.pack_start(*frame, true, true);
/* find all ports. build control elements for all appropriate control ports */
+ std::vector<ControlUI *> cui_controls_list;
for (i = 0; i < plugin->parameter_count(); ++i) {
@@ -187,34 +245,15 @@
}
ControlUI* cui;
-
- /* if we are scrollable, just use one long column */
-
- if (!is_scrollable) {
- if (x++ > 20){
- frame = manage (new Frame);
- frame->set_name ("BaseFrame");
- box = manage (new VBox);
-
- box->set_border_width (5);
- box->set_spacing (1);
-
- frame->add (*box);
- hpacker.pack_start(*frame,true,true);
-
- x = 1;
- }
- }
-
if ((cui = build_control_ui (i, plugin->get_nth_control (i))) == 0) {
error << string_compose(_("Plugin Editor: could not build control element for port %1"), i) << endmsg;
continue;
}
if (cui->control || cui->clickbox || cui->combo) {
-
- box->pack_start (*cui, false, false);
-
+ // get all of the controls into a list so we can lay them out
+ // a bit more nicely
+ cui_controls_list.push_back(cui);
} else if (cui->button) {
if (button_row == button_rows) {
@@ -240,35 +279,99 @@
output_cols ++;
output_table.resize (output_rows, output_cols);
}
-
- /* old code, which divides meters into
- * columns first, rows later. New code divides into one row
-
- if (output_row == output_rows) {
- output_row = 0;
- if (++output_col == output_cols) {
- output_cols += 2;
- output_table.resize (output_rows, output_cols);
+ }
+ }
+ }
+
+ string label, previous_label = "";
+ int numbers_in_labels[cui_controls_list.size()];
+
+ float similarity_scores[cui_controls_list.size()];
+ float most_similar = 0.0, least_similar = 1.0;
+
+ i = 0;
+ for (vector<ControlUI*>::iterator cuip = cui_controls_list.begin(); cuip != cui_controls_list.end(); ++cuip, ++i) {
+ label = (*cuip)->label.get_text();
+ numbers_in_labels[i] = get_number(label);
+
+ if (i > 0) {
+ // a hand-wavy calculation of how similar this control's
+ // label is to the previous.
+ similarity_scores[i] =
+ (float) (
+ ( matching_chars_at_head(label, previous_label) +
+ matching_chars_at_tail(label, previous_label) +
+ 1
+ )
+ ) / (label.length() + previous_label.length());
+ if (numbers_in_labels[i] >= 0) {
+ similarity_scores[i] += (numbers_in_labels[i] == numbers_in_labels[i-1]);
+ }
+ least_similar = min(least_similar, similarity_scores[i]);
+ most_similar = max(most_similar, similarity_scores[i]);
+ } else {
+ similarity_scores[0] = 1.0;
+ }
+
+ // cerr << "label: " << label << " sim: " << fixed << setprecision(3) << similarity_scores[i] << " num: " << numbers_in_labels[i] << endl;
+ previous_label = label;
+ }
+
+
+ // cerr << "most similar: " << most_similar << ", least similar: " << least_similar << endl;
+ float similarity_threshold;
+
+ if (most_similar > 1.0) {
+ similarity_threshold = default_similarity_threshold;
+ } else {
+ similarity_threshold = most_similar - (1 - default_similarity_threshold);
+ }
+
+
+ i = 0;
+ for (vector<ControlUI*>::iterator cuip = cui_controls_list.begin(); cuip != cui_controls_list.end(); ++cuip, ++i) {
+
+ ControlUI* cui = *cuip;
+ /* if we are scrollable, just use one long column */
+ if (!is_scrollable) {
+ x++;
+ if (x > max_controls_per_column || similarity_scores[i] <= similarity_threshold) {
+ if (x > min_controls_per_column) {
+ frame = manage (new Frame);
+ frame->set_name ("BaseFrame");
+ frame->set_label (_("Controls"));
+ box = manage (new VBox);
+
+ box->set_border_width (5);
+ box->set_spacing (1);
+
+ frame->add (*box);
+ hpacker.pack_start(*frame,true,true);
+
+ if (tallest_column < x) {
+ tallest_column = x;
}
+ x = 0;
+ } else {
+ HSeparator *split = new HSeparator();
+ split->set_size_request(-1, 5);
+ box->pack_start(*split, false, false, 0);
}
-
- output_table.attach (*cui, output_col, output_col + 1, output_row, output_row+1,
- FILL|EXPAND, FILL);
-
- output_row++;
- */
}
-
- /* HACK: ideally the preferred height would be queried from
- the complete hpacker, but I can't seem to get that
- information in time, so this is an estimation
- */
+ }
- prefheight += 30;
+ box->pack_start (*cui, false, false);
+ previous_label = cui->label.get_text();
- }
}
+
+ /* HACK: ideally the preferred height would be queried from
+ the complete hpacker, but I can't seem to get that
+ information in time, so this is an estimation
+ */
+ prefheight = (tallest_column + 1) * 20;
+
n_ins = plugin->get_info()->n_inputs;
n_outs = plugin->get_info()->n_outputs;
@@ -283,6 +386,7 @@
if (!output_table.children().empty()) {
frame = manage (new Frame);
frame->set_name ("BaseFrame");
+ frame->set_label(_("Meters"));
frame->add (output_table);
hpacker.pack_end (*frame, true, true);
}
|
|
|
Well, still pretty much a hack, but I tidied up my original patch a bit, so I thought I might as well upload a newer version here, just in case anyone's interested. |
|
|
Assigning to Paul to take a look at the patch. Seablade |
|
2011-05-26 16:41
|
generic-plugin-sections-a3.patch (6,786 bytes)
Index: gtk2_ardour/generic_pluginui.cc
===================================================================
--- gtk2_ardour/generic_pluginui.cc (revision 9600)
+++ gtk2_ardour/generic_pluginui.cc (working copy)
@@ -138,6 +138,57 @@
}
}
+// Some functions for calculating the 'similarity' of two plugin
+// control labels.
+
+static int get_number(string label) {
+static const char *digits = "0123456789";
+int value = -1;
+
+ unsigned int first_digit_pos = label.find_first_of(digits);
+ if (first_digit_pos != string::npos) {
+ // found some digits: there's a number in there somewhere
+ stringstream s;
+ s << label.substr(first_digit_pos);
+ s >> value;
+ }
+ return value;
+}
+
+static int match_or_digit(char c1, char c2) {
+ return c1 == c2 || (isdigit(c1) && isdigit(c2));
+}
+
+static int matching_chars_at_head(const string s1, const string s2) {
+int n = 0;
+int length;
+
+ length = min(s1.length(), s2.length());
+ while (n < length) {
+ if (!match_or_digit(s1[n], s2[n]))
+ break;
+ n++;
+ }
+ return n;
+}
+
+static int matching_chars_at_tail(const string s1, const string s2) {
+int n = 0;
+int s1pos, s2pos;
+
+ s1pos = s1.length();
+ s2pos = s2.length();
+ while (s1pos-- > 0 && s2pos-- > 0) {
+ if (!match_or_digit(s1[s1pos], s2[s2pos]) )
+ break;
+ n++;
+ }
+ return n;
+}
+
+static const guint32 min_controls_per_column = 17, max_controls_per_column = 24;
+static const float default_similarity_threshold = 0.3;
+
void
GenericPluginUI::build ()
{
@@ -151,7 +202,6 @@
int output_rows, output_cols;
int button_rows, button_cols;
- prefheight = 30;
hpacker.set_spacing (10);
output_rows = initial_output_rows;
@@ -176,6 +226,7 @@
bt_frame = manage (new Frame);
bt_frame->set_name ("BaseFrame");
+ bt_frame->set_label (_("Switches"));
bt_frame->add (button_table);
hpacker.pack_start(*bt_frame, true, true);
@@ -190,6 +241,7 @@
hpacker.pack_start(*frame, true, true);
/* find all ports. build control elements for all appropriate control ports */
+ std::vector<ControlUI *> cui_controls_list;
for (i = 0; i < plugin->parameter_count(); ++i) {
@@ -203,24 +255,6 @@
ControlUI* cui;
- /* if we are scrollable, just use one long column */
-
- if (!is_scrollable) {
- if (x++ > 20){
- frame = manage (new Frame);
- frame->set_name ("BaseFrame");
- box = manage (new VBox);
-
- box->set_border_width (5);
- box->set_spacing (1);
-
- frame->add (*box);
- hpacker.pack_start(*frame, true, true);
-
- x = 1;
- }
- }
-
boost::shared_ptr<ARDOUR::AutomationControl> c
= boost::dynamic_pointer_cast<ARDOUR::AutomationControl>(
insert->control(Evoral::Parameter(PluginAutomation, 0, i)));
@@ -231,9 +265,9 @@
}
if (cui->controller || cui->clickbox || cui->combo) {
-
- box->pack_start (*cui, false, false);
-
+ // Get all of the controls into a list, so that
+ // we can lay them out a bit more nicely later.
+ cui_controls_list.push_back(cui);
} else if (cui->button) {
if (button_row == button_rows) {
@@ -259,33 +293,89 @@
output_cols ++;
output_table.resize (output_rows, output_cols);
}
+ }
+ }
+ }
- /* old code, which divides meters into
- * columns first, rows later. New code divides into one row
+ // Iterate over the list of controls to find which adjacent controls
+ // are similar enough to be grouped together.
+
+ string label, previous_label = "";
+ int numbers_in_labels[cui_controls_list.size()];
+
+ float similarity_scores[cui_controls_list.size()];
+ float most_similar = 0.0, least_similar = 1.0;
+
+ i = 0;
+ for (vector<ControlUI*>::iterator cuip = cui_controls_list.begin(); cuip != cui_controls_list.end(); ++cuip, ++i) {
+ label = (*cuip)->label.get_text();
+ numbers_in_labels[i] = get_number(label);
- if (output_row == output_rows) {
- output_row = 0;
- if (++output_col == output_cols) {
- output_cols += 2;
- output_table.resize (output_rows, output_cols);
- }
- }
-
- output_table.attach (*cui, output_col, output_col + 1, output_row, output_row+1,
- FILL|EXPAND, FILL);
-
- output_row++;
- */
+ if (i > 0) {
+ // A hand-wavy calculation of how similar this control's
+ // label is to the previous.
+ similarity_scores[i] =
+ (float) (
+ ( matching_chars_at_head(label, previous_label) +
+ matching_chars_at_tail(label, previous_label) +
+ 1
+ )
+ ) / (label.length() + previous_label.length());
+ if (numbers_in_labels[i] >= 0) {
+ similarity_scores[i] += (numbers_in_labels[i] == numbers_in_labels[i-1]);
}
+ least_similar = min(least_similar, similarity_scores[i]);
+ most_similar = max(most_similar, similarity_scores[i]);
+ } else {
+ similarity_scores[0] = 1.0;
+ }
- /* HACK: ideally the preferred height would be queried from
- the complete hpacker, but I can't seem to get that
- information in time, so this is an estimation
- */
+ // cerr << "label: " << label << " sim: " << fixed << setprecision(3) << similarity_scores[i] << " num: " << numbers_in_labels[i] << endl;
+ previous_label = label;
+ }
- prefheight += 30;
+
+ // cerr << "most similar: " << most_similar << ", least similar: " << least_similar << endl;
+ float similarity_threshold;
+
+ if (most_similar > 1.0) {
+ similarity_threshold = default_similarity_threshold;
+ } else {
+ similarity_threshold = most_similar - (1 - default_similarity_threshold);
+ }
+
+ // Now iterate over the list of controls to display them, placing an
+ // HSeparator between controls of less than a certain similarity, and
+ // starting a new column when necessary.
+
+ i = 0;
+ for (vector<ControlUI*>::iterator cuip = cui_controls_list.begin(); cuip != cui_controls_list.end(); ++cuip, ++i) {
+ ControlUI* cui = *cuip;
+
+ if (!is_scrollable) {
+ x++;
}
+
+ if (x > max_controls_per_column || similarity_scores[i] <= similarity_threshold) {
+ if (x > min_controls_per_column) {
+ frame = manage (new Frame);
+ frame->set_name ("BaseFrame");
+ frame->set_label (_("Controls"));
+ box = manage (new VBox);
+ box->set_border_width (5);
+ box->set_spacing (1);
+ frame->add (*box);
+ hpacker.pack_start(*frame,true,true);
+ x = 0;
+ } else {
+ HSeparator *split = new HSeparator();
+ split->set_size_request(-1, 5);
+ box->pack_start(*split, false, false, 0);
+ }
+
+ }
+ box->pack_start (*cui, false, false);
}
if (box->children().empty()) {
@@ -299,6 +389,7 @@
if (!output_table.children().empty()) {
frame = manage (new Frame);
frame->set_name ("BaseFrame");
+ frame->set_label(_("Meters"));
frame->add (output_table);
hpacker.pack_end (*frame, true, true);
}
|
|
|
Well, I've got thoroughly used to the controls of my LADSPA plugins grouping themselves with this patch applied to A2, and I was missing it in A3, so I spent a few idle moments porting the patch to A3. Still just as much of a hack as it ever was, but just in case anyone else is interested, here it is... I'll very happily take any suggestions of how to make it better, too. |
|
|
colin - patch is being applied as i write. lets talk about the xpms. i like the brushed/illuminated ones, the others i think interfere with either readability or theme consistency. |
|
|
the patch crashes because of the use of int rather than string::size_t in the static helper functions. it also seems to blow up the vertical size of the plugin editor windows. fix these two and i'll be happy to commit (a3) |
|
|
Thanks very much for looking at this, Paul. I've only tested on 32 bit systems, so there may well be some int/size_t oddities on 64 bits. Hopefully fixed now. The vertical size blow-up was because I somehow completely removed the assignment to prefheight, leaving it undefined. It didn't always blow up the vertical size: sometimes it worked properly, and just occasionally it crashed. I think that initialising prefheight to 0 in the GenericPluginUI constructor is probably the right thing to do: it only needs to be set to something more if (is_scrollable) is set. |
|
2011-07-11 11:15
|
generic-plugin-sections-a3-v2.patch (7,143 bytes)
Index: gtk2_ardour/generic_pluginui.cc
===================================================================
--- gtk2_ardour/generic_pluginui.cc (revision 9834)
+++ gtk2_ardour/generic_pluginui.cc (working copy)
@@ -128,6 +128,7 @@
bypass_button.set_active (!pi->active());
+ prefheight = 0;
build ();
}
@@ -138,6 +139,55 @@
}
}
+// Some functions for calculating the 'similarity' of two plugin
+// control labels.
+
+static int get_number(string label) {
+static const char *digits = "0123456789";
+int value = -1;
+
+ std::size_t first_digit_pos = label.find_first_of(digits);
+ if (first_digit_pos != string::npos) {
+ // found some digits: there's a number in there somewhere
+ stringstream s;
+ s << label.substr(first_digit_pos);
+ s >> value;
+ }
+ return value;
+}
+
+static int match_or_digit(char c1, char c2) {
+ return c1 == c2 || (isdigit(c1) && isdigit(c2));
+}
+
+static std::size_t matching_chars_at_head(const string s1, const string s2) {
+std::size_t length, n = 0;
+
+ length = min(s1.length(), s2.length());
+ while (n < length) {
+ if (!match_or_digit(s1[n], s2[n]))
+ break;
+ n++;
+ }
+ return n;
+}
+
+static std::size_t matching_chars_at_tail(const string s1, const string s2) {
+std::size_t s1pos, s2pos, n = 0;
+
+ s1pos = s1.length();
+ s2pos = s2.length();
+ while (s1pos-- > 0 && s2pos-- > 0) {
+ if (!match_or_digit(s1[s1pos], s2[s2pos]) )
+ break;
+ n++;
+ }
+ return n;
+}
+
+static const guint32 min_controls_per_column = 17, max_controls_per_column = 24;
+static const float default_similarity_threshold = 0.3;
+
void
GenericPluginUI::build ()
{
@@ -151,7 +201,6 @@
int output_rows, output_cols;
int button_rows, button_cols;
- prefheight = 30;
hpacker.set_spacing (10);
output_rows = initial_output_rows;
@@ -176,6 +225,7 @@
bt_frame = manage (new Frame);
bt_frame->set_name ("BaseFrame");
+ bt_frame->set_label (_("Switches"));
bt_frame->add (button_table);
hpacker.pack_start(*bt_frame, true, true);
@@ -190,6 +240,7 @@
hpacker.pack_start(*frame, true, true);
/* find all ports. build control elements for all appropriate control ports */
+ std::vector<ControlUI *> cui_controls_list;
for (i = 0; i < plugin->parameter_count(); ++i) {
@@ -203,24 +254,6 @@
ControlUI* cui;
- /* if we are scrollable, just use one long column */
-
- if (!is_scrollable) {
- if (x++ > 20){
- frame = manage (new Frame);
- frame->set_name ("BaseFrame");
- box = manage (new VBox);
-
- box->set_border_width (5);
- box->set_spacing (1);
-
- frame->add (*box);
- hpacker.pack_start(*frame, true, true);
-
- x = 1;
- }
- }
-
boost::shared_ptr<ARDOUR::AutomationControl> c
= boost::dynamic_pointer_cast<ARDOUR::AutomationControl>(
insert->control(Evoral::Parameter(PluginAutomation, 0, i)));
@@ -231,12 +264,12 @@
}
if (cui->controller || cui->clickbox || cui->combo) {
-
- box->pack_start (*cui, false, false);
-
+ // Get all of the controls into a list, so that
+ // we can lay them out a bit more nicely later.
+ cui_controls_list.push_back(cui);
} else if (cui->button) {
- if (button_row == button_rows) {
+ if (!is_scrollable && button_row == button_rows) {
button_row = 0;
if (++button_col == button_cols) {
button_cols += 2;
@@ -259,35 +292,95 @@
output_cols ++;
output_table.resize (output_rows, output_cols);
}
+ }
+ }
+ }
- /* old code, which divides meters into
- * columns first, rows later. New code divides into one row
+ // Iterate over the list of controls to find which adjacent controls
+ // are similar enough to be grouped together.
+
+ string label, previous_label = "";
+ int numbers_in_labels[cui_controls_list.size()];
+
+ float similarity_scores[cui_controls_list.size()];
+ float most_similar = 0.0, least_similar = 1.0;
+
+ i = 0;
+ for (vector<ControlUI*>::iterator cuip = cui_controls_list.begin(); cuip != cui_controls_list.end(); ++cuip, ++i) {
+ label = (*cuip)->label.get_text();
+ numbers_in_labels[i] = get_number(label);
- if (output_row == output_rows) {
- output_row = 0;
- if (++output_col == output_cols) {
- output_cols += 2;
- output_table.resize (output_rows, output_cols);
- }
- }
-
- output_table.attach (*cui, output_col, output_col + 1, output_row, output_row+1,
- FILL|EXPAND, FILL);
-
- output_row++;
- */
+ if (i > 0) {
+ // A hand-wavy calculation of how similar this control's
+ // label is to the previous.
+ similarity_scores[i] =
+ (float) (
+ ( matching_chars_at_head(label, previous_label) +
+ matching_chars_at_tail(label, previous_label) +
+ 1
+ )
+ ) / (label.length() + previous_label.length());
+ if (numbers_in_labels[i] >= 0) {
+ similarity_scores[i] += (numbers_in_labels[i] == numbers_in_labels[i-1]);
}
+ least_similar = min(least_similar, similarity_scores[i]);
+ most_similar = max(most_similar, similarity_scores[i]);
+ } else {
+ similarity_scores[0] = 1.0;
+ }
- /* HACK: ideally the preferred height would be queried from
- the complete hpacker, but I can't seem to get that
- information in time, so this is an estimation
- */
+ // cerr << "label: " << label << " sim: " << fixed << setprecision(3) << similarity_scores[i] << " num: " << numbers_in_labels[i] << endl;
+ previous_label = label;
+ }
- prefheight += 30;
+
+ // cerr << "most similar: " << most_similar << ", least similar: " << least_similar << endl;
+ float similarity_threshold;
+
+ if (most_similar > 1.0) {
+ similarity_threshold = default_similarity_threshold;
+ } else {
+ similarity_threshold = most_similar - (1 - default_similarity_threshold);
+ }
+
+ // Now iterate over the list of controls to display them, placing an
+ // HSeparator between controls of less than a certain similarity, and
+ // starting a new column when necessary.
+
+ i = 0;
+ for (vector<ControlUI*>::iterator cuip = cui_controls_list.begin(); cuip != cui_controls_list.end(); ++cuip, ++i) {
+ ControlUI* cui = *cuip;
+
+ if (!is_scrollable) {
+ x++;
}
+
+ if (x > max_controls_per_column || similarity_scores[i] <= similarity_threshold) {
+ if (x > min_controls_per_column) {
+ frame = manage (new Frame);
+ frame->set_name ("BaseFrame");
+ frame->set_label (_("Controls"));
+ box = manage (new VBox);
+ box->set_border_width (5);
+ box->set_spacing (1);
+ frame->add (*box);
+ hpacker.pack_start(*frame, true, true);
+ x = 0;
+ } else {
+ HSeparator *split = new HSeparator();
+ split->set_size_request(-1, 5);
+ box->pack_start(*split, false, false, 0);
+ }
+
+ }
+ box->pack_start (*cui, false, false);
}
+ if (is_scrollable) {
+ prefheight = 30 * i;
+ }
+
if (box->children().empty()) {
hpacker.remove (*frame);
}
@@ -299,6 +392,7 @@
if (!output_table.children().empty()) {
frame = manage (new Frame);
frame->set_name ("BaseFrame");
+ frame->set_label(_("Meters"));
frame->add (output_table);
hpacker.pack_end (*frame, true, true);
}
|
|
|
As for the XPM's, I didn't expect anyone to take that idea seriously. I don't think I even have the patch any more; I do remember it was a sufficiently horrid hack that I was ashamed to even post it here... I think the images I used mostly came from Nick Copeland's Bristol synth, but I found a few others around too: I know the pink marble was from the JAMin sources. |
|
|
committed as rev 9835. thanks! |
|
|
Closing this long-resolved issue. |
| Date Modified | Username | Field | Change |
|---|---|---|---|
| 2008-12-05 14:19 | colinf | New Issue | |
| 2008-12-05 14:19 | colinf | File Added: generic-plugin-sections.patch | |
| 2008-12-07 01:52 | seablade | Note Added: 0005504 | |
| 2008-12-07 01:52 | seablade | Status | new => acknowledged |
| 2009-04-12 22:57 | colinf | File Added: 4-band-parametric-before.png | |
| 2009-04-12 22:58 | colinf | File Added: 4-band-parametric-after.png | |
| 2009-04-12 22:58 | colinf | File Added: tap-equalizer-bw-before.png | |
| 2009-04-12 22:59 | colinf | File Added: tap-equalizer-bw-after.png | |
| 2009-04-12 23:07 | colinf | Note Added: 0005893 | |
| 2009-11-10 18:44 | colinf | File Added: ardour-plugins-aargh-my-eyes.png | |
| 2009-11-10 18:45 | colinf | Note Added: 0007099 | |
| 2009-11-11 14:16 | thorgal | Note Added: 0007101 | |
| 2010-04-22 13:40 | colinf | File Added: generic-plugin-sections-v2.patch | |
| 2010-04-22 13:43 | colinf | Note Added: 0007517 | |
| 2010-04-22 14:47 | seablade | Note Added: 0007518 | |
| 2010-04-22 14:47 | seablade | Status | acknowledged => assigned |
| 2010-04-22 14:47 | seablade | Assigned To | => paul |
| 2011-05-26 16:41 | colinf | File Added: generic-plugin-sections-a3.patch | |
| 2011-05-26 16:42 | colinf | Note Added: 0010805 | |
| 2011-05-26 16:50 | cth103 | cost | => 0.00 |
| 2011-05-26 16:50 | cth103 | Target Version | => 3.0-beta1 |
| 2011-07-08 19:01 | paul | Note Added: 0011002 | |
| 2011-07-08 19:35 | paul | Note Added: 0011003 | |
| 2011-07-11 10:57 | colinf | Note Added: 0011059 | |
| 2011-07-11 11:15 | colinf | File Added: generic-plugin-sections-a3-v2.patch | |
| 2011-07-11 12:14 | colinf | Note Added: 0011063 | |
| 2011-07-11 12:34 | paul | Note Added: 0011064 | |
| 2011-07-11 12:34 | paul | Status | assigned => resolved |
| 2011-07-11 12:34 | paul | Resolution | open => fixed |
| 2012-05-23 16:45 | colinf | Note Added: 0013282 | |
| 2012-05-23 16:45 | colinf | Status | resolved => closed |