diff options
Diffstat (limited to 'src/engine/Machine.cpp')
-rw-r--r-- | src/engine/Machine.cpp | 389 |
1 files changed, 389 insertions, 0 deletions
diff --git a/src/engine/Machine.cpp b/src/engine/Machine.cpp new file mode 100644 index 0000000..b144b6f --- /dev/null +++ b/src/engine/Machine.cpp @@ -0,0 +1,389 @@ +/* + This file is part of Machina. + Copyright 2007-2013 David Robillard <http://drobilla.net> + + Machina 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 any later version. + + Machina 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 Machina. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include <cstdlib> +#include <map> + +#include "sord/sordmm.hpp" + +#include "machina/Atom.hpp" +#include "machina/Context.hpp" +#include "machina/Machine.hpp" +#include "machina/URIs.hpp" +#include "machina/Updates.hpp" +#include "machina/types.hpp" + +#include "Edge.hpp" +#include "Node.hpp" +#include "LearnRequest.hpp" +#include "MidiAction.hpp" + +using namespace std; +using namespace Raul; + +namespace machina { + +Machine::Machine(TimeUnit unit) + : fitness(0.0) + , _initial_node(new Node(TimeStamp(unit, 0, 0), true)) + , _active_nodes(MAX_ACTIVE_NODES, SPtr<Node>()) + , _time(unit, 0, 0) + , _is_finished(false) +{ + _nodes.insert(_initial_node); +} + +void +Machine::assign(const Machine& copy) +{ + std::map< SPtr<Node>, SPtr<Node> > replacements; + + replacements[copy.initial_node()] = _initial_node; + + for (const auto& n : copy._nodes) { + if (!n->is_initial()) { + SPtr<machina::Node> node(new machina::Node(*n.get())); + _nodes.insert(node); + replacements[n] = node; + } + } + + for (const auto& n : _nodes) { + for (const auto& e : n->edges()) { + e->set_tail(n); + e->set_head(replacements[e->head()]); + } + } +} + +Machine::Machine(const Machine& copy) + : Stateful() // don't copy RDF ID + , fitness(0.0) + , _initial_node(new Node(TimeStamp(copy.time().unit(), 0, 0), true)) + , _active_nodes(MAX_ACTIVE_NODES, SPtr<Node>()) + , _time(copy.time()) + , _is_finished(false) +{ + _nodes.insert(_initial_node); + assign(copy); +} + +Machine& +Machine::operator=(const Machine& copy) +{ + if (© == this) { + return *this; + } + + fitness = copy.fitness; + + _active_nodes = std::vector< SPtr<Node> >(MAX_ACTIVE_NODES, SPtr<Node>()); + _is_finished = false; + _time = copy._time; + _pending_learn = SPtr<LearnRequest>(); + + _nodes.clear(); + _nodes.insert(_initial_node); + assign(copy); + + return *this; +} + +bool +Machine::operator==(const Machine& rhs) const +{ + return false; +} + +void +Machine::merge(const Machine& machine) +{ + for (const auto& m : machine.nodes()) { + if (m->is_initial()) { + for (const auto& e : m->edges()) { + e->set_tail(_initial_node); + _initial_node->edges().insert(e); + } + } else { + _nodes.insert(m); + } + } +} + +/** Always returns a node, unless there are none */ +SPtr<Node> +Machine::random_node() +{ + if (_nodes.empty()) { + return SPtr<Node>(); + } + + size_t i = rand() % _nodes.size(); + + // FIXME: O(n) worst case :( + for (Nodes::const_iterator n = _nodes.begin(); n != _nodes.end(); ++n, + --i) { + if (i == 0) { + return *n; + } + } + + return SPtr<Node>(); +} + +/** May return NULL even if edges exist (with low probability) */ +SPtr<Edge> +Machine::random_edge() +{ + SPtr<Node> tail = random_node(); + + for (size_t i = 0; i < _nodes.size() && tail->edges().empty(); ++i) { + tail = random_node(); + } + + return tail ? tail->random_edge() : SPtr<Edge>(); +} + +void +Machine::add_node(SPtr<Node> node) +{ + _nodes.insert(node); +} + +void +Machine::remove_node(SPtr<Node> node) +{ + _nodes.erase(node); + + for (Nodes::const_iterator n = _nodes.begin(); n != _nodes.end(); ++n) { + (*n)->remove_edge_to(node); + } +} + +void +Machine::reset(MIDISink* sink, Raul::TimeStamp time) +{ + if (!_is_finished) { + for (auto& n : _active_nodes) { + if (n) { + n->exit(sink, time); + n.reset(); + } + } + } + + _time = TimeStamp(_time.unit(), 0, 0); + _is_finished = false; +} + +SPtr<Node> +Machine::earliest_node() const +{ + SPtr<Node> earliest; + + for (const auto& n : _active_nodes) { + if (n && (!earliest || n->exit_time() < earliest->exit_time())) { + earliest = n; + } + } + + return earliest; +} + +bool +Machine::enter_node(Context& context, + SPtr<Node> node, + SPtr<Raul::RingBuffer> updates) +{ + assert(!node->is_active()); + assert(_active_nodes.size() == MAX_ACTIVE_NODES); + + /* FIXME: Would be best to use the MIDI note here as a hash key, at least + * while all actions are still MIDI notes... */ + size_t index = (rand() % MAX_ACTIVE_NODES); + for (size_t i = 0; i < MAX_ACTIVE_NODES; ++i) { + if (!_active_nodes[index]) { + node->enter(context.sink(), _time); + _active_nodes[index] = node; + + write_set(updates, + node->id(), + URIs::instance().machina_active, + context.forge().make(true)); + return true; + } + index = (index + 1) % MAX_ACTIVE_NODES; + } + + // If we get here, ran out of active node spots. Don't enter node + return false; +} + +void +Machine::exit_node(Context& context, + SPtr<Node> node, + SPtr<Raul::RingBuffer> updates) +{ + // Exit node + node->exit(context.sink(), _time); + + // Notify UI + write_set(updates, + node->id(), + URIs::instance().machina_active, + context.forge().make(false)); + + // Remove node from _active_nodes + for (auto& n : _active_nodes) { + if (n == node) { + n.reset(); + } + } + + // Activate successors + if (node->is_selector()) { + const double rand_normal = rand() / (double)RAND_MAX; // [0, 1] + double range_min = 0; + + for (const auto& e : node->edges()) { + if (!e->head()->is_active() + && rand_normal > range_min + && rand_normal < range_min + e->probability()) { + + enter_node(context, e->head(), updates); + break; + + } else { + range_min += e->probability(); + } + } + } else { + for (const auto& e : node->edges()) { + const double rand_normal = rand() / (double)RAND_MAX; // [0, 1] + if (rand_normal <= e->probability()) { + SPtr<Node> head = e->head(); + + if (!head->is_active()) { + enter_node(context, head, updates); + } + } + } + } +} + +uint32_t +Machine::run(Context& context, SPtr<Raul::RingBuffer> updates) +{ + if (_is_finished) { + return 0; + } + + const Raul::TimeSlice& time = context.time(); + + const TimeStamp end_frames = (time.start_ticks() + time.length_ticks()); + const TimeStamp end_beats = time.ticks_to_beats(end_frames); + + if (_time.is_zero()) { // Initial run + // Exit any active nodes + for (auto& n : _active_nodes) { + if (n && n->is_active()) { + n->exit(context.sink(), _time); + write_set(updates, + n->id(), + URIs::instance().machina_active, + context.forge().make(false)); + } + n.reset(); + } + + // Enter initial node + enter_node(context, _initial_node, updates); + + if (_initial_node->edges().empty()) { // Nowhere to go, exit + _is_finished = true; + return 0; + } + } + + while (true) { + SPtr<Node> earliest = earliest_node(); + if (!earliest) { + // No more active states, machine is finished + _is_finished = true; + break; + } + + const TimeStamp exit_time = earliest->exit_time(); + if (time.beats_to_ticks(exit_time) < end_frames) { + // Earliest active state ends this cycle, exit it + _time = earliest->exit_time(); + exit_node(context, earliest, updates); + + } else { + // Earliest active state ends in the future, done this cycle + _time = end_beats; + break; + } + + } + + return time.beats_to_ticks(_time).ticks() - time.start_ticks().ticks(); +} + +void +Machine::learn(SPtr<Raul::Maid> maid, SPtr<Node> node) +{ + _pending_learn = LearnRequest::create(maid, node); +} + +void +Machine::write_state(Sord::Model& model) +{ + using namespace Raul; + + model.add_statement(model.base_uri(), + Sord::URI(model.world(), MACHINA_URI_RDF "type"), + Sord::URI(model.world(), MACHINA_NS_Machine)); + + for (Nodes::const_iterator n = _nodes.begin(); n != _nodes.end(); ++n) { + (*n)->write_state(model); + + if ((*n)->is_initial()) { + model.add_statement(model.base_uri(), + Sord::URI(model.world(), MACHINA_NS_start), + (*n)->rdf_id(model.world())); + } else { + model.add_statement(model.base_uri(), + Sord::URI(model.world(), MACHINA_NS_node), + (*n)->rdf_id(model.world())); + } + } + + for (Nodes::const_iterator n = _nodes.begin(); n != _nodes.end(); ++n) { + for (Node::Edges::const_iterator e = (*n)->edges().begin(); + e != (*n)->edges().end(); ++e) { + + (*e)->write_state(model); + + model.add_statement(model.base_uri(), + Sord::URI(model.world(), MACHINA_NS_arc), + (*e)->rdf_id(model.world())); + } + + } +} + +} // namespace machina |