diff options
Diffstat (limited to 'libs/ardour')
-rw-r--r-- | libs/ardour/ardour/midi_model.h | 2 | ||||
-rw-r--r-- | libs/ardour/ardour/transform.h | 145 | ||||
-rw-r--r-- | libs/ardour/ardour/variant.h | 11 | ||||
-rw-r--r-- | libs/ardour/midi_model.cc | 14 | ||||
-rw-r--r-- | libs/ardour/transform.cc | 162 | ||||
-rw-r--r-- | libs/ardour/wscript | 1 |
6 files changed, 333 insertions, 2 deletions
diff --git a/libs/ardour/ardour/midi_model.h b/libs/ardour/ardour/midi_model.h index 52bb5a6b27..b86a7436bb 100644 --- a/libs/ardour/ardour/midi_model.h +++ b/libs/ardour/ardour/midi_model.h @@ -124,6 +124,8 @@ public: static Variant get_value (const NotePtr note, Property prop); + static Variant::Type value_type (Property prop); + private: struct NoteChange { NoteDiffCommand::Property property; diff --git a/libs/ardour/ardour/transform.h b/libs/ardour/ardour/transform.h new file mode 100644 index 0000000000..2b63bb6af0 --- /dev/null +++ b/libs/ardour/ardour/transform.h @@ -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__ */ diff --git a/libs/ardour/ardour/variant.h b/libs/ardour/ardour/variant.h index d99c0e4fd3..0402ffaa0b 100644 --- a/libs/ardour/ardour/variant.h +++ b/libs/ardour/ardour/variant.h @@ -49,7 +49,8 @@ public: URI ///< URI string }; - explicit Variant() : _type(NOTHING) { _long = 0; } + Variant() : _type(NOTHING) { _long = 0; } + explicit Variant(bool value) : _type(BOOL) { _bool = value; } explicit Variant(double value) : _type(DOUBLE) { _double = value; } explicit Variant(float value) : _type(FLOAT) { _float = value; } @@ -92,6 +93,9 @@ public: _long = (int64_t)lrint(std::max((double)INT64_MIN, std::min(value, (double)INT64_MAX))); break; + case BEATS: + _beats = Evoral::MusicalTime(value); + break; default: _type = NOTHING; _long = 0; @@ -106,6 +110,7 @@ public: case FLOAT: return _float; case INT: return _int; case LONG: return _long; + case BEATS: return _beats.to_double(); default: return 0.0; } } @@ -157,6 +162,8 @@ public: return _type == BEATS && _beats == v; } + bool operator!() const { return _type == NOTHING; } + Variant& operator=(Evoral::MusicalTime v) { _type = BEATS; _beats = v; @@ -171,7 +178,7 @@ public: static bool type_is_numeric(Type 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; default: return false; diff --git a/libs/ardour/midi_model.cc b/libs/ardour/midi_model.cc index e68068de2b..b1dbb759d5 100644 --- a/libs/ardour/midi_model.cc +++ b/libs/ardour/midi_model.cc @@ -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 MidiModel::NoteDiffCommand::change (const NotePtr note, Property prop, diff --git a/libs/ardour/transform.cc b/libs/ardour/transform.cc new file mode 100644 index 0000000000..ddf23aeecc --- /dev/null +++ b/libs/ardour/transform.cc @@ -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 diff --git a/libs/ardour/wscript b/libs/ardour/wscript index 76d39bd0c3..748c412312 100644 --- a/libs/ardour/wscript +++ b/libs/ardour/wscript @@ -213,6 +213,7 @@ libardour_sources = [ 'ticker.cc', 'track.cc', 'transient_detector.cc', + 'transform.cc', 'unknown_processor.cc', 'user_bundle.cc', 'utils.cc', |