ian@0
|
1 // Copyright 2011, Ian Hobson.
|
ian@0
|
2 //
|
ian@0
|
3 // This file is part of gpsynth.
|
ian@0
|
4 //
|
ian@0
|
5 // gpsynth is free software: you can redistribute it and/or modify
|
ian@0
|
6 // it under the terms of the GNU General Public License as published by
|
ian@0
|
7 // the Free Software Foundation, either version 3 of the License, or
|
ian@0
|
8 // (at your option) any later version.
|
ian@0
|
9 //
|
ian@0
|
10 // gpsynth is distributed in the hope that it will be useful,
|
ian@0
|
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
|
ian@0
|
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
ian@0
|
13 // GNU General Public License for more details.
|
ian@0
|
14 //
|
ian@0
|
15 // You should have received a copy of the GNU General Public License
|
ian@0
|
16 // along with gpsynth in the file COPYING.
|
ian@0
|
17 // If not, see http://www.gnu.org/licenses/.
|
ian@0
|
18
|
ian@0
|
19 #include "sc_converter.hpp"
|
ian@0
|
20
|
ian@0
|
21 #include "boost_ex.hpp"
|
ian@0
|
22 #include "logger.hpp"
|
ian@0
|
23
|
ian@0
|
24 #include "boost/graph/graphviz.hpp"
|
ian@0
|
25 #include "boost/graph/topological_sort.hpp"
|
ian@0
|
26
|
ian@0
|
27 #include <iomanip>
|
ian@0
|
28 #include <fstream>
|
ian@0
|
29
|
ian@0
|
30
|
ian@0
|
31 namespace {
|
ian@0
|
32
|
ian@0
|
33 const std::string g_VariablePrefix("v");
|
ian@0
|
34 const std::string g_ArgumentPrefix("arg_");
|
ian@0
|
35 const int g_label_decimal_places = 2;
|
ian@0
|
36
|
ian@0
|
37 } // namespace
|
ian@0
|
38
|
ian@0
|
39
|
ian@0
|
40 namespace sc {
|
ian@0
|
41
|
ian@0
|
42 // picks the lowest available variable for a command, or creates a new one
|
ian@0
|
43 int Converter::AssignVariable(sg::Vertex vertex) {
|
ian@0
|
44 int variable;
|
ian@0
|
45 if (available_variables_.size() > 0) {
|
ian@0
|
46 std::set<int>::iterator lowest_variable = available_variables_.begin();
|
ian@0
|
47 variable = *(lowest_variable);
|
ian@0
|
48 available_variables_.erase(lowest_variable);
|
ian@0
|
49 } else {
|
ian@0
|
50 variable = variables_created_++;
|
ian@0
|
51 }
|
ian@0
|
52 // store the command's variable
|
ian@0
|
53 variable_map_[vertex] = variable;
|
ian@0
|
54 return variable;
|
ian@0
|
55 }
|
ian@0
|
56
|
ian@0
|
57 // retrieves the assigned variable for a specific command vertex
|
ian@0
|
58 int Converter::GetVariable(sg::Vertex vertex) {
|
ian@0
|
59 int variable = variable_map_[vertex];
|
ian@0
|
60 variable_map_.erase(vertex);
|
ian@0
|
61 available_variables_.insert(variable);
|
ian@0
|
62 return variable;
|
ian@0
|
63 }
|
ian@0
|
64
|
ian@0
|
65 void Converter::SynthDefSpecial(sg::Vertex vertex,
|
ian@0
|
66 const sg::Node& node,
|
ian@0
|
67 const sg::Graph& graph) {
|
ian@0
|
68 if ((node.id_ == sc::Command::kMixer)
|
ian@0
|
69 || (node.id_ == sc::Command::kMultiplier)) {
|
ian@0
|
70 // e.g. v0 + v1 + v2
|
ian@0
|
71 command_script_ << '\t';
|
ian@0
|
72 // is this the root mixer?
|
ian@0
|
73 bool root_command = (boost::out_degree(vertex, graph) == 0);
|
ian@0
|
74 if (root_command) {
|
ian@0
|
75 command_script_ << "Out.ar([0, 1], ";
|
ian@0
|
76 } else {
|
ian@0
|
77 command_script_ << g_VariablePrefix << AssignVariable(vertex) << " = ";
|
ian@0
|
78 }
|
ian@0
|
79 // clip output
|
ian@0
|
80 command_script_ << "Clip.";
|
ian@0
|
81 if (node.rate_ == sg::kAudioRate) {
|
ian@0
|
82 command_script_ << "ar(";
|
ian@0
|
83 } else {
|
ian@0
|
84 command_script_ << "kr(";
|
ian@0
|
85 }
|
ian@0
|
86 // process command parameter, based on vertex inputs
|
ian@0
|
87 sg::Graph::in_edge_iterator in_edge;
|
ian@0
|
88 sg::Graph::in_edge_iterator in_edge_end;
|
ian@0
|
89 bool first_argument = true;
|
ian@0
|
90 bool parentheses = (boost::in_degree(vertex, graph) > 1);
|
ian@0
|
91 // get separator for this command
|
ian@0
|
92 std::string separator;
|
ian@0
|
93 if (node.id_ == sc::Command::kMixer) {
|
ian@0
|
94 separator = " + ";
|
ian@0
|
95 } else {
|
ian@0
|
96 separator = " * ";
|
ian@0
|
97 }
|
ian@0
|
98 for (boost::tie(in_edge, in_edge_end) = boost::in_edges(vertex, graph);
|
ian@0
|
99 in_edge != in_edge_end;
|
ian@0
|
100 ++in_edge) {
|
ian@0
|
101 const sg::Connection& connection = graph[*in_edge];
|
ian@0
|
102 if (!first_argument) {
|
ian@0
|
103 command_script_ << separator;
|
ian@0
|
104 } else {
|
ian@0
|
105 first_argument = false;
|
ian@0
|
106 }
|
ian@0
|
107 const sg::Vertex& source_vertex = boost::source(*in_edge, graph);
|
ian@0
|
108 const sg::Node& source = graph[source_vertex];
|
ian@0
|
109 sg::NodeType source_type = source.type_;
|
ian@0
|
110 if (source_type == sg::kConstant) {
|
ian@0
|
111 // calculate weighted constant value
|
ian@0
|
112 double value = source.constant_value_;
|
ian@0
|
113 command_script_ << (value * connection.weight_ + connection.offset_);
|
ian@0
|
114 } else {
|
ian@0
|
115 if (parentheses) {
|
ian@0
|
116 command_script_ << '(';
|
ian@0
|
117 }
|
ian@0
|
118 if (source_type == sg::kParameter) {
|
ian@0
|
119 command_script_ << g_ArgumentPrefix << source.id_;
|
ian@0
|
120 } else if (source_type == sg::kCommand) {
|
ian@0
|
121 command_script_ << g_VariablePrefix << GetVariable(source_vertex);
|
ian@0
|
122 } else if (source_type == sg::kSpecial) {
|
ian@0
|
123 command_script_ << g_VariablePrefix << GetVariable(source_vertex);
|
ian@0
|
124 }
|
ian@0
|
125 if (connection.weight_ != 1) {
|
ian@0
|
126 command_script_ << " * " << connection.weight_;
|
ian@0
|
127 }
|
ian@0
|
128 if (connection.offset_ != 0) {
|
ian@0
|
129 command_script_ << " + " << connection.offset_;
|
ian@0
|
130 }
|
ian@0
|
131 if (parentheses) {
|
ian@0
|
132 command_script_ << ')';
|
ian@0
|
133 }
|
ian@0
|
134 }
|
ian@0
|
135 }
|
ian@0
|
136 // end of Clip command
|
ian@0
|
137 if (node.rate_ == sg::kAudioRate) {
|
ian@0
|
138 command_script_ << ", -1, 1)";
|
ian@0
|
139 } else {
|
ian@0
|
140 command_script_ << ", 0, 1)";
|
ian@0
|
141 }
|
ian@0
|
142 // end of Out.ar command for root mixer
|
ian@0
|
143 if (root_command) {
|
ian@0
|
144 command_script_ << ')';
|
ian@0
|
145 }
|
ian@0
|
146 command_script_ << ";\n";
|
ian@0
|
147 }
|
ian@0
|
148 }
|
ian@0
|
149
|
ian@0
|
150 void Converter::SynthDefCommand(sg::Vertex vertex,
|
ian@0
|
151 const sg::Node& node,
|
ian@0
|
152 const sg::Graph& graph) {
|
ian@0
|
153 const sc::Command& command = commands_[node.id_];
|
ian@0
|
154 command_script_ << '\t';
|
ian@0
|
155 command_script_ << g_VariablePrefix << AssignVariable(vertex) << " = ";
|
ian@0
|
156 command_script_ << command.Name() << '.';
|
ian@0
|
157 // command type
|
ian@0
|
158 if (node.rate_ == sg::kAudioRate) {
|
ian@0
|
159 command_script_ << "ar(";
|
ian@0
|
160 } else {
|
ian@0
|
161 command_script_ << "kr(";
|
ian@0
|
162 }
|
ian@0
|
163 // command inputs
|
ian@0
|
164 sg::Graph::in_edge_iterator in_edge;
|
ian@0
|
165 sg::Graph::in_edge_iterator in_edge_end;
|
ian@0
|
166 bool first_argument = true;
|
ian@0
|
167 for (boost::tie(in_edge, in_edge_end) = boost::in_edges(vertex, graph);
|
ian@0
|
168 in_edge != in_edge_end;
|
ian@0
|
169 ++in_edge) {
|
ian@0
|
170 const sg::Connection& connection = graph[*in_edge];
|
ian@0
|
171 const sc::Argument& argument = command.GetArgument(connection.input_);
|
ian@0
|
172 if (!first_argument) {
|
ian@0
|
173 command_script_ << ", ";
|
ian@0
|
174 } else {
|
ian@0
|
175 first_argument = false;
|
ian@0
|
176 }
|
ian@0
|
177 // argument value
|
ian@0
|
178 command_script_ << argument.name_ << ": ";
|
ian@0
|
179 const sg::Vertex& source_vertex = boost::source(*in_edge, graph);
|
ian@0
|
180 const sg::Node& source = graph[source_vertex];
|
ian@0
|
181 sg::NodeType source_type = source.type_;
|
ian@0
|
182 if (source_type == sg::kConstant) {
|
ian@0
|
183 // calculate weighted constant value
|
ian@0
|
184 double value = source.constant_value_;
|
ian@0
|
185 command_script_ << (value * connection.weight_ + connection.offset_);
|
ian@0
|
186 } else {
|
ian@0
|
187 if (source_type == sg::kParameter) {
|
ian@0
|
188 command_script_ << g_ArgumentPrefix << source.id_;
|
ian@0
|
189 } else if (source_type == sg::kCommand) {
|
ian@0
|
190 command_script_ << g_VariablePrefix << GetVariable(source_vertex);
|
ian@0
|
191 } else if (source_type == sg::kSpecial) {
|
ian@0
|
192 command_script_ << g_VariablePrefix << GetVariable(source_vertex);
|
ian@0
|
193 }
|
ian@0
|
194 if (connection.weight_ != 1) {
|
ian@0
|
195 command_script_ << " * " << connection.weight_;
|
ian@0
|
196 }
|
ian@0
|
197 if (connection.offset_ != 0) {
|
ian@0
|
198 command_script_ << " + " << connection.offset_;
|
ian@0
|
199 }
|
ian@0
|
200 }
|
ian@0
|
201 }
|
ian@0
|
202 // write preset constant arguments
|
ian@0
|
203 foreach (const sc::Argument& argument, command.Arguments()) {
|
ian@0
|
204 if (argument.scaling_mode_ == sc::Argument::kConstant) {
|
ian@0
|
205 if (!first_argument) {
|
ian@0
|
206 command_script_ << ", ";
|
ian@0
|
207 }
|
ian@0
|
208 first_argument = false;
|
ian@0
|
209 command_script_ << argument.name_ << ": " << argument.constant_value_;
|
ian@0
|
210 }
|
ian@0
|
211 }
|
ian@0
|
212 //command end
|
ian@0
|
213 command_script_ << ");\n";
|
ian@0
|
214 }
|
ian@0
|
215
|
ian@0
|
216 // convert a given vertex
|
ian@0
|
217 void Converter::SynthDefVertex(sg::Vertex vertex, const sg::Graph& graph) {
|
ian@0
|
218 const sg::Node& node = graph[vertex];
|
ian@0
|
219 sg::NodeType type = node.type_;
|
ian@0
|
220 if (type == sg::kSpecial) {
|
ian@0
|
221 SynthDefSpecial(vertex, node, graph);
|
ian@0
|
222 }
|
ian@0
|
223 if (type == sg::kCommand) {
|
ian@0
|
224 SynthDefCommand(vertex, node, graph);
|
ian@0
|
225 }
|
ian@0
|
226 }
|
ian@0
|
227
|
ian@0
|
228 // prepares a synthdef from a synth graph
|
ian@0
|
229 std::string Converter::ToSynthDef(const sg::Graph& graph,
|
ian@0
|
230 const std::string& synth_name,
|
ian@0
|
231 bool convert_for_realtime,
|
ian@0
|
232 const std::string& synthdef_path) {
|
ian@0
|
233 command_script_.str("");
|
ian@0
|
234 variable_map_.clear();
|
ian@0
|
235 variables_created_ = 0;
|
ian@0
|
236 available_variables_.clear();
|
ian@0
|
237
|
ian@0
|
238 // preamble
|
ian@0
|
239 std::stringstream synth_def;
|
ian@0
|
240 synth_def << "SynthDef(\"" << synth_name << "\", {\n";
|
ian@0
|
241 // arguments
|
ian@0
|
242 std::size_t parameters = graph[boost::graph_bundle].parameters_.size();
|
ian@0
|
243 if (parameters > 0) {
|
ian@0
|
244 synth_def << "\targ ";
|
ian@0
|
245 for (std::size_t i = 1; i <= parameters; i++) {
|
ian@0
|
246 synth_def << g_ArgumentPrefix << i;
|
ian@0
|
247 if (i < parameters) {
|
ian@0
|
248 synth_def << ", ";
|
ian@0
|
249 }
|
ian@0
|
250 }
|
ian@0
|
251 synth_def << ";\n";
|
ian@0
|
252 }
|
ian@0
|
253 // commands
|
ian@0
|
254 std::vector<sg::Vertex> vertices;
|
ian@0
|
255 vertices.reserve(boost::num_vertices(graph));
|
ian@0
|
256 // perform topological sort on graph to find dependency order
|
ian@0
|
257 boost::topological_sort(graph, std::back_inserter(vertices));
|
ian@0
|
258 // traverse commands in reversed topological order
|
ian@0
|
259 std::map<sg::Vertex, int> variable_map;
|
ian@0
|
260 foreach_reverse (sg::Vertex& vertex, vertices) {
|
ian@0
|
261 SynthDefVertex(vertex, graph);
|
ian@0
|
262 }
|
ian@0
|
263 // declare variables required by the commands
|
ian@0
|
264 if (variables_created_ > 0) {
|
ian@0
|
265 synth_def << "\tvar ";
|
ian@0
|
266 for (int i = 0; i < variables_created_; i++) {
|
ian@0
|
267 synth_def << g_VariablePrefix << i;
|
ian@0
|
268 if (i < variables_created_ - 1) {
|
ian@0
|
269 synth_def << ", ";
|
ian@0
|
270 }
|
ian@0
|
271 }
|
ian@0
|
272 synth_def << ";\n";
|
ian@0
|
273 }
|
ian@0
|
274 // store the commands in the synthdef
|
ian@0
|
275 synth_def << command_script_.str();
|
ian@0
|
276 // ending
|
ian@0
|
277 if (convert_for_realtime) {
|
ian@0
|
278 synth_def << "}).add;\n";
|
ian@0
|
279 } else {
|
ian@0
|
280 synth_def << "}).writeDefFile(\"" << synthdef_path << "\");\n";
|
ian@0
|
281 }
|
ian@0
|
282 return synth_def.str();
|
ian@0
|
283 }
|
ian@0
|
284
|
ian@0
|
285 std::string Converter::ToScore(const sg::Graph& graph,
|
ian@0
|
286 const std::string& synth_name,
|
ian@0
|
287 const std::string& score_variable,
|
ian@0
|
288 bool convert_for_realtime,
|
ian@0
|
289 const std::string& synthdef_path,
|
ian@0
|
290 double render_length) {
|
ian@0
|
291 std::stringstream score;
|
ian@0
|
292 score << score_variable << " = [\n";
|
ian@0
|
293 if (!convert_for_realtime) {
|
ian@0
|
294 // load the synthdef
|
ian@0
|
295 score << "[0.0, [\\d_load, \""
|
ian@0
|
296 << synthdef_path << '/' << synth_name << ".scsyndef\"]],\n";
|
ian@0
|
297 }
|
ian@0
|
298 // start the synth
|
ian@0
|
299 score << "[0.0, [\\s_new, \\" + synth_name + ", 1000, 0, 0";
|
ian@0
|
300 // initial parameter values
|
ian@0
|
301 const sg::GraphProperties& graph_properties = graph[boost::graph_bundle];
|
ian@0
|
302 const std::vector<double>& parameters = graph_properties.parameters_;
|
ian@0
|
303 for (int i = 0; i < parameters.size(); i++) {
|
ian@0
|
304 score << ", \\" << g_ArgumentPrefix << (i + 1) << ", " << parameters[i];
|
ian@0
|
305 }
|
ian@0
|
306 // end of \s_new command
|
ian@0
|
307 score << "]],\n";
|
ian@0
|
308 if (!convert_for_realtime) {
|
ian@0
|
309 // end of score, stop sound
|
ian@0
|
310 score << "[" << render_length << ", [\\c_set, 0, 0]]\n";
|
ian@0
|
311 }
|
ian@0
|
312 // score end
|
ian@0
|
313 score << "];\n";
|
ian@0
|
314 return score.str();
|
ian@0
|
315 }
|
ian@0
|
316
|
ian@0
|
317 void Converter::Export(const sg::Graph& graph,
|
ian@0
|
318 const std::string& export_folder,
|
ian@0
|
319 const std::string& export_name) {
|
ian@0
|
320 std::stringstream export_path;
|
ian@0
|
321 export_path << export_folder << '/' << export_name << ".sc";
|
ian@0
|
322 std::ofstream export_file(export_path.str().c_str());
|
ian@0
|
323 export_file << ToSynthDef(graph, export_name)
|
ian@0
|
324 << ToScore(graph, export_name, "x");
|
ian@0
|
325 export_file << "Score.play(x);";
|
ian@0
|
326 }
|
ian@0
|
327
|
ian@0
|
328
|
ian@0
|
329 #pragma mark - dot converter
|
ian@0
|
330
|
ian@0
|
331 // formats a command node for dot
|
ian@0
|
332 class CommandWriter {
|
ian@0
|
333 const sg::Graph& graph_;
|
ian@0
|
334 const std::vector<sc::Command>& commands_;
|
ian@0
|
335
|
ian@0
|
336 public:
|
ian@0
|
337 CommandWriter(const sg::Graph& graph,
|
ian@0
|
338 const std::vector<sc::Command>& commands)
|
ian@0
|
339 : graph_(graph),
|
ian@0
|
340 commands_(commands)
|
ian@0
|
341 {}
|
ian@0
|
342
|
ian@0
|
343 void operator()(std::ostream& out, const sg::Vertex& vertex) const {
|
ian@0
|
344 const sg::Node& command_info = graph_[vertex];
|
ian@0
|
345 sg::NodeType type = command_info.type_;
|
ian@0
|
346 out << " [label=\"";
|
ian@0
|
347 if (type == sg::kConstant) {
|
ian@0
|
348 // calculate weighted constant value
|
ian@0
|
349 const sg::Edge& out_edge = *(boost::out_edges(vertex, graph_).first);
|
ian@0
|
350 const sg::Connection& connection = graph_[out_edge];
|
ian@0
|
351 double value = command_info.constant_value_;
|
ian@0
|
352 value = value * connection.weight_ + connection.offset_;
|
ian@0
|
353 out << std::setprecision(2) << std::fixed << value
|
ian@0
|
354 << "\", shape=diamond";
|
ian@0
|
355 } else if (type == sg::kSpecial) {
|
ian@0
|
356 if (command_info.id_ == sc::Command::kMixer) {
|
ian@0
|
357 // check if this is the root mixer
|
ian@0
|
358 if (boost::out_degree(vertex, graph_) > 0) {
|
ian@0
|
359 out << "+\"";
|
ian@0
|
360 if (command_info.rate_ == sg::kControlRate) {
|
ian@0
|
361 out << ", shape=box";
|
ian@0
|
362 }
|
ian@0
|
363 } else {
|
ian@0
|
364 out << "Output\"";
|
ian@0
|
365 }
|
ian@0
|
366 } else if (command_info.id_ == sc::Command::kMultiplier) {
|
ian@0
|
367 out << "*\"";
|
ian@0
|
368 if (command_info.rate_ == sg::kControlRate) {
|
ian@0
|
369 out << ", shape=box";
|
ian@0
|
370 }
|
ian@0
|
371 }
|
ian@0
|
372 } else if (type == sg::kCommand) {
|
ian@0
|
373 out << commands_[command_info.id_].Name() << "\"";
|
ian@0
|
374 if (command_info.rate_ == sg::kControlRate) {
|
ian@0
|
375 out << ", shape=box";
|
ian@0
|
376 }
|
ian@0
|
377 } else if (type == sg::kParameter) {
|
ian@0
|
378 out << g_ArgumentPrefix << command_info.id_
|
ian@0
|
379 << "\", shape=invhouse";
|
ian@0
|
380 }
|
ian@0
|
381 // end
|
ian@0
|
382 out << "]";
|
ian@0
|
383 }
|
ian@0
|
384 };
|
ian@0
|
385
|
ian@0
|
386 // formats a connection for dot
|
ian@0
|
387 class ConnectionWriter {
|
ian@0
|
388 const sg::Graph& graph_;
|
ian@0
|
389 const std::vector<sc::Command>& commands_;
|
ian@0
|
390
|
ian@0
|
391 public:
|
ian@0
|
392 ConnectionWriter(const sg::Graph& graph,
|
ian@0
|
393 const std::vector<sc::Command>& commands)
|
ian@0
|
394 : graph_(graph),
|
ian@0
|
395 commands_(commands)
|
ian@0
|
396 {}
|
ian@0
|
397
|
ian@0
|
398 void operator()(std::ostream& out, const sg::Edge& edge) const {
|
ian@0
|
399 const sg::Connection& connection = graph_[edge];
|
ian@0
|
400 out << "[label=\"";
|
ian@0
|
401 const sg::Node& target = graph_[boost::target(edge, graph_)];
|
ian@0
|
402 if (target.type_ == sg::kCommand) {
|
ian@0
|
403 const sc::Command& command = commands_[target.id_];
|
ian@0
|
404 const sc::Argument& argument = command.GetArgument(connection.input_);
|
ian@0
|
405 out << " [" << argument.name_ << ']';
|
ian@0
|
406 }
|
ian@0
|
407 if (connection.IsActive()) {
|
ian@0
|
408 // connection weighting
|
ian@0
|
409 // skip constants as they have weighting applied to label
|
ian@0
|
410 const sg::Node& source = graph_[boost::source(edge, graph_)];
|
ian@0
|
411 if (source.type_ != sg::kConstant) {
|
ian@0
|
412 out << " (";
|
ian@0
|
413 out << std::fixed << std::setprecision(g_label_decimal_places);
|
ian@0
|
414 if (connection.offset_ != 0) {
|
ian@0
|
415 out << connection.offset_
|
ian@0
|
416 << " - "
|
ian@0
|
417 << (connection.offset_ + connection.weight_);
|
ian@0
|
418 } else {
|
ian@0
|
419 out << "* " << connection.weight_;
|
ian@0
|
420 }
|
ian@0
|
421 out << ")";
|
ian@0
|
422 }
|
ian@0
|
423 }
|
ian@0
|
424 out << "\"]";
|
ian@0
|
425 }
|
ian@0
|
426 };
|
ian@0
|
427
|
ian@0
|
428 class GraphPropertiesWriter {
|
ian@0
|
429 const sg::Graph& graph_;
|
ian@0
|
430 public:
|
ian@0
|
431 GraphPropertiesWriter(const sg::Graph& graph) : graph_(graph) {}
|
ian@0
|
432 void operator()(std::ostream& out) const {
|
ian@0
|
433 out << "edge[fontsize=10]\n";
|
ian@0
|
434 }
|
ian@0
|
435 };
|
ian@0
|
436
|
ian@0
|
437 std::string Converter::ToDOT(const sg::Graph& graph) {
|
ian@0
|
438 std::stringstream buffer;
|
ian@0
|
439 // prepare the writer classes
|
ian@0
|
440 CommandWriter command_writer(graph, commands_);
|
ian@0
|
441 ConnectionWriter connection_writer(graph, commands_);
|
ian@0
|
442 GraphPropertiesWriter graph_writer(graph);
|
ian@0
|
443 // write the graph to the buffer
|
ian@0
|
444 boost::write_graphviz(buffer, graph,
|
ian@0
|
445 command_writer, connection_writer, graph_writer);
|
ian@0
|
446 return buffer.str();
|
ian@0
|
447 }
|
ian@0
|
448
|
ian@0
|
449 } // sc namespace
|