Mercurial > hg > gpsynth
comparison src/sc_grammar.cpp @ 0:add35537fdbb tip
Initial import
author | irh <ian.r.hobson@gmail.com> |
---|---|
date | Thu, 25 Aug 2011 11:05:55 +0100 |
parents | |
children |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 0:add35537fdbb |
---|---|
1 // Copyright 2011, Ian Hobson. | |
2 // | |
3 // This file is part of gpsynth. | |
4 // | |
5 // gpsynth is free software: you can redistribute it and/or modify | |
6 // it under the terms of the GNU General Public License as published by | |
7 // the Free Software Foundation, either version 3 of the License, or | |
8 // (at your option) any later version. | |
9 // | |
10 // gpsynth is distributed in the hope that it will be useful, | |
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of | |
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
13 // GNU General Public License for more details. | |
14 // | |
15 // You should have received a copy of the GNU General Public License | |
16 // along with gpsynth in the file COPYING. | |
17 // If not, see http://www.gnu.org/licenses/. | |
18 | |
19 #include "sc_grammar.hpp" | |
20 | |
21 #include "boost_ex.hpp" | |
22 #include "graph_helpers.hpp" | |
23 #include "logger.hpp" | |
24 #include "statistics.hpp" | |
25 #include "std_ex.hpp" | |
26 | |
27 #include "json/json.h" | |
28 | |
29 #include "boost/graph/random.hpp" | |
30 #include "boost/graph/topological_sort.hpp" | |
31 | |
32 #include <iostream> | |
33 #include <iterator> | |
34 #include <fstream> | |
35 #include <set> | |
36 #include <sstream> | |
37 | |
38 const int g_maximum_mixer_channels = 3; | |
39 | |
40 const double g_probability_command_node_command = 0.85; | |
41 const double g_probability_command_node_special = 0.15; | |
42 | |
43 const double g_probability_mod_node_command = 0.25; | |
44 const double g_probability_mod_node_special = 0.15; | |
45 const double g_probability_mod_node_constant = 0.4; | |
46 | |
47 const double g_probability_argument_affected = 1.0; | |
48 | |
49 const double g_probability_input_mutation = 1.0; | |
50 const double g_probability_connection_mutation = 1.0; | |
51 | |
52 const double g_minimum_connection_weight = 0.001; | |
53 | |
54 namespace { | |
55 | |
56 double MapCoefficientToRange(double c, const stdx::Range<double>& range) { | |
57 return range.minimum_ + (range.Size() * c); | |
58 } | |
59 | |
60 // Returns argument connection weight and offset, scaled to argument input | |
61 void GenerateWeightAndOffset(const sc::Argument& argument, | |
62 sg::CommandRate rate, | |
63 sg::Connection* connection) { | |
64 const stdx::Range<double>& scale_range = (rate == sg::kControlRate) | |
65 ? argument.range_control_ | |
66 : argument.range_; | |
67 if (argument.fixed_range_) { | |
68 connection->weight_ = scale_range.Size(); | |
69 connection->offset_ = scale_range.Minimum(); | |
70 } else if (argument.name_ == "in") { | |
71 // input connections don't have an offset | |
72 double weight = stdx::RandomCoefficient(); | |
73 if (rate == sg::kAudioRate) { | |
74 // square audio weightings | |
75 connection->weight_ = weight * weight; | |
76 } | |
77 } else { | |
78 // generate random weight and offset | |
79 double weight; | |
80 do { | |
81 weight = stdx::RandomCoefficient(); | |
82 } while (weight < g_minimum_connection_weight); | |
83 double offset = stdx::RandomRange(0.0, 1.0 - weight); | |
84 switch (argument.scaling_mode_) { | |
85 case sc::Argument::kScalingLinear: | |
86 connection->weight_ = MapCoefficientToRange(weight, scale_range); | |
87 connection->offset_ = MapCoefficientToRange(offset, scale_range); | |
88 break; | |
89 | |
90 case sc::Argument::kScalingLog: | |
91 connection->weight_ = MapCoefficientToRange(weight * weight * weight, | |
92 scale_range); | |
93 connection->offset_ = MapCoefficientToRange(offset * offset * offset, | |
94 scale_range); | |
95 break; | |
96 | |
97 case sc::Argument::kConstant: | |
98 throw std::runtime_error("ApplyRandomWeightAndOffset - preset argument provided"); | |
99 } | |
100 } | |
101 } | |
102 | |
103 sc::Argument ParseArgument(const Json::Value& json) { | |
104 sc::Argument arg; | |
105 arg.name_ = json.get("name", "").asString(); | |
106 if (json.isMember("constant")) { | |
107 arg.scaling_mode_ = sc::Argument::kConstant; | |
108 arg.constant_value_ = json["constant"].asDouble(); | |
109 return arg; | |
110 } | |
111 if (json.isMember("range")) { | |
112 const Json::Value range = json["range"]; | |
113 arg.range_.SetMinimum(range[0].asDouble()); | |
114 arg.range_.SetMaximum(range[1].asDouble()); | |
115 } | |
116 if (json.isMember("range_control")) { | |
117 const Json::Value range = json["range_control"]; | |
118 arg.range_control_.SetMinimum(range[0].asDouble()); | |
119 arg.range_control_.SetMaximum(range[1].asDouble()); | |
120 } else { | |
121 arg.range_control_ = arg.range_; | |
122 } | |
123 if (json.isMember("fixed_range")) { | |
124 arg.fixed_range_ = json["fixed_range"].asBool(); | |
125 } | |
126 std::string scaling = json.get("scaling", "linear").asString(); | |
127 if (scaling == "linear") { | |
128 arg.scaling_mode_ = sc::Argument::kScalingLinear; | |
129 } else if (scaling == "log") { | |
130 arg.scaling_mode_ = sc::Argument::kScalingLog; | |
131 } | |
132 return arg; | |
133 } | |
134 | |
135 sc::Command ParseCommand(const Json::Value& json, | |
136 const std::map<std::string, sc::Argument>& types, | |
137 sc::Command::CommandMode command_mode) { | |
138 if (json.type() == Json::stringValue) { | |
139 return sc::Command(json.asString(), command_mode); | |
140 } | |
141 // name | |
142 std::string name = json["name"].asString(); | |
143 // output type | |
144 sc::Command::OutputType output_type = sc::Command::kAll; | |
145 if (json.isMember("output")) { | |
146 std::string output = json["output"].asString(); | |
147 if (output == "control") { | |
148 output_type = sc::Command::kControl; | |
149 } else if (output == "audio") { | |
150 output_type = sc::Command::kAudio; | |
151 } | |
152 } | |
153 // arguments | |
154 std::vector<sc::Argument> args; | |
155 // [in] argument for modifiers | |
156 if (command_mode == sc::Command::kModifier) { | |
157 args.push_back(sc::Argument("in")); | |
158 } | |
159 Json::Value json_args = json["args"]; | |
160 Json::ValueType arg_type = json_args.type(); | |
161 if (arg_type == Json::stringValue) { | |
162 // single argument as string | |
163 args.push_back(sc::Argument(json_args.asString())); | |
164 } else if (arg_type == Json::objectValue) { | |
165 // single argument object | |
166 if (json_args.isMember("type")) { | |
167 args.push_back(stdx::GetFromMap(types, json_args["type"].asString())); | |
168 args.back().name_ = json_args["name"].asString(); | |
169 } else { | |
170 args.push_back(ParseArgument(json_args)); | |
171 } | |
172 } else { | |
173 // array of arguments | |
174 for (int i = 0; i < json_args.size(); i++) { | |
175 Json::Value arg = json_args[i]; | |
176 if (arg.type() == Json::stringValue) { | |
177 args.push_back(sc::Argument(arg.asString())); | |
178 } else { | |
179 if (arg.isMember("type")) { | |
180 args.push_back(stdx::GetFromMap(types, arg["type"].asString())); | |
181 args.back().name_ = arg["name"].asString(); | |
182 } else { | |
183 args.push_back(ParseArgument(arg)); | |
184 } | |
185 } | |
186 } | |
187 } | |
188 // success | |
189 return sc::Command(name, command_mode, output_type, args); | |
190 } | |
191 | |
192 | |
193 sc::Command ParseSource(const Json::Value& json, | |
194 const std::map<std::string, sc::Argument>& types) { | |
195 return ParseCommand(json, types, sc::Command::kSource); | |
196 } | |
197 | |
198 sc::Command ParseModifier(const Json::Value& json, | |
199 const std::map<std::string, sc::Argument>& types) { | |
200 return ParseCommand(json, types, sc::Command::kModifier); | |
201 } | |
202 | |
203 void MutateValue(double* value, bool* mutation_occurred, | |
204 double range_min = 0.0, double range_max = 1.0) { | |
205 // add gaussian noise to values, low variance | |
206 static bx::GaussianGenerator generator = bx::MakeGaussianGenerator(0, 0.125); | |
207 double new_value = stdx::Clamp(*value + generator(), range_min, range_max); | |
208 if (*value != new_value) { | |
209 *value = new_value; | |
210 *mutation_occurred = true; | |
211 } | |
212 } | |
213 | |
214 } // namespace | |
215 | |
216 | |
217 | |
218 namespace sc { | |
219 | |
220 Grammar::Grammar(const std::string& json_data) | |
221 : max_depth_(3) | |
222 { | |
223 ParseJSON(json_data); | |
224 } | |
225 | |
226 void Grammar::ParseJSON(const std::string& json_data) { | |
227 commands_.clear(); | |
228 Json::Reader reader; | |
229 Json::Value root; | |
230 if (!reader.parse(json_data, root)) { | |
231 std::stringstream message; | |
232 message << "Grammar::Parse: Failed to parse data\n" | |
233 << reader.getFormattedErrorMessages(); | |
234 throw std::runtime_error(message.str()); | |
235 } | |
236 try { | |
237 // argument types | |
238 // stores predefined argument types while parsing grammar | |
239 std::map<std::string, Argument> argument_types; | |
240 const Json::Value& args = root["arg-types"]; | |
241 for (int i = 0; i < args.size(); i++) { | |
242 const Json::Value& arg = args[i]; | |
243 argument_types[arg["type"].asString()] = ParseArgument(arg); | |
244 } | |
245 // sound sources | |
246 const Json::Value& sources = root["sources"]; | |
247 for (int i = 0; i < sources.size(); i++) { | |
248 commands_.push_back(ParseSource(sources[i], argument_types)); | |
249 } | |
250 // modifiers | |
251 const Json::Value& modifiers = root["modifiers"]; | |
252 for (int i = 0; i < modifiers.size(); i++) { | |
253 commands_.push_back(ParseModifier(modifiers[i], argument_types)); | |
254 } | |
255 } catch (const std::exception& e) { | |
256 std::stringstream message; | |
257 message << "Grammar::Parse: " << e.what(); | |
258 throw std::runtime_error(message.str()); | |
259 } | |
260 } | |
261 | |
262 int Grammar::RandomCommand(sg::CommandRate rate, | |
263 int tree_depth, | |
264 bool must_be_modifier /* = false */) const { | |
265 Command::OutputType output; | |
266 Command::CommandMode mode; | |
267 CommandID command = -1; | |
268 CommandID number_of_commands = commands_.size(); | |
269 // if we're at or one away from maximum depth then the command needs to | |
270 // be a source, headroom of 1 for some parameter control | |
271 bool must_be_source = (tree_depth >= (max_depth_ - 1)); | |
272 do { | |
273 command = stdx::Random(number_of_commands); | |
274 output = commands_[command].Output(); | |
275 if (rate == sg::kAudioRate) { | |
276 do { | |
277 command = stdx::Random(number_of_commands); | |
278 output = commands_[command].Output(); | |
279 } while (!(output == Command::kAll || output == Command::kAudio)); | |
280 } else { | |
281 do { | |
282 command = stdx::Random(number_of_commands); | |
283 output = commands_[command].Output(); | |
284 } while (!(output == Command::kAll || output == Command::kControl)); | |
285 } | |
286 mode = commands_[command].Mode(); | |
287 } while ((must_be_source && (mode != Command::kSource)) | |
288 || (must_be_modifier && (mode != Command::kModifier))); | |
289 return static_cast<int>(command); | |
290 } | |
291 | |
292 sg::Vertex Grammar::CreateCommand(sg::Graph &graph, | |
293 sg::CommandRate rate, | |
294 int depth, | |
295 bool must_be_modifier /* = false */, | |
296 bool make_modifier_input /* = true */) const { | |
297 // select a random command for the node | |
298 int command_id = RandomCommand(rate, depth, must_be_modifier); | |
299 // create the node and add it to the graph | |
300 sg::Node command_info(command_id, sg::kCommand, rate); | |
301 sg::Vertex vertex = boost::add_vertex(command_info, graph); | |
302 // a modifier command has to have further nodes added to its main input | |
303 const Command& command = commands_[command_id]; | |
304 for (int i = 0; i < command.Arguments().size(); i++) { | |
305 const Argument& argument = command.GetArgument(i); | |
306 if (argument.name_ == "in") { | |
307 if (make_modifier_input) { | |
308 // modifier input must be given a command tree | |
309 sg::Vertex tree = RandomTree(graph, rate, kTreeMode_Command, depth + 1); | |
310 sg::Connection connection(i); | |
311 GenerateWeightAndOffset(argument, rate, &connection); | |
312 boost::add_edge(tree, vertex, connection, graph); | |
313 } | |
314 } else if (argument.scaling_mode_ == sc::Argument::kConstant) { | |
315 // don't attach inputs to predefined arguments | |
316 continue; | |
317 } else if (stdx::Chance(g_probability_argument_affected)) { | |
318 // randomly create input for command arguments | |
319 sg::Vertex tree = RandomTree(graph, | |
320 sg::kControlRate, | |
321 kTreeMode_Mod, | |
322 depth + 1); | |
323 sg::Connection connection(i); | |
324 GenerateWeightAndOffset(argument, rate, &connection); | |
325 boost::add_edge(tree, vertex, connection, graph); | |
326 } | |
327 } | |
328 return vertex; | |
329 } | |
330 | |
331 sg::Vertex Grammar::RandomTree(sg::Graph &graph, | |
332 sg::CommandRate rate, | |
333 TreeMode tree_mode, | |
334 int depth) const { | |
335 enum NodeType { | |
336 kCommand, | |
337 kSpecial, | |
338 kConstant, | |
339 kParameter | |
340 }; | |
341 static stats::ProbabilitySelector node_type_selector; | |
342 if (!node_type_selector.Initialized()) { | |
343 std::vector<double> probabilities(3); | |
344 probabilities[kCommand] = g_probability_mod_node_command; | |
345 probabilities[kSpecial] = g_probability_mod_node_special; | |
346 probabilities[kConstant] = g_probability_mod_node_constant; | |
347 node_type_selector.SetProbabilities(probabilities); | |
348 } | |
349 // establish the type of node to create | |
350 int node_type; | |
351 if (depth >= max_depth_) { | |
352 if (tree_mode == kTreeMode_Command) { | |
353 // if we're at maximum depth in command mode then create a command | |
354 node_type = kCommand; | |
355 } else { | |
356 // in mod mode at maximum depth pick either a constant or parameter | |
357 if (stdx::Chance(g_probability_mod_node_constant)) { | |
358 node_type = kConstant; | |
359 } else { | |
360 node_type = kParameter; | |
361 } | |
362 } | |
363 } else { | |
364 if (tree_mode == kTreeMode_Command) { | |
365 if (stdx::Chance(g_probability_command_node_command)) { | |
366 node_type = kCommand; | |
367 } else { | |
368 node_type = kSpecial; | |
369 } | |
370 } else { | |
371 // mod mode below max depth, any node type is ok | |
372 node_type = static_cast<NodeType>(node_type_selector()); | |
373 } | |
374 } | |
375 if (node_type == kCommand) { | |
376 return CreateCommand(graph, rate, depth); | |
377 } else if (node_type == kSpecial) { | |
378 int command = stdx::Random(Command::kNumberOfSpecialCommands); | |
379 return SpecialCommand(command, graph, rate, tree_mode, depth + 1); | |
380 } else if (node_type == kConstant) { | |
381 return boost::add_vertex(sg::Node(stdx::RandomCoefficient()), graph); | |
382 } else if (node_type == kParameter) { | |
383 return boost::add_vertex(sg::Node(RandomParameter(graph), sg::kParameter), | |
384 graph); | |
385 } | |
386 throw std::runtime_error("Grammar::RandomTree - unknown node type"); | |
387 } | |
388 | |
389 int Grammar::RandomParameter(sg::Graph& graph) const { | |
390 // as a simple rule, randomly pick an existing parameter, or if chosen | |
391 // parameter is greater than # of parameters, then add a new one | |
392 std::vector<double>& parameters = graph[boost::graph_bundle].parameters_; | |
393 int number_of_parameters = static_cast<int>(parameters.size()); | |
394 // parameter ids start at 1 | |
395 int parameter = stdx::RandomRangeInt(1, number_of_parameters + 1); | |
396 // bump parameter count if we've got a new parameter | |
397 if (parameter > number_of_parameters) { | |
398 // store a random value for the new parameter | |
399 parameters.push_back(stdx::RandomCoefficient()); | |
400 } | |
401 return parameter; | |
402 } | |
403 | |
404 void Grammar::AddTreeToSpecialCommand(sg::Vertex command_node, | |
405 sg::Graph& graph, | |
406 int input_id, | |
407 TreeMode tree_mode, | |
408 sg::CommandRate command_rate, | |
409 bool* constant_added, | |
410 std::set<int>* parameters, | |
411 int depth) const { | |
412 bool success = false; | |
413 while (!success) { | |
414 sg::Vertex tree; | |
415 // first channel or audio command should enforce a command source | |
416 if (input_id == 0) { | |
417 tree = RandomTree(graph, command_rate, kTreeMode_Command, depth + 1); | |
418 } else { | |
419 tree = RandomTree(graph, command_rate, tree_mode, depth + 1); | |
420 } | |
421 sg::NodeType tree_type = graph[tree].type_; | |
422 if (tree_type == sg::kConstant) { | |
423 // we only need one constant per mixer | |
424 if (*constant_added) { | |
425 RemoveSubTree(tree, graph); | |
426 continue; | |
427 } | |
428 *constant_added = true; | |
429 } else if (tree_type == sg::kParameter) { | |
430 // we don't want duplicated parameters for special command inputs | |
431 if (parameters->find(graph[tree].id_) != parameters->end()) { | |
432 RemoveSubTree(tree, graph); | |
433 continue; | |
434 } | |
435 // store the parameter id | |
436 parameters->insert(graph[tree].id_); | |
437 } | |
438 // apply weighting | |
439 double weight = stdx::RandomCoefficient(); | |
440 if (command_rate == sg::kAudioRate) { | |
441 // scale audio to curve | |
442 weight *= weight; | |
443 } | |
444 // make the connection to the generated tree | |
445 boost::add_edge(tree, command_node, sg::Connection(weight), graph); | |
446 success = true; | |
447 } | |
448 } | |
449 | |
450 sg::Vertex Grammar::SpecialCommand(int command_id, | |
451 sg::Graph& graph, | |
452 sg::CommandRate command_rate, | |
453 TreeMode tree_mode, | |
454 int depth /* = 0 */, | |
455 int minimum_channels /* = 2 */) const { | |
456 // create the special command node | |
457 sg::Node command_info(command_id, sg::kSpecial, command_rate); | |
458 sg::Vertex mixer = boost::add_vertex(command_info, graph); | |
459 // pick a random number of channels to create for this command | |
460 int channels = stdx::RandomRangeInt(minimum_channels, | |
461 g_maximum_mixer_channels); | |
462 bool constant_added = false; | |
463 std::set<int> parameters; | |
464 // add a tree for each channel | |
465 for (int i = 0; i < channels; i++) { | |
466 AddTreeToSpecialCommand(mixer, | |
467 graph, | |
468 i, | |
469 tree_mode, | |
470 command_rate, | |
471 &constant_added, | |
472 ¶meters, | |
473 depth); | |
474 } | |
475 return mixer; | |
476 } | |
477 | |
478 void Grammar::RandomGraph(sg::Graph& graph) const { | |
479 graph.clear(); | |
480 // root output node | |
481 SpecialCommand(sc::Command::kMixer, graph, sg::kAudioRate, kTreeMode_Command); | |
482 } | |
483 | |
484 void Grammar::PickCrossoverNodes(const sg::Graph& parent_1, | |
485 const sg::Graph& parent_2, | |
486 sg::Vertex* crossover_node_1, | |
487 sg::Vertex* crossover_node_2) const { | |
488 sg::Vertex source_node_1; | |
489 sg::Vertex source_node_2; | |
490 bool nodes_found = false; | |
491 do { | |
492 // pick random edges and their nodes | |
493 source_node_1 = boost::random_vertex(parent_1, bx::RandomEngine()); | |
494 source_node_2 = boost::random_vertex(parent_2, bx::RandomEngine()); | |
495 // avoid the root output node | |
496 if ((source_node_1 == 0) || (source_node_2 == 0)) { | |
497 continue; | |
498 } | |
499 // check it's ok to swap these nodes | |
500 const sg::Node& info_1 = parent_1[source_node_1]; | |
501 const sg::Node& info_2 = parent_2[source_node_2]; | |
502 // command rates need to match | |
503 if (info_1.rate_ != info_2.rate_) { | |
504 continue; | |
505 } | |
506 // we don't want to swap two constants | |
507 if (info_1.type_ == sg::kConstant && info_2.type_ == sg::kConstant) { | |
508 continue; | |
509 } | |
510 // we don't want to swap parameter inputs with same parameter id | |
511 if ((info_1.type_ == sg::kParameter) | |
512 && (info_2.type_ == sg::kParameter) | |
513 && (info_1.id_ == info_2.id_)) { | |
514 continue; | |
515 } | |
516 // check that we're not replacing a control modifer's input with non-command | |
517 sg::Edge connection_1 = *(boost::out_edges(source_node_1, parent_1).first); | |
518 sg::Edge connection_2 = *(boost::out_edges(source_node_2, parent_2).first); | |
519 sg::Vertex target_node_1 = boost::target(connection_1, parent_1); | |
520 sg::Vertex target_node_2 = boost::target(connection_2, parent_2); | |
521 const sg::Node& info_target_1 = parent_1[target_node_1]; | |
522 const sg::Node& info_target_2 = parent_2[target_node_2]; | |
523 if ((info_target_1.type_ == sg::kCommand) | |
524 || (info_target_1.type_ == sg::kSpecial)) { | |
525 const sc::Command& target_command_1 = commands_[info_target_1.id_]; | |
526 const std::vector<Argument>& arguments_1 = target_command_1.Arguments(); | |
527 const sg::Connection& info_connection_1 = parent_1[connection_1]; | |
528 if (arguments_1[info_connection_1.input_].name_ == "in") { | |
529 // target input is a modifier's 'in' argument, only allow commands to | |
530 // be swapped in | |
531 if (!(info_2.type_ == sg::kCommand || info_2.type_ == sg::kSpecial)) { | |
532 continue; | |
533 } | |
534 } | |
535 } | |
536 if ((info_target_2.type_ == sg::kCommand) | |
537 || (info_target_2.type_ == sg::kSpecial)) { | |
538 const sc::Command& target_command_2 = commands_[info_target_2.id_]; | |
539 const std::vector<Argument>& arguments_2 = target_command_2.Arguments(); | |
540 const sg::Connection& info_connection_2 = parent_1[connection_2]; | |
541 if (arguments_2[info_connection_2.input_].name_ == "in") { | |
542 // target input is a modifier's 'in' argument, only allow commands to | |
543 // be swapped in | |
544 if (!(info_1.type_ == sg::kCommand || info_1.type_ == sg::kSpecial)) { | |
545 continue; | |
546 } | |
547 } | |
548 } | |
549 nodes_found = true; | |
550 } while (!nodes_found); | |
551 *crossover_node_1 = source_node_1; | |
552 *crossover_node_2 = source_node_2; | |
553 } | |
554 | |
555 bool Grammar::MutationReplaceSubTree(sg::Graph& graph) const { | |
556 // sanity check | |
557 if (boost::num_vertices(graph) <= 1) { | |
558 throw std::runtime_error("sc::Grammar::MutationReplaceSubTree - graph has too few nodes"); | |
559 } | |
560 // pick node to replace | |
561 sg::Vertex node_to_replace; | |
562 do { | |
563 node_to_replace = boost::random_vertex(graph, bx::RandomEngine()); | |
564 } while (node_to_replace == 0); | |
565 sg::CommandRate new_tree_rate = graph[node_to_replace].rate_; | |
566 sg::Edge connection = *(boost::out_edges(node_to_replace, graph).first); | |
567 sg::Vertex connection_target = boost::target(connection, graph); | |
568 // store the connection info contained in the connection edge | |
569 sg::Connection connection_info = graph[connection]; | |
570 const sg::Node& target_info = graph[connection_target]; | |
571 // determine new tree mode | |
572 TreeMode new_tree_mode = kTreeMode_Mod; | |
573 if (new_tree_rate == sg::kAudioRate) { | |
574 // audio rate nodes always require command tree replacements | |
575 new_tree_mode = kTreeMode_Command; | |
576 } else if (target_info.type_ == sg::kCommand) { | |
577 // if the connection target is a modifier | |
578 // and the connection input is 0 then a command tree is needed | |
579 if (commands_[target_info.id_].Mode() == Command::kModifier) { | |
580 if (connection_info.input_ == 0) { | |
581 new_tree_mode = kTreeMode_Command; | |
582 } | |
583 } | |
584 } else if (target_info.type_ == sg::kSpecial) { | |
585 // first input to special commands is a command tree | |
586 if (connection_info.input_ == 0) { | |
587 new_tree_mode = kTreeMode_Command; | |
588 } | |
589 } | |
590 // remove the sub tree | |
591 RemoveSubTree(connection, graph); | |
592 // generate new tree | |
593 sg::Vertex new_tree = RandomTree(graph, new_tree_rate, new_tree_mode, 0); | |
594 // connect tree to graph | |
595 boost::add_edge(new_tree, connection_target, connection_info, graph); | |
596 return true; | |
597 } | |
598 | |
599 | |
600 | |
601 bool Grammar::MutationAddSubTree(sg::Graph& graph) const { | |
602 // sanity check | |
603 if (graph[0].type_ != sg::kSpecial) { | |
604 throw std::runtime_error("sc::Grammar::MutationAddSubTree - missing root mixer node"); | |
605 } | |
606 bool mutation_occurred = false; | |
607 while (!mutation_occurred) { | |
608 // pick a command or special node to add a tree input to | |
609 sg::Vertex node; | |
610 sg::NodeType node_type; | |
611 do { | |
612 node = boost::random_vertex(graph, bx::RandomEngine()); | |
613 node_type = graph[node].type_; | |
614 } while (!(node_type == sg::kCommand || node_type == sg::kSpecial)); | |
615 if (node_type == sg::kCommand) { | |
616 int node_id = graph[node].id_; | |
617 const std::vector<Argument>& arguments = commands_[node_id].Arguments(); | |
618 std::size_t number_of_arguments = arguments.size(); | |
619 sg::InEdgeIterator edge; | |
620 sg::InEdgeIterator edge_end; | |
621 std::vector<bool> used_arguments(arguments.size(), false); | |
622 for (boost::tie(edge, edge_end) = boost::in_edges(node, graph); | |
623 edge != edge_end; | |
624 ++edge) { | |
625 used_arguments[graph[*edge].input_] = true; | |
626 } | |
627 // check that there is an unused argument available for this command | |
628 bool argument_available = false; | |
629 for (std::size_t i = 0; i < number_of_arguments; i++) { | |
630 if (used_arguments[i] == false) { | |
631 // we don't want to attach a tree to a preset argument | |
632 if (arguments[i].scaling_mode_ != Argument::kConstant) { | |
633 argument_available = true; | |
634 break; | |
635 } | |
636 } | |
637 } | |
638 if (argument_available) { | |
639 // find a random argument to insert a tree into | |
640 std::size_t argument; | |
641 do { | |
642 argument = stdx::Random(arguments.size()); | |
643 } while (used_arguments[argument] == true | |
644 || arguments[argument].scaling_mode_ == Argument::kConstant); | |
645 // add a tree and connect to the unused argument | |
646 sg::Vertex tree = RandomTree(graph, | |
647 sg::kControlRate, | |
648 kTreeMode_Mod, | |
649 0); | |
650 sg::Connection connection(static_cast<int>(argument)); | |
651 GenerateWeightAndOffset(arguments[argument], | |
652 sg::kControlRate, | |
653 &connection); | |
654 boost::add_edge(tree, node, connection, graph); | |
655 mutation_occurred = true; | |
656 } | |
657 } else if (node_type == sg::kSpecial) { | |
658 int node_inputs = static_cast<int>(boost::in_degree(node, graph)); | |
659 // we need to check the special command's inputs to avoid adding redundant | |
660 // constants or parameters | |
661 bool constant_added = false; | |
662 std::set<int> parameters; | |
663 sg::InEdgeIterator edge; | |
664 sg::InEdgeIterator edge_end; | |
665 for (boost::tie(edge, edge_end) = boost::in_edges(node, graph); | |
666 edge != edge_end; | |
667 ++edge) { | |
668 const sg::Node& source = graph[boost::source(*edge, graph)]; | |
669 if (source.type_ == sg::kConstant) { | |
670 constant_added = true; | |
671 } | |
672 if (source.type_ == sg::kParameter) { | |
673 parameters.insert(source.id_); | |
674 } | |
675 } | |
676 // select tree mode based on command rate | |
677 TreeMode tree_mode; | |
678 if (graph[node].rate_ == sg::kAudioRate) { | |
679 tree_mode = kTreeMode_Command; | |
680 } else { | |
681 tree_mode = kTreeMode_Mod; | |
682 } | |
683 // add the tree | |
684 AddTreeToSpecialCommand(node, | |
685 graph, | |
686 node_inputs, | |
687 tree_mode, | |
688 graph[node].rate_, | |
689 &constant_added, | |
690 ¶meters, | |
691 0); | |
692 mutation_occurred = true; | |
693 } | |
694 } | |
695 return true; | |
696 } | |
697 | |
698 bool Grammar::MutationModifyInputs(sg::Graph& graph) const { | |
699 // sanity check | |
700 if (graph[boost::graph_bundle].parameters_.size() == 0) { | |
701 sg::VertexIterator vertex; | |
702 sg::VertexIterator vertex_end; | |
703 bool constant_found = false; | |
704 for (boost::tie(vertex, vertex_end) = boost::vertices(graph); | |
705 vertex != vertex_end; | |
706 ++vertex) { | |
707 sg::Node& node = graph[*vertex]; | |
708 if (node.type_ == sg::kConstant) { | |
709 constant_found = true; | |
710 break; | |
711 } | |
712 } | |
713 if (!constant_found) { | |
714 // no parameters, no constants, nothing to do.. | |
715 return false; | |
716 } | |
717 } | |
718 // make a random generator with a narrow guassian distribution | |
719 bx::GaussianGenerator gaussian = bx::MakeGaussianGenerator(0, 0.125); | |
720 bool mutation_occurred = false; | |
721 while (!mutation_occurred) { | |
722 // mutate parameters | |
723 foreach (double& parameter, graph[boost::graph_bundle].parameters_) { | |
724 if (stdx::Chance(g_probability_input_mutation)) { | |
725 MutateValue(¶meter, &mutation_occurred); | |
726 } | |
727 } | |
728 // mutate constants | |
729 sg::VertexIterator vertex; | |
730 sg::VertexIterator vertex_end; | |
731 for (boost::tie(vertex, vertex_end) = boost::vertices(graph); | |
732 vertex != vertex_end; | |
733 ++vertex) { | |
734 sg::Node& node = graph[*vertex]; | |
735 if (node.type_ == sg::kConstant) { | |
736 // constant value | |
737 if (stdx::Chance(g_probability_input_mutation)) { | |
738 MutateValue(&node.constant_value_, &mutation_occurred); | |
739 } | |
740 } | |
741 } | |
742 } | |
743 return true; | |
744 } | |
745 | |
746 bool Grammar::MutationModifyConnections(sg::Graph& graph) const { | |
747 // sanity check | |
748 if (boost::num_edges(graph) < 1) { | |
749 throw std::runtime_error("sc::Grammar::MutationModifyConnections - graph has no connections"); | |
750 } | |
751 bx::GaussianGenerator gaussian = bx::MakeGaussianGenerator(0, 0.125); | |
752 bool mutation_occurred = false; | |
753 while (!mutation_occurred) { | |
754 sg::EdgeIterator edge; | |
755 sg::EdgeIterator edge_end; | |
756 for (boost::tie(edge, edge_end) = boost::edges(graph); | |
757 edge != edge_end; | |
758 ++edge) { | |
759 if (stdx::Chance(g_probability_connection_mutation)) { | |
760 sg::Connection& connection = graph[*edge]; | |
761 const sg::Node& target = graph[boost::target(*edge, graph)]; | |
762 const sc::Argument& argument = GetCommandArgument(target.id_, | |
763 connection.input_); | |
764 if (!argument.fixed_range_) { | |
765 // mutate weight | |
766 MutateValue(&connection.weight_, | |
767 &mutation_occurred, | |
768 g_minimum_connection_weight); | |
769 // special command inputs and modifier command input arguments | |
770 // have offsets set to zero | |
771 if ((target.type_ != sg::kSpecial) && (argument.name_ != "in")) { | |
772 MutateValue(&connection.offset_, &mutation_occurred, | |
773 0.0, 1.0 - connection.weight_); | |
774 } | |
775 } | |
776 } | |
777 } | |
778 } | |
779 return true; | |
780 } | |
781 | |
782 const std::vector<sc::Argument>& Grammar::CommandArguments(int command) const { | |
783 return commands_[command].Arguments(); | |
784 } | |
785 | |
786 bool Grammar::MutationReplaceCommand(sg::Graph& graph) const { | |
787 // sanity check | |
788 if (boost::num_vertices(graph) <= 1) { | |
789 throw std::runtime_error("sc::Grammar::MutationReplaceCommand - graph has too few nodes"); | |
790 } | |
791 sg::Vertex node_to_replace; | |
792 sg::Node* node_info; | |
793 // find command to replace | |
794 do { | |
795 node_to_replace = boost::random_vertex(graph, bx::RandomEngine()); | |
796 node_info = &graph[node_to_replace]; | |
797 } while (node_info->type_ != sg::kCommand); | |
798 int old_id = node_info->id_; | |
799 // generate new command id | |
800 int new_id; | |
801 int number_of_commands = static_cast<int>(commands_.size()); | |
802 do { | |
803 // new command must match old output and mode | |
804 new_id = stdx::Random(number_of_commands); | |
805 } while (new_id != old_id | |
806 && commands_[new_id].Output() != commands_[old_id].Output() | |
807 && commands_[new_id].Mode() != commands_[old_id].Mode()); | |
808 // new command info | |
809 const std::vector<sc::Argument>& arguments = CommandArguments(new_id); | |
810 std::size_t number_of_arguments = commands_[new_id].Arguments().size(); | |
811 // remove invalid parameters | |
812 // removing an in edge invalidates the iterators, so we need to restart the | |
813 // search each time an edge is removed | |
814 sg::InEdgeIterator edge; | |
815 sg::InEdgeIterator edge_end; | |
816 bool do_it_again; | |
817 do { | |
818 do_it_again = false; | |
819 for (boost::tie(edge, edge_end) = boost::in_edges(node_to_replace, graph); | |
820 edge != edge_end; | |
821 ++edge) { | |
822 int input = graph[*edge].input_; | |
823 // remove input tree if it's for an extraneous parameter | |
824 // or if the argument is a constant | |
825 if (input >= number_of_arguments | |
826 || arguments[input].scaling_mode_ == sc::Argument::kConstant) { | |
827 RemoveSubTree(*edge, graph); | |
828 do_it_again = true; | |
829 // break out of for loop | |
830 break; | |
831 } | |
832 } | |
833 } while (do_it_again); | |
834 // now invalid parameters have been removed, validate the rest | |
835 for (boost::tie(edge, edge_end) = boost::in_edges(node_to_replace, graph); | |
836 edge != edge_end; | |
837 ++edge) { | |
838 // if the new argument is fixed range then correct the connection weight | |
839 int input = graph[*edge].input_; | |
840 if (arguments[input].fixed_range_) { | |
841 GenerateWeightAndOffset(arguments[input], | |
842 node_info->rate_, | |
843 &graph[*edge]); | |
844 } | |
845 } | |
846 // apply new command id | |
847 node_info->id_ = new_id; | |
848 return true; | |
849 } | |
850 | |
851 int Grammar::GetCommandInputID(int command) const { | |
852 const std::vector<sc::Argument>& arguments = commands_[command].Arguments(); | |
853 for (std::size_t i = 0, size = arguments.size(); i < size; i++) { | |
854 if (arguments[i].name_ == "in") { | |
855 return static_cast<int>(i); | |
856 } | |
857 } | |
858 return -1; | |
859 } | |
860 | |
861 const Argument& Grammar::GetCommandArgument(int command_id, | |
862 int argument_id) const { | |
863 return commands_[command_id].Arguments()[argument_id]; | |
864 } | |
865 | |
866 bool Grammar::MutationInsertCommand(sg::Graph& graph) const { | |
867 // sanity check | |
868 if (boost::num_vertices(graph) == 0) { | |
869 throw std::runtime_error("sc::Grammar::MutationInsertCommand - graph is empty"); | |
870 } | |
871 // pick a random edge as insertion point | |
872 sg::Edge edge_to_replace; | |
873 sg::Vertex source_command; | |
874 sg::NodeType source_type; | |
875 do { | |
876 edge_to_replace = boost::random_edge(graph, bx::RandomEngine()); | |
877 source_command = boost::source(edge_to_replace, graph); | |
878 source_type = graph[source_command].type_; | |
879 } while (source_type != sg::kCommand); | |
880 // store target connection info | |
881 sg::Vertex target_command = boost::target(edge_to_replace, graph); | |
882 sg::Connection target_connection = graph[edge_to_replace]; | |
883 // make the new command | |
884 const sg::Node& source_node = graph[source_command]; | |
885 sg::CommandRate rate = source_node.rate_; | |
886 sg::Vertex new_command = CreateCommand(graph, rate, 0, true, false); | |
887 // remove the existing connection | |
888 boost::remove_edge(source_command, target_command, graph); | |
889 // connect the new command to the source | |
890 sg::Connection source_connection(GetCommandInputID(graph[new_command].id_)); | |
891 // generate weighting for new connection | |
892 double weight = stdx::RandomCoefficient(); | |
893 if (rate == sg::kAudioRate) { | |
894 weight *= weight; | |
895 } | |
896 source_connection.weight_ = weight; | |
897 boost::add_edge(source_command, new_command, source_connection, graph); | |
898 // connect new command to the target, use old connection weighting | |
899 boost::add_edge(new_command, target_command, target_connection, graph); | |
900 // success! | |
901 return true; | |
902 } | |
903 | |
904 } // sc namespace |