fix crash when copy'ing latent plugins
[ardour.git] / libs / ardour / transform.cc
1 /*
2     Copyright (C) 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 <glib.h>
21
22 #include "ardour/transform.h"
23 #include "ardour/midi_model.h"
24
25 namespace ARDOUR {
26
27 Transform::Transform(const Program& prog)
28         : _prog(prog)
29 {}
30
31 Variant
32 Transform::Context::pop()
33 {
34         if (stack.empty()) {
35                 return Variant();
36         }
37
38         const Variant top = stack.top();
39         stack.pop();
40         return top;
41 }
42
43 Variant
44 Transform::Value::eval(const Context& ctx) const
45 {
46         switch (source) {
47         case NOWHERE:
48                 return Variant();
49         case THIS_NOTE:
50                 return MidiModel::NoteDiffCommand::get_value(ctx.this_note, prop);
51         case PREV_NOTE:
52                 if (!ctx.prev_note) {
53                         return Variant();
54                 }
55                 return MidiModel::NoteDiffCommand::get_value(ctx.prev_note, prop);
56         case INDEX:
57                 return Variant(Variant::INT, ctx.index);
58         case N_NOTES:
59                 return Variant(Variant::INT, ctx.n_notes);
60         case LITERAL:
61                 return value;
62         case RANDOM:
63                 return Variant(g_random_double());
64         }
65
66         return Variant();
67 }
68
69 void
70 Transform::Operation::eval(Context& ctx) const
71 {
72         if (op == PUSH) {
73                 const Variant a = arg.eval(ctx);
74                 if (!!a) {
75                         /* Argument evaluated to a value, push it to the stack.  Otherwise,
76                            there was a reference to the previous note, but this is the
77                            first, so skip this operation and do nothing. */
78                         ctx.stack.push(a);
79                 }
80                 return;
81         }
82
83         // Pop operands off the stack
84         const Variant rhs = ctx.pop();
85         const Variant lhs = ctx.pop();
86         if (!lhs || !rhs) {
87                 // Stack underflow (probably previous note reference), do nothing
88                 return;
89         }
90
91         // We can get away with just using double math and converting twice
92         double value = lhs.to_double();
93         switch (op) {
94         case ADD:
95                 value += rhs.to_double();
96                 break;
97         case SUB:
98                 value -= rhs.to_double();
99                 break;
100         case MULT:
101                 value *= rhs.to_double();
102                 break;
103         case DIV:
104                 if (rhs.to_double() == 0.0) {
105                         return;  // Program will fail safely
106                 }
107                 value /= rhs.to_double();
108                 break;
109         case MOD:
110                 if (rhs.to_double() == 0.0) {
111                         return;  // Program will fail safely
112                 }
113                 value = fmod(value, rhs.to_double());
114                 break;
115         default: break;
116         }
117
118         // Push result on to the stack
119         ctx.stack.push(Variant(lhs.type(), value));
120 }
121
122 Command*
123 Transform::operator()(boost::shared_ptr<MidiModel> model,
124                       Evoral::Beats                position,
125                       std::vector<Notes>&          seqs)
126 {
127         typedef MidiModel::NoteDiffCommand Command;
128
129         Command* cmd = new Command(model, name());
130
131         for (std::vector<Notes>::iterator s = seqs.begin(); s != seqs.end(); ++s) {
132                 Context ctx;
133                 ctx.n_notes = (*s).size();
134                 for (Notes::const_iterator i = (*s).begin(); i != (*s).end(); ++i) {
135                         const NotePtr note = *i;
136
137                         // Clear stack and run program
138                         ctx.stack     = std::stack<Variant>();
139                         ctx.this_note = note;
140                         for (std::list<Operation>::const_iterator o = _prog.ops.begin();
141                              o != _prog.ops.end();
142                              ++o) {
143                                 (*o).eval(ctx);
144                         }
145
146                         // Result is on top of the stack
147                         if (!ctx.stack.empty() && !!ctx.stack.top()) {
148                                 // Get the result from the top of the stack
149                                 Variant result = ctx.stack.top();
150                                 if (result.type() != Command::value_type(_prog.prop)) {
151                                         // Coerce to appropriate type
152                                         result = Variant(Command::value_type(_prog.prop),
153                                                          result.to_double());
154                                 }
155
156                                 // Apply change
157                                 cmd->change(note, _prog.prop, result);
158                         }
159                         // else error or reference to note before the first, skip
160
161                         // Move forward
162                         ctx.prev_note = note;
163                         ++ctx.index;
164                 }
165         }
166
167         return cmd;
168 }
169
170 }  // namespace ARDOUR