ardour
transform_dialog.cc
Go to the documentation of this file.
1 /*
2  Copyright (C) 2009-2014 Paul Davis
3  Author: David Robillard
4 
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.
9 
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.
14 
15  You should have received a copy of the GNU General Public License
16  along with this program; if not, write to the Free Software
17  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
18 */
19 
20 #include <gtkmm/box.h>
21 #include <gtkmm/label.h>
22 #include <gtkmm/stock.h>
23 
24 #include "transform_dialog.h"
25 
26 #include "i18n.h"
27 
28 using namespace std;
29 using namespace Gtk;
30 using namespace ARDOUR;
31 
33  : source_list(Gtk::ListStore::create(source_cols))
34  , property_list(Gtk::ListStore::create(property_cols))
35  , operator_list(Gtk::ListStore::create(operator_cols))
36 {
37  static const char* source_labels[] = {
38  /* no NOTHING */
39  _("this note's"),
40  _("the previous note's"),
41  _("this note's index"),
42  _("the number of notes"),
43  _("exactly"),
44  _("a random number from"),
45  NULL
46  };
47  for (int s = 0; source_labels[s]; ++s) {
48  Gtk::TreeModel::Row row = *(source_list->append());
49  row[source_cols.source] = (Source)(s + 1); // Skip NOTHING
50  row[source_cols.label] = source_labels[s];
51  }
52  // Special row for ramp, which doesn't correspond to a source
53  Gtk::TreeModel::Row row = *(source_list->append());
55  row[source_cols.label] = _("equal steps from");
56 
57  static const char* property_labels[] = {
58  _("note number"),
59  _("velocity"),
60  _("start time"),
61  _("length"),
62  _("channel"),
63  NULL
64  };
65  for (int p = 0; property_labels[p]; ++p) {
66  Gtk::TreeModel::Row row = *(property_list->append());
68  row[property_cols.label] = property_labels[p];
69  }
70 
71  static const char* operator_labels[] = {
72  /* no PUSH */ "+", "-", "*", "/", "mod", NULL
73  };
74  for (int o = 0; operator_labels[o]; ++o) {
75  Gtk::TreeModel::Row row = *(operator_list->append());
76  row[operator_cols.op] = (Operator)(o + 1); // Skip PUSH
77  row[operator_cols.label] = operator_labels[o];
78  }
79 }
80 
82  : ArdourDialog(_("Transform"), false, false)
83 {
86  _property_combo.set_active(1);
87  _property_combo.signal_changed().connect(
88  sigc::mem_fun(this, &TransformDialog::property_changed));
89 
90  Gtk::HBox* property_hbox = Gtk::manage(new Gtk::HBox);
91  property_hbox->pack_start(*Gtk::manage(new Gtk::Label(_("Set "))), false, false);
92  property_hbox->pack_start(_property_combo, false, false);
93  property_hbox->pack_start(*Gtk::manage(new Gtk::Label(_(" to "))), false, false);
94 
95  _seed_chooser = Gtk::manage(new ValueChooser(_model));
96  _seed_chooser->set_target_property(MidiModel::NoteDiffCommand::Velocity);
97  _seed_chooser->source_combo.set_active(0);
98  property_hbox->pack_start(*_seed_chooser, false, false);
99 
100  Gtk::HBox* add_hbox = Gtk::manage(new Gtk::HBox);
101  _add_button.add(
102  *manage(new Gtk::Image(Gtk::Stock::ADD, Gtk::ICON_SIZE_BUTTON)));
103  add_hbox->pack_start(_add_button, false, false);
104  _add_button.signal_clicked().connect(
105  sigc::mem_fun(*this, &TransformDialog::add_clicked));
106 
107  get_vbox()->set_spacing(6);
108  get_vbox()->pack_start(*property_hbox, false, false);
109  get_vbox()->pack_start(_operations_box, false, false);
110  get_vbox()->pack_start(*add_hbox, false, false);
111 
112  add_button(Stock::CANCEL, Gtk::RESPONSE_CANCEL);
113  add_button(_("Transform"), Gtk::RESPONSE_OK);
114 
115  show_all();
117 }
118 
120  : model(model)
121  , target_property((Property)1)
122  , to_label(" to ")
123 {
124  source_combo.set_model(model.source_list);
125  source_combo.pack_start(model.source_cols.label);
126  source_combo.signal_changed().connect(
127  sigc::mem_fun(this, &TransformDialog::ValueChooser::source_changed));
128 
129  property_combo.set_model(model.property_list);
130  property_combo.pack_start(model.property_cols.label);
131 
132  set_spacing(4);
133  pack_start(source_combo, false, false);
134  pack_start(property_combo, false, false);
135  pack_start(value_spinner, false, false);
136  pack_start(to_label, false, false);
137  pack_start(max_spinner, false, false);
138  show_all();
139 
140  source_combo.set_active(4);
141  property_combo.set_active(1);
142  set_target_property(MidiModel::NoteDiffCommand::Velocity);
143  max_spinner.set_value(127);
144  source_changed();
145 }
146 
147 static void
148 set_spinner_for(Gtk::SpinButton& spinner,
150 {
151  switch (prop) {
152  case MidiModel::NoteDiffCommand::NoteNumber:
153  case MidiModel::NoteDiffCommand::Velocity:
154  spinner.get_adjustment()->set_lower(1); // no 0, note off
155  spinner.get_adjustment()->set_upper(127);
156  spinner.get_adjustment()->set_step_increment(1);
157  spinner.get_adjustment()->set_page_increment(10);
158  spinner.set_digits(0);
159  break;
160  case MidiModel::NoteDiffCommand::StartTime:
161  spinner.get_adjustment()->set_lower(0);
162  spinner.get_adjustment()->set_upper(1024);
163  spinner.get_adjustment()->set_step_increment(0.125);
164  spinner.get_adjustment()->set_page_increment(1.0);
165  spinner.set_digits(2);
166  break;
167  case MidiModel::NoteDiffCommand::Length:
168  spinner.get_adjustment()->set_lower(1.0 / 64.0);
169  spinner.get_adjustment()->set_upper(32);
170  spinner.get_adjustment()->set_step_increment(1.0 / 64.0);
171  spinner.get_adjustment()->set_page_increment(1.0);
172  spinner.set_digits(2);
173  break;
174  case MidiModel::NoteDiffCommand::Channel:
175  spinner.get_adjustment()->set_lower(0);
176  spinner.get_adjustment()->set_upper(15);
177  spinner.get_adjustment()->set_step_increment(1);
178  spinner.get_adjustment()->set_page_increment(10);
179  spinner.set_digits(0);
180  break;
181  }
182  spinner.set_value(
183  std::min(spinner.get_adjustment()->get_upper(),
184  std::max(spinner.get_adjustment()->get_lower(), spinner.get_value())));
185 }
186 
187 void
189 {
190  target_property = prop;
191  set_spinner_for(value_spinner, prop);
192  set_spinner_for(max_spinner, prop);
193 }
194 
195 void
197 {
198  Gtk::TreeModel::const_iterator s = source_combo.get_active();
199  const Source source = (*s)[model.source_cols.source];
200 
201  value_spinner.hide();
202  to_label.hide();
203  max_spinner.hide();
204  if (source == Value::LITERAL) {
205  value_spinner.show();
206  property_combo.hide();
207  } else if (source == Value::RANDOM) {
208  value_spinner.show();
209  to_label.show();
210  max_spinner.show();
211  property_combo.hide();
212  } else if (source == Value::NOWHERE) {
213  /* Bit of a kludge, hijack this for ramps since it's the only thing
214  that doesn't correspond to a source. When we add more fancy
215  code-generating value chooser options, the column model will need to
216  be changed a bit to reflect this. */
217  value_spinner.show();
218  to_label.show();
219  max_spinner.show();
220  property_combo.hide();
221  } else if (source == Value::INDEX || source == Value::N_NOTES) {
222  value_spinner.hide();
223  property_combo.hide();
224  } else {
225  value_spinner.hide();
226  property_combo.show();
227  }
228 }
229 
230 void
231 TransformDialog::ValueChooser::get(std::list<Operation>& ops)
232 {
233  Gtk::TreeModel::const_iterator s = source_combo.get_active();
234  const Source source = (*s)[model.source_cols.source];
235 
236  if (source == Transform::Value::RANDOM) {
237  /* Special case: a RANDOM value is always 0..1, so here we produce some
238  code to produce a random number in a range: "rand value *". */
239  const double a = value_spinner.get_value();
240  const double b = max_spinner.get_value();
241  const double min = std::min(a, b);
242  const double max = std::max(a, b);
243  const double range = max - min;
244 
245  // "rand range * min +" ((rand * range) + min)
246  ops.push_back(Operation(Operation::PUSH, Value(Value::RANDOM)));
247  ops.push_back(Operation(Operation::PUSH, Value(range)));
248  ops.push_back(Operation(Operation::MULT));
249  ops.push_back(Operation(Operation::PUSH, Value(min)));
250  ops.push_back(Operation(Operation::ADD));
251  return;
252  } else if (source == Transform::Value::NOWHERE) {
253  /* Special case: hijack NOWHERE for ramps (see above). The language
254  knows nothing of ramps, we generate code to calculate the
255  appropriate value here. */
256  const double first = value_spinner.get_value();
257  const double last = max_spinner.get_value();
258  const double rise = last - first;
259 
260  // "index rise * n_notes 1 - / first +" (index * rise / (n_notes - 1) + first)
261  ops.push_back(Operation(Operation::PUSH, Value(Value::INDEX)));
262  ops.push_back(Operation(Operation::PUSH, Value(rise)));
263  ops.push_back(Operation(Operation::MULT));
264  ops.push_back(Operation(Operation::PUSH, Value(Value::N_NOTES)));
265  ops.push_back(Operation(Operation::PUSH, Value(1)));
266  ops.push_back(Operation(Operation::SUB));
267  ops.push_back(Operation(Operation::DIV));
268  ops.push_back(Operation(Operation::PUSH, Value(first)));
269  ops.push_back(Operation(Operation::ADD));
270  return;
271  }
272 
273  // Produce a simple Value
274  Value val((*s)[model.source_cols.source]);
275  if (val.source == Transform::Value::THIS_NOTE ||
276  val.source == Transform::Value::PREV_NOTE) {
277  Gtk::TreeModel::const_iterator p = property_combo.get_active();
278  val.prop = (*p)[model.property_cols.property];
279  } else if (val.source == Transform::Value::LITERAL) {
280  val.value = Variant(
281  MidiModel::NoteDiffCommand::value_type(target_property),
282  value_spinner.get_value());
283  }
284  ops.push_back(Operation(Operation::PUSH, val));
285 }
286 
288  : model(model)
289  , value_chooser(model)
290 {
291  operator_combo.set_model(model.operator_list);
292  operator_combo.pack_start(model.operator_cols.label);
293  operator_combo.set_active(0);
294 
295  pack_start(operator_combo, false, false);
296  pack_start(value_chooser, false, false);
297  pack_start(*Gtk::manage(new Gtk::Label(" ")), true, true);
298  pack_start(remove_button, false, false);
299 
300  remove_button.add(
301  *manage(new Gtk::Image(Gtk::Stock::REMOVE, Gtk::ICON_SIZE_BUTTON)));
302 
303  remove_button.signal_clicked().connect(
305 
306  value_chooser.source_combo.set_active(0);
307 
308  show_all();
310  value_chooser.value_spinner.set_value(1);
311 }
312 
313 void
314 TransformDialog::OperationChooser::get(std::list<Operation>& ops)
315 {
316  Gtk::TreeModel::const_iterator o = operator_combo.get_active();
317 
318  value_chooser.get(ops);
319  ops.push_back(Operation((*o)[model.operator_cols.op]));
320 }
321 
322 void
324 {
325  delete this;
326 }
327 
330 {
331  Transform::Program prog;
332 
333  // Set target property
334  prog.prop = (*_property_combo.get_active())[_model.property_cols.property];
335 
336  // Append code to push seed to stack
337  _seed_chooser->get(prog.ops);
338 
339  // Append all operations' code to program
340  const std::vector<Gtk::Widget*>& choosers = _operations_box.get_children();
341  for (std::vector<Gtk::Widget*>::const_iterator o = choosers.begin();
342  o != choosers.end(); ++o) {
343  OperationChooser* chooser = dynamic_cast<OperationChooser*>(*o);
344  if (chooser) {
345  chooser->get(prog.ops);
346  }
347  }
348 
349  return prog;
350 }
351 
352 void
354 {
355  Gtk::TreeModel::const_iterator i = _property_combo.get_active();
357 }
358 
359 void
361 {
362  _operations_box.pack_start(
363  *Gtk::manage(new OperationChooser(_model)), false, false);
364 }
static void set_spinner_for(Gtk::SpinButton &spinner, MidiModel::NoteDiffCommand::Property prop)
Gtk::TreeModelColumn< Source > source
Glib::RefPtr< Gtk::ListStore > operator_list
ARDOUR::MidiModel::NoteDiffCommand::Property Property
Gtk::TreeModelColumn< std::string > label
Gtk::SpinButton value_spinner
Value or minimum for RANDOM.
Total number of notes to process.
Definition: transform.h:80
ValueChooser * _seed_chooser
Gtk::Button _add_button
Definition: ardour_ui.h:130
ARDOUR::Transform::Operation Operation
Property prop
Property to calculate.
Definition: transform.h:128
Definition: Beats.hpp:239
Gtk::TreeModelColumn< std::string > label
Gtk::TreeModelColumn< Operator > op
Gtk::ComboBox _property_combo
Subtract top from second-top.
Definition: transform.h:107
ARDOUR::Transform::Program get()
Variant value
Value for LITERAL.
Definition: transform.h:94
Divide second-top by top.
Definition: transform.h:109
#define _(Text)
Definition: i18n.h:11
ARDOUR::Transform::Value::Source Source
Glib::RefPtr< Gtk::ListStore > property_list
void get(std::list< Operation > &ops)
Index of the current note.
Definition: transform.h:79
Definition: amp.h:29
Add top two values.
Definition: transform.h:106
Source source
Source of value.
Definition: transform.h:93
Gtk::TreeModelColumn< Property > property
Gtk::ComboBox source_combo
Value source chooser.
Gtk::VBox _operations_box
Gtk::TreeModelColumn< std::string > label
Gtk::SpinButton max_spinner
Maximum for RANDOM.
void set_target_property(Property prop)
void get(std::list< Operation > &ops)
Gtk::ComboBox property_combo
Property chooser.
Glib::RefPtr< Gtk::ListStore > source_list
ARDOUR::Transform::Operation::Operator Operator
Gtk::Label to_label
"to" label for RANDOM
Given literal value.
Definition: transform.h:81
Push argument to the stack.
Definition: transform.h:105
std::list< Operation > ops
List of operations.
Definition: transform.h:129
ValueChooser(const Model &model)
Multiply top two values.
Definition: transform.h:108
ARDOUR::Transform::Value Value
Property prop
Property for all other sources.
Definition: transform.h:95