MIDI transform dialog.
This commit is contained in:
parent
ec947ff8fd
commit
4c0cebf7f9
@ -5771,6 +5771,8 @@ Editor::popup_note_context_menu (ArdourCanvas::Item* item, GdkEvent* event)
|
|||||||
sigc::bind(sigc::mem_fun(*this, &Editor::quantize_regions), rs)));
|
sigc::bind(sigc::mem_fun(*this, &Editor::quantize_regions), rs)));
|
||||||
items.push_back(MenuElem(_("Remove Overlap"),
|
items.push_back(MenuElem(_("Remove Overlap"),
|
||||||
sigc::bind(sigc::mem_fun(*this, &Editor::legatize_regions), rs, true)));
|
sigc::bind(sigc::mem_fun(*this, &Editor::legatize_regions), rs, true)));
|
||||||
|
items.push_back(MenuElem(_("Transform..."),
|
||||||
|
sigc::bind(sigc::mem_fun(*this, &Editor::transform_regions), rs)));
|
||||||
|
|
||||||
_note_context_menu.popup (event->button.button, event->button.time);
|
_note_context_menu.popup (event->button.button, event->button.time);
|
||||||
}
|
}
|
||||||
|
@ -1233,6 +1233,8 @@ class Editor : public PublicEditor, public PBD::ScopedConnectionList, public ARD
|
|||||||
void quantize_regions (const RegionSelection& rs);
|
void quantize_regions (const RegionSelection& rs);
|
||||||
void legatize_region (bool shrink_only);
|
void legatize_region (bool shrink_only);
|
||||||
void legatize_regions (const RegionSelection& rs, bool shrink_only);
|
void legatize_regions (const RegionSelection& rs, bool shrink_only);
|
||||||
|
void transform_region ();
|
||||||
|
void transform_regions (const RegionSelection& rs);
|
||||||
void insert_patch_change (bool from_context);
|
void insert_patch_change (bool from_context);
|
||||||
void fork_region ();
|
void fork_region ();
|
||||||
|
|
||||||
|
@ -1938,6 +1938,7 @@ Editor::register_region_actions ()
|
|||||||
|
|
||||||
reg_sens (_region_actions, "quantize-region", _("Quantize..."), sigc::mem_fun (*this, &Editor::quantize_region));
|
reg_sens (_region_actions, "quantize-region", _("Quantize..."), sigc::mem_fun (*this, &Editor::quantize_region));
|
||||||
reg_sens (_region_actions, "legatize-region", _("Legatize"), sigc::bind(sigc::mem_fun (*this, &Editor::legatize_region), false));
|
reg_sens (_region_actions, "legatize-region", _("Legatize"), sigc::bind(sigc::mem_fun (*this, &Editor::legatize_region), false));
|
||||||
|
reg_sens (_region_actions, "transform-region", _("Transform..."), sigc::mem_fun (*this, &Editor::transform_region));
|
||||||
reg_sens (_region_actions, "remove-overlap", _("Remove Overlap"), sigc::bind(sigc::mem_fun (*this, &Editor::legatize_region), true));
|
reg_sens (_region_actions, "remove-overlap", _("Remove Overlap"), sigc::bind(sigc::mem_fun (*this, &Editor::legatize_region), true));
|
||||||
reg_sens (_region_actions, "insert-patch-change", _("Insert Patch Change..."), sigc::bind (sigc::mem_fun (*this, &Editor::insert_patch_change), false));
|
reg_sens (_region_actions, "insert-patch-change", _("Insert Patch Change..."), sigc::bind (sigc::mem_fun (*this, &Editor::insert_patch_change), false));
|
||||||
reg_sens (_region_actions, "insert-patch-change-context", _("Insert Patch Change..."), sigc::bind (sigc::mem_fun (*this, &Editor::insert_patch_change), true));
|
reg_sens (_region_actions, "insert-patch-change-context", _("Insert Patch Change..."), sigc::bind (sigc::mem_fun (*this, &Editor::insert_patch_change), true));
|
||||||
|
@ -97,6 +97,7 @@
|
|||||||
#include "strip_silence_dialog.h"
|
#include "strip_silence_dialog.h"
|
||||||
#include "time_axis_view.h"
|
#include "time_axis_view.h"
|
||||||
#include "transpose_dialog.h"
|
#include "transpose_dialog.h"
|
||||||
|
#include "transform_dialog.h"
|
||||||
|
|
||||||
#include "i18n.h"
|
#include "i18n.h"
|
||||||
|
|
||||||
@ -5038,6 +5039,33 @@ Editor::legatize_regions (const RegionSelection& rs, bool shrink_only)
|
|||||||
apply_midi_note_edit_op (legatize, rs);
|
apply_midi_note_edit_op (legatize, rs);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
Editor::transform_region ()
|
||||||
|
{
|
||||||
|
if (_session) {
|
||||||
|
transform_regions(get_regions_from_selection_and_entered ());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
Editor::transform_regions (const RegionSelection& rs)
|
||||||
|
{
|
||||||
|
if (rs.n_midi_regions() == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
TransformDialog* td = new TransformDialog();
|
||||||
|
|
||||||
|
td->present();
|
||||||
|
const int r = td->run();
|
||||||
|
td->hide();
|
||||||
|
|
||||||
|
if (r == Gtk::RESPONSE_OK) {
|
||||||
|
Transform transform(td->get());
|
||||||
|
apply_midi_note_edit_op(transform, rs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
Editor::insert_patch_change (bool from_context)
|
Editor::insert_patch_change (bool from_context)
|
||||||
{
|
{
|
||||||
|
359
gtk2_ardour/transform_dialog.cc
Normal file
359
gtk2_ardour/transform_dialog.cc
Normal file
@ -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);
|
||||||
|
}
|
140
gtk2_ardour/transform_dialog.h
Normal file
140
gtk2_ardour/transform_dialog.h
Normal file
@ -0,0 +1,140 @@
|
|||||||
|
/*
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef __transform_dialog_h__
|
||||||
|
#define __transform_dialog_h__
|
||||||
|
|
||||||
|
#include <list>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include <gtkmm/combobox.h>
|
||||||
|
#include <gtkmm/liststore.h>
|
||||||
|
#include <gtkmm/treemodel.h>
|
||||||
|
#include <gtkmm/spinbutton.h>
|
||||||
|
|
||||||
|
#include "ardour/midi_model.h"
|
||||||
|
#include "ardour/transform.h"
|
||||||
|
#include "ardour/types.h"
|
||||||
|
#include "evoral/types.hpp"
|
||||||
|
|
||||||
|
#include "ardour_dialog.h"
|
||||||
|
|
||||||
|
/** Dialog for building a MIDI note transformation.
|
||||||
|
*
|
||||||
|
* This can build transformations with any number of operations, but is limited
|
||||||
|
* in power and can't build arbitrary transformations since there is no way to do
|
||||||
|
* conceptually parenthetical things (i.e. push things to the stack).
|
||||||
|
*
|
||||||
|
* With this, it is possible to build transformations that process a single
|
||||||
|
* value in a series of steps starting with a seed, like: "value = seed OP
|
||||||
|
* value OP value ..." where OP is +, -, *, or /, left associative with no
|
||||||
|
* precedence. This is simple and pretty clear to the user what's going to
|
||||||
|
* happen, though a bit limited. It would be nice if the GUI could build
|
||||||
|
* fancier transformations, but it's not obvious how to do this without making
|
||||||
|
* things more confusing.
|
||||||
|
*/
|
||||||
|
class TransformDialog : public ArdourDialog
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
TransformDialog();
|
||||||
|
|
||||||
|
ARDOUR::Transform::Program get();
|
||||||
|
|
||||||
|
private:
|
||||||
|
typedef ARDOUR::MidiModel::NoteDiffCommand::Property Property;
|
||||||
|
typedef ARDOUR::Transform::Value Value;
|
||||||
|
typedef ARDOUR::Transform::Value::Source Source;
|
||||||
|
typedef ARDOUR::Transform::Operation::Operator Operator;
|
||||||
|
typedef ARDOUR::Transform::Operation Operation;
|
||||||
|
|
||||||
|
struct SourceCols : public Gtk::TreeModelColumnRecord {
|
||||||
|
SourceCols() { add(source); add(label); }
|
||||||
|
|
||||||
|
Gtk::TreeModelColumn<Source> source;
|
||||||
|
Gtk::TreeModelColumn<std::string> label;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct PropertyCols : public Gtk::TreeModelColumnRecord {
|
||||||
|
PropertyCols() { add(property); add(label); }
|
||||||
|
|
||||||
|
Gtk::TreeModelColumn<Property> property;
|
||||||
|
Gtk::TreeModelColumn<std::string> label;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct OperatorCols : public Gtk::TreeModelColumnRecord {
|
||||||
|
OperatorCols() { add(op); add(label); }
|
||||||
|
|
||||||
|
Gtk::TreeModelColumn<Operator> op;
|
||||||
|
Gtk::TreeModelColumn<std::string> label;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Model {
|
||||||
|
Model();
|
||||||
|
|
||||||
|
SourceCols source_cols;
|
||||||
|
Glib::RefPtr<Gtk::ListStore> source_list;
|
||||||
|
PropertyCols property_cols;
|
||||||
|
Glib::RefPtr<Gtk::ListStore> property_list;
|
||||||
|
OperatorCols operator_cols;
|
||||||
|
Glib::RefPtr<Gtk::ListStore> operator_list;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ValueChooser : public Gtk::HBox {
|
||||||
|
ValueChooser(const Model& model);
|
||||||
|
|
||||||
|
/** Append code to `ops` that pushes value to stack. */
|
||||||
|
void get(std::list<Operation>& ops);
|
||||||
|
|
||||||
|
void set_target_property(Property prop);
|
||||||
|
void source_changed();
|
||||||
|
|
||||||
|
const Model& model; ///< Models for combo boxes
|
||||||
|
Property target_property; ///< Property on source
|
||||||
|
Gtk::ComboBox source_combo; ///< Value source chooser
|
||||||
|
Gtk::ComboBox property_combo; ///< Property chooser
|
||||||
|
Gtk::SpinButton value_spinner; ///< Value or minimum for RANDOM
|
||||||
|
Gtk::Label to_label; ///< "to" label for RANDOM
|
||||||
|
Gtk::SpinButton max_spinner; ///< Maximum for RANDOM
|
||||||
|
};
|
||||||
|
|
||||||
|
struct OperationChooser : public Gtk::HBox {
|
||||||
|
OperationChooser(const Model& model);
|
||||||
|
|
||||||
|
/** Append operations to `ops`. */
|
||||||
|
void get(std::list<Operation>& ops);
|
||||||
|
|
||||||
|
void remove_clicked();
|
||||||
|
|
||||||
|
const Model& model;
|
||||||
|
Gtk::ComboBox operator_combo;
|
||||||
|
ValueChooser value_chooser;
|
||||||
|
Gtk::Button remove_button;
|
||||||
|
};
|
||||||
|
|
||||||
|
void property_changed();
|
||||||
|
void add_clicked();
|
||||||
|
|
||||||
|
Model _model;
|
||||||
|
Gtk::ComboBox _property_combo;
|
||||||
|
ValueChooser* _seed_chooser;
|
||||||
|
Gtk::VBox _operations_box;
|
||||||
|
Gtk::Button _add_button;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif /* __transform_dialog_h__ */
|
@ -233,6 +233,7 @@ gtk2_ardour_sources = [
|
|||||||
'time_selection.cc',
|
'time_selection.cc',
|
||||||
'track_selection.cc',
|
'track_selection.cc',
|
||||||
'track_view_list.cc',
|
'track_view_list.cc',
|
||||||
|
'transform_dialog.cc',
|
||||||
'transpose_dialog.cc',
|
'transpose_dialog.cc',
|
||||||
'ui_config.cc',
|
'ui_config.cc',
|
||||||
'utils.cc',
|
'utils.cc',
|
||||||
|
@ -124,6 +124,8 @@ public:
|
|||||||
|
|
||||||
static Variant get_value (const NotePtr note, Property prop);
|
static Variant get_value (const NotePtr note, Property prop);
|
||||||
|
|
||||||
|
static Variant::Type value_type (Property prop);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
struct NoteChange {
|
struct NoteChange {
|
||||||
NoteDiffCommand::Property property;
|
NoteDiffCommand::Property property;
|
||||||
|
145
libs/ardour/ardour/transform.h
Normal file
145
libs/ardour/ardour/transform.h
Normal file
@ -0,0 +1,145 @@
|
|||||||
|
/*
|
||||||
|
Copyright (C) 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef __ardour_transform_h__
|
||||||
|
#define __ardour_transform_h__
|
||||||
|
|
||||||
|
#include <stack>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include "ardour/libardour_visibility.h"
|
||||||
|
#include "ardour/midi_model.h"
|
||||||
|
#include "ardour/midi_operator.h"
|
||||||
|
#include "ardour/types.h"
|
||||||
|
#include "ardour/variant.h"
|
||||||
|
|
||||||
|
namespace ARDOUR {
|
||||||
|
|
||||||
|
/** Transform notes with a user-defined transformation.
|
||||||
|
*
|
||||||
|
* This is essentially an interpreter for a simple concatenative note
|
||||||
|
* transformation language (as an AST only, no source code). A "program"
|
||||||
|
* calculates a note property value from operations on literal values, and/or
|
||||||
|
* values from the current or previous note in the sequence. This allows
|
||||||
|
* simple things like "set all notes' velocity to 64" or transitions over time
|
||||||
|
* like "set velocity to the previous note's velocity + 10".
|
||||||
|
*
|
||||||
|
* The language is forth-like: everything is on a stack, operations pop their
|
||||||
|
* arguments from the stack and push their result back on to it.
|
||||||
|
*
|
||||||
|
* This is a sweet spot between simplicity and power, it should be simple to
|
||||||
|
* use this (with perhaps some minor extensions) to do most "linear-ish"
|
||||||
|
* transformations, though it could be extended to have random access
|
||||||
|
* and more special values as the need arises.
|
||||||
|
*/
|
||||||
|
class LIBARDOUR_API Transform : public MidiOperator {
|
||||||
|
public:
|
||||||
|
typedef Evoral::Sequence<Evoral::MusicalTime>::NotePtr NotePtr;
|
||||||
|
typedef Evoral::Sequence<Evoral::MusicalTime>::Notes Notes;
|
||||||
|
typedef ARDOUR::MidiModel::NoteDiffCommand::Property Property;
|
||||||
|
|
||||||
|
/** Context while iterating over notes during transformation. */
|
||||||
|
struct Context {
|
||||||
|
Context() : index(0) {}
|
||||||
|
|
||||||
|
Variant pop();
|
||||||
|
|
||||||
|
std::stack<Variant> stack; ///< The stack of everything
|
||||||
|
size_t index; ///< Index of current note
|
||||||
|
size_t n_notes; ///< Total number of notes to process
|
||||||
|
NotePtr prev_note; ///< Previous note
|
||||||
|
NotePtr this_note; ///< Current note
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Value in a transformation expression. */
|
||||||
|
struct Value {
|
||||||
|
/** Value source. Some of these would be better modeled as properties,
|
||||||
|
like note.index or sequence.size, but until the sequence stuff is
|
||||||
|
more fundamentally property based, we special-case them here. */
|
||||||
|
enum Source {
|
||||||
|
NOWHERE, ///< Null
|
||||||
|
THIS_NOTE, ///< Value from this note
|
||||||
|
PREV_NOTE, ///< Value from the previous note
|
||||||
|
INDEX, ///< Index of the current note
|
||||||
|
N_NOTES, ///< Total number of notes to process
|
||||||
|
LITERAL, ///< Given literal value
|
||||||
|
RANDOM ///< Random normal
|
||||||
|
};
|
||||||
|
|
||||||
|
Value() : source(NOWHERE) {}
|
||||||
|
Value(Source s) : source(s) {}
|
||||||
|
Value(const Variant& v) : source(LITERAL), value(v) {}
|
||||||
|
Value(double v) : source(LITERAL), value(Variant(v)) {}
|
||||||
|
|
||||||
|
/** Calculate and return value. */
|
||||||
|
Variant eval(const Context& context) const;
|
||||||
|
|
||||||
|
Source source; ///< Source of value
|
||||||
|
Variant value; ///< Value for LITERAL
|
||||||
|
Property prop; ///< Property for all other sources
|
||||||
|
};
|
||||||
|
|
||||||
|
/** An operation to transform the running result.
|
||||||
|
*
|
||||||
|
* All operations except PUSH take their arguments from the stack, and put
|
||||||
|
* the result back on the stack.
|
||||||
|
*/
|
||||||
|
struct Operation {
|
||||||
|
enum Operator {
|
||||||
|
PUSH, ///< Push argument to the stack
|
||||||
|
ADD, ///< Add top two values
|
||||||
|
SUB, ///< Subtract top from second-top
|
||||||
|
MULT, ///< Multiply top two values
|
||||||
|
DIV ///< Divide second-top by top
|
||||||
|
};
|
||||||
|
|
||||||
|
Operation(Operator o, const Value& a=Value()) : op(o), arg(a) {}
|
||||||
|
|
||||||
|
/** Apply operation. */
|
||||||
|
void eval(Context& context) const;
|
||||||
|
|
||||||
|
Operator op;
|
||||||
|
Value arg;
|
||||||
|
};
|
||||||
|
|
||||||
|
/** A transformation program.
|
||||||
|
*
|
||||||
|
* A program is a list of operations to calculate the target property's
|
||||||
|
* final value. The first operation must be a PUSH to seed the stack.
|
||||||
|
*/
|
||||||
|
struct Program {
|
||||||
|
Property prop; ///< Property to calculate
|
||||||
|
std::list<Operation> ops; ///< List of operations
|
||||||
|
};
|
||||||
|
|
||||||
|
Transform(const Program& prog);
|
||||||
|
|
||||||
|
Command* operator()(boost::shared_ptr<ARDOUR::MidiModel> model,
|
||||||
|
Evoral::MusicalTime position,
|
||||||
|
std::vector<Notes>& seqs);
|
||||||
|
|
||||||
|
std::string name() const { return std::string ("transform"); }
|
||||||
|
|
||||||
|
private:
|
||||||
|
const Program _prog;
|
||||||
|
};
|
||||||
|
|
||||||
|
} /* namespace */
|
||||||
|
|
||||||
|
#endif /* __ardour_transform_h__ */
|
@ -49,7 +49,8 @@ public:
|
|||||||
URI ///< URI string
|
URI ///< URI string
|
||||||
};
|
};
|
||||||
|
|
||||||
explicit Variant() : _type(NOTHING) { _long = 0; }
|
Variant() : _type(NOTHING) { _long = 0; }
|
||||||
|
|
||||||
explicit Variant(bool value) : _type(BOOL) { _bool = value; }
|
explicit Variant(bool value) : _type(BOOL) { _bool = value; }
|
||||||
explicit Variant(double value) : _type(DOUBLE) { _double = value; }
|
explicit Variant(double value) : _type(DOUBLE) { _double = value; }
|
||||||
explicit Variant(float value) : _type(FLOAT) { _float = value; }
|
explicit Variant(float value) : _type(FLOAT) { _float = value; }
|
||||||
@ -92,6 +93,9 @@ public:
|
|||||||
_long = (int64_t)lrint(std::max((double)INT64_MIN,
|
_long = (int64_t)lrint(std::max((double)INT64_MIN,
|
||||||
std::min(value, (double)INT64_MAX)));
|
std::min(value, (double)INT64_MAX)));
|
||||||
break;
|
break;
|
||||||
|
case BEATS:
|
||||||
|
_beats = Evoral::MusicalTime(value);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
_type = NOTHING;
|
_type = NOTHING;
|
||||||
_long = 0;
|
_long = 0;
|
||||||
@ -106,6 +110,7 @@ public:
|
|||||||
case FLOAT: return _float;
|
case FLOAT: return _float;
|
||||||
case INT: return _int;
|
case INT: return _int;
|
||||||
case LONG: return _long;
|
case LONG: return _long;
|
||||||
|
case BEATS: return _beats.to_double();
|
||||||
default: return 0.0;
|
default: return 0.0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -157,6 +162,8 @@ public:
|
|||||||
return _type == BEATS && _beats == v;
|
return _type == BEATS && _beats == v;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool operator!() const { return _type == NOTHING; }
|
||||||
|
|
||||||
Variant& operator=(Evoral::MusicalTime v) {
|
Variant& operator=(Evoral::MusicalTime v) {
|
||||||
_type = BEATS;
|
_type = BEATS;
|
||||||
_beats = v;
|
_beats = v;
|
||||||
@ -171,7 +178,7 @@ public:
|
|||||||
|
|
||||||
static bool type_is_numeric(Type type) {
|
static bool type_is_numeric(Type type) {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case BOOL: case DOUBLE: case FLOAT: case INT: case LONG:
|
case BOOL: case DOUBLE: case FLOAT: case INT: case LONG: case BEATS:
|
||||||
return true;
|
return true;
|
||||||
default:
|
default:
|
||||||
return false;
|
return false;
|
||||||
|
@ -181,6 +181,20 @@ MidiModel::NoteDiffCommand::get_value (const NotePtr note, Property prop)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Variant::Type
|
||||||
|
MidiModel::NoteDiffCommand::value_type(Property prop)
|
||||||
|
{
|
||||||
|
switch (prop) {
|
||||||
|
case NoteNumber:
|
||||||
|
case Velocity:
|
||||||
|
case Channel:
|
||||||
|
return Variant::INT;
|
||||||
|
case StartTime:
|
||||||
|
case Length:
|
||||||
|
return Variant::BEATS;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
MidiModel::NoteDiffCommand::change (const NotePtr note,
|
MidiModel::NoteDiffCommand::change (const NotePtr note,
|
||||||
Property prop,
|
Property prop,
|
||||||
|
162
libs/ardour/transform.cc
Normal file
162
libs/ardour/transform.cc
Normal file
@ -0,0 +1,162 @@
|
|||||||
|
/*
|
||||||
|
Copyright (C) 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 <glib.h>
|
||||||
|
|
||||||
|
#include "ardour/transform.h"
|
||||||
|
#include "ardour/midi_model.h"
|
||||||
|
|
||||||
|
namespace ARDOUR {
|
||||||
|
|
||||||
|
Transform::Transform(const Program& prog)
|
||||||
|
: _prog(prog)
|
||||||
|
{}
|
||||||
|
|
||||||
|
Variant
|
||||||
|
Transform::Context::pop()
|
||||||
|
{
|
||||||
|
if (stack.empty()) {
|
||||||
|
return Variant();
|
||||||
|
}
|
||||||
|
|
||||||
|
const Variant top = stack.top();
|
||||||
|
stack.pop();
|
||||||
|
return top;
|
||||||
|
}
|
||||||
|
|
||||||
|
Variant
|
||||||
|
Transform::Value::eval(const Context& ctx) const
|
||||||
|
{
|
||||||
|
switch (source) {
|
||||||
|
case NOWHERE:
|
||||||
|
return Variant();
|
||||||
|
case THIS_NOTE:
|
||||||
|
return MidiModel::NoteDiffCommand::get_value(ctx.this_note, prop);
|
||||||
|
case PREV_NOTE:
|
||||||
|
if (!ctx.prev_note) {
|
||||||
|
return Variant();
|
||||||
|
}
|
||||||
|
return MidiModel::NoteDiffCommand::get_value(ctx.prev_note, prop);
|
||||||
|
case INDEX:
|
||||||
|
return Variant(Variant::INT, ctx.index);
|
||||||
|
case N_NOTES:
|
||||||
|
return Variant(Variant::INT, ctx.n_notes);
|
||||||
|
case LITERAL:
|
||||||
|
return value;
|
||||||
|
case RANDOM:
|
||||||
|
return Variant(g_random_double());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
Transform::Operation::eval(Context& ctx) const
|
||||||
|
{
|
||||||
|
if (op == PUSH) {
|
||||||
|
const Variant a = arg.eval(ctx);
|
||||||
|
if (!!a) {
|
||||||
|
/* Argument evaluated to a value, push it to the stack. Otherwise,
|
||||||
|
there was a reference to the previous note, but this is the
|
||||||
|
first, so skip this operation and do nothing. */
|
||||||
|
ctx.stack.push(a);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pop operands off the stack
|
||||||
|
const Variant rhs = ctx.pop();
|
||||||
|
const Variant lhs = ctx.pop();
|
||||||
|
if (!lhs || !rhs) {
|
||||||
|
// Stack underflow (probably previous note reference), do nothing
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We can get away with just using double math and converting twice
|
||||||
|
double value = lhs.to_double();
|
||||||
|
switch (op) {
|
||||||
|
case ADD:
|
||||||
|
value += rhs.to_double();
|
||||||
|
break;
|
||||||
|
case SUB:
|
||||||
|
value -= rhs.to_double();
|
||||||
|
break;
|
||||||
|
case MULT:
|
||||||
|
value *= rhs.to_double();
|
||||||
|
break;
|
||||||
|
case DIV:
|
||||||
|
if (rhs.to_double() == 0.0) {
|
||||||
|
return; // Program will fail safely
|
||||||
|
}
|
||||||
|
value /= rhs.to_double();
|
||||||
|
break;
|
||||||
|
default: break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Push result on to the stack
|
||||||
|
ctx.stack.push(Variant(lhs.type(), value));
|
||||||
|
}
|
||||||
|
|
||||||
|
Command*
|
||||||
|
Transform::operator()(boost::shared_ptr<MidiModel> model,
|
||||||
|
Evoral::MusicalTime position,
|
||||||
|
std::vector<Notes>& seqs)
|
||||||
|
{
|
||||||
|
typedef MidiModel::NoteDiffCommand Command;
|
||||||
|
|
||||||
|
Command* cmd = new Command(model, name());
|
||||||
|
|
||||||
|
for (std::vector<Notes>::iterator s = seqs.begin(); s != seqs.end(); ++s) {
|
||||||
|
Context ctx;
|
||||||
|
ctx.n_notes = (*s).size();
|
||||||
|
for (Notes::const_iterator i = (*s).begin(); i != (*s).end(); ++i) {
|
||||||
|
const NotePtr note = *i;
|
||||||
|
|
||||||
|
// Clear stack and run program
|
||||||
|
ctx.stack = std::stack<Variant>();
|
||||||
|
ctx.this_note = note;
|
||||||
|
for (std::list<Operation>::const_iterator o = _prog.ops.begin();
|
||||||
|
o != _prog.ops.end();
|
||||||
|
++o) {
|
||||||
|
(*o).eval(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Result is on top of the stack
|
||||||
|
if (!ctx.stack.empty() && !!ctx.stack.top()) {
|
||||||
|
// Get the result from the top of the stack
|
||||||
|
Variant result = ctx.stack.top();
|
||||||
|
if (result.type() != Command::value_type(_prog.prop)) {
|
||||||
|
// Coerce to appropriate type
|
||||||
|
result = Variant(Command::value_type(_prog.prop),
|
||||||
|
result.to_double());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply change
|
||||||
|
cmd->change(note, _prog.prop, result);
|
||||||
|
}
|
||||||
|
// else error or reference to note before the first, skip
|
||||||
|
|
||||||
|
// Move forward
|
||||||
|
ctx.prev_note = note;
|
||||||
|
++ctx.index;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return cmd;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace ARDOUR
|
@ -213,6 +213,7 @@ libardour_sources = [
|
|||||||
'ticker.cc',
|
'ticker.cc',
|
||||||
'track.cc',
|
'track.cc',
|
||||||
'transient_detector.cc',
|
'transient_detector.cc',
|
||||||
|
'transform.cc',
|
||||||
'unknown_processor.cc',
|
'unknown_processor.cc',
|
||||||
'user_bundle.cc',
|
'user_bundle.cc',
|
||||||
'utils.cc',
|
'utils.cc',
|
||||||
|
Loading…
Reference in New Issue
Block a user