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