From 67a8adbc93991acfb688f378f52392995a272fac Mon Sep 17 00:00:00 2001 From: David Robillard Date: Sun, 13 Jan 2013 07:49:49 +0000 Subject: Change model to have a single initial node. Merge multiple recording into branches off the same initial node. Make transport state sane with 3 distinct states. Handle announcing objects several times correctly. Don't send useless zero coordinates for new nodes, position in visible area. Rewrite and clean up Machine code. Update help. git-svn-id: http://svn.drobilla.net/lad/trunk/machina@4954 a436a847-0d15-0410-975c-d299462d15a1 --- src/engine/Controller.cpp | 105 +++++++++-------- src/engine/Edge.cpp | 5 +- src/engine/Engine.cpp | 4 - src/engine/JackDriver.cpp | 77 ++++++++----- src/engine/JackDriver.hpp | 13 +-- src/engine/Loader.cpp | 4 +- src/engine/Machine.cpp | 253 ++++++++++++++++------------------------- src/engine/MachineBuilder.cpp | 11 +- src/engine/Node.cpp | 30 ++--- src/engine/Node.hpp | 3 +- src/engine/SMFDriver.hpp | 2 + src/engine/machina/Driver.hpp | 24 ++-- src/engine/machina/Machine.hpp | 39 ++++--- 13 files changed, 279 insertions(+), 291 deletions(-) (limited to 'src/engine') diff --git a/src/engine/Controller.cpp b/src/engine/Controller.cpp index cc3c0de..c63d11e 100644 --- a/src/engine/Controller.cpp +++ b/src/engine/Controller.cpp @@ -40,9 +40,10 @@ Controller::Controller(SPtr engine, uint64_t Controller::create(const client::ClientObject& properties) { - TimeDuration dur(_engine->machine()->time().unit(), - properties.get(URIs::instance().machina_duration).get_float()); - SPtr node(new machina::Node(dur)); + TimeDuration dur( + _engine->machine()->time().unit(), + properties.get(URIs::instance().machina_duration).get_float()); + SPtr node(new Node(dur)); SPtr obj( new client::ClientObject(properties, node->id())); _objects.insert(node); @@ -56,48 +57,56 @@ Controller::announce(SPtr machine) { Raul::Forge& forge = _engine->forge(); - for (machina::Machine::Nodes::const_iterator n = machine->nodes().begin(); - n != machine->nodes().end(); ++n) { - SPtr obj( - new machina::client::ClientObject((*n)->id())); - obj->set(URIs::instance().rdf_type, - forge.make_urid(URIs::instance().machina_Node)); - obj->set(URIs::instance().machina_duration, - forge.make(float((*n)->duration().to_double()))); - obj->set(URIs::instance().machina_canvas_x, forge.make(0.0f)); - obj->set(URIs::instance().machina_canvas_y, forge.make(0.0f)); - - SPtr midi_action = dynamic_ptr_cast( - (*n)->enter_action()); - if (midi_action) { - SPtr action( - new machina::client::ClientObject(midi_action->id())); - action->set(URIs::instance().machina_note_number, - forge.make((int32_t)midi_action->event()[1])); - _client_model.new_object(action); - obj->set(URIs::instance().machina_enter_action, - forge.make(int32_t((*n)->enter_action()->id()))); + for (const auto& n : machine->nodes()) { + // Find or create a new client object if necessary + SPtr obj = _client_model.find(n->id()); + if (!obj) { + obj = SPtr(new client::ClientObject(n->id())); + obj->set(URIs::instance().rdf_type, + forge.make_urid(URIs::instance().machina_Node)); + obj->set(URIs::instance().machina_duration, + forge.make(float(n->duration().to_double()))); + if (n->is_initial()) { + obj->set(URIs::instance().machina_initial, forge.make(true)); + } + + SPtr midi_action = dynamic_ptr_cast( + n->enter_action()); + if (midi_action) { + SPtr action = _client_model.find( + midi_action->id()); + if (!action) { + action = SPtr( + new client::ClientObject(midi_action->id())); + action->set(URIs::instance().machina_note_number, + forge.make((int32_t)midi_action->event()[1])); + _client_model.new_object(action); + } + obj->set(URIs::instance().machina_enter_action, + forge.make(int32_t(n->enter_action()->id()))); + } } - _objects.insert(*n); + _objects.insert(n); _client_model.new_object(obj); } - for (machina::Machine::Nodes::const_iterator n = machine->nodes().begin(); - n != machine->nodes().end(); ++n) { - for (machina::Node::Edges::const_iterator e = (*n)->edges().begin(); - e != (*n)->edges().end(); ++e) { - _objects.insert(*e); - SPtr eobj( - new client::ClientObject((*e)->id())); - eobj->set(URIs::instance().rdf_type, - forge.make_urid(URIs::instance().machina_Edge)); - eobj->set(URIs::instance().machina_probability, - forge.make((*e)->probability())); - eobj->set(URIs::instance().machina_tail_id, - forge.make((int32_t)(*n)->id())); - eobj->set(URIs::instance().machina_head_id, - forge.make((int32_t)(*e)->head()->id())); - + for (const auto& n : machine->nodes()) { + for (const auto& e : n->edges()) { + SPtr eobj = _client_model.find(e->id()); + if (!eobj) { + eobj = SPtr( + new client::ClientObject(e->id())); + eobj->set(URIs::instance().rdf_type, + forge.make_urid(URIs::instance().machina_Edge)); + eobj->set(URIs::instance().machina_probability, + forge.make(e->probability())); + eobj->set(URIs::instance().machina_tail_id, + forge.make((int32_t)n->id())); + eobj->set(URIs::instance().machina_head_id, + forge.make((int32_t)e->head()->id())); + } + + _objects.insert(e); _client_model.new_object(eobj); } } @@ -117,7 +126,7 @@ Controller::find(uint64_t id) void Controller::learn(SPtr maid, uint64_t node_id) { - SPtr node = dynamic_ptr_cast(find(node_id)); + SPtr node = dynamic_ptr_cast(find(node_id)); if (node) { _engine->machine()->learn(maid, node); } else { @@ -141,10 +150,10 @@ Controller::set_property(uint64_t object_id, uint64_t Controller::connect(uint64_t tail_id, uint64_t head_id) { - SPtr tail = dynamic_ptr_cast(find(tail_id)); - SPtr head = dynamic_ptr_cast(find(head_id)); + SPtr tail = dynamic_ptr_cast(find(tail_id)); + SPtr head = dynamic_ptr_cast(find(head_id)); - SPtr edge(new machina::Edge(tail, head)); + SPtr edge(new Edge(tail, head)); tail->add_edge(edge); _objects.insert(edge); @@ -165,8 +174,8 @@ Controller::connect(uint64_t tail_id, uint64_t head_id) void Controller::disconnect(uint64_t tail_id, uint64_t head_id) { - SPtr tail = dynamic_ptr_cast(find(tail_id)); - SPtr head = dynamic_ptr_cast(find(head_id)); + SPtr tail = dynamic_ptr_cast(find(tail_id)); + SPtr head = dynamic_ptr_cast(find(head_id)); SPtr edge = tail->remove_edge_to(head); if (edge) { @@ -204,7 +213,7 @@ Controller::process_updates() Raul::Atom value; for (uint32_t i = 0; i < read_space; ) { i += read_set(_updates, &subject, &key, &value); - SPtr obj = _client_model.find(subject); + SPtr obj = _client_model.find(subject); if (obj) { obj->set(key, value); } else { diff --git a/src/engine/Edge.cpp b/src/engine/Edge.cpp index 0538c3c..85a4ab5 100644 --- a/src/engine/Edge.cpp +++ b/src/engine/Edge.cpp @@ -21,13 +21,16 @@ #include "Edge.hpp" #include "Node.hpp" +#include "machina/URIs.hpp" namespace machina { void Edge::set(URIInt key, const Raul::Atom& value) { - std::cout << "EDGE SET " << key << std::endl; + if (key == URIs::instance().machina_probability) { + _probability.set(value.get_float()); + } } void diff --git a/src/engine/Engine.cpp b/src/engine/Engine.cpp index 12c351d..90aea9b 100644 --- a/src/engine/Engine.cpp +++ b/src/engine/Engine.cpp @@ -67,7 +67,6 @@ Engine::load_machine(const Glib::ustring& uri) SPtr old_machine; if (machine) { old_machine = _driver->machine(); // Keep a reference to old machine... - machine->activate(); _driver->set_machine(machine); // Switch driver to new machine } @@ -89,7 +88,6 @@ Engine::load_machine_midi(const Glib::ustring& uri, SPtr old_machine; if (machine) { old_machine = _driver->machine(); // Keep a reference to old machine... - machine->activate(); _driver->set_machine(machine); // Switch driver to new machine } @@ -101,7 +99,6 @@ Engine::load_machine_midi(const Glib::ustring& uri, void Engine::import_machine(SPtr machine) { - machine->activate(); _driver->machine()->nodes().insert(machine->nodes().begin(), machine->nodes().end()); // FIXME: thread safe? @@ -118,7 +115,6 @@ Engine::export_midi(const Glib::ustring& filename, Raul::TimeDuration dur) const bool activated = _driver->is_activated(); if (activated) { _driver->deactivate(); // FIXME: disable instead - } file_driver->writer()->start(filename, TimeStamp(dur.unit(), 0.0)); file_driver->run(machine, dur); diff --git a/src/engine/JackDriver.cpp b/src/engine/JackDriver.cpp index 72b1cb1..0276cc5 100644 --- a/src/engine/JackDriver.cpp +++ b/src/engine/JackDriver.cpp @@ -16,13 +16,13 @@ */ #include -#include #include "machina/Context.hpp" #include "machina/URIs.hpp" #include "machina/Updates.hpp" #include "machina_config.h" +#include "Edge.hpp" #include "JackDriver.hpp" #include "LearnRequest.hpp" #include "MidiAction.hpp" @@ -46,7 +46,6 @@ JackDriver::JackDriver(Raul::Forge& forge, SPtr machine) , _stop(0) , _stop_flag(false) , _record_dur(_frames_unit) // = 0 - , _recording(false) , _is_activated(false) { _context.set_sink(this); @@ -108,16 +107,12 @@ JackDriver::attach(const std::string& client_name) new Machine(TimeUnit::frames( jack_get_sample_rate(jack_client())))); } - - _machine->activate(); } } void JackDriver::detach() { - _machine->deactivate(); - if (_input_port) { jack_port_unregister(jack_client(), _input_port); _input_port = NULL; @@ -339,13 +334,18 @@ JackDriver::on_process(jack_nframes_t nframes) machine->reset(_context.sink(), _context.time().start_beats()); } - if (_recording) { - read_input_recording(machine, _context.time()); - } else { + switch (_play_state) { + case PlayState::STOPPED: + break; + case PlayState::PLAYING: read_input_playing(machine, _context.time()); + break; + case PlayState::RECORDING: + read_input_recording(machine, _context.time()); + break; } - if (machine->is_empty() || !machine->is_activated()) { + if (machine->is_empty()) { goto end; } @@ -392,36 +392,61 @@ end: } void -JackDriver::stop() +JackDriver::set_play_state(PlayState state) { - if (recording()) { - finish_record(); + switch (state) { + case PlayState::STOPPED: + switch (_play_state) { + case PlayState::STOPPED: + break; + case PlayState::RECORDING: + finish_record(); + // nobreak + case PlayState::PLAYING: + _stop_flag = true; + _stop.wait(); + } + break; + case PlayState::RECORDING: + start_record(false); // FIXME: step record? + break; + case PlayState::PLAYING: + if (_play_state == PlayState::RECORDING) { + finish_record(); + } } - - _stop_flag = true; - _stop.wait(); - _machine->deactivate(); + _play_state = state; } void JackDriver::start_record(bool step) { - // FIXME: Choose an appropriate maximum ringbuffer size - _recorder = SPtr( - new Recorder(_forge, 1024, _beats_unit, _quantization.get(), step)); - _recorder->start(); - _record_dur = 0; - _recording = true; + if (_play_state.load() != PlayState::RECORDING) { + // FIXME: Choose an appropriate maximum ringbuffer size + _recorder = SPtr( + new Recorder(_forge, 1024, _beats_unit, _quantization.get(), step)); + _recorder->start(); + _record_dur = 0; + _play_state = PlayState::RECORDING; + } } void JackDriver::finish_record() { - _recording = false; + _play_state = PlayState::PLAYING; SPtr machine = _recorder->finish(); _recorder.reset(); - machine->activate(); - _machine->nodes().insert(machine->nodes().begin(), machine->nodes().end()); + for (const auto& m : machine->nodes()) { + if (m->is_initial()) { + for (const auto& e : m->edges()) { + e->set_tail(_machine->initial_node()); + _machine->initial_node()->edges().insert(e); + } + } else { + _machine->nodes().insert(m); + } + } } int diff --git a/src/engine/JackDriver.hpp b/src/engine/JackDriver.hpp index 3045165..14e84ec 100644 --- a/src/engine/JackDriver.hpp +++ b/src/engine/JackDriver.hpp @@ -43,8 +43,7 @@ class Node; * "Ticks" are individual frames when running under this driver, and all code * in the processing context must be realtime safe (non-blocking). */ -class JackDriver - : public machina::Driver +class JackDriver : public machina::Driver { public: JackDriver(Raul::Forge& forge, @@ -66,11 +65,7 @@ public: void set_bpm(double bpm) { _bpm.set(bpm); } void set_quantization(double q) { _quantization.set(q); } - void stop(); - - bool recording() { return _recording.load(); } - void start_record(bool step); - void finish_record(); + void set_play_state(PlayState state); void start_transport() { jack_transport_start(_client); } void stop_transport() { jack_transport_stop(_client); } @@ -100,6 +95,9 @@ private: static int jack_process_cb(jack_nframes_t nframes, void* me); static void jack_shutdown_cb(void* me); + void start_record(bool step); + void finish_record(); + void on_process(jack_nframes_t nframes); jack_client_t* _client; @@ -122,7 +120,6 @@ private: bool _stop_flag; Raul::TimeDuration _record_dur; - std::atomic _recording; SPtr _recorder; bool _is_activated; }; diff --git a/src/engine/Loader.cpp b/src/engine/Loader.cpp index 0ee5ba7..3c05401 100644 --- a/src/engine/Loader.cpp +++ b/src/engine/Loader.cpp @@ -82,7 +82,7 @@ Loader::load(const Glib::ustring& uri) Sord::Node nil; Sord::URI rdf_type(_rdf_world, NS_RDF "type"); - Sord::URI machina_SelectorNode(_rdf_world, NS_MACHINA "initialNode"); + Sord::URI machina_SelectorNode(_rdf_world, NS_MACHINA "SelectorNode"); Sord::URI machina_node(_rdf_world, NS_MACHINA "node"); Sord::URI machina_initialNode(_rdf_world, NS_MACHINA "initialNode"); Sord::URI machina_duration(_rdf_world, NS_MACHINA "duration"); @@ -192,7 +192,7 @@ Loader::load(const Glib::ustring& uri) } - if (machine && machine->nodes().size() > 0) { + if (machine && !machine->nodes().empty()) { machine->reset(NULL, machine->time()); return machine; } else { diff --git a/src/engine/Machine.cpp b/src/engine/Machine.cpp index fc026f9..a6abcff 100644 --- a/src/engine/Machine.cpp +++ b/src/engine/Machine.cpp @@ -1,5 +1,5 @@ /* This file is part of Machina. - * Copyright 2007-2011 David Robillard + * Copyright 2007-2013 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 @@ -39,71 +39,63 @@ using namespace Raul; namespace machina { Machine::Machine(TimeUnit unit) - : _active_nodes(MAX_ACTIVE_NODES, SPtr()) + : _initial_node(new Node(TimeStamp(unit, 0, 0), true)) + , _active_nodes(MAX_ACTIVE_NODES, SPtr()) , _time(unit, 0, 0) - , _is_activated(false) , _is_finished(false) -{} +{ + _nodes.insert(_initial_node); +} -/** 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, SPtr()) - , _time(copy.time()) - , _is_activated(false) - , _is_finished(false) +void +Machine::assign(const Machine& copy) { std::map< SPtr, SPtr > replacements; - for (Nodes::const_iterator n = copy._nodes.begin(); n != copy._nodes.end(); - ++n) { - SPtr node(new machina::Node(*n->get())); - _nodes.insert(node); - replacements[*n] = node; + replacements[copy.initial_node()] = _initial_node; + + for (const auto& n : copy._nodes) { + if (!n->is_initial()) { + SPtr node(new machina::Node(*n.get())); + _nodes.insert(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()); + 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 + , _initial_node(new Node(TimeStamp(copy.time().unit(), 0, 0), true)) + , _active_nodes(MAX_ACTIVE_NODES, SPtr()) + , _time(copy.time()) + , _is_finished(false) +{ + _nodes.insert(_initial_node); + assign(copy); +} + Machine& -Machine::operator=(const Machine& other) +Machine::operator=(const Machine& copy) { - _active_nodes = std::vector< SPtr >(MAX_ACTIVE_NODES, - SPtr()); - _is_activated = false; + if (© == this) { + return *this; + } + + _active_nodes = std::vector< SPtr >(MAX_ACTIVE_NODES, SPtr()); _is_finished = false; - _time = other._time; + _time = copy._time; _pending_learn = SPtr(); - _nodes.clear(); - map< SPtr, SPtr > replacements; - - for (Nodes::const_iterator n = other._nodes.begin(); n != other._nodes.end(); - ++n) { - SPtr node(new machina::Node(*n->get())); - _nodes.insert(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()); - } - } + _nodes.clear(); + _nodes.insert(_initial_node); + assign(copy); return *this; } @@ -158,24 +150,15 @@ Machine::remove_node(SPtr 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) { - SPtr node = (*n); - - if (node->is_active()) { - node->exit(sink, time); + for (auto& n : _active_nodes) { + if (n) { + n->exit(sink, time); + n.reset(); } - - assert(!node->is_active()); - } - - for (size_t i = 0; i < MAX_ACTIVE_NODES; ++i) { - _active_nodes.at(i).reset(); } } @@ -190,26 +173,19 @@ Machine::earliest_node() const { SPtr earliest; - for (size_t i = 0; i < MAX_ACTIVE_NODES; ++i) { - SPtr 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; - } + for (const auto& n : _active_nodes) { + if (n && (!earliest || n->exit_time() < earliest->exit_time())) { + earliest = n; } } return earliest; } -/** Enter a state at the current _time. +/** Enter a node at the current _time. * - * Returns true if node was entered, or false if the maximum active nodes has been reached. + * Returns true if node was entered, or false if the maximum active nodes has + * been reached. */ bool Machine::enter_node(Context& context, @@ -217,15 +193,15 @@ Machine::enter_node(Context& context, SPtr 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.at(index) == NULL) { + if (!_active_nodes[index]) { node->enter(context.sink(), _time); - assert(node->is_active()); - _active_nodes.at(index) = node; + _active_nodes[index] = node; write_set(updates, node->id(), @@ -240,78 +216,58 @@ Machine::enter_node(Context& context, return false; } -/** Exit an active node at the current _time. - */ void Machine::exit_node(Context& context, SPtr node, SPtr updates) { + // Exit node node->exit(context.sink(), _time); + + // Notify UI 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(); + // Remove node from _active_nodes + for (auto& n : _active_nodes) { + if (n == node) { + n.reset(); } } - // Activate successors to this node - // (that aren't aready active right now) - + // Activate successors 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() + for (const auto& e : node->edges()) { + if (!e->head()->is_active() && rand_normal > range_min - && rand_normal < range_min + (*s)->probability()) { + && rand_normal < range_min + e->probability()) { - enter_node(context, (*s)->head(), updates); + enter_node(context, e->head(), updates); break; } else { - range_min += (*s)->probability(); + range_min += e->probability(); } } - } else { - - for (Node::Edges::const_iterator e = node->edges().begin(); - e != node->edges().end(); ++e) { - + for (const auto& e : node->edges()) { const double rand_normal = rand() / (double)RAND_MAX; // [0, 1] - - if (rand_normal <= (*e)->probability()) { - SPtr head = (*e)->head(); + if (rand_normal <= e->probability()) { + SPtr 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, SPtr updates) { @@ -319,77 +275,58 @@ Machine::run(Context& context, SPtr updates) 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)); - } + const Raul::TimeSlice& time = context.time(); - if ((*n)->is_initial()) { - if (enter_node(context, (*n), updates)) { - entered = true; - } - } + 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(); } - if (!entered) { + + // 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 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 + 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 = cycle_end; + _time = end_beats; break; } } - return context.time().beats_to_ticks(_time).ticks() - - context.time().start_ticks().ticks(); + return time.beats_to_ticks(_time).ticks() - time.start_ticks().ticks(); } -/** Push a node onto the learn stack. - * - * NOT realtime (actions are allocated here). - */ void Machine::learn(SPtr maid, SPtr node) { diff --git a/src/engine/MachineBuilder.cpp b/src/engine/MachineBuilder.cpp index d8fcff1..0a515a4 100644 --- a/src/engine/MachineBuilder.cpp +++ b/src/engine/MachineBuilder.cpp @@ -92,7 +92,7 @@ MachineBuilder::connect_nodes(SPtr m, SPtr delay_node; - if (is_delay_node(tail) && tail->edges().size() == 0) { + if (is_delay_node(tail) && tail->edges().empty()) { // Tail is a delay node, just accumulate the time difference into it set_node_duration(tail, tail->duration() + head_start_time - tail_end_time); @@ -176,7 +176,7 @@ MachineBuilder::resolve_note(Raul::TimeStamp t, for (PolyList::iterator j = _poly_nodes.begin(); j != _poly_nodes.end(); ++j) { _machine->add_node(j->second); - if (j->second->edges().size() == 0) { + if (j->second->edges().empty()) { connect_nodes(_machine, j->second, j->first + j->second->duration(), _connect_node, t); @@ -238,7 +238,8 @@ MachineBuilder::event(Raul::TimeStamp time_offset, } else if ((buf[0] & 0xF0) == LV2_MIDI_MSG_NOTE_OFF) { for (ActiveList::iterator i = _active_nodes.begin(); i != _active_nodes.end(); ++i) { - SPtr action = dynamic_ptr_cast((*i)->enter_action()); + SPtr action = dynamic_ptr_cast( + (*i)->enter_action()); if (!action) { continue; } @@ -293,8 +294,8 @@ MachineBuilder::resolve() _active_nodes.clear(); } - // Add initial note if necessary - if (_machine->nodes().size() > 0) { + // Add initial node if necessary + if (!_machine->nodes().empty()) { _machine->add_node(_initial_node); } } diff --git a/src/engine/Node.cpp b/src/engine/Node.cpp index 962a3c7..9aba95a 100644 --- a/src/engine/Node.cpp +++ b/src/engine/Node.cpp @@ -131,29 +131,29 @@ Node::set_exit_action(SPtr action) void Node::enter(MIDISink* sink, TimeStamp time) { - assert(!_is_active); + if (!_is_active) { + _changed = true; + _is_active = true; + _enter_time = time; - _changed = true; - _is_active = true; - _enter_time = time; - - if (sink && _enter_action) { - _enter_action->execute(sink, time); + if (sink && _enter_action) { + _enter_action->execute(sink, time); + } } } void Node::exit(MIDISink* sink, TimeStamp time) { - assert(_is_active); + if (_is_active) { + if (sink && _exit_action) { + _exit_action->execute(sink, time); + } - if (sink && _exit_action) { - _exit_action->execute(sink, time); + _changed = true; + _is_active = false; + _enter_time = 0; } - - _changed = true; - _is_active = false; - _enter_time = 0; } SPtr @@ -208,7 +208,7 @@ void Node::set(URIInt key, const Raul::Atom& value) { if (key == URIs::instance().machina_initial) { - set_initial(value.get_bool()); + std::cerr << "error: Attempt to change node initial state" << std::endl; } else if (key == URIs::instance().machina_selector) { set_selector(value.get_bool()); } diff --git a/src/engine/Node.hpp b/src/engine/Node.hpp index 575f39e..7baff9b 100644 --- a/src/engine/Node.hpp +++ b/src/engine/Node.hpp @@ -68,12 +68,11 @@ public: void write_state(Sord::Model& model); bool is_initial() const { return _is_initial; } - void set_initial(bool i) { _is_initial = i; } bool is_active() const { return _is_active; } TimeStamp enter_time() const { return _enter_time; } TimeStamp exit_time() const { return _enter_time + _duration; } TimeDuration duration() const { return _duration; } - void set_duration(TimeDuration d) { _duration = d; } + void set_duration(TimeDuration d) { _duration = d; } bool is_selector() const { return _is_selector; } void set_selector(bool i); diff --git a/src/engine/SMFDriver.hpp b/src/engine/SMFDriver.hpp index df726e8..83892d5 100644 --- a/src/engine/SMFDriver.hpp +++ b/src/engine/SMFDriver.hpp @@ -56,6 +56,8 @@ public: void set_bpm(double /*bpm*/) {} void set_quantization(double /*quantization*/) {} + void set_play_state(PlayState state) {} + SPtr writer() { return _writer; } private: diff --git a/src/engine/machina/Driver.hpp b/src/engine/machina/Driver.hpp index 3e65c9d..84a6cfd 100644 --- a/src/engine/machina/Driver.hpp +++ b/src/engine/machina/Driver.hpp @@ -18,6 +18,8 @@ #ifndef MACHINA_DRIVER_HPP #define MACHINA_DRIVER_HPP +#include + #include "raul/RingBuffer.hpp" #include "machina/types.hpp" @@ -28,15 +30,21 @@ namespace machina { class Machine; -class Driver - : public MIDISink +class Driver : public MIDISink { public: Driver(Raul::Forge& forge, SPtr machine) : _forge(forge) , _machine(machine) + , _play_state(PlayState::STOPPED) {} + enum class PlayState { + STOPPED, + PLAYING, + RECORDING + }; + virtual ~Driver() {} SPtr machine() { return _machine; } @@ -51,23 +59,21 @@ public: _updates = b; } - virtual void set_bpm(double bpm) = 0; - virtual void set_quantization(double q) = 0; + virtual void set_bpm(double bpm) = 0; + virtual void set_quantization(double q) = 0; + virtual void set_play_state(PlayState state) = 0; virtual bool is_activated() const { return false; } virtual void activate() {} virtual void deactivate() {} - virtual void stop() {} - - virtual bool recording() { return false; } - virtual void start_record(bool step) {} - virtual void finish_record() {} + PlayState play_state() const { return _play_state.load(); } protected: Raul::Forge& _forge; SPtr _machine; SPtr _updates; + std::atomic _play_state; }; } // namespace machina diff --git a/src/engine/machina/Machine.hpp b/src/engine/machina/Machine.hpp index e82f992..8a98e4c 100644 --- a/src/engine/machina/Machine.hpp +++ b/src/engine/machina/Machine.hpp @@ -43,20 +43,18 @@ class Machine { public: Machine(TimeUnit unit); + + /** Copy a Machine. + * + * Creates a deep copy which is the 'same' machine, but with fresh state, + * i.e. all nodes are inactive and time is at zero. + */ Machine(const Machine& copy); Machine& operator=(const Machine& other); - // Kluge to appease Eugene - bool operator==(const Machine& other) { return false; } - - // Main context - void activate() { _is_activated = true; } - void deactivate() { _is_activated = false; } - - bool is_empty() { return _nodes.empty(); } - bool is_finished() { return _is_finished; } - bool is_activated() { return _is_activated; } + bool is_empty() { return _nodes.empty(); } + bool is_finished() { return _is_finished; } void add_node(SPtr node); void remove_node(SPtr node); @@ -64,8 +62,19 @@ public: void write_state(Sord::Model& model); - // Audio context - void reset(MIDISink* sink, Raul::TimeStamp time); + /** Exit all active nodes and reset time to 0. + */ + void reset(MIDISink* sink, Raul::TimeStamp time); + + /** Run the machine for a (real) time slice. + * + * Returns the duration of time the machine actually ran in frames. + * + * Caller can check is_finished() to determine if the machine still has any + * active nodes. 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 run(Context& context, SPtr updates); // Any context @@ -78,6 +87,8 @@ public: Nodes& nodes() { return _nodes; } const Nodes& nodes() const { return _nodes; } + SPtr initial_node() const { return _initial_node; } + SPtr random_node(); SPtr random_edge(); @@ -85,6 +96,8 @@ private: // Audio context SPtr earliest_node() const; + void assign(const Machine& other); + bool enter_node(Context& context, SPtr node, SPtr updates); @@ -95,12 +108,12 @@ private: static const size_t MAX_ACTIVE_NODES = 128; + SPtr _initial_node; std::vector< SPtr > _active_nodes; SPtr _pending_learn; Nodes _nodes; Raul::TimeStamp _time; - bool _is_activated; bool _is_finished; }; -- cgit v1.2.1