Mercurial > hg > gpsynth
view 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 source
// 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