2 * Copyright (C) 2014-2019 David Robillard <d@drobilla.net>
3 * Copyright (C) 2015-2017 Robin Gareus <robin@gareus.org>
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License along
16 * with this program; if not, write to the Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20 #include <gtkmm/box.h>
21 #include <gtkmm/label.h>
22 #include <gtkmm/stock.h>
24 #include "transform_dialog.h"
28 using namespace ARDOUR;
30 TransformDialog::Model::Model()
31 : source_list(Gtk::ListStore::create(source_cols))
32 , property_list(Gtk::ListStore::create(property_cols))
33 , operator_list(Gtk::ListStore::create(operator_cols))
35 static const char* source_labels[] = {
38 _("the previous note's"),
39 _("this note's index"),
40 _("the number of notes"),
42 _("a random number from"),
45 for (int s = 0; source_labels[s]; ++s) {
46 Gtk::TreeModel::Row row = *(source_list->append());
47 row[source_cols.source] = (Source)(s + 1); // Skip NOTHING
48 row[source_cols.label] = source_labels[s];
50 // Special row for ramp, which doesn't correspond to a source
51 Gtk::TreeModel::Row row = *(source_list->append());
52 row[source_cols.source] = Value::NOWHERE;
53 row[source_cols.label] = _("equal steps from");
55 static const char* property_labels[] = {
63 for (int p = 0; property_labels[p]; ++p) {
64 Gtk::TreeModel::Row row = *(property_list->append());
65 row[property_cols.property] = (Property)p;
66 row[property_cols.label] = property_labels[p];
69 static const char* operator_labels[] = {
70 /* no PUSH */ "+", "-", "*", "/", "mod", NULL
72 for (int o = 0; operator_labels[o]; ++o) {
73 Gtk::TreeModel::Row row = *(operator_list->append());
74 row[operator_cols.op] = (Operator)(o + 1); // Skip PUSH
75 row[operator_cols.label] = operator_labels[o];
79 TransformDialog::TransformDialog()
80 : ArdourDialog(_("Transform"), false, false)
82 _property_combo.set_model(_model.property_list);
83 _property_combo.pack_start(_model.property_cols.label);
84 _property_combo.set_active(1);
85 _property_combo.signal_changed().connect(
86 sigc::mem_fun(this, &TransformDialog::property_changed));
88 Gtk::HBox* property_hbox = Gtk::manage(new Gtk::HBox);
89 property_hbox->pack_start(*Gtk::manage(new Gtk::Label(_("Set "))), false, false);
90 property_hbox->pack_start(_property_combo, false, false);
91 property_hbox->pack_start(*Gtk::manage(new Gtk::Label(_(" to "))), false, false);
93 _seed_chooser = Gtk::manage(new ValueChooser(_model));
94 _seed_chooser->set_target_property(MidiModel::NoteDiffCommand::Velocity);
95 _seed_chooser->source_combo.set_active(0);
96 property_hbox->pack_start(*_seed_chooser, false, false);
98 Gtk::HBox* add_hbox = Gtk::manage(new Gtk::HBox);
100 *Gtk::manage(new Gtk::Image(Gtk::Stock::ADD, Gtk::ICON_SIZE_BUTTON)));
101 add_hbox->pack_start(_add_button, false, false);
102 _add_button.signal_clicked().connect(
103 sigc::mem_fun(*this, &TransformDialog::add_clicked));
105 get_vbox()->set_spacing(6);
106 get_vbox()->pack_start(*property_hbox, false, false);
107 get_vbox()->pack_start(_operations_box, false, false);
108 get_vbox()->pack_start(*add_hbox, false, false);
110 add_button(Gtk::Stock::CANCEL, Gtk::RESPONSE_CANCEL);
111 add_button(_("Transform"), Gtk::RESPONSE_OK);
114 _seed_chooser->value_spinner.hide();
115 _seed_chooser->source_changed();
118 TransformDialog::ValueChooser::ValueChooser(const Model& model)
120 , target_property((Property)1)
123 source_combo.set_model(model.source_list);
124 source_combo.pack_start(model.source_cols.label);
125 source_combo.signal_changed().connect(
126 sigc::mem_fun(this, &TransformDialog::ValueChooser::source_changed));
128 property_combo.set_model(model.property_list);
129 property_combo.pack_start(model.property_cols.label);
132 pack_start(source_combo, false, false);
133 pack_start(property_combo, false, false);
134 pack_start(value_spinner, false, false);
135 pack_start(to_label, false, false);
136 pack_start(max_spinner, false, false);
139 source_combo.set_active(4);
140 property_combo.set_active(1);
141 set_target_property(MidiModel::NoteDiffCommand::Velocity);
142 max_spinner.set_value(127);
147 set_spinner_for(Gtk::SpinButton& spinner,
148 MidiModel::NoteDiffCommand::Property prop)
151 case MidiModel::NoteDiffCommand::NoteNumber:
152 case MidiModel::NoteDiffCommand::Velocity:
153 spinner.get_adjustment()->set_lower(1); // no 0, note off
154 spinner.get_adjustment()->set_upper(127);
155 spinner.get_adjustment()->set_step_increment(1);
156 spinner.get_adjustment()->set_page_increment(10);
157 spinner.set_digits(0);
159 case MidiModel::NoteDiffCommand::StartTime:
160 spinner.get_adjustment()->set_lower(0);
161 spinner.get_adjustment()->set_upper(1024);
162 spinner.get_adjustment()->set_step_increment(0.125);
163 spinner.get_adjustment()->set_page_increment(1.0);
164 spinner.set_digits(2);
166 case MidiModel::NoteDiffCommand::Length:
167 spinner.get_adjustment()->set_lower(1.0 / 64.0);
168 spinner.get_adjustment()->set_upper(32);
169 spinner.get_adjustment()->set_step_increment(1.0 / 64.0);
170 spinner.get_adjustment()->set_page_increment(1.0);
171 spinner.set_digits(2);
173 case MidiModel::NoteDiffCommand::Channel:
174 spinner.get_adjustment()->set_lower(1);
175 spinner.get_adjustment()->set_upper(16);
176 spinner.get_adjustment()->set_step_increment(1);
177 spinner.get_adjustment()->set_page_increment(10);
178 spinner.set_digits(0);
182 std::min(spinner.get_adjustment()->get_upper(),
183 std::max(spinner.get_adjustment()->get_lower(), spinner.get_value())));
187 TransformDialog::ValueChooser::set_target_property(Property prop)
189 target_property = prop;
190 set_spinner_for(value_spinner, prop);
191 set_spinner_for(max_spinner, prop);
195 TransformDialog::ValueChooser::source_changed()
197 Gtk::TreeModel::const_iterator s = source_combo.get_active();
198 const Source source = (*s)[model.source_cols.source];
200 value_spinner.hide();
203 if (source == Value::LITERAL) {
204 value_spinner.show();
205 property_combo.hide();
206 } else if (source == Value::RANDOM) {
207 value_spinner.show();
210 property_combo.hide();
211 } else if (source == Value::NOWHERE) {
212 /* Bit of a kludge, hijack this for ramps since it's the only thing
213 that doesn't correspond to a source. When we add more fancy
214 code-generating value chooser options, the column model will need to
215 be changed a bit to reflect this. */
216 value_spinner.show();
219 property_combo.hide();
220 } else if (source == Value::INDEX || source == Value::N_NOTES) {
221 value_spinner.hide();
222 property_combo.hide();
224 value_spinner.hide();
225 property_combo.show();
230 TransformDialog::ValueChooser::get_value() const
232 const bool is_channel = target_property == MidiModel::NoteDiffCommand::Channel;
233 return value_spinner.get_value() + (is_channel ? -1.0 : 0.0);
237 TransformDialog::ValueChooser::get_max() const
239 const bool is_channel = target_property == MidiModel::NoteDiffCommand::Channel;
240 return max_spinner.get_value() + (is_channel ? -1.0 : 0.0);
244 TransformDialog::ValueChooser::get(std::list<Operation>& ops)
246 Gtk::TreeModel::const_iterator s = source_combo.get_active();
247 const Source source = (*s)[model.source_cols.source];
249 if (source == Transform::Value::RANDOM) {
250 /* Special case: a RANDOM value is always 0..1, so here we produce some
251 code to produce a random number in a range: "rand value *". */
252 const double a = get_value();
253 const double b = get_max();
254 const double min = std::min(a, b);
255 const double max = std::max(a, b);
256 const double range = max - min;
258 // "rand range * min +" ((rand * range) + min)
259 ops.push_back(Operation(Operation::PUSH, Value(Value::RANDOM)));
260 ops.push_back(Operation(Operation::PUSH, Value(range)));
261 ops.push_back(Operation(Operation::MULT));
262 ops.push_back(Operation(Operation::PUSH, Value(min)));
263 ops.push_back(Operation(Operation::ADD));
265 } else if (source == Transform::Value::NOWHERE) {
266 /* Special case: hijack NOWHERE for ramps (see above). The language
267 knows nothing of ramps, we generate code to calculate the
268 appropriate value here. */
269 const double first = get_value();
270 const double last = get_max();
271 const double rise = last - first;
273 // "index rise * n_notes 1 - / first +" (index * rise / (n_notes - 1) + first)
274 ops.push_back(Operation(Operation::PUSH, Value(Value::INDEX)));
275 ops.push_back(Operation(Operation::PUSH, Value(rise)));
276 ops.push_back(Operation(Operation::MULT));
277 ops.push_back(Operation(Operation::PUSH, Value(Value::N_NOTES)));
278 ops.push_back(Operation(Operation::PUSH, Value(1)));
279 ops.push_back(Operation(Operation::SUB));
280 ops.push_back(Operation(Operation::DIV));
281 ops.push_back(Operation(Operation::PUSH, Value(first)));
282 ops.push_back(Operation(Operation::ADD));
286 // Produce a simple Value
287 Value val((*s)[model.source_cols.source]);
288 if (val.source == Transform::Value::THIS_NOTE ||
289 val.source == Transform::Value::PREV_NOTE) {
290 Gtk::TreeModel::const_iterator p = property_combo.get_active();
291 val.prop = (*p)[model.property_cols.property];
292 } else if (val.source == Transform::Value::LITERAL) {
294 MidiModel::NoteDiffCommand::value_type(target_property),
297 ops.push_back(Operation(Operation::PUSH, val));
300 TransformDialog::OperationChooser::OperationChooser(const Model& model)
302 , value_chooser(model)
304 operator_combo.set_model(model.operator_list);
305 operator_combo.pack_start(model.operator_cols.label);
306 operator_combo.set_active(0);
308 pack_start(operator_combo, false, false);
309 pack_start(value_chooser, false, false);
310 pack_start(*Gtk::manage(new Gtk::Label(" ")), true, true);
311 pack_start(remove_button, false, false);
314 *Gtk::manage(new Gtk::Image(Gtk::Stock::REMOVE, Gtk::ICON_SIZE_BUTTON)));
316 remove_button.signal_clicked().connect(
317 sigc::mem_fun(*this, &TransformDialog::OperationChooser::remove_clicked));
319 value_chooser.source_combo.set_active(0);
322 value_chooser.property_combo.hide();
323 value_chooser.value_spinner.set_value(1);
324 value_chooser.source_changed();
328 TransformDialog::OperationChooser::get(std::list<Operation>& ops)
330 Gtk::TreeModel::const_iterator o = operator_combo.get_active();
332 value_chooser.get(ops);
333 ops.push_back(Operation((*o)[model.operator_cols.op]));
337 TransformDialog::OperationChooser::remove_clicked()
343 TransformDialog::get()
345 Transform::Program prog;
347 // Set target property
348 prog.prop = (*_property_combo.get_active())[_model.property_cols.property];
350 // Append code to push seed to stack
351 _seed_chooser->get(prog.ops);
353 // Append all operations' code to program
354 const std::vector<Gtk::Widget*>& choosers = _operations_box.get_children();
355 for (std::vector<Gtk::Widget*>::const_iterator o = choosers.begin();
356 o != choosers.end(); ++o) {
357 OperationChooser* chooser = dynamic_cast<OperationChooser*>(*o);
359 chooser->get(prog.ops);
367 TransformDialog::property_changed()
369 Gtk::TreeModel::const_iterator i = _property_combo.get_active();
370 _seed_chooser->set_target_property((*i)[_model.property_cols.property]);
374 TransformDialog::add_clicked()
376 _operations_box.pack_start(
377 *Gtk::manage(new OperationChooser(_model)), false, false);