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