comparison src/sc_converter.cpp @ 0:add35537fdbb tip

Initial import
author irh <ian.r.hobson@gmail.com>
date Thu, 25 Aug 2011 11:05:55 +0100
parents
children
comparison
equal deleted inserted replaced
-1:000000000000 0:add35537fdbb
1 // Copyright 2011, Ian Hobson.
2 //
3 // This file is part of gpsynth.
4 //
5 // gpsynth 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 3 of the License, or
8 // (at your option) any later version.
9 //
10 // gpsynth 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 gpsynth in the file COPYING.
17 // If not, see http://www.gnu.org/licenses/.
18
19 #include "sc_converter.hpp"
20
21 #include "boost_ex.hpp"
22 #include "logger.hpp"
23
24 #include "boost/graph/graphviz.hpp"
25 #include "boost/graph/topological_sort.hpp"
26
27 #include <iomanip>
28 #include <fstream>
29
30
31 namespace {
32
33 const std::string g_VariablePrefix("v");
34 const std::string g_ArgumentPrefix("arg_");
35 const int g_label_decimal_places = 2;
36
37 } // namespace
38
39
40 namespace sc {
41
42 // picks the lowest available variable for a command, or creates a new one
43 int Converter::AssignVariable(sg::Vertex vertex) {
44 int variable;
45 if (available_variables_.size() > 0) {
46 std::set<int>::iterator lowest_variable = available_variables_.begin();
47 variable = *(lowest_variable);
48 available_variables_.erase(lowest_variable);
49 } else {
50 variable = variables_created_++;
51 }
52 // store the command's variable
53 variable_map_[vertex] = variable;
54 return variable;
55 }
56
57 // retrieves the assigned variable for a specific command vertex
58 int Converter::GetVariable(sg::Vertex vertex) {
59 int variable = variable_map_[vertex];
60 variable_map_.erase(vertex);
61 available_variables_.insert(variable);
62 return variable;
63 }
64
65 void Converter::SynthDefSpecial(sg::Vertex vertex,
66 const sg::Node& node,
67 const sg::Graph& graph) {
68 if ((node.id_ == sc::Command::kMixer)
69 || (node.id_ == sc::Command::kMultiplier)) {
70 // e.g. v0 + v1 + v2
71 command_script_ << '\t';
72 // is this the root mixer?
73 bool root_command = (boost::out_degree(vertex, graph) == 0);
74 if (root_command) {
75 command_script_ << "Out.ar([0, 1], ";
76 } else {
77 command_script_ << g_VariablePrefix << AssignVariable(vertex) << " = ";
78 }
79 // clip output
80 command_script_ << "Clip.";
81 if (node.rate_ == sg::kAudioRate) {
82 command_script_ << "ar(";
83 } else {
84 command_script_ << "kr(";
85 }
86 // process command parameter, based on vertex inputs
87 sg::Graph::in_edge_iterator in_edge;
88 sg::Graph::in_edge_iterator in_edge_end;
89 bool first_argument = true;
90 bool parentheses = (boost::in_degree(vertex, graph) > 1);
91 // get separator for this command
92 std::string separator;
93 if (node.id_ == sc::Command::kMixer) {
94 separator = " + ";
95 } else {
96 separator = " * ";
97 }
98 for (boost::tie(in_edge, in_edge_end) = boost::in_edges(vertex, graph);
99 in_edge != in_edge_end;
100 ++in_edge) {
101 const sg::Connection& connection = graph[*in_edge];
102 if (!first_argument) {
103 command_script_ << separator;
104 } else {
105 first_argument = false;
106 }
107 const sg::Vertex& source_vertex = boost::source(*in_edge, graph);
108 const sg::Node& source = graph[source_vertex];
109 sg::NodeType source_type = source.type_;
110 if (source_type == sg::kConstant) {
111 // calculate weighted constant value
112 double value = source.constant_value_;
113 command_script_ << (value * connection.weight_ + connection.offset_);
114 } else {
115 if (parentheses) {
116 command_script_ << '(';
117 }
118 if (source_type == sg::kParameter) {
119 command_script_ << g_ArgumentPrefix << source.id_;
120 } else if (source_type == sg::kCommand) {
121 command_script_ << g_VariablePrefix << GetVariable(source_vertex);
122 } else if (source_type == sg::kSpecial) {
123 command_script_ << g_VariablePrefix << GetVariable(source_vertex);
124 }
125 if (connection.weight_ != 1) {
126 command_script_ << " * " << connection.weight_;
127 }
128 if (connection.offset_ != 0) {
129 command_script_ << " + " << connection.offset_;
130 }
131 if (parentheses) {
132 command_script_ << ')';
133 }
134 }
135 }
136 // end of Clip command
137 if (node.rate_ == sg::kAudioRate) {
138 command_script_ << ", -1, 1)";
139 } else {
140 command_script_ << ", 0, 1)";
141 }
142 // end of Out.ar command for root mixer
143 if (root_command) {
144 command_script_ << ')';
145 }
146 command_script_ << ";\n";
147 }
148 }
149
150 void Converter::SynthDefCommand(sg::Vertex vertex,
151 const sg::Node& node,
152 const sg::Graph& graph) {
153 const sc::Command& command = commands_[node.id_];
154 command_script_ << '\t';
155 command_script_ << g_VariablePrefix << AssignVariable(vertex) << " = ";
156 command_script_ << command.Name() << '.';
157 // command type
158 if (node.rate_ == sg::kAudioRate) {
159 command_script_ << "ar(";
160 } else {
161 command_script_ << "kr(";
162 }
163 // command inputs
164 sg::Graph::in_edge_iterator in_edge;
165 sg::Graph::in_edge_iterator in_edge_end;
166 bool first_argument = true;
167 for (boost::tie(in_edge, in_edge_end) = boost::in_edges(vertex, graph);
168 in_edge != in_edge_end;
169 ++in_edge) {
170 const sg::Connection& connection = graph[*in_edge];
171 const sc::Argument& argument = command.GetArgument(connection.input_);
172 if (!first_argument) {
173 command_script_ << ", ";
174 } else {
175 first_argument = false;
176 }
177 // argument value
178 command_script_ << argument.name_ << ": ";
179 const sg::Vertex& source_vertex = boost::source(*in_edge, graph);
180 const sg::Node& source = graph[source_vertex];
181 sg::NodeType source_type = source.type_;
182 if (source_type == sg::kConstant) {
183 // calculate weighted constant value
184 double value = source.constant_value_;
185 command_script_ << (value * connection.weight_ + connection.offset_);
186 } else {
187 if (source_type == sg::kParameter) {
188 command_script_ << g_ArgumentPrefix << source.id_;
189 } else if (source_type == sg::kCommand) {
190 command_script_ << g_VariablePrefix << GetVariable(source_vertex);
191 } else if (source_type == sg::kSpecial) {
192 command_script_ << g_VariablePrefix << GetVariable(source_vertex);
193 }
194 if (connection.weight_ != 1) {
195 command_script_ << " * " << connection.weight_;
196 }
197 if (connection.offset_ != 0) {
198 command_script_ << " + " << connection.offset_;
199 }
200 }
201 }
202 // write preset constant arguments
203 foreach (const sc::Argument& argument, command.Arguments()) {
204 if (argument.scaling_mode_ == sc::Argument::kConstant) {
205 if (!first_argument) {
206 command_script_ << ", ";
207 }
208 first_argument = false;
209 command_script_ << argument.name_ << ": " << argument.constant_value_;
210 }
211 }
212 //command end
213 command_script_ << ");\n";
214 }
215
216 // convert a given vertex
217 void Converter::SynthDefVertex(sg::Vertex vertex, const sg::Graph& graph) {
218 const sg::Node& node = graph[vertex];
219 sg::NodeType type = node.type_;
220 if (type == sg::kSpecial) {
221 SynthDefSpecial(vertex, node, graph);
222 }
223 if (type == sg::kCommand) {
224 SynthDefCommand(vertex, node, graph);
225 }
226 }
227
228 // prepares a synthdef from a synth graph
229 std::string Converter::ToSynthDef(const sg::Graph& graph,
230 const std::string& synth_name,
231 bool convert_for_realtime,
232 const std::string& synthdef_path) {
233 command_script_.str("");
234 variable_map_.clear();
235 variables_created_ = 0;
236 available_variables_.clear();
237
238 // preamble
239 std::stringstream synth_def;
240 synth_def << "SynthDef(\"" << synth_name << "\", {\n";
241 // arguments
242 std::size_t parameters = graph[boost::graph_bundle].parameters_.size();
243 if (parameters > 0) {
244 synth_def << "\targ ";
245 for (std::size_t i = 1; i <= parameters; i++) {
246 synth_def << g_ArgumentPrefix << i;
247 if (i < parameters) {
248 synth_def << ", ";
249 }
250 }
251 synth_def << ";\n";
252 }
253 // commands
254 std::vector<sg::Vertex> vertices;
255 vertices.reserve(boost::num_vertices(graph));
256 // perform topological sort on graph to find dependency order
257 boost::topological_sort(graph, std::back_inserter(vertices));
258 // traverse commands in reversed topological order
259 std::map<sg::Vertex, int> variable_map;
260 foreach_reverse (sg::Vertex& vertex, vertices) {
261 SynthDefVertex(vertex, graph);
262 }
263 // declare variables required by the commands
264 if (variables_created_ > 0) {
265 synth_def << "\tvar ";
266 for (int i = 0; i < variables_created_; i++) {
267 synth_def << g_VariablePrefix << i;
268 if (i < variables_created_ - 1) {
269 synth_def << ", ";
270 }
271 }
272 synth_def << ";\n";
273 }
274 // store the commands in the synthdef
275 synth_def << command_script_.str();
276 // ending
277 if (convert_for_realtime) {
278 synth_def << "}).add;\n";
279 } else {
280 synth_def << "}).writeDefFile(\"" << synthdef_path << "\");\n";
281 }
282 return synth_def.str();
283 }
284
285 std::string Converter::ToScore(const sg::Graph& graph,
286 const std::string& synth_name,
287 const std::string& score_variable,
288 bool convert_for_realtime,
289 const std::string& synthdef_path,
290 double render_length) {
291 std::stringstream score;
292 score << score_variable << " = [\n";
293 if (!convert_for_realtime) {
294 // load the synthdef
295 score << "[0.0, [\\d_load, \""
296 << synthdef_path << '/' << synth_name << ".scsyndef\"]],\n";
297 }
298 // start the synth
299 score << "[0.0, [\\s_new, \\" + synth_name + ", 1000, 0, 0";
300 // initial parameter values
301 const sg::GraphProperties& graph_properties = graph[boost::graph_bundle];
302 const std::vector<double>& parameters = graph_properties.parameters_;
303 for (int i = 0; i < parameters.size(); i++) {
304 score << ", \\" << g_ArgumentPrefix << (i + 1) << ", " << parameters[i];
305 }
306 // end of \s_new command
307 score << "]],\n";
308 if (!convert_for_realtime) {
309 // end of score, stop sound
310 score << "[" << render_length << ", [\\c_set, 0, 0]]\n";
311 }
312 // score end
313 score << "];\n";
314 return score.str();
315 }
316
317 void Converter::Export(const sg::Graph& graph,
318 const std::string& export_folder,
319 const std::string& export_name) {
320 std::stringstream export_path;
321 export_path << export_folder << '/' << export_name << ".sc";
322 std::ofstream export_file(export_path.str().c_str());
323 export_file << ToSynthDef(graph, export_name)
324 << ToScore(graph, export_name, "x");
325 export_file << "Score.play(x);";
326 }
327
328
329 #pragma mark - dot converter
330
331 // formats a command node for dot
332 class CommandWriter {
333 const sg::Graph& graph_;
334 const std::vector<sc::Command>& commands_;
335
336 public:
337 CommandWriter(const sg::Graph& graph,
338 const std::vector<sc::Command>& commands)
339 : graph_(graph),
340 commands_(commands)
341 {}
342
343 void operator()(std::ostream& out, const sg::Vertex& vertex) const {
344 const sg::Node& command_info = graph_[vertex];
345 sg::NodeType type = command_info.type_;
346 out << " [label=\"";
347 if (type == sg::kConstant) {
348 // calculate weighted constant value
349 const sg::Edge& out_edge = *(boost::out_edges(vertex, graph_).first);
350 const sg::Connection& connection = graph_[out_edge];
351 double value = command_info.constant_value_;
352 value = value * connection.weight_ + connection.offset_;
353 out << std::setprecision(2) << std::fixed << value
354 << "\", shape=diamond";
355 } else if (type == sg::kSpecial) {
356 if (command_info.id_ == sc::Command::kMixer) {
357 // check if this is the root mixer
358 if (boost::out_degree(vertex, graph_) > 0) {
359 out << "+\"";
360 if (command_info.rate_ == sg::kControlRate) {
361 out << ", shape=box";
362 }
363 } else {
364 out << "Output\"";
365 }
366 } else if (command_info.id_ == sc::Command::kMultiplier) {
367 out << "*\"";
368 if (command_info.rate_ == sg::kControlRate) {
369 out << ", shape=box";
370 }
371 }
372 } else if (type == sg::kCommand) {
373 out << commands_[command_info.id_].Name() << "\"";
374 if (command_info.rate_ == sg::kControlRate) {
375 out << ", shape=box";
376 }
377 } else if (type == sg::kParameter) {
378 out << g_ArgumentPrefix << command_info.id_
379 << "\", shape=invhouse";
380 }
381 // end
382 out << "]";
383 }
384 };
385
386 // formats a connection for dot
387 class ConnectionWriter {
388 const sg::Graph& graph_;
389 const std::vector<sc::Command>& commands_;
390
391 public:
392 ConnectionWriter(const sg::Graph& graph,
393 const std::vector<sc::Command>& commands)
394 : graph_(graph),
395 commands_(commands)
396 {}
397
398 void operator()(std::ostream& out, const sg::Edge& edge) const {
399 const sg::Connection& connection = graph_[edge];
400 out << "[label=\"";
401 const sg::Node& target = graph_[boost::target(edge, graph_)];
402 if (target.type_ == sg::kCommand) {
403 const sc::Command& command = commands_[target.id_];
404 const sc::Argument& argument = command.GetArgument(connection.input_);
405 out << " [" << argument.name_ << ']';
406 }
407 if (connection.IsActive()) {
408 // connection weighting
409 // skip constants as they have weighting applied to label
410 const sg::Node& source = graph_[boost::source(edge, graph_)];
411 if (source.type_ != sg::kConstant) {
412 out << " (";
413 out << std::fixed << std::setprecision(g_label_decimal_places);
414 if (connection.offset_ != 0) {
415 out << connection.offset_
416 << " - "
417 << (connection.offset_ + connection.weight_);
418 } else {
419 out << "* " << connection.weight_;
420 }
421 out << ")";
422 }
423 }
424 out << "\"]";
425 }
426 };
427
428 class GraphPropertiesWriter {
429 const sg::Graph& graph_;
430 public:
431 GraphPropertiesWriter(const sg::Graph& graph) : graph_(graph) {}
432 void operator()(std::ostream& out) const {
433 out << "edge[fontsize=10]\n";
434 }
435 };
436
437 std::string Converter::ToDOT(const sg::Graph& graph) {
438 std::stringstream buffer;
439 // prepare the writer classes
440 CommandWriter command_writer(graph, commands_);
441 ConnectionWriter connection_writer(graph, commands_);
442 GraphPropertiesWriter graph_writer(graph);
443 // write the graph to the buffer
444 boost::write_graphviz(buffer, graph,
445 command_writer, connection_writer, graph_writer);
446 return buffer.str();
447 }
448
449 } // sc namespace