diff options
Diffstat (limited to 'gtk2_ardour/transform_dialog.cc')
-rw-r--r-- | gtk2_ardour/transform_dialog.cc | 359 |
1 files changed, 359 insertions, 0 deletions
diff --git a/gtk2_ardour/transform_dialog.cc b/gtk2_ardour/transform_dialog.cc new file mode 100644 index 0000000000..35df027187 --- /dev/null +++ b/gtk2_ardour/transform_dialog.cc @@ -0,0 +1,359 @@ +/* + Copyright (C) 2009-2014 Paul Davis + Author: David Robillard + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#include <gtkmm/box.h> +#include <gtkmm/label.h> +#include <gtkmm/stock.h> + +#include "transform_dialog.h" + +#include "i18n.h" + +using namespace std; +using namespace Gtk; +using namespace ARDOUR; + +TransformDialog::Model::Model() + : source_list(Gtk::ListStore::create(source_cols)) + , property_list(Gtk::ListStore::create(property_cols)) + , operator_list(Gtk::ListStore::create(operator_cols)) +{ + static const char* source_labels[] = { + /* no NOTHING */ + _("this note's"), + _("the previous note's"), + _("this note's index"), + _("the number of notes"), + _("exactly"), + _("a random number from"), + NULL + }; + for (int s = 0; source_labels[s]; ++s) { + Gtk::TreeModel::Row row = *(source_list->append()); + row[source_cols.source] = (Source)(s + 1); // Skip NOTHING + row[source_cols.label] = source_labels[s]; + } + // Special row for ramp, which doesn't correspond to a source + Gtk::TreeModel::Row row = *(source_list->append()); + row[source_cols.source] = Value::NOWHERE; + row[source_cols.label] = _("equal steps from"); + + static const char* property_labels[] = { + _("note number"), + _("velocity"), + _("start time"), + _("length"), + _("channel"), + NULL + }; + for (int p = 0; property_labels[p]; ++p) { + Gtk::TreeModel::Row row = *(property_list->append()); + row[property_cols.property] = (Property)p; + row[property_cols.label] = property_labels[p]; + } + + static const char* operator_labels[] = { + /* no PUSH */ "+", "-", "*", "/", NULL + }; + for (int o = 0; operator_labels[o]; ++o) { + Gtk::TreeModel::Row row = *(operator_list->append()); + row[operator_cols.op] = (Operator)(o + 1); // Skip PUSH + row[operator_cols.label] = operator_labels[o]; + } +} + +TransformDialog::TransformDialog() + : ArdourDialog(_("Transform"), false, false) +{ + _property_combo.set_model(_model.property_list); + _property_combo.pack_start(_model.property_cols.label); + _property_combo.set_active(1); + _property_combo.signal_changed().connect( + sigc::mem_fun(this, &TransformDialog::property_changed)); + + Gtk::HBox* property_hbox = Gtk::manage(new Gtk::HBox); + property_hbox->pack_start(*Gtk::manage(new Gtk::Label(_("Set "))), false, false); + property_hbox->pack_start(_property_combo, false, false); + property_hbox->pack_start(*Gtk::manage(new Gtk::Label(_(" to "))), false, false); + + _seed_chooser = Gtk::manage(new ValueChooser(_model)); + _seed_chooser->set_target_property(MidiModel::NoteDiffCommand::Velocity); + _seed_chooser->source_combo.set_active(0); + property_hbox->pack_start(*_seed_chooser, false, false); + + Gtk::HBox* add_hbox = Gtk::manage(new Gtk::HBox); + _add_button.add( + *manage(new Gtk::Image(Gtk::Stock::ADD, Gtk::ICON_SIZE_BUTTON))); + add_hbox->pack_start(_add_button, false, false); + _add_button.signal_clicked().connect( + sigc::mem_fun(*this, &TransformDialog::add_clicked)); + + get_vbox()->set_spacing(6); + get_vbox()->pack_start(*property_hbox, false, false); + get_vbox()->pack_start(_operations_box, false, false); + get_vbox()->pack_start(*add_hbox, false, false); + + add_button(Stock::CANCEL, Gtk::RESPONSE_CANCEL); + add_button(_("Transform"), Gtk::RESPONSE_OK); + + show_all(); + _seed_chooser->value_spinner.hide(); +} + +TransformDialog::ValueChooser::ValueChooser(const Model& model) + : model(model) + , target_property((Property)1) + , to_label(" to ") +{ + source_combo.set_model(model.source_list); + source_combo.pack_start(model.source_cols.label); + source_combo.signal_changed().connect( + sigc::mem_fun(this, &TransformDialog::ValueChooser::source_changed)); + + property_combo.set_model(model.property_list); + property_combo.pack_start(model.property_cols.label); + + set_spacing(4); + pack_start(source_combo, false, false); + pack_start(property_combo, false, false); + pack_start(value_spinner, false, false); + pack_start(to_label, false, false); + pack_start(max_spinner, false, false); + show_all(); + + source_combo.set_active(4); + property_combo.set_active(1); + set_target_property(MidiModel::NoteDiffCommand::Velocity); + max_spinner.set_value(127); + source_changed(); +} + +static void +set_spinner_for(Gtk::SpinButton& spinner, + MidiModel::NoteDiffCommand::Property prop) +{ + switch (prop) { + case MidiModel::NoteDiffCommand::NoteNumber: + case MidiModel::NoteDiffCommand::Velocity: + spinner.get_adjustment()->set_lower(1); // no 0, note off + spinner.get_adjustment()->set_upper(127); + spinner.get_adjustment()->set_step_increment(1); + spinner.get_adjustment()->set_page_increment(10); + spinner.set_digits(0); + break; + case MidiModel::NoteDiffCommand::StartTime: + spinner.get_adjustment()->set_lower(0); + spinner.get_adjustment()->set_upper(1024); + spinner.get_adjustment()->set_step_increment(0.125); + spinner.get_adjustment()->set_page_increment(1.0); + spinner.set_digits(2); + break; + case MidiModel::NoteDiffCommand::Length: + spinner.get_adjustment()->set_lower(1.0 / 64.0); + spinner.get_adjustment()->set_upper(32); + spinner.get_adjustment()->set_step_increment(1.0 / 64.0); + spinner.get_adjustment()->set_page_increment(1.0); + spinner.set_digits(2); + break; + case MidiModel::NoteDiffCommand::Channel: + spinner.get_adjustment()->set_lower(0); + spinner.get_adjustment()->set_upper(15); + spinner.get_adjustment()->set_step_increment(1); + spinner.get_adjustment()->set_page_increment(10); + spinner.set_digits(0); + break; + } +} + +void +TransformDialog::ValueChooser::set_target_property(Property prop) +{ + target_property = prop; + set_spinner_for(value_spinner, prop); + set_spinner_for(max_spinner, prop); +} + +void +TransformDialog::ValueChooser::source_changed() +{ + Gtk::TreeModel::const_iterator s = source_combo.get_active(); + const Source source = (*s)[model.source_cols.source]; + + value_spinner.hide(); + to_label.hide(); + max_spinner.hide(); + if (source == Value::LITERAL) { + value_spinner.show(); + property_combo.hide(); + } else if (source == Value::RANDOM) { + value_spinner.show(); + to_label.show(); + max_spinner.show(); + property_combo.hide(); + } else if (source == Value::NOWHERE) { + /* Bit of a kludge, hijack this for ramps since it's the only thing + that doesn't correspond to a source. When we add more fancy + code-generating value chooser options, the column model will need to + be changed a bit to reflect this. */ + value_spinner.show(); + to_label.show(); + max_spinner.show(); + property_combo.hide(); + } else if (source == Value::INDEX || source == Value::N_NOTES) { + value_spinner.hide(); + property_combo.hide(); + } else { + value_spinner.hide(); + property_combo.show(); + } +} + +void +TransformDialog::ValueChooser::get(std::list<Operation>& ops) +{ + Gtk::TreeModel::const_iterator s = source_combo.get_active(); + const Source source = (*s)[model.source_cols.source]; + + if (source == Transform::Value::RANDOM) { + /* Special case: a RANDOM value is always 0..1, so here we produce some + code to produce a random number in a range: "rand value *". */ + const double a = value_spinner.get_value(); + const double b = max_spinner.get_value(); + const double min = std::min(a, b); + const double max = std::max(a, b); + const double range = max - min; + + // "rand range * min +" (i.e. (rand * range) + min) + ops.push_back(Operation(Operation::PUSH, Value(Value::RANDOM))); + ops.push_back(Operation(Operation::PUSH, Value(range))); + ops.push_back(Operation(Operation::MULT)); + ops.push_back(Operation(Operation::PUSH, Value(min))); + ops.push_back(Operation(Operation::ADD)); + return; + } else if (source == Transform::Value::NOWHERE) { + /* Special case: hijack NOWHERE for ramps (see above). The language + knows nothing of ramps, we generate code to calculate the + appropriate value here. */ + const double first = value_spinner.get_value(); + const double last = max_spinner.get_value(); + const double rise = last - first; + + // "index rise * n_notes / first +" (i.e. index * rise / n_notes + first) + ops.push_back(Operation(Operation::PUSH, Value(Value::INDEX))); + ops.push_back(Operation(Operation::PUSH, Value(rise))); + ops.push_back(Operation(Operation::MULT)); + ops.push_back(Operation(Operation::PUSH, Value(Value::N_NOTES))); + ops.push_back(Operation(Operation::DIV)); + ops.push_back(Operation(Operation::PUSH, Value(first))); + ops.push_back(Operation(Operation::ADD)); + return; + } + + // Produce a simple Value + Value val((*s)[model.source_cols.source]); + if (val.source == Transform::Value::THIS_NOTE || + val.source == Transform::Value::PREV_NOTE) { + Gtk::TreeModel::const_iterator p = property_combo.get_active(); + val.prop = (*p)[model.property_cols.property]; + } else if (val.source == Transform::Value::LITERAL) { + val.value = Variant( + MidiModel::NoteDiffCommand::value_type(target_property), + value_spinner.get_value()); + } + ops.push_back(Operation(Operation::PUSH, val)); +} + +TransformDialog::OperationChooser::OperationChooser(const Model& model) + : model(model) + , value_chooser(model) +{ + operator_combo.set_model(model.operator_list); + operator_combo.pack_start(model.operator_cols.label); + operator_combo.set_active(0); + + pack_start(operator_combo, false, false); + pack_start(value_chooser, false, false); + pack_start(*Gtk::manage(new Gtk::Label(" ")), true, true); + pack_start(remove_button, false, false); + + remove_button.add( + *manage(new Gtk::Image(Gtk::Stock::REMOVE, Gtk::ICON_SIZE_BUTTON))); + + remove_button.signal_clicked().connect( + sigc::mem_fun(*this, &TransformDialog::OperationChooser::remove_clicked)); + + value_chooser.source_combo.set_active(0); + + show_all(); + value_chooser.property_combo.hide(); + value_chooser.value_spinner.set_value(1); +} + +void +TransformDialog::OperationChooser::get(std::list<Operation>& ops) +{ + Gtk::TreeModel::const_iterator o = operator_combo.get_active(); + + value_chooser.get(ops); + ops.push_back(Operation((*o)[model.operator_cols.op])); +} + +void +TransformDialog::OperationChooser::remove_clicked() +{ + delete this; +} + +Transform::Program +TransformDialog::get() +{ + Transform::Program prog; + + // Set target property + prog.prop = (*_property_combo.get_active())[_model.property_cols.property]; + + // Append code to push seed to stack + _seed_chooser->get(prog.ops); + + // Append all operations' code to program + const std::vector<Gtk::Widget*>& choosers = _operations_box.get_children(); + for (std::vector<Gtk::Widget*>::const_iterator o = choosers.begin(); + o != choosers.end(); ++o) { + OperationChooser* chooser = dynamic_cast<OperationChooser*>(*o); + if (chooser) { + chooser->get(prog.ops); + } + } + + return prog; +} + +void +TransformDialog::property_changed() +{ + Gtk::TreeModel::const_iterator i = _property_combo.get_active(); + _seed_chooser->set_target_property((*i)[_model.property_cols.property]); +} + +void +TransformDialog::add_clicked() +{ + _operations_box.pack_start( + *Gtk::manage(new OperationChooser(_model)), false, false); +} |