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