Mercurial > hg > gpsynth
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 |