Mercurial > hg > gpsynth
diff 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 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/sc_grammar.cpp Thu Aug 25 11:05:55 2011 +0100 @@ -0,0 +1,904 @@ +// Copyright 2011, Ian Hobson. +// +// This file is part of gpsynth. +// +// gpsynth is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// gpsynth is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with gpsynth in the file COPYING. +// If not, see http://www.gnu.org/licenses/. + +#include "sc_grammar.hpp" + +#include "boost_ex.hpp" +#include "graph_helpers.hpp" +#include "logger.hpp" +#include "statistics.hpp" +#include "std_ex.hpp" + +#include "json/json.h" + +#include "boost/graph/random.hpp" +#include "boost/graph/topological_sort.hpp" + +#include <iostream> +#include <iterator> +#include <fstream> +#include <set> +#include <sstream> + +const int g_maximum_mixer_channels = 3; + +const double g_probability_command_node_command = 0.85; +const double g_probability_command_node_special = 0.15; + +const double g_probability_mod_node_command = 0.25; +const double g_probability_mod_node_special = 0.15; +const double g_probability_mod_node_constant = 0.4; + +const double g_probability_argument_affected = 1.0; + +const double g_probability_input_mutation = 1.0; +const double g_probability_connection_mutation = 1.0; + +const double g_minimum_connection_weight = 0.001; + +namespace { + + double MapCoefficientToRange(double c, const stdx::Range<double>& range) { + return range.minimum_ + (range.Size() * c); + } + + // Returns argument connection weight and offset, scaled to argument input + void GenerateWeightAndOffset(const sc::Argument& argument, + sg::CommandRate rate, + sg::Connection* connection) { + const stdx::Range<double>& scale_range = (rate == sg::kControlRate) + ? argument.range_control_ + : argument.range_; + if (argument.fixed_range_) { + connection->weight_ = scale_range.Size(); + connection->offset_ = scale_range.Minimum(); + } else if (argument.name_ == "in") { + // input connections don't have an offset + double weight = stdx::RandomCoefficient(); + if (rate == sg::kAudioRate) { + // square audio weightings + connection->weight_ = weight * weight; + } + } else { + // generate random weight and offset + double weight; + do { + weight = stdx::RandomCoefficient(); + } while (weight < g_minimum_connection_weight); + double offset = stdx::RandomRange(0.0, 1.0 - weight); + switch (argument.scaling_mode_) { + case sc::Argument::kScalingLinear: + connection->weight_ = MapCoefficientToRange(weight, scale_range); + connection->offset_ = MapCoefficientToRange(offset, scale_range); + break; + + case sc::Argument::kScalingLog: + connection->weight_ = MapCoefficientToRange(weight * weight * weight, + scale_range); + connection->offset_ = MapCoefficientToRange(offset * offset * offset, + scale_range); + break; + + case sc::Argument::kConstant: + throw std::runtime_error("ApplyRandomWeightAndOffset - preset argument provided"); + } + } + } + +sc::Argument ParseArgument(const Json::Value& json) { + sc::Argument arg; + arg.name_ = json.get("name", "").asString(); + if (json.isMember("constant")) { + arg.scaling_mode_ = sc::Argument::kConstant; + arg.constant_value_ = json["constant"].asDouble(); + return arg; + } + if (json.isMember("range")) { + const Json::Value range = json["range"]; + arg.range_.SetMinimum(range[0].asDouble()); + arg.range_.SetMaximum(range[1].asDouble()); + } + if (json.isMember("range_control")) { + const Json::Value range = json["range_control"]; + arg.range_control_.SetMinimum(range[0].asDouble()); + arg.range_control_.SetMaximum(range[1].asDouble()); + } else { + arg.range_control_ = arg.range_; + } + if (json.isMember("fixed_range")) { + arg.fixed_range_ = json["fixed_range"].asBool(); + } + std::string scaling = json.get("scaling", "linear").asString(); + if (scaling == "linear") { + arg.scaling_mode_ = sc::Argument::kScalingLinear; + } else if (scaling == "log") { + arg.scaling_mode_ = sc::Argument::kScalingLog; + } + return arg; +} + +sc::Command ParseCommand(const Json::Value& json, + const std::map<std::string, sc::Argument>& types, + sc::Command::CommandMode command_mode) { + if (json.type() == Json::stringValue) { + return sc::Command(json.asString(), command_mode); + } + // name + std::string name = json["name"].asString(); + // output type + sc::Command::OutputType output_type = sc::Command::kAll; + if (json.isMember("output")) { + std::string output = json["output"].asString(); + if (output == "control") { + output_type = sc::Command::kControl; + } else if (output == "audio") { + output_type = sc::Command::kAudio; + } + } + // arguments + std::vector<sc::Argument> args; + // [in] argument for modifiers + if (command_mode == sc::Command::kModifier) { + args.push_back(sc::Argument("in")); + } + Json::Value json_args = json["args"]; + Json::ValueType arg_type = json_args.type(); + if (arg_type == Json::stringValue) { + // single argument as string + args.push_back(sc::Argument(json_args.asString())); + } else if (arg_type == Json::objectValue) { + // single argument object + if (json_args.isMember("type")) { + args.push_back(stdx::GetFromMap(types, json_args["type"].asString())); + args.back().name_ = json_args["name"].asString(); + } else { + args.push_back(ParseArgument(json_args)); + } + } else { + // array of arguments + for (int i = 0; i < json_args.size(); i++) { + Json::Value arg = json_args[i]; + if (arg.type() == Json::stringValue) { + args.push_back(sc::Argument(arg.asString())); + } else { + if (arg.isMember("type")) { + args.push_back(stdx::GetFromMap(types, arg["type"].asString())); + args.back().name_ = arg["name"].asString(); + } else { + args.push_back(ParseArgument(arg)); + } + } + } + } + // success + return sc::Command(name, command_mode, output_type, args); +} + + +sc::Command ParseSource(const Json::Value& json, + const std::map<std::string, sc::Argument>& types) { + return ParseCommand(json, types, sc::Command::kSource); +} + +sc::Command ParseModifier(const Json::Value& json, + const std::map<std::string, sc::Argument>& types) { + return ParseCommand(json, types, sc::Command::kModifier); +} + +void MutateValue(double* value, bool* mutation_occurred, + double range_min = 0.0, double range_max = 1.0) { + // add gaussian noise to values, low variance + static bx::GaussianGenerator generator = bx::MakeGaussianGenerator(0, 0.125); + double new_value = stdx::Clamp(*value + generator(), range_min, range_max); + if (*value != new_value) { + *value = new_value; + *mutation_occurred = true; + } +} + +} // namespace + + + +namespace sc { + +Grammar::Grammar(const std::string& json_data) +: max_depth_(3) +{ + ParseJSON(json_data); +} + +void Grammar::ParseJSON(const std::string& json_data) { + commands_.clear(); + Json::Reader reader; + Json::Value root; + if (!reader.parse(json_data, root)) { + std::stringstream message; + message << "Grammar::Parse: Failed to parse data\n" + << reader.getFormattedErrorMessages(); + throw std::runtime_error(message.str()); + } + try { + // argument types + // stores predefined argument types while parsing grammar + std::map<std::string, Argument> argument_types; + const Json::Value& args = root["arg-types"]; + for (int i = 0; i < args.size(); i++) { + const Json::Value& arg = args[i]; + argument_types[arg["type"].asString()] = ParseArgument(arg); + } + // sound sources + const Json::Value& sources = root["sources"]; + for (int i = 0; i < sources.size(); i++) { + commands_.push_back(ParseSource(sources[i], argument_types)); + } + // modifiers + const Json::Value& modifiers = root["modifiers"]; + for (int i = 0; i < modifiers.size(); i++) { + commands_.push_back(ParseModifier(modifiers[i], argument_types)); + } + } catch (const std::exception& e) { + std::stringstream message; + message << "Grammar::Parse: " << e.what(); + throw std::runtime_error(message.str()); + } +} + +int Grammar::RandomCommand(sg::CommandRate rate, + int tree_depth, + bool must_be_modifier /* = false */) const { + Command::OutputType output; + Command::CommandMode mode; + CommandID command = -1; + CommandID number_of_commands = commands_.size(); + // if we're at or one away from maximum depth then the command needs to + // be a source, headroom of 1 for some parameter control + bool must_be_source = (tree_depth >= (max_depth_ - 1)); + do { + command = stdx::Random(number_of_commands); + output = commands_[command].Output(); + if (rate == sg::kAudioRate) { + do { + command = stdx::Random(number_of_commands); + output = commands_[command].Output(); + } while (!(output == Command::kAll || output == Command::kAudio)); + } else { + do { + command = stdx::Random(number_of_commands); + output = commands_[command].Output(); + } while (!(output == Command::kAll || output == Command::kControl)); + } + mode = commands_[command].Mode(); + } while ((must_be_source && (mode != Command::kSource)) + || (must_be_modifier && (mode != Command::kModifier))); + return static_cast<int>(command); +} + +sg::Vertex Grammar::CreateCommand(sg::Graph &graph, + sg::CommandRate rate, + int depth, + bool must_be_modifier /* = false */, + bool make_modifier_input /* = true */) const { + // select a random command for the node + int command_id = RandomCommand(rate, depth, must_be_modifier); + // create the node and add it to the graph + sg::Node command_info(command_id, sg::kCommand, rate); + sg::Vertex vertex = boost::add_vertex(command_info, graph); + // a modifier command has to have further nodes added to its main input + const Command& command = commands_[command_id]; + for (int i = 0; i < command.Arguments().size(); i++) { + const Argument& argument = command.GetArgument(i); + if (argument.name_ == "in") { + if (make_modifier_input) { + // modifier input must be given a command tree + sg::Vertex tree = RandomTree(graph, rate, kTreeMode_Command, depth + 1); + sg::Connection connection(i); + GenerateWeightAndOffset(argument, rate, &connection); + boost::add_edge(tree, vertex, connection, graph); + } + } else if (argument.scaling_mode_ == sc::Argument::kConstant) { + // don't attach inputs to predefined arguments + continue; + } else if (stdx::Chance(g_probability_argument_affected)) { + // randomly create input for command arguments + sg::Vertex tree = RandomTree(graph, + sg::kControlRate, + kTreeMode_Mod, + depth + 1); + sg::Connection connection(i); + GenerateWeightAndOffset(argument, rate, &connection); + boost::add_edge(tree, vertex, connection, graph); + } + } + return vertex; +} + +sg::Vertex Grammar::RandomTree(sg::Graph &graph, + sg::CommandRate rate, + TreeMode tree_mode, + int depth) const { + enum NodeType { + kCommand, + kSpecial, + kConstant, + kParameter + }; + static stats::ProbabilitySelector node_type_selector; + if (!node_type_selector.Initialized()) { + std::vector<double> probabilities(3); + probabilities[kCommand] = g_probability_mod_node_command; + probabilities[kSpecial] = g_probability_mod_node_special; + probabilities[kConstant] = g_probability_mod_node_constant; + node_type_selector.SetProbabilities(probabilities); + } + // establish the type of node to create + int node_type; + if (depth >= max_depth_) { + if (tree_mode == kTreeMode_Command) { + // if we're at maximum depth in command mode then create a command + node_type = kCommand; + } else { + // in mod mode at maximum depth pick either a constant or parameter + if (stdx::Chance(g_probability_mod_node_constant)) { + node_type = kConstant; + } else { + node_type = kParameter; + } + } + } else { + if (tree_mode == kTreeMode_Command) { + if (stdx::Chance(g_probability_command_node_command)) { + node_type = kCommand; + } else { + node_type = kSpecial; + } + } else { + // mod mode below max depth, any node type is ok + node_type = static_cast<NodeType>(node_type_selector()); + } + } + if (node_type == kCommand) { + return CreateCommand(graph, rate, depth); + } else if (node_type == kSpecial) { + int command = stdx::Random(Command::kNumberOfSpecialCommands); + return SpecialCommand(command, graph, rate, tree_mode, depth + 1); + } else if (node_type == kConstant) { + return boost::add_vertex(sg::Node(stdx::RandomCoefficient()), graph); + } else if (node_type == kParameter) { + return boost::add_vertex(sg::Node(RandomParameter(graph), sg::kParameter), + graph); + } + throw std::runtime_error("Grammar::RandomTree - unknown node type"); +} + +int Grammar::RandomParameter(sg::Graph& graph) const { + // as a simple rule, randomly pick an existing parameter, or if chosen + // parameter is greater than # of parameters, then add a new one + std::vector<double>& parameters = graph[boost::graph_bundle].parameters_; + int number_of_parameters = static_cast<int>(parameters.size()); + // parameter ids start at 1 + int parameter = stdx::RandomRangeInt(1, number_of_parameters + 1); + // bump parameter count if we've got a new parameter + if (parameter > number_of_parameters) { + // store a random value for the new parameter + parameters.push_back(stdx::RandomCoefficient()); + } + return parameter; +} + +void Grammar::AddTreeToSpecialCommand(sg::Vertex command_node, + sg::Graph& graph, + int input_id, + TreeMode tree_mode, + sg::CommandRate command_rate, + bool* constant_added, + std::set<int>* parameters, + int depth) const { + bool success = false; + while (!success) { + sg::Vertex tree; + // first channel or audio command should enforce a command source + if (input_id == 0) { + tree = RandomTree(graph, command_rate, kTreeMode_Command, depth + 1); + } else { + tree = RandomTree(graph, command_rate, tree_mode, depth + 1); + } + sg::NodeType tree_type = graph[tree].type_; + if (tree_type == sg::kConstant) { + // we only need one constant per mixer + if (*constant_added) { + RemoveSubTree(tree, graph); + continue; + } + *constant_added = true; + } else if (tree_type == sg::kParameter) { + // we don't want duplicated parameters for special command inputs + if (parameters->find(graph[tree].id_) != parameters->end()) { + RemoveSubTree(tree, graph); + continue; + } + // store the parameter id + parameters->insert(graph[tree].id_); + } + // apply weighting + double weight = stdx::RandomCoefficient(); + if (command_rate == sg::kAudioRate) { + // scale audio to curve + weight *= weight; + } + // make the connection to the generated tree + boost::add_edge(tree, command_node, sg::Connection(weight), graph); + success = true; + } +} + +sg::Vertex Grammar::SpecialCommand(int command_id, + sg::Graph& graph, + sg::CommandRate command_rate, + TreeMode tree_mode, + int depth /* = 0 */, + int minimum_channels /* = 2 */) const { + // create the special command node + sg::Node command_info(command_id, sg::kSpecial, command_rate); + sg::Vertex mixer = boost::add_vertex(command_info, graph); + // pick a random number of channels to create for this command + int channels = stdx::RandomRangeInt(minimum_channels, + g_maximum_mixer_channels); + bool constant_added = false; + std::set<int> parameters; + // add a tree for each channel + for (int i = 0; i < channels; i++) { + AddTreeToSpecialCommand(mixer, + graph, + i, + tree_mode, + command_rate, + &constant_added, + ¶meters, + depth); + } + return mixer; +} + +void Grammar::RandomGraph(sg::Graph& graph) const { + graph.clear(); + // root output node + SpecialCommand(sc::Command::kMixer, graph, sg::kAudioRate, kTreeMode_Command); +} + +void Grammar::PickCrossoverNodes(const sg::Graph& parent_1, + const sg::Graph& parent_2, + sg::Vertex* crossover_node_1, + sg::Vertex* crossover_node_2) const { + sg::Vertex source_node_1; + sg::Vertex source_node_2; + bool nodes_found = false; + do { + // pick random edges and their nodes + source_node_1 = boost::random_vertex(parent_1, bx::RandomEngine()); + source_node_2 = boost::random_vertex(parent_2, bx::RandomEngine()); + // avoid the root output node + if ((source_node_1 == 0) || (source_node_2 == 0)) { + continue; + } + // check it's ok to swap these nodes + const sg::Node& info_1 = parent_1[source_node_1]; + const sg::Node& info_2 = parent_2[source_node_2]; + // command rates need to match + if (info_1.rate_ != info_2.rate_) { + continue; + } + // we don't want to swap two constants + if (info_1.type_ == sg::kConstant && info_2.type_ == sg::kConstant) { + continue; + } + // we don't want to swap parameter inputs with same parameter id + if ((info_1.type_ == sg::kParameter) + && (info_2.type_ == sg::kParameter) + && (info_1.id_ == info_2.id_)) { + continue; + } + // check that we're not replacing a control modifer's input with non-command + sg::Edge connection_1 = *(boost::out_edges(source_node_1, parent_1).first); + sg::Edge connection_2 = *(boost::out_edges(source_node_2, parent_2).first); + sg::Vertex target_node_1 = boost::target(connection_1, parent_1); + sg::Vertex target_node_2 = boost::target(connection_2, parent_2); + const sg::Node& info_target_1 = parent_1[target_node_1]; + const sg::Node& info_target_2 = parent_2[target_node_2]; + if ((info_target_1.type_ == sg::kCommand) + || (info_target_1.type_ == sg::kSpecial)) { + const sc::Command& target_command_1 = commands_[info_target_1.id_]; + const std::vector<Argument>& arguments_1 = target_command_1.Arguments(); + const sg::Connection& info_connection_1 = parent_1[connection_1]; + if (arguments_1[info_connection_1.input_].name_ == "in") { + // target input is a modifier's 'in' argument, only allow commands to + // be swapped in + if (!(info_2.type_ == sg::kCommand || info_2.type_ == sg::kSpecial)) { + continue; + } + } + } + if ((info_target_2.type_ == sg::kCommand) + || (info_target_2.type_ == sg::kSpecial)) { + const sc::Command& target_command_2 = commands_[info_target_2.id_]; + const std::vector<Argument>& arguments_2 = target_command_2.Arguments(); + const sg::Connection& info_connection_2 = parent_1[connection_2]; + if (arguments_2[info_connection_2.input_].name_ == "in") { + // target input is a modifier's 'in' argument, only allow commands to + // be swapped in + if (!(info_1.type_ == sg::kCommand || info_1.type_ == sg::kSpecial)) { + continue; + } + } + } + nodes_found = true; + } while (!nodes_found); + *crossover_node_1 = source_node_1; + *crossover_node_2 = source_node_2; +} + +bool Grammar::MutationReplaceSubTree(sg::Graph& graph) const { + // sanity check + if (boost::num_vertices(graph) <= 1) { + throw std::runtime_error("sc::Grammar::MutationReplaceSubTree - graph has too few nodes"); + } + // pick node to replace + sg::Vertex node_to_replace; + do { + node_to_replace = boost::random_vertex(graph, bx::RandomEngine()); + } while (node_to_replace == 0); + sg::CommandRate new_tree_rate = graph[node_to_replace].rate_; + sg::Edge connection = *(boost::out_edges(node_to_replace, graph).first); + sg::Vertex connection_target = boost::target(connection, graph); + // store the connection info contained in the connection edge + sg::Connection connection_info = graph[connection]; + const sg::Node& target_info = graph[connection_target]; + // determine new tree mode + TreeMode new_tree_mode = kTreeMode_Mod; + if (new_tree_rate == sg::kAudioRate) { + // audio rate nodes always require command tree replacements + new_tree_mode = kTreeMode_Command; + } else if (target_info.type_ == sg::kCommand) { + // if the connection target is a modifier + // and the connection input is 0 then a command tree is needed + if (commands_[target_info.id_].Mode() == Command::kModifier) { + if (connection_info.input_ == 0) { + new_tree_mode = kTreeMode_Command; + } + } + } else if (target_info.type_ == sg::kSpecial) { + // first input to special commands is a command tree + if (connection_info.input_ == 0) { + new_tree_mode = kTreeMode_Command; + } + } + // remove the sub tree + RemoveSubTree(connection, graph); + // generate new tree + sg::Vertex new_tree = RandomTree(graph, new_tree_rate, new_tree_mode, 0); + // connect tree to graph + boost::add_edge(new_tree, connection_target, connection_info, graph); + return true; +} + + + +bool Grammar::MutationAddSubTree(sg::Graph& graph) const { + // sanity check + if (graph[0].type_ != sg::kSpecial) { + throw std::runtime_error("sc::Grammar::MutationAddSubTree - missing root mixer node"); + } + bool mutation_occurred = false; + while (!mutation_occurred) { + // pick a command or special node to add a tree input to + sg::Vertex node; + sg::NodeType node_type; + do { + node = boost::random_vertex(graph, bx::RandomEngine()); + node_type = graph[node].type_; + } while (!(node_type == sg::kCommand || node_type == sg::kSpecial)); + if (node_type == sg::kCommand) { + int node_id = graph[node].id_; + const std::vector<Argument>& arguments = commands_[node_id].Arguments(); + std::size_t number_of_arguments = arguments.size(); + sg::InEdgeIterator edge; + sg::InEdgeIterator edge_end; + std::vector<bool> used_arguments(arguments.size(), false); + for (boost::tie(edge, edge_end) = boost::in_edges(node, graph); + edge != edge_end; + ++edge) { + used_arguments[graph[*edge].input_] = true; + } + // check that there is an unused argument available for this command + bool argument_available = false; + for (std::size_t i = 0; i < number_of_arguments; i++) { + if (used_arguments[i] == false) { + // we don't want to attach a tree to a preset argument + if (arguments[i].scaling_mode_ != Argument::kConstant) { + argument_available = true; + break; + } + } + } + if (argument_available) { + // find a random argument to insert a tree into + std::size_t argument; + do { + argument = stdx::Random(arguments.size()); + } while (used_arguments[argument] == true + || arguments[argument].scaling_mode_ == Argument::kConstant); + // add a tree and connect to the unused argument + sg::Vertex tree = RandomTree(graph, + sg::kControlRate, + kTreeMode_Mod, + 0); + sg::Connection connection(static_cast<int>(argument)); + GenerateWeightAndOffset(arguments[argument], + sg::kControlRate, + &connection); + boost::add_edge(tree, node, connection, graph); + mutation_occurred = true; + } + } else if (node_type == sg::kSpecial) { + int node_inputs = static_cast<int>(boost::in_degree(node, graph)); + // we need to check the special command's inputs to avoid adding redundant + // constants or parameters + bool constant_added = false; + std::set<int> parameters; + sg::InEdgeIterator edge; + sg::InEdgeIterator edge_end; + for (boost::tie(edge, edge_end) = boost::in_edges(node, graph); + edge != edge_end; + ++edge) { + const sg::Node& source = graph[boost::source(*edge, graph)]; + if (source.type_ == sg::kConstant) { + constant_added = true; + } + if (source.type_ == sg::kParameter) { + parameters.insert(source.id_); + } + } + // select tree mode based on command rate + TreeMode tree_mode; + if (graph[node].rate_ == sg::kAudioRate) { + tree_mode = kTreeMode_Command; + } else { + tree_mode = kTreeMode_Mod; + } + // add the tree + AddTreeToSpecialCommand(node, + graph, + node_inputs, + tree_mode, + graph[node].rate_, + &constant_added, + ¶meters, + 0); + mutation_occurred = true; + } + } + return true; +} + +bool Grammar::MutationModifyInputs(sg::Graph& graph) const { + // sanity check + if (graph[boost::graph_bundle].parameters_.size() == 0) { + sg::VertexIterator vertex; + sg::VertexIterator vertex_end; + bool constant_found = false; + for (boost::tie(vertex, vertex_end) = boost::vertices(graph); + vertex != vertex_end; + ++vertex) { + sg::Node& node = graph[*vertex]; + if (node.type_ == sg::kConstant) { + constant_found = true; + break; + } + } + if (!constant_found) { + // no parameters, no constants, nothing to do.. + return false; + } + } + // make a random generator with a narrow guassian distribution + bx::GaussianGenerator gaussian = bx::MakeGaussianGenerator(0, 0.125); + bool mutation_occurred = false; + while (!mutation_occurred) { + // mutate parameters + foreach (double& parameter, graph[boost::graph_bundle].parameters_) { + if (stdx::Chance(g_probability_input_mutation)) { + MutateValue(¶meter, &mutation_occurred); + } + } + // mutate constants + sg::VertexIterator vertex; + sg::VertexIterator vertex_end; + for (boost::tie(vertex, vertex_end) = boost::vertices(graph); + vertex != vertex_end; + ++vertex) { + sg::Node& node = graph[*vertex]; + if (node.type_ == sg::kConstant) { + // constant value + if (stdx::Chance(g_probability_input_mutation)) { + MutateValue(&node.constant_value_, &mutation_occurred); + } + } + } + } + return true; +} + +bool Grammar::MutationModifyConnections(sg::Graph& graph) const { + // sanity check + if (boost::num_edges(graph) < 1) { + throw std::runtime_error("sc::Grammar::MutationModifyConnections - graph has no connections"); + } + bx::GaussianGenerator gaussian = bx::MakeGaussianGenerator(0, 0.125); + bool mutation_occurred = false; + while (!mutation_occurred) { + sg::EdgeIterator edge; + sg::EdgeIterator edge_end; + for (boost::tie(edge, edge_end) = boost::edges(graph); + edge != edge_end; + ++edge) { + if (stdx::Chance(g_probability_connection_mutation)) { + sg::Connection& connection = graph[*edge]; + const sg::Node& target = graph[boost::target(*edge, graph)]; + const sc::Argument& argument = GetCommandArgument(target.id_, + connection.input_); + if (!argument.fixed_range_) { + // mutate weight + MutateValue(&connection.weight_, + &mutation_occurred, + g_minimum_connection_weight); + // special command inputs and modifier command input arguments + // have offsets set to zero + if ((target.type_ != sg::kSpecial) && (argument.name_ != "in")) { + MutateValue(&connection.offset_, &mutation_occurred, + 0.0, 1.0 - connection.weight_); + } + } + } + } + } + return true; +} + +const std::vector<sc::Argument>& Grammar::CommandArguments(int command) const { + return commands_[command].Arguments(); +} + +bool Grammar::MutationReplaceCommand(sg::Graph& graph) const { + // sanity check + if (boost::num_vertices(graph) <= 1) { + throw std::runtime_error("sc::Grammar::MutationReplaceCommand - graph has too few nodes"); + } + sg::Vertex node_to_replace; + sg::Node* node_info; + // find command to replace + do { + node_to_replace = boost::random_vertex(graph, bx::RandomEngine()); + node_info = &graph[node_to_replace]; + } while (node_info->type_ != sg::kCommand); + int old_id = node_info->id_; + // generate new command id + int new_id; + int number_of_commands = static_cast<int>(commands_.size()); + do { + // new command must match old output and mode + new_id = stdx::Random(number_of_commands); + } while (new_id != old_id + && commands_[new_id].Output() != commands_[old_id].Output() + && commands_[new_id].Mode() != commands_[old_id].Mode()); + // new command info + const std::vector<sc::Argument>& arguments = CommandArguments(new_id); + std::size_t number_of_arguments = commands_[new_id].Arguments().size(); + // remove invalid parameters + // removing an in edge invalidates the iterators, so we need to restart the + // search each time an edge is removed + sg::InEdgeIterator edge; + sg::InEdgeIterator edge_end; + bool do_it_again; + do { + do_it_again = false; + for (boost::tie(edge, edge_end) = boost::in_edges(node_to_replace, graph); + edge != edge_end; + ++edge) { + int input = graph[*edge].input_; + // remove input tree if it's for an extraneous parameter + // or if the argument is a constant + if (input >= number_of_arguments + || arguments[input].scaling_mode_ == sc::Argument::kConstant) { + RemoveSubTree(*edge, graph); + do_it_again = true; + // break out of for loop + break; + } + } + } while (do_it_again); + // now invalid parameters have been removed, validate the rest + for (boost::tie(edge, edge_end) = boost::in_edges(node_to_replace, graph); + edge != edge_end; + ++edge) { + // if the new argument is fixed range then correct the connection weight + int input = graph[*edge].input_; + if (arguments[input].fixed_range_) { + GenerateWeightAndOffset(arguments[input], + node_info->rate_, + &graph[*edge]); + } + } + // apply new command id + node_info->id_ = new_id; + return true; +} + +int Grammar::GetCommandInputID(int command) const { + const std::vector<sc::Argument>& arguments = commands_[command].Arguments(); + for (std::size_t i = 0, size = arguments.size(); i < size; i++) { + if (arguments[i].name_ == "in") { + return static_cast<int>(i); + } + } + return -1; +} + +const Argument& Grammar::GetCommandArgument(int command_id, + int argument_id) const { + return commands_[command_id].Arguments()[argument_id]; +} + +bool Grammar::MutationInsertCommand(sg::Graph& graph) const { + // sanity check + if (boost::num_vertices(graph) == 0) { + throw std::runtime_error("sc::Grammar::MutationInsertCommand - graph is empty"); + } + // pick a random edge as insertion point + sg::Edge edge_to_replace; + sg::Vertex source_command; + sg::NodeType source_type; + do { + edge_to_replace = boost::random_edge(graph, bx::RandomEngine()); + source_command = boost::source(edge_to_replace, graph); + source_type = graph[source_command].type_; + } while (source_type != sg::kCommand); + // store target connection info + sg::Vertex target_command = boost::target(edge_to_replace, graph); + sg::Connection target_connection = graph[edge_to_replace]; + // make the new command + const sg::Node& source_node = graph[source_command]; + sg::CommandRate rate = source_node.rate_; + sg::Vertex new_command = CreateCommand(graph, rate, 0, true, false); + // remove the existing connection + boost::remove_edge(source_command, target_command, graph); + // connect the new command to the source + sg::Connection source_connection(GetCommandInputID(graph[new_command].id_)); + // generate weighting for new connection + double weight = stdx::RandomCoefficient(); + if (rate == sg::kAudioRate) { + weight *= weight; + } + source_connection.weight_ = weight; + boost::add_edge(source_command, new_command, source_connection, graph); + // connect new command to the target, use old connection weighting + boost::add_edge(new_command, target_command, target_connection, graph); + // success! + return true; +} + +} // sc namespace