diff 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
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/sc_converter.cpp	Thu Aug 25 11:05:55 2011 +0100
@@ -0,0 +1,449 @@
+//  Copyright 2011, Ian Hobson.
+//
+//  This file is part of gpsynth.
+//
+//  gpsynth 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 3 of the License, or
+//  (at your option) any later version.
+//
+//  gpsynth 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 gpsynth in the file COPYING. 
+//  If not, see http://www.gnu.org/licenses/.
+
+#include "sc_converter.hpp"
+
+#include "boost_ex.hpp"
+#include "logger.hpp"
+
+#include "boost/graph/graphviz.hpp"
+#include "boost/graph/topological_sort.hpp"
+
+#include <iomanip>
+#include <fstream>
+
+
+namespace {
+
+const std::string g_VariablePrefix("v");
+const std::string g_ArgumentPrefix("arg_");
+const int g_label_decimal_places = 2;
+  
+} // namespace
+
+
+namespace sc {
+
+// picks the lowest available variable for a command, or creates a new one 
+int Converter::AssignVariable(sg::Vertex vertex) {
+  int variable;
+  if (available_variables_.size() > 0) {
+    std::set<int>::iterator lowest_variable = available_variables_.begin();
+    variable = *(lowest_variable);
+    available_variables_.erase(lowest_variable);
+  } else {
+    variable = variables_created_++;
+  }
+  // store the command's variable
+  variable_map_[vertex] = variable;
+  return variable;
+}
+
+// retrieves the assigned variable for a specific command vertex
+int Converter::GetVariable(sg::Vertex vertex) {
+  int variable = variable_map_[vertex];
+  variable_map_.erase(vertex);
+  available_variables_.insert(variable);
+  return variable;
+}
+
+void Converter::SynthDefSpecial(sg::Vertex vertex,
+                                const sg::Node& node,
+                                const sg::Graph& graph) {
+  if ((node.id_ == sc::Command::kMixer)
+      || (node.id_ == sc::Command::kMultiplier)) {
+    // e.g. v0 + v1 + v2
+    command_script_ << '\t';
+    // is this the root mixer?
+    bool root_command = (boost::out_degree(vertex, graph) == 0);
+    if (root_command) {
+      command_script_ << "Out.ar([0, 1], ";
+    } else {
+      command_script_ << g_VariablePrefix << AssignVariable(vertex) << " = ";
+    }
+    // clip output
+    command_script_ << "Clip.";
+    if (node.rate_ == sg::kAudioRate) {
+      command_script_ << "ar(";
+    } else {
+      command_script_ << "kr(";
+    }
+    // process command parameter, based on vertex inputs
+    sg::Graph::in_edge_iterator in_edge;
+    sg::Graph::in_edge_iterator in_edge_end;
+    bool first_argument = true;
+    bool parentheses = (boost::in_degree(vertex, graph) > 1);
+    // get separator for this command
+    std::string separator;
+    if (node.id_ == sc::Command::kMixer) {
+      separator = " + ";
+    } else {
+      separator = " * ";
+    }
+    for (boost::tie(in_edge, in_edge_end) = boost::in_edges(vertex, graph);
+         in_edge != in_edge_end;
+         ++in_edge) {
+      const sg::Connection& connection = graph[*in_edge];
+      if (!first_argument) {
+        command_script_ << separator;
+      } else {
+        first_argument = false;
+      }
+      const sg::Vertex& source_vertex = boost::source(*in_edge, graph);
+      const sg::Node& source = graph[source_vertex];
+      sg::NodeType source_type = source.type_;
+      if (source_type == sg::kConstant) {
+        // calculate weighted constant value 
+        double value = source.constant_value_;
+        command_script_ << (value * connection.weight_ + connection.offset_);
+      } else {
+        if (parentheses) {
+          command_script_ << '(';
+        }
+        if (source_type == sg::kParameter) {
+          command_script_ << g_ArgumentPrefix << source.id_;
+        } else if (source_type == sg::kCommand) {
+          command_script_ << g_VariablePrefix << GetVariable(source_vertex);
+        } else if (source_type == sg::kSpecial) {
+          command_script_ << g_VariablePrefix << GetVariable(source_vertex);
+        }
+        if (connection.weight_ != 1) {
+          command_script_ << " * " << connection.weight_;
+        }
+        if (connection.offset_ != 0) {
+          command_script_ << " + " << connection.offset_;
+        }
+        if (parentheses) {
+          command_script_ << ')';
+        }
+      }
+    }
+    // end of Clip command
+    if (node.rate_ == sg::kAudioRate) {
+      command_script_ << ", -1, 1)";
+    } else {
+      command_script_ << ", 0, 1)";
+    }
+    // end of Out.ar command for root mixer
+    if (root_command) {
+      command_script_ << ')';
+    }
+    command_script_ << ";\n";
+  }
+}
+
+void Converter::SynthDefCommand(sg::Vertex vertex,
+                                const sg::Node& node,
+                                const sg::Graph& graph) {
+  const sc::Command& command = commands_[node.id_];
+  command_script_ << '\t';
+  command_script_ << g_VariablePrefix << AssignVariable(vertex) << " = ";
+  command_script_ << command.Name() << '.';
+  // command type
+  if (node.rate_ == sg::kAudioRate) {
+    command_script_ << "ar(";
+  } else {
+    command_script_ << "kr(";
+  }
+  // command inputs
+  sg::Graph::in_edge_iterator in_edge;
+  sg::Graph::in_edge_iterator in_edge_end;
+  bool first_argument = true;
+  for (boost::tie(in_edge, in_edge_end) = boost::in_edges(vertex, graph);
+       in_edge != in_edge_end;
+       ++in_edge) {
+    const sg::Connection& connection = graph[*in_edge];
+    const sc::Argument& argument = command.GetArgument(connection.input_);
+    if (!first_argument) {
+      command_script_ << ", ";
+    } else {
+      first_argument = false; 
+    }
+    // argument value
+    command_script_ << argument.name_ << ": ";
+    const sg::Vertex& source_vertex = boost::source(*in_edge, graph);
+    const sg::Node& source = graph[source_vertex];
+    sg::NodeType source_type = source.type_;
+    if (source_type == sg::kConstant) {
+      // calculate weighted constant value 
+      double value = source.constant_value_;
+      command_script_ << (value * connection.weight_ + connection.offset_);
+    } else {
+      if (source_type == sg::kParameter) {
+        command_script_ << g_ArgumentPrefix << source.id_;
+      } else if (source_type == sg::kCommand) {
+        command_script_ << g_VariablePrefix << GetVariable(source_vertex);
+      } else if (source_type == sg::kSpecial) {
+        command_script_ << g_VariablePrefix << GetVariable(source_vertex);
+      }
+      if (connection.weight_ != 1) {
+        command_script_ << " * " << connection.weight_;
+      }
+      if (connection.offset_ != 0) {
+        command_script_ << " + " << connection.offset_;
+      }
+    }
+  }
+  // write preset constant arguments
+  foreach (const sc::Argument& argument, command.Arguments()) {
+    if (argument.scaling_mode_ == sc::Argument::kConstant) {
+      if (!first_argument) {
+        command_script_ << ", ";
+      }
+      first_argument = false;
+      command_script_ << argument.name_ << ": " << argument.constant_value_;
+    }
+  }
+  //command end
+  command_script_ << ");\n";
+}
+  
+// convert a given vertex
+void Converter::SynthDefVertex(sg::Vertex vertex, const sg::Graph& graph) {
+  const sg::Node& node = graph[vertex];
+  sg::NodeType type = node.type_;
+  if (type == sg::kSpecial) {
+    SynthDefSpecial(vertex, node, graph);
+  }
+  if (type == sg::kCommand) {
+    SynthDefCommand(vertex, node, graph);
+  }
+}
+
+// prepares a synthdef from a synth graph
+std::string Converter::ToSynthDef(const sg::Graph& graph,
+                                  const std::string& synth_name,
+                                  bool convert_for_realtime,
+                                  const std::string& synthdef_path) {
+  command_script_.str("");
+  variable_map_.clear();
+  variables_created_ = 0;
+  available_variables_.clear();
+  
+  // preamble
+  std::stringstream synth_def;
+  synth_def << "SynthDef(\"" << synth_name << "\", {\n";
+  // arguments
+  std::size_t parameters = graph[boost::graph_bundle].parameters_.size();
+  if (parameters > 0) {
+    synth_def << "\targ ";
+    for (std::size_t i = 1; i <= parameters; i++) {
+      synth_def << g_ArgumentPrefix << i;
+      if (i < parameters) {
+        synth_def << ", ";
+      }
+    }
+    synth_def << ";\n";
+  }
+  // commands
+  std::vector<sg::Vertex> vertices;
+  vertices.reserve(boost::num_vertices(graph));
+  // perform topological sort on graph to find dependency order
+  boost::topological_sort(graph, std::back_inserter(vertices));
+  // traverse commands in reversed topological order
+  std::map<sg::Vertex, int> variable_map;
+  foreach_reverse (sg::Vertex& vertex, vertices) {
+    SynthDefVertex(vertex, graph);
+  }
+  // declare variables required by the commands
+  if (variables_created_ > 0) {
+    synth_def << "\tvar ";
+    for (int i = 0; i < variables_created_; i++) {
+      synth_def << g_VariablePrefix << i;  
+      if (i < variables_created_ - 1) {
+        synth_def << ", ";
+      }
+    }
+    synth_def << ";\n";
+  }
+  // store the commands in the synthdef
+  synth_def << command_script_.str();
+  // ending
+  if (convert_for_realtime) {
+    synth_def << "}).add;\n";
+  } else {
+    synth_def << "}).writeDefFile(\"" << synthdef_path << "\");\n";
+  }
+  return synth_def.str();
+}
+
+std::string Converter::ToScore(const sg::Graph& graph,
+                               const std::string& synth_name,
+                               const std::string& score_variable,
+                               bool convert_for_realtime,
+                               const std::string& synthdef_path,
+                               double render_length) {
+  std::stringstream score;
+  score << score_variable << " = [\n";
+  if (!convert_for_realtime) {
+    // load the synthdef
+    score << "[0.0, [\\d_load, \"" 
+          << synthdef_path << '/' << synth_name << ".scsyndef\"]],\n";
+  }
+  // start the synth
+  score << "[0.0, [\\s_new, \\" + synth_name + ", 1000, 0, 0";
+  // initial parameter values
+  const sg::GraphProperties& graph_properties = graph[boost::graph_bundle];
+  const std::vector<double>& parameters = graph_properties.parameters_;
+  for (int i = 0; i < parameters.size(); i++) {
+    score << ", \\" << g_ArgumentPrefix << (i + 1) << ", " << parameters[i];
+  }
+  // end of \s_new command
+  score << "]],\n";
+  if (!convert_for_realtime) {
+    // end of score, stop sound
+    score << "[" << render_length << ", [\\c_set, 0, 0]]\n";
+  }
+  // score end
+  score << "];\n";
+  return score.str();
+}
+  
+void Converter::Export(const sg::Graph& graph,
+                       const std::string& export_folder,
+                       const std::string& export_name) {
+  std::stringstream export_path;
+  export_path << export_folder << '/' << export_name << ".sc";
+  std::ofstream export_file(export_path.str().c_str());
+  export_file << ToSynthDef(graph, export_name)
+              << ToScore(graph, export_name, "x");
+  export_file << "Score.play(x);";
+}
+
+  
+#pragma mark - dot converter
+  
+// formats a command node for dot
+class CommandWriter {
+  const sg::Graph& graph_;
+  const std::vector<sc::Command>& commands_;
+  
+public:
+  CommandWriter(const sg::Graph& graph,
+                const std::vector<sc::Command>& commands) 
+  : graph_(graph),
+    commands_(commands)
+  {}
+  
+  void operator()(std::ostream& out, const sg::Vertex& vertex) const {
+    const sg::Node& command_info = graph_[vertex];
+    sg::NodeType type = command_info.type_;
+    out << " [label=\"";
+    if (type == sg::kConstant) {
+      // calculate weighted constant value
+      const sg::Edge& out_edge = *(boost::out_edges(vertex, graph_).first);
+      const sg::Connection& connection = graph_[out_edge];
+      double value = command_info.constant_value_;
+      value = value * connection.weight_ + connection.offset_;
+      out << std::setprecision(2) << std::fixed << value 
+          << "\", shape=diamond";
+    } else if (type == sg::kSpecial) {
+      if (command_info.id_ == sc::Command::kMixer) {
+        // check if this is the root mixer
+        if (boost::out_degree(vertex, graph_) > 0) {
+          out << "+\"";
+          if (command_info.rate_ == sg::kControlRate) {
+            out << ", shape=box";
+          }
+        } else {
+          out << "Output\"";
+        }
+      } else if (command_info.id_ == sc::Command::kMultiplier) {
+        out << "*\"";
+        if (command_info.rate_ == sg::kControlRate) {
+          out << ", shape=box";
+        }        
+      }
+    } else if (type == sg::kCommand) {
+      out << commands_[command_info.id_].Name() << "\"";
+      if (command_info.rate_ == sg::kControlRate) {
+        out << ", shape=box";
+      }
+    } else if (type == sg::kParameter) {
+      out << g_ArgumentPrefix << command_info.id_ 
+          << "\", shape=invhouse";
+    }
+    // end
+    out << "]";
+  }
+};
+  
+// formats a connection for dot
+class ConnectionWriter {
+  const sg::Graph& graph_;
+  const std::vector<sc::Command>& commands_;
+  
+public:
+  ConnectionWriter(const sg::Graph& graph,
+                   const std::vector<sc::Command>& commands) 
+  : graph_(graph),
+    commands_(commands)
+  {}
+  
+  void operator()(std::ostream& out, const sg::Edge& edge) const {
+    const sg::Connection& connection = graph_[edge];
+    out << "[label=\"";
+    const sg::Node& target = graph_[boost::target(edge, graph_)];
+    if (target.type_ == sg::kCommand) {
+      const sc::Command& command = commands_[target.id_];
+      const sc::Argument& argument = command.GetArgument(connection.input_);
+      out << " [" << argument.name_ << ']';
+    }
+    if (connection.IsActive()) {
+      // connection weighting
+      // skip constants as they have weighting applied to label
+      const sg::Node& source = graph_[boost::source(edge, graph_)];
+      if (source.type_ != sg::kConstant) {
+        out << " (";
+        out << std::fixed << std::setprecision(g_label_decimal_places);
+        if (connection.offset_ != 0) {
+          out << connection.offset_ 
+          << " - " 
+          << (connection.offset_ + connection.weight_);
+        } else {
+          out << "* " << connection.weight_;
+        }
+        out << ")";        
+      }
+    }
+    out << "\"]";
+  }
+};
+
+class GraphPropertiesWriter {
+  const sg::Graph& graph_;
+public:
+  GraphPropertiesWriter(const sg::Graph& graph) : graph_(graph) {}
+  void operator()(std::ostream& out) const {
+    out << "edge[fontsize=10]\n";
+  }
+};
+
+std::string Converter::ToDOT(const sg::Graph& graph) {
+  std::stringstream buffer;
+  // prepare the writer classes
+  CommandWriter command_writer(graph, commands_);
+  ConnectionWriter connection_writer(graph, commands_);
+  GraphPropertiesWriter graph_writer(graph);
+  // write the graph to the buffer
+  boost::write_graphviz(buffer, graph,
+                        command_writer, connection_writer, graph_writer);
+  return buffer.str();
+}
+
+} // sc namespace