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