/* This file is part of Machina. * Copyright 2007-2011 David Robillard * * 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 * (at your option) 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 . */ #include #include #include "raul/Atom.hpp" #include "raul/SharedPtr.hpp" #include "sord/sordmm.hpp" #include "machina/Machine.hpp" #include "machina/Updates.hpp" #include "machina/URIs.hpp" #include "machina/Context.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) : _active_nodes(MAX_ACTIVE_NODES, SharedPtr()) , _time(unit, 0, 0) , _is_activated(false) , _is_finished(false) {} /** Copy a Machine. * * Creates a deep copy which is the 'same' machine, but with * fresh state (deactivated, rewound) */ Machine::Machine(const Machine& copy) : Stateful() // don't copy RDF ID , _active_nodes(MAX_ACTIVE_NODES, SharedPtr()) , _time(copy.time()) , _is_activated(false) , _is_finished(false) { std::map< SharedPtr, SharedPtr > replacements; for (Nodes::const_iterator n = copy._nodes.begin(); n != copy._nodes.end(); ++n) { SharedPtr node(new Machina::Node(*n->get())); _nodes.push_back(node); replacements[*n] = node; } 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)->set_tail(*n); (*e)->set_head(replacements[(*e)->head()]); assert((*e)->head()); } } } Machine& Machine::operator=(const Machine& other) { _active_nodes = std::vector< SharedPtr >(MAX_ACTIVE_NODES, SharedPtr()); _is_activated = false; _is_finished = false; _time = other._time; _pending_learn = SharedPtr(); _nodes.clear(); map< SharedPtr, SharedPtr > replacements; for (Nodes::const_iterator n = other._nodes.begin(); n != other._nodes.end(); ++n) { SharedPtr node(new Machina::Node(*n->get())); _nodes.push_back(node); replacements[*n] = node; } 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)->set_tail(*n); (*e)->set_head(replacements[(*e)->head()]); assert((*e)->head()); } } return *this; } /** Always returns a node, unless there are none */ SharedPtr Machine::random_node() { if (_nodes.empty()) { return SharedPtr(); } 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 SharedPtr(); } /** May return NULL even if edges exist (with low probability) */ SharedPtr Machine::random_edge() { SharedPtr tail = random_node(); for (size_t i = 0; i < _nodes.size() && tail->edges().empty(); ++i) { tail = random_node(); } return tail ? tail->random_edge() : SharedPtr(); } void Machine::add_node(SharedPtr node) { assert(std::find(_nodes.begin(), _nodes.end(), node) == _nodes.end()); _nodes.push_back(node); } void Machine::remove_node(SharedPtr node) { _nodes.erase(std::find(_nodes.begin(), _nodes.end(), node)); for (Nodes::const_iterator n = _nodes.begin(); n != _nodes.end(); ++n) { (*n)->remove_edge_to(node); } } /** Exit all active states and reset time to 0. */ void Machine::reset(MIDISink* sink, Raul::TimeStamp time) { if (!_is_finished) { for (Nodes::const_iterator n = _nodes.begin(); n != _nodes.end(); ++n) { SharedPtr node = (*n); if (node->is_active()) { node->exit(sink, time); } assert(!node->is_active()); } for (size_t i = 0; i < MAX_ACTIVE_NODES; ++i) { _active_nodes.at(i).reset(); } } _time = TimeStamp(_time.unit(), 0, 0); _is_finished = false; } /** Return the active Node with the earliest exit time. */ SharedPtr Machine::earliest_node() const { SharedPtr earliest; for (size_t i = 0; i < MAX_ACTIVE_NODES; ++i) { SharedPtr node = _active_nodes.at(i); if (node) { if (!node->is_active()) { std::cerr << "Inactive node in active node list" << std::endl; continue; } if (!earliest || (node->exit_time() < earliest->exit_time()) ) { earliest = node; } } } return earliest; } /** Enter a state at the current _time. * * Returns true if node was entered, or false if the maximum active nodes has been reached. */ bool Machine::enter_node(Context& context, SharedPtr node, SharedPtr updates) { assert(!node->is_active()); /* 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.at(index) == NULL) { node->enter(context.sink(), _time); assert(node->is_active()); _active_nodes.at(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; } /** Exit an active node at the current _time. */ void Machine::exit_node(Context& context, SharedPtr node, SharedPtr updates) { node->exit(context.sink(), _time); write_set(updates, node->id(), URIs::instance().machina_active, context.forge().make(false)); assert(!node->is_active()); for (size_t i = 0; i < MAX_ACTIVE_NODES; ++i) { if (_active_nodes.at(i) == node) { _active_nodes.at(i).reset(); } } // Activate successors to this node // (that aren't aready active right now) if (node->is_selector()) { const double rand_normal = rand() / (double)RAND_MAX; // [0, 1] double range_min = 0; for (Node::Edges::const_iterator s = node->edges().begin(); s != node->edges().end(); ++s) { if (!(*s)->head()->is_active() && ( rand_normal > range_min) && ( rand_normal < range_min + (*s)->probability()) ) { enter_node(context, (*s)->head(), updates); break; } else { range_min += (*s)->probability(); } } } else { for (Node::Edges::const_iterator e = node->edges().begin(); e != node->edges().end(); ++e) { const double rand_normal = rand() / (double)RAND_MAX; // [0, 1] if (rand_normal <= (*e)->probability()) { SharedPtr head = (*e)->head(); if (!head->is_active()) { enter_node(context, head, updates); } } } } } /** Run the machine for a (real) time slice. * * Returns the duration of time the machine actually ran. * * Caller can check is_finished() to determine if the machine still has any * active states. If not, time() will return the exact time stamp the * machine actually finished on (so it can be restarted immediately * with sample accuracy if necessary). */ uint32_t Machine::run(Context& context, SharedPtr updates) { if (_is_finished) { return 0; } const TimeStamp cycle_end_frames = context.time().start_ticks() + context.time().length_ticks(); const TimeStamp cycle_end = context.time().ticks_to_beats( cycle_end_frames); assert(_is_activated); // Initial run, enter all initial states if (_time.is_zero()) { bool entered = false; if (!_nodes.empty()) { for (Nodes::const_iterator n = _nodes.begin(); n != _nodes.end(); ++n) { if ((*n)->is_active()) { (*n)->exit(context.sink(), _time); write_set(updates, (*n)->id(), URIs::instance().machina_active, context.forge().make(false)); } if ((*n)->is_initial()) { if (enter_node(context, (*n), updates)) { entered = true; } } } } if (!entered) { _is_finished = true; return 0; } } while (true) { SharedPtr earliest = earliest_node(); if (!earliest) { // No more active states, machine is finished #ifndef NDEBUG for (Nodes::const_iterator n = _nodes.begin(); n != _nodes.end(); ++n) { assert(!(*n)->is_active()); } #endif _is_finished = true; break; } else if (context.time().beats_to_ticks(earliest->exit_time()) < cycle_end_frames) { // Earliest active state ends this cycle _time = earliest->exit_time(); exit_node(context, earliest, updates); } else { // Earliest active state ends in the future, done this cycle _time = cycle_end; break; } } return context.time().beats_to_ticks(_time).ticks() - context.time().start_ticks().ticks(); } /** Push a node onto the learn stack. * * NOT realtime (actions are allocated here). */ void Machine::learn(SharedPtr maid, SharedPtr node) { _pending_learn = LearnRequest::create(maid, node); } void Machine::write_state(Sord::Model& model) { using namespace Raul; model.world().add_prefix("machina", "http://drobilla.net/ns/machina#"); model.add_statement(model.base_uri(), Sord::Curie(model.world(), "rdf:type"), Sord::Curie(model.world(), "machina: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::Curie(model.world(), "machina:initialNode"), (*n)->rdf_id(model.world())); } else { model.add_statement(model.base_uri(), Sord::Curie(model.world(), "machina: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::Curie(model.world(), "machina:edge"), (*e)->rdf_id(model.world())); } } } } // namespace Machina