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