aboutsummaryrefslogtreecommitdiffstats
path: root/src/engine
diff options
context:
space:
mode:
Diffstat (limited to 'src/engine')
-rw-r--r--src/engine/Action.hpp60
-rw-r--r--src/engine/ActionFactory.cpp55
-rw-r--r--src/engine/ActionFactory.hpp34
-rw-r--r--src/engine/Controller.cpp228
-rw-r--r--src/engine/Edge.cpp64
-rw-r--r--src/engine/Edge.hpp60
-rw-r--r--src/engine/Engine.cpp126
-rw-r--r--src/engine/Evolver.cpp139
-rw-r--r--src/engine/JackDriver.cpp479
-rw-r--r--src/engine/JackDriver.hpp119
-rw-r--r--src/engine/LearnRequest.cpp56
-rw-r--r--src/engine/LearnRequest.hpp61
-rw-r--r--src/engine/Loader.cpp193
-rw-r--r--src/engine/MIDISink.hpp37
-rw-r--r--src/engine/Machine.cpp389
-rw-r--r--src/engine/MachineBuilder.cpp325
-rw-r--r--src/engine/MachineBuilder.hpp87
-rw-r--r--src/engine/MidiAction.cpp103
-rw-r--r--src/engine/MidiAction.hpp57
-rw-r--r--src/engine/Mutation.cpp190
-rw-r--r--src/engine/Node.cpp271
-rw-r--r--src/engine/Node.hpp116
-rw-r--r--src/engine/Problem.cpp383
-rw-r--r--src/engine/Problem.hpp135
-rw-r--r--src/engine/Recorder.cpp69
-rw-r--r--src/engine/Recorder.hpp68
-rw-r--r--src/engine/SMFDriver.cpp162
-rw-r--r--src/engine/SMFDriver.hpp67
-rw-r--r--src/engine/SMFReader.cpp323
-rw-r--r--src/engine/SMFReader.hpp87
-rw-r--r--src/engine/SMFWriter.cpp241
-rw-r--r--src/engine/SMFWriter.hpp73
-rw-r--r--src/engine/Schrodinbit.hpp42
-rw-r--r--src/engine/Slave.hpp73
-rw-r--r--src/engine/Stateful.cpp39
-rw-r--r--src/engine/Stateful.hpp70
-rw-r--r--src/engine/URIs.cpp23
-rw-r--r--src/engine/Updates.cpp66
-rw-r--r--src/engine/machina/Atom.hpp217
-rw-r--r--src/engine/machina/Context.hpp51
-rw-r--r--src/engine/machina/Controller.hpp80
-rw-r--r--src/engine/machina/Driver.hpp86
-rw-r--r--src/engine/machina/Engine.hpp68
-rw-r--r--src/engine/machina/Evolver.hpp72
-rw-r--r--src/engine/machina/Loader.hpp49
-rw-r--r--src/engine/machina/Machine.hpp132
-rw-r--r--src/engine/machina/Model.hpp44
-rw-r--r--src/engine/machina/Mutation.hpp60
-rw-r--r--src/engine/machina/URIs.hpp93
-rw-r--r--src/engine/machina/Updates.hpp46
-rw-r--r--src/engine/machina/types.hpp69
-rw-r--r--src/engine/quantize.hpp46
-rw-r--r--src/engine/quantize_test.cpp41
-rw-r--r--src/engine/smf_test.cpp84
-rw-r--r--src/engine/wscript45
55 files changed, 6453 insertions, 0 deletions
diff --git a/src/engine/Action.hpp b/src/engine/Action.hpp
new file mode 100644
index 0000000..2916099
--- /dev/null
+++ b/src/engine/Action.hpp
@@ -0,0 +1,60 @@
+/*
+ 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/>.
+*/
+
+#ifndef MACHINA_ACTION_HPP
+#define MACHINA_ACTION_HPP
+
+#include <string>
+#include <iostream>
+
+#include "raul/Maid.hpp"
+#include "raul/TimeSlice.hpp"
+
+#include "machina/types.hpp"
+
+#include "MIDISink.hpp"
+#include "Stateful.hpp"
+
+namespace machina {
+
+/** An Action, executed on entering or exiting of a state.
+ */
+struct Action
+ : public Raul::Maid::Disposable
+ , public Stateful
+{
+ bool operator==(const Action& rhs) const { return false; }
+
+ virtual void execute(MIDISink* sink, Raul::TimeStamp time) = 0;
+
+ virtual void write_state(Sord::Model& model) {}
+};
+
+class PrintAction : public Action
+{
+public:
+ explicit PrintAction(const std::string& msg) : _msg(msg) {}
+
+ void execute(MIDISink* sink, Raul::TimeStamp time)
+ { std::cout << "t=" << time << ": " << _msg << std::endl; }
+
+private:
+ std::string _msg;
+};
+
+} // namespace machina
+
+#endif // MACHINA_ACTION_HPP
diff --git a/src/engine/ActionFactory.cpp b/src/engine/ActionFactory.cpp
new file mode 100644
index 0000000..f031149
--- /dev/null
+++ b/src/engine/ActionFactory.cpp
@@ -0,0 +1,55 @@
+/*
+ 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 "ActionFactory.hpp"
+#include "MidiAction.hpp"
+
+namespace machina {
+
+SPtr<Action>
+ActionFactory::copy(SPtr<Action> copy)
+{
+ SPtr<MidiAction> ma = dynamic_ptr_cast<MidiAction>(copy);
+ if (ma) {
+ return SPtr<Action>(new MidiAction(ma->event_size(), ma->event()));
+ } else {
+ return SPtr<Action>();
+ }
+}
+
+SPtr<Action>
+ActionFactory::note_on(uint8_t note, uint8_t velocity)
+{
+ uint8_t buf[3];
+ buf[0] = 0x90;
+ buf[1] = note;
+ buf[2] = velocity;
+
+ return SPtr<Action>(new MidiAction(3, buf));
+}
+
+SPtr<Action>
+ActionFactory::note_off(uint8_t note, uint8_t velocity)
+{
+ uint8_t buf[3];
+ buf[0] = 0x80;
+ buf[1] = note;
+ buf[2] = velocity;
+
+ return SPtr<Action>(new MidiAction(3, buf));
+}
+
+} // namespace Machine
diff --git a/src/engine/ActionFactory.hpp b/src/engine/ActionFactory.hpp
new file mode 100644
index 0000000..db7aa68
--- /dev/null
+++ b/src/engine/ActionFactory.hpp
@@ -0,0 +1,34 @@
+/*
+ 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/>.
+*/
+
+#ifndef MACHINA_ACTIONFACTORY_HPP
+#define MACHINA_ACTIONFACTORY_HPP
+
+#include "machina/types.hpp"
+
+namespace machina {
+
+struct Action;
+
+namespace ActionFactory {
+SPtr<Action> copy(SPtr<Action> copy);
+SPtr<Action> note_on(uint8_t note, uint8_t velocity=64);
+SPtr<Action> note_off(uint8_t note, uint8_t velocity=64);
+}
+
+} // namespace machina
+
+#endif // MACHINA_ACTIONFACTORY_HPP
diff --git a/src/engine/Controller.cpp b/src/engine/Controller.cpp
new file mode 100644
index 0000000..8f9e1a2
--- /dev/null
+++ b/src/engine/Controller.cpp
@@ -0,0 +1,228 @@
+/*
+ This file is part of Machina.
+ Copyright 2007-2014 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 "machina/Controller.hpp"
+#include "machina/Engine.hpp"
+#include "machina/Machine.hpp"
+#include "machina/Updates.hpp"
+
+#include "Edge.hpp"
+#include "MidiAction.hpp"
+
+namespace machina {
+
+Controller::Controller(SPtr<Engine> engine,
+ Model& model)
+ : _engine(engine)
+ , _model(model)
+ , _updates(new Raul::RingBuffer(4096))
+{
+ _engine->driver()->set_update_sink(_updates);
+}
+
+uint64_t
+Controller::create(const Properties& properties)
+{
+ std::map<URIInt, Atom>::const_iterator d = properties.find(
+ URIs::instance().machina_duration);
+
+ double duration = 0.0;
+ if (d != properties.end()) {
+ duration = d->second.get<float>();
+ } else {
+ std::cerr << "warning: new object has no duration" << std::endl;
+ }
+
+ TimeDuration dur(_engine->machine()->time().unit(), duration);
+ SPtr<Node> node(new Node(dur));
+ _objects.insert(node);
+ _model.new_object(node->id(), properties);
+ _engine->machine()->add_node(node);
+ return node->id();
+}
+
+void
+Controller::announce(SPtr<Machine> machine)
+{
+ if (!machine) {
+ return;
+ }
+
+ Forge& forge = _engine->forge();
+
+ // Announce nodes
+ for (const auto& n : machine->nodes()) {
+ Properties properties {
+ { URIs::instance().rdf_type,
+ forge.make_urid(URIs::instance().machina_Node) },
+ { URIs::instance().machina_duration,
+ forge.make(float(n->duration().to_double())) } };
+ if (n->is_initial()) {
+ properties.insert({ URIs::instance().machina_initial,
+ forge.make(true) });
+ }
+
+ SPtr<MidiAction> midi_action = dynamic_ptr_cast<MidiAction>(
+ n->enter_action());
+ if (midi_action) {
+ Properties action_properties {
+ { URIs::instance().machina_note_number,
+ forge.make((int32_t)midi_action->event()[1]) } };
+
+ _model.new_object(midi_action->id(), action_properties);
+ properties.insert({ URIs::instance().machina_enter_action,
+ forge.make(int32_t(n->enter_action()->id())) });
+ }
+
+ _objects.insert(n);
+ _model.new_object(n->id(), properties);
+ }
+
+ // Announce edges
+ for (const auto& n : machine->nodes()) {
+ for (const auto& e : n->edges()) {
+ const Properties properties {
+ { URIs::instance().rdf_type,
+ forge.make_urid(URIs::instance().machina_Edge) },
+ { URIs::instance().machina_probability,
+ forge.make(e->probability()) },
+ { URIs::instance().machina_tail_id,
+ forge.make((int32_t)n->id()) },
+ { URIs::instance().machina_head_id,
+ forge.make((int32_t)e->head()->id()) } };
+
+ _objects.insert(e);
+ _model.new_object(e->id(), properties);
+ }
+ }
+}
+
+SPtr<Stateful>
+Controller::find(uint64_t id)
+{
+ SPtr<StatefulKey> key(new StatefulKey(id));
+ Objects::iterator i = _objects.find(key);
+ if (i != _objects.end()) {
+ return *i;
+ }
+ return SPtr<Stateful>();
+}
+
+void
+Controller::learn(SPtr<Raul::Maid> maid, uint64_t node_id)
+{
+ SPtr<Node> node = dynamic_ptr_cast<Node>(find(node_id));
+ if (node) {
+ _engine->machine()->learn(maid, node);
+ } else {
+ std::cerr << "Failed to find node " << node_id << " for learn"
+ << std::endl;
+ }
+}
+
+void
+Controller::set_property(uint64_t object_id,
+ URIInt key,
+ const Atom& value)
+{
+ SPtr<Stateful> object = find(object_id);
+ if (object) {
+ object->set(key, value);
+ _model.set(object_id, key, value);
+ }
+}
+
+uint64_t
+Controller::connect(uint64_t tail_id, uint64_t head_id)
+{
+ SPtr<Node> tail = dynamic_ptr_cast<Node>(find(tail_id));
+ SPtr<Node> head = dynamic_ptr_cast<Node>(find(head_id));
+
+ if (!tail) {
+ std::cerr << "error: tail node " << tail_id << " not found" << std::endl;
+ return 0;
+ } else if (!head) {
+ std::cerr << "error: head node " << head_id << " not found" << std::endl;
+ return 0;
+ }
+
+ SPtr<Edge> edge(new Edge(tail, head));
+ tail->add_edge(edge);
+ _objects.insert(edge);
+
+ Forge& forge = _engine->forge();
+
+ const Properties properties = {
+ { URIs::instance().rdf_type,
+ forge.make_urid(URIs::instance().machina_Edge) },
+ { URIs::instance().machina_probability, forge.make(1.0f) },
+ { URIs::instance().machina_tail_id,
+ forge.make((int32_t)tail->id()) },
+ { URIs::instance().machina_head_id,
+ forge.make((int32_t)head->id()) } };
+
+ _model.new_object(edge->id(), properties);
+
+ return edge->id();
+}
+
+void
+Controller::disconnect(uint64_t tail_id, uint64_t head_id)
+{
+ SPtr<Node> tail = dynamic_ptr_cast<Node>(find(tail_id));
+ SPtr<Node> head = dynamic_ptr_cast<Node>(find(head_id));
+
+ SPtr<Edge> edge = tail->remove_edge_to(head);
+ if (edge) {
+ _model.erase_object(edge->id());
+ } else {
+ std::cerr << "Edge not found" << std::endl;
+ }
+}
+
+void
+Controller::erase(uint64_t id)
+{
+ SPtr<StatefulKey> key(new StatefulKey(id));
+ Objects::iterator i = _objects.find(key);
+ if (i == _objects.end()) {
+ return;
+ }
+
+ SPtr<Node> node = dynamic_ptr_cast<Node>(*i);
+ if (node) {
+ _engine->machine()->remove_node(node);
+ }
+
+ _model.erase_object((*i)->id());
+ _objects.erase(i);
+}
+
+void
+Controller::process_updates()
+{
+ const uint32_t read_space = _updates->read_space();
+
+ uint64_t subject;
+ URIInt key;
+ Atom value;
+ for (uint32_t i = 0; i < read_space; ) {
+ i += read_set(_updates, &subject, &key, &value);
+ _model.set(subject, key, value);
+ }
+}
+
+}
diff --git a/src/engine/Edge.cpp b/src/engine/Edge.cpp
new file mode 100644
index 0000000..7c206f9
--- /dev/null
+++ b/src/engine/Edge.cpp
@@ -0,0 +1,64 @@
+/*
+ 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 "machina/Atom.hpp"
+#include "machina/URIs.hpp"
+#include "sord/sordmm.hpp"
+
+#include "Edge.hpp"
+#include "Node.hpp"
+
+namespace machina {
+
+void
+Edge::set(URIInt key, const Atom& value)
+{
+ if (key == URIs::instance().machina_probability) {
+ _probability = value.get<float>();
+ }
+}
+
+void
+Edge::write_state(Sord::Model& model)
+{
+ using namespace Raul;
+
+ const Sord::Node& rdf_id = this->rdf_id(model.world());
+
+ SPtr<Node> tail = _tail.lock();
+ SPtr<Node> head = _head;
+
+ if (!tail || !head)
+ return;
+
+ assert(tail->rdf_id(model.world()).is_valid()
+ && head->rdf_id(model.world()).is_valid());
+
+ model.add_statement(rdf_id,
+ Sord::URI(model.world(), MACHINA_NS_tail),
+ tail->rdf_id(model.world()));
+
+ model.add_statement(rdf_id,
+ Sord::URI(model.world(), MACHINA_NS_head),
+ head->rdf_id(model.world()));
+
+ model.add_statement(
+ rdf_id,
+ Sord::URI(model.world(), MACHINA_NS_probability),
+ Sord::Literal::decimal(model.world(), _probability, 7));
+}
+
+} // namespace machina
diff --git a/src/engine/Edge.hpp b/src/engine/Edge.hpp
new file mode 100644
index 0000000..a04dd73
--- /dev/null
+++ b/src/engine/Edge.hpp
@@ -0,0 +1,60 @@
+/*
+ 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/>.
+*/
+
+#ifndef MACHINA_EDGE_HPP
+#define MACHINA_EDGE_HPP
+
+#include <list>
+
+#include "machina/types.hpp"
+
+#include "Action.hpp"
+#include "Stateful.hpp"
+
+namespace machina {
+
+class Node;
+
+class Edge : public Stateful
+{
+public:
+ Edge(WPtr<Node> tail, SPtr<Node> head, float probability=1.0f)
+ : _tail(tail)
+ , _head(head)
+ , _probability(probability)
+ {}
+
+ void set(URIInt key, const Atom& value);
+ void write_state(Sord::Model& model);
+
+ WPtr<Node> tail() { return _tail; }
+ SPtr<Node> head() { return _head; }
+
+ void set_tail(WPtr<Node> tail) { _tail = tail; }
+ void set_head(SPtr<Node> head) { _head = head; }
+
+ inline float probability() const { return _probability; }
+ inline void set_probability(float p) { _probability = p; }
+
+private:
+ WPtr<Node> _tail;
+ SPtr<Node> _head;
+ float _probability;
+};
+
+} // namespace machina
+
+#endif // MACHINA_EDGE_HPP
diff --git a/src/engine/Engine.cpp b/src/engine/Engine.cpp
new file mode 100644
index 0000000..1aed7b6
--- /dev/null
+++ b/src/engine/Engine.cpp
@@ -0,0 +1,126 @@
+/*
+ This file is part of Machina.
+ Copyright 2007-2014 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 "machina_config.h"
+#include "machina/Engine.hpp"
+#include "machina/Loader.hpp"
+#include "machina/Machine.hpp"
+#include "SMFDriver.hpp"
+#ifdef HAVE_JACK
+#include "JackDriver.hpp"
+#endif
+
+namespace machina {
+
+Engine::Engine(Forge& forge,
+ SPtr<Driver> driver,
+ Sord::World& rdf_world)
+ : _driver(driver)
+ , _rdf_world(rdf_world)
+ , _loader(_forge, _rdf_world)
+ , _forge(forge)
+{}
+
+SPtr<Driver>
+Engine::new_driver(Forge& forge,
+ const std::string& name,
+ SPtr<Machine> machine)
+{
+ if (name == "jack") {
+#ifdef HAVE_JACK
+ JackDriver* driver = new JackDriver(forge, machine);
+ driver->attach("machina");
+ return SPtr<Driver>(driver);
+#else
+ return SPtr<Driver>();
+#endif
+ }
+ if (name == "smf") {
+ return SPtr<Driver>(new SMFDriver(forge, machine->time().unit()));
+ }
+
+ std::cerr << "Error: Unknown driver type `" << name << "'" << std::endl;
+ return SPtr<Driver>();
+}
+
+/** Load the machine at `uri`, and run it (replacing current machine).
+ * Safe to call while engine is processing.
+ */
+SPtr<Machine>
+Engine::load_machine(const std::string& uri)
+{
+ SPtr<Machine> machine = _loader.load(uri);
+ SPtr<Machine> old_machine = _driver->machine(); // Keep reference
+ if (machine) {
+ _driver->set_machine(machine); // Switch driver to new machine and wait
+ }
+
+ // Drop (possibly last) reference to old_machine in this thread
+ return machine;
+}
+
+/** Build a machine from the MIDI at `uri`, and run it (replacing current machine).
+ * Safe to call while engine is processing.
+ */
+SPtr<Machine>
+Engine::load_machine_midi(const std::string& uri,
+ double q,
+ Raul::TimeDuration dur)
+{
+ SPtr<Machine> machine = _loader.load_midi(uri, q, dur);
+ SPtr<Machine> old_machine = _driver->machine(); // Keep reference
+ if (machine) {
+ _driver->set_machine(machine); // Switch driver to new machine and wait
+ }
+
+ // Drop (possibly last) reference to old_machine in this thread
+ return machine;
+}
+
+void
+Engine::export_midi(const std::string& filename, Raul::TimeDuration dur)
+{
+ SPtr<Machine> machine = _driver->machine();
+ SPtr<machina::SMFDriver> file_driver(
+ new machina::SMFDriver(_forge, dur.unit()));
+
+ 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);
+ machine->reset(NULL, machine->time());
+ file_driver->writer()->finish();
+
+ if (activated) {
+ _driver->activate();
+ }
+}
+
+void
+Engine::set_bpm(double bpm)
+{
+ _driver->set_bpm(bpm);
+}
+
+void
+Engine::set_quantization(double q)
+{
+ _driver->set_quantization(q);
+}
+
+} // namespace machina
diff --git a/src/engine/Evolver.cpp b/src/engine/Evolver.cpp
new file mode 100644
index 0000000..973c9c8
--- /dev/null
+++ b/src/engine/Evolver.cpp
@@ -0,0 +1,139 @@
+/*
+ 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 <iostream>
+
+#include "eugene/HybridMutation.hpp"
+#include "eugene/Mutation.hpp"
+#include "eugene/TournamentSelection.hpp"
+
+#include "machina/Evolver.hpp"
+#include "machina/Mutation.hpp"
+
+#include "Problem.hpp"
+
+using namespace std;
+using namespace eugene;
+
+namespace machina {
+
+Evolver::Evolver(TimeUnit unit,
+ const string& target_midi,
+ SPtr<Machine> seed)
+ : _rng(0)
+ , _problem(new Problem(unit, target_midi, seed))
+ , _seed_fitness(-FLT_MAX)
+ , _exit_flag(false)
+{
+ SPtr<eugene::HybridMutation<Machine> > m(new HybridMutation<Machine>());
+
+ m->append_mutation(1 / 6.0f, std::shared_ptr< eugene::Mutation<Machine> >(
+ new Mutation::Compress()));
+ m->append_mutation(1 / 6.0f, std::shared_ptr< eugene::Mutation<Machine> >(
+ new Mutation::AddNode()));
+ //m->append_mutation(1/6.0f, std::shared_ptr< eugene::Mutation<Machine> >(
+ // new Mutation::RemoveNode()));
+ //m->append_mutation(1/6.0f, std::shared_ptr< eugene::Mutation<Machine> >(
+ // new Mutation::AdjustNode()));
+ m->append_mutation(1 / 6.0f, std::shared_ptr< eugene::Mutation<Machine> >(
+ new Mutation::SwapNodes()));
+ m->append_mutation(1 / 6.0f, std::shared_ptr< eugene::Mutation<Machine> >(
+ new Mutation::AddEdge()));
+ m->append_mutation(1 / 6.0f, std::shared_ptr< eugene::Mutation<Machine> >(
+ new Mutation::RemoveEdge()));
+ m->append_mutation(1 / 6.0f, std::shared_ptr< eugene::Mutation<Machine> >(
+ new Mutation::AdjustEdge()));
+
+ std::shared_ptr< Selection<Machine> > s(
+ new TournamentSelection<Machine>(*_problem.get(), 3, 0.8));
+ std::shared_ptr< Crossover<Machine> > crossover;
+ size_t gene_length = 20; // FIXME
+ Problem::Population pop;
+ _ga = SPtr<MachinaGA>(
+ new MachinaGA(_rng,
+ _problem,
+ s,
+ crossover,
+ m,
+ pop,
+ gene_length,
+ 20,
+ 2,
+ 1.0,
+ 0.0));
+}
+
+void
+Evolver::start()
+{
+ if (!_thread) {
+ _thread = std::unique_ptr<std::thread>(
+ new std::thread(&Evolver::run, this));
+ }
+}
+
+void
+Evolver::join()
+{
+ if (_thread && _thread->joinable()) {
+ _exit_flag = true;
+ _thread->join();
+ }
+}
+
+void
+Evolver::seed(SPtr<Machine> parent)
+{
+ /*_best = SPtr<Machine>(new Machine(*parent.get()));
+ _best_fitness = _problem->fitness(*_best.get());*/
+ _problem->seed(parent);
+ _seed_fitness = _problem->evaluate(*parent.get());
+}
+
+void
+Evolver::run()
+{
+ float old_best = _ga->best_fitness();
+
+ //cout << "ORIGINAL BEST: " << _ga->best_fitness() << endl;
+
+ _improvement = true;
+
+ while (!_exit_flag) {
+ //cout << "{" << endl;
+ _problem->clear_fitness_cache();
+ _ga->iteration();
+
+ float new_best = _ga->best_fitness();
+
+ /*cout << _problem->fitness_less(old_best, *_ga->best().get()) << endl;
+ cout << "best: " << _ga->best().get() << endl;
+ cout << "best fitness: " << _problem->fitness(*_ga->best().get()) << endl;
+ cout << "old best: " << old_best << endl;
+ cout << "new best: " << new_best << endl;*/
+ cout << "generation best: " << new_best << endl;
+
+ if (_problem->fitness_less_than(old_best, new_best)) {
+ _improvement = true;
+ old_best = new_best;
+ cout << "*** NEW BEST: " << new_best << endl;
+ }
+
+ //cout << "}" << endl;
+ }
+}
+
+} // namespace machina
diff --git a/src/engine/JackDriver.cpp b/src/engine/JackDriver.cpp
new file mode 100644
index 0000000..e7f2e97
--- /dev/null
+++ b/src/engine/JackDriver.cpp
@@ -0,0 +1,479 @@
+/*
+ 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 <iostream>
+
+#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 "MachineBuilder.hpp"
+#include "MidiAction.hpp"
+
+using namespace machina;
+using namespace std;
+
+namespace machina {
+
+JackDriver::JackDriver(Forge& forge, SPtr<Machine> machine)
+ : Driver(forge, machine)
+ , _client(NULL)
+ , _machine_changed(0)
+ , _input_port(NULL)
+ , _output_port(NULL)
+ , _context(forge, 48000, MACHINA_PPQN, 120.0)
+ , _frames_unit(TimeUnit::FRAMES, 48000)
+ , _beats_unit(TimeUnit::BEATS, 19200)
+ , _stop(0)
+ , _stop_flag(false)
+ , _record_dur(_frames_unit) // = 0
+ , _is_activated(false)
+{
+ _context.set_sink(this);
+}
+
+JackDriver::~JackDriver()
+{
+ detach();
+}
+
+void
+JackDriver::attach(const std::string& client_name)
+{
+ // Already connected
+ if (_client) {
+ return;
+ }
+
+ jack_set_error_function(jack_error_cb);
+
+ _client = jack_client_open(client_name.c_str(), JackNullOption, NULL, NULL);
+
+ if (_client == NULL) {
+ _is_activated = false;
+ } else {
+ jack_set_error_function(jack_error_cb);
+ jack_on_shutdown(_client, jack_shutdown_cb, this);
+ jack_set_process_callback(_client, jack_process_cb, this);
+ }
+
+ if (jack_client()) {
+
+ _context.time().set_tick_rate(sample_rate());
+
+ _input_port = jack_port_register(jack_client(),
+ "in",
+ JACK_DEFAULT_MIDI_TYPE,
+ JackPortIsInput,
+ 0);
+
+ if (!_input_port) {
+ std::cerr << "WARNING: Failed to create MIDI input port."
+ << std::endl;
+ }
+
+ _output_port = jack_port_register(jack_client(),
+ "out",
+ JACK_DEFAULT_MIDI_TYPE,
+ JackPortIsOutput,
+ 0);
+
+ if (!_output_port) {
+ std::cerr << "WARNING: Failed to create MIDI output port."
+ << std::endl;
+ }
+
+ if (!_machine) {
+ _machine = SPtr<Machine>(
+ new Machine(TimeUnit::frames(
+ jack_get_sample_rate(jack_client()))));
+ }
+ }
+}
+
+void
+JackDriver::detach()
+{
+ if (_is_activated) {
+ _is_activated = false;
+ _stop.timed_wait(std::chrono::seconds(1));
+ }
+
+ if (_input_port) {
+ jack_port_unregister(jack_client(), _input_port);
+ _input_port = NULL;
+ }
+
+ if (_output_port) {
+ jack_port_unregister(jack_client(), _output_port);
+ _output_port = NULL;
+ }
+
+ if (_client) {
+ deactivate();
+ jack_client_close(_client);
+ _client = NULL;
+ _is_activated = false;
+ }
+}
+
+void
+JackDriver::activate()
+{
+ if (!jack_activate(_client)) {
+ _is_activated = true;
+ } else {
+ _is_activated = false;
+ }
+}
+
+void
+JackDriver::deactivate()
+{
+ if (_client) {
+ jack_deactivate(_client);
+ }
+
+ _is_activated = false;
+}
+
+void
+JackDriver::set_machine(SPtr<Machine> machine)
+{
+ if (machine == _machine) {
+ return;
+ }
+
+ SPtr<Machine> last_machine = _last_machine; // Keep a reference
+ _machine_changed.reset(0);
+ assert(!last_machine.unique());
+ _machine = machine;
+ if (is_activated()) {
+ _machine_changed.wait();
+ }
+ assert(_machine == machine);
+ last_machine.reset();
+}
+
+void
+JackDriver::read_input_recording(SPtr<Machine> machine,
+ const Raul::TimeSlice& time)
+{
+ const jack_nframes_t nframes = time.length_ticks().ticks();
+ void* buf = jack_port_get_buffer(_input_port, nframes);
+ const jack_nframes_t n_events = jack_midi_get_event_count(buf);
+
+ for (jack_nframes_t i = 0; i < n_events; ++i) {
+ jack_midi_event_t ev;
+ jack_midi_event_get(&ev, buf, i);
+
+ const TimeStamp rel_time_frames = TimeStamp(_frames_unit, ev.time);
+ const TimeStamp time_frames = _record_dur + rel_time_frames;
+ _recorder->write(time.ticks_to_beats(time_frames), ev.size, ev.buffer);
+ }
+
+ if (n_events > 0) {
+ _recorder->whip();
+ }
+
+ _record_dur += time.length_ticks();
+}
+
+void
+JackDriver::read_input_playing(SPtr<Machine> machine,
+ const Raul::TimeSlice& time)
+{
+ const jack_nframes_t nframes = time.length_ticks().ticks();
+ void* buf = jack_port_get_buffer(_input_port, nframes);
+ const jack_nframes_t n_events = jack_midi_get_event_count(buf);
+
+ for (jack_nframes_t i = 0; i < n_events; ++i) {
+ jack_midi_event_t ev;
+ jack_midi_event_get(&ev, buf, i);
+
+ if (ev.buffer[0] == 0x90) {
+ const SPtr<LearnRequest> learn = machine->pending_learn();
+ if (learn) {
+ learn->enter_action()->set_event(ev.size, ev.buffer);
+ learn->start(_quantization,
+ TimeStamp(TimeUnit::frames(sample_rate()),
+ jack_last_frame_time(_client)
+ + ev.time, 0));
+ }
+
+ } else if (ev.buffer[0] == 0x80) {
+ const SPtr<LearnRequest> learn = machine->pending_learn();
+ if (learn && learn->started()) {
+ learn->exit_action()->set_event(ev.size, ev.buffer);
+ learn->finish(TimeStamp(TimeUnit::frames(sample_rate()),
+ jack_last_frame_time(_client) + ev.time,
+ 0));
+
+ const uint64_t id = Stateful::next_id();
+ write_set(_updates, id,
+ URIs::instance().rdf_type,
+ _forge.make_urid(URIs::instance().machina_MidiAction));
+ write_set(_updates, learn->node()->id(),
+ URIs::instance().machina_enter_action,
+ _forge.make((int32_t)id));
+ write_set(_updates, id,
+ URIs::instance().machina_note_number,
+ _forge.make((int32_t)ev.buffer[1]));
+
+ machine->clear_pending_learn();
+ }
+ }
+ }
+}
+
+void
+JackDriver::write_event(Raul::TimeStamp time,
+ size_t size,
+ const byte* event)
+{
+ if (!_output_port) {
+ return;
+ }
+
+ const Raul::TimeSlice& slice = _context.time();
+
+ if (slice.beats_to_ticks(time) + slice.offset_ticks() <
+ slice.start_ticks()) {
+ std::cerr << "ERROR: Missed event by "
+ << (slice.start_ticks()
+ - slice.beats_to_ticks(time)
+ + slice.offset_ticks())
+ << " ticks"
+ << "\n\tbpm: " << slice.bpm()
+ << "\n\tev time: " << slice.beats_to_ticks(time)
+ << "\n\tcycle_start: " << slice.start_ticks()
+ << "\n\tcycle_end: " << (slice.start_ticks()
+ + slice.length_ticks())
+ << "\n\tcycle_length: " << slice.length_ticks()
+ << std::endl << std::endl;
+ return;
+ }
+
+ const TimeDuration nframes = slice.length_ticks();
+ const TimeStamp offset = slice.beats_to_ticks(time)
+ + slice.offset_ticks() - slice.start_ticks();
+
+ if (!(offset < slice.offset_ticks() + nframes)) {
+ std::cerr << "ERROR: Event offset " << offset << " outside cycle "
+ << "\n\tbpm: " << slice.bpm()
+ << "\n\tev time: " << slice.beats_to_ticks(time)
+ << "\n\tcycle_start: " << slice.start_ticks()
+ << "\n\tcycle_end: " << slice.start_ticks()
+ + slice.length_ticks()
+ << "\n\tcycle_length: " << slice.length_ticks() << std::endl;
+ } else {
+#ifdef JACK_MIDI_NEEDS_NFRAMES
+ jack_midi_event_write(
+ jack_port_get_buffer(_output_port, nframes), offset,
+ event, size, nframes);
+#else
+ jack_midi_event_write(
+ jack_port_get_buffer(_output_port, nframes.ticks()), offset.ticks(),
+ event, size);
+#endif
+ }
+}
+
+void
+JackDriver::on_process(jack_nframes_t nframes)
+{
+ if (!_is_activated) {
+ _stop.post();
+ return;
+ }
+
+ _context.time().set_bpm(_bpm);
+
+ assert(_output_port);
+ jack_midi_clear_buffer(jack_port_get_buffer(_output_port, nframes));
+
+ TimeStamp length_ticks(TimeStamp(_context.time().ticks_unit(), nframes));
+
+ _context.time().set_length(length_ticks);
+ _context.time().set_offset(TimeStamp(_context.time().ticks_unit(), 0, 0));
+
+ /* Take a reference to machine here and use only it during the process
+ * cycle so _machine can be switched with set_machine during a cycle. */
+ SPtr<Machine> machine = _machine;
+
+ // Machine was switched since last cycle, finalize old machine.
+ if (machine != _last_machine) {
+ if (_last_machine) {
+ assert(!_last_machine.unique()); // Realtime, can't delete
+ _last_machine->reset(_context.sink(), _last_machine->time()); // Exit all active states
+ _last_machine.reset(); // Cut our reference
+ }
+ _machine_changed.post(); // Signal we're done with it
+ }
+
+ if (!machine) {
+ _last_machine = machine;
+ goto end;
+ }
+
+ if (_stop_flag) {
+ machine->reset(_context.sink(), _context.time().start_beats());
+ }
+
+ switch (_play_state) {
+ case PlayState::STOPPED:
+ break;
+ case PlayState::PLAYING:
+ read_input_playing(machine, _context.time());
+ break;
+ case PlayState::RECORDING:
+ case PlayState::STEP_RECORDING:
+ read_input_recording(machine, _context.time());
+ break;
+ }
+
+ if (machine->is_empty()) {
+ goto end;
+ }
+
+ while (true) {
+ const uint32_t run_dur_frames = machine->run(_context, _updates);
+
+ if (run_dur_frames == 0) {
+ // Machine didn't run at all (machine has no initial states)
+ machine->reset(_context.sink(), machine->time()); // Try again next cycle
+ _context.time().set_slice(TimeStamp(_frames_unit, 0, 0),
+ TimeStamp(_frames_unit, 0, 0));
+ break;
+
+ } else if (machine->is_finished()) {
+ // Machine ran for portion of cycle and is finished
+ machine->reset(_context.sink(), machine->time());
+
+ _context.time().set_slice(TimeStamp(_frames_unit, 0, 0),
+ TimeStamp(_frames_unit, nframes
+ - run_dur_frames, 0));
+ _context.time().set_offset(TimeStamp(_frames_unit, run_dur_frames,
+ 0));
+
+ } else {
+ // Machine ran for entire cycle
+ _context.time().set_slice(
+ _context.time().start_ticks() + _context.time().length_ticks(),
+ TimeStamp(_frames_unit, 0, 0));
+ break;
+ }
+ }
+
+end:
+ /* Remember the last machine run, in case a switch happens and
+ * we need to finalize it next cycle. */
+ _last_machine = machine;
+
+ if (_stop_flag) {
+ _context.time().set_slice(TimeStamp(_frames_unit, 0, 0),
+ TimeStamp(_frames_unit, 0, 0));
+ _stop_flag = false;
+ _stop.post();
+ }
+}
+
+void
+JackDriver::set_play_state(PlayState state)
+{
+ switch (state) {
+ case PlayState::STOPPED:
+ switch (_play_state) {
+ case PlayState::STOPPED:
+ break;
+ case PlayState::RECORDING:
+ case PlayState::STEP_RECORDING:
+ finish_record();
+ // fallthru
+ case PlayState::PLAYING:
+ _stop_flag = true;
+ _stop.wait();
+ }
+ break;
+ case PlayState::RECORDING:
+ start_record(false);
+ break;
+ case PlayState::STEP_RECORDING:
+ start_record(true);
+ break;
+ case PlayState::PLAYING:
+ if (_play_state == PlayState::RECORDING ||
+ _play_state == PlayState::STEP_RECORDING) {
+ finish_record();
+ }
+ }
+ Driver::set_play_state(state);
+}
+
+void
+JackDriver::start_record(bool step)
+{
+ const double q = (step || _quantize_record) ? _quantization : 0.0;
+ switch (_play_state) {
+ case PlayState::STOPPED:
+ case PlayState::PLAYING:
+ _recorder = SPtr<Recorder>(
+ new Recorder(_forge, 1024, _beats_unit, q, step));
+ _record_dur = 0;
+ break;
+ case PlayState::RECORDING:
+ case PlayState::STEP_RECORDING:
+ _recorder->builder()->set_step(true);
+ break;
+ }
+ _play_state = step ? PlayState::STEP_RECORDING : PlayState::RECORDING;
+}
+
+void
+JackDriver::finish_record()
+{
+ _play_state = PlayState::PLAYING;
+ SPtr<Machine> machine = _recorder->finish();
+ _recorder.reset();
+ _machine->merge(*machine.get());
+}
+
+int
+JackDriver::jack_process_cb(jack_nframes_t nframes, void* jack_driver)
+{
+ JackDriver* me = reinterpret_cast<JackDriver*>(jack_driver);
+ me->on_process(nframes);
+ return 0;
+}
+
+void
+JackDriver::jack_shutdown_cb(void* jack_driver)
+{
+ JackDriver* me = reinterpret_cast<JackDriver*>(jack_driver);
+ me->_client = NULL;
+}
+
+void
+JackDriver::jack_error_cb(const char* msg)
+{
+ cerr << "[JACK] Error: " << msg << endl;
+}
+
+} // namespace machina
diff --git a/src/engine/JackDriver.hpp b/src/engine/JackDriver.hpp
new file mode 100644
index 0000000..4ebfaa1
--- /dev/null
+++ b/src/engine/JackDriver.hpp
@@ -0,0 +1,119 @@
+/*
+ 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/>.
+*/
+
+#ifndef MACHINA_JACKDRIVER_HPP
+#define MACHINA_JACKDRIVER_HPP
+
+#include <jack/jack.h>
+#include <jack/midiport.h>
+
+#include "raul/Semaphore.hpp"
+
+#include "machina/Context.hpp"
+#include "machina/Driver.hpp"
+#include "machina/Machine.hpp"
+#include "machina/types.hpp"
+
+#include "Recorder.hpp"
+
+namespace machina {
+
+class MidiAction;
+class Node;
+
+/** Realtime JACK Driver.
+ *
+ * "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
+{
+public:
+ JackDriver(Forge& forge,
+ SPtr<Machine> machine = SPtr<Machine>());
+ ~JackDriver();
+
+ void attach(const std::string& client_name);
+ void detach();
+
+ void activate();
+ void deactivate();
+
+ void set_machine(SPtr<Machine> machine);
+
+ void write_event(Raul::TimeStamp time,
+ size_t size,
+ const unsigned char* event);
+
+ void set_play_state(PlayState state);
+
+ void start_transport() { jack_transport_start(_client); }
+ void stop_transport() { jack_transport_stop(_client); }
+
+ void rewind_transport() {
+ jack_position_t zero;
+ zero.frame = 0;
+ zero.valid = (jack_position_bits_t)0;
+ jack_transport_reposition(_client, &zero);
+ }
+
+ bool is_activated() const { return _is_activated; }
+ bool is_attached() const { return _client != NULL; }
+ bool is_realtime() const { return _client && jack_is_realtime(_client); }
+
+ jack_nframes_t sample_rate() const { return jack_get_sample_rate(_client); }
+ jack_client_t* jack_client() const { return _client; }
+
+private:
+ void read_input_recording(SPtr<Machine> machine,
+ const Raul::TimeSlice& time);
+
+ void read_input_playing(SPtr<Machine> machine,
+ const Raul::TimeSlice& time);
+
+ static void jack_error_cb(const char* msg);
+ 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;
+
+ Raul::Semaphore _machine_changed;
+ SPtr<Machine> _last_machine;
+
+ jack_port_t* _input_port;
+ jack_port_t* _output_port;
+
+ Context _context;
+
+ Raul::TimeUnit _frames_unit;
+ Raul::TimeUnit _beats_unit;
+
+ Raul::Semaphore _stop;
+ bool _stop_flag;
+
+ Raul::TimeDuration _record_dur;
+ SPtr<Recorder> _recorder;
+ bool _is_activated;
+};
+
+} // namespace machina
+
+#endif // MACHINA_JACKDRIVER_HPP
diff --git a/src/engine/LearnRequest.cpp b/src/engine/LearnRequest.cpp
new file mode 100644
index 0000000..5756dd1
--- /dev/null
+++ b/src/engine/LearnRequest.cpp
@@ -0,0 +1,56 @@
+/*
+ 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 "LearnRequest.hpp"
+#include "quantize.hpp"
+
+namespace machina {
+
+LearnRequest::LearnRequest(SPtr<Raul::Maid> maid, SPtr<Node> node)
+ : _started(false)
+ , _start_time(TimeUnit(TimeUnit::BEATS, 19200), 0, 0) // irrelevant
+ , _quantization(0) // irrelevant
+ , _node(node)
+ , _enter_action(new MidiAction(4, NULL))
+ , _exit_action(new MidiAction(4, NULL))
+{
+}
+
+SPtr<LearnRequest>
+LearnRequest::create(SPtr<Raul::Maid> maid, SPtr<Node> node)
+{
+ return SPtr<LearnRequest>(new LearnRequest(maid, node));
+}
+
+void
+LearnRequest::start(double q, Raul::TimeStamp time)
+{
+ _started = true;
+ _start_time = time;
+ _quantization = q;
+}
+
+/** Add the learned actions to the node */
+void
+LearnRequest::finish(TimeStamp time)
+{
+ _node->set_enter_action(_enter_action);
+ _node->set_exit_action(_exit_action);
+
+ _node->set_duration(time - _start_time);
+}
+
+}
diff --git a/src/engine/LearnRequest.hpp b/src/engine/LearnRequest.hpp
new file mode 100644
index 0000000..9cdfc3c
--- /dev/null
+++ b/src/engine/LearnRequest.hpp
@@ -0,0 +1,61 @@
+/*
+ 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/>.
+*/
+
+#ifndef MACHINA_LEARNREQUEST_HPP
+#define MACHINA_LEARNREQUEST_HPP
+
+#include "raul/Maid.hpp"
+
+#include "machina/types.hpp"
+
+#include "MidiAction.hpp"
+#include "Node.hpp"
+
+namespace machina {
+
+class Node;
+class MidiAction;
+
+/** A request to MIDI learn a certain node.
+ */
+class LearnRequest : public Raul::Maid::Disposable
+{
+public:
+ static SPtr<LearnRequest> create(SPtr<Raul::Maid> maid, SPtr<Node> node);
+
+ void start(double q, Raul::TimeStamp time);
+ void finish(TimeStamp time);
+
+ inline bool started() const { return _started; }
+
+ const SPtr<Node>& node() { return _node; }
+ const SPtr<MidiAction>& enter_action() { return _enter_action; }
+ const SPtr<MidiAction>& exit_action() { return _exit_action; }
+
+private:
+ LearnRequest(SPtr<Raul::Maid> maid, SPtr<Node> node);
+
+ bool _started;
+ TimeStamp _start_time;
+ double _quantization;
+ SPtr<Node> _node;
+ SPtr<MidiAction> _enter_action;
+ SPtr<MidiAction> _exit_action;
+};
+
+} // namespace machina
+
+#endif // MACHINA_LEARNREQUEST_HPP
diff --git a/src/engine/Loader.cpp b/src/engine/Loader.cpp
new file mode 100644
index 0000000..1c662d2
--- /dev/null
+++ b/src/engine/Loader.cpp
@@ -0,0 +1,193 @@
+/*
+ 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 <cmath>
+#include <iostream>
+#include <map>
+
+#include "lv2/lv2plug.in/ns/ext/midi/midi.h"
+
+#include "machina/Loader.hpp"
+#include "machina/URIs.hpp"
+#include "machina/Machine.hpp"
+
+#include "Edge.hpp"
+#include "MidiAction.hpp"
+#include "Node.hpp"
+#include "SMFDriver.hpp"
+#include "machina_config.h"
+
+using namespace Raul;
+using namespace std;
+
+namespace machina {
+
+Loader::Loader(Forge& forge, Sord::World& rdf_world)
+ : _forge(forge)
+ , _rdf_world(rdf_world)
+{}
+
+static SPtr<Action>
+load_action(Sord::Model& model, Sord::Node node)
+{
+ if (!node.is_valid()) {
+ return SPtr<Action>();
+ }
+
+ Sord::URI rdf_type(model.world(), MACHINA_URI_RDF "type");
+ Sord::URI midi_NoteOn(model.world(), LV2_MIDI__NoteOn);
+ Sord::URI midi_NoteOff(model.world(), LV2_MIDI__NoteOff);
+ Sord::URI midi_noteNumber(model.world(), LV2_MIDI__noteNumber);
+ Sord::URI midi_velocity(model.world(), LV2_MIDI__velocity);
+
+ Sord::Node type = model.get(node, rdf_type, Sord::Node());
+ uint8_t status = 0;
+ if (type == midi_NoteOn) {
+ status = LV2_MIDI_MSG_NOTE_ON;
+ } else if (type == midi_NoteOff) {
+ status = LV2_MIDI_MSG_NOTE_OFF;
+ } else {
+ return SPtr<Action>();
+ }
+
+ Sord::Node num_node = model.get(node, midi_noteNumber, Sord::Node());
+ Sord::Node vel_node = model.get(node, midi_velocity, Sord::Node());
+
+ const uint8_t num = num_node.is_int() ? num_node.to_int() : 64;
+ const uint8_t vel = vel_node.is_int() ? vel_node.to_int() : 64;
+ const uint8_t event[3] = { status, num, vel };
+
+ return SPtr<Action>(new MidiAction(sizeof(event), event));
+}
+
+/** Load (create) all objects from RDF into the engine.
+ *
+ * @param uri URI of machine (resolvable URI to an RDF document).
+ * @return Loaded Machine.
+ */
+SPtr<Machine>
+Loader::load(const std::string& uri)
+{
+ std::string document_uri = uri;
+
+ // If "URI" doesn't contain a colon, try to resolve as a filename
+ if (uri.find(":") == string::npos) {
+ document_uri = "file://" + document_uri;
+ }
+
+ cout << "Loading " << document_uri << endl;
+
+ TimeUnit beats(TimeUnit::BEATS, MACHINA_PPQN);
+
+ SPtr<Machine> machine(new Machine(beats));
+
+ typedef std::map<Sord::Node, SPtr<Node> > Created;
+ Created created;
+
+ Sord::URI base_uri(_rdf_world, document_uri);
+ Sord::Model model(_rdf_world, document_uri);
+
+ SerdEnv* env = serd_env_new(base_uri.to_serd_node());
+ model.load_file(env, SERD_TURTLE, document_uri);
+ serd_env_free(env);
+
+ Sord::Node nil;
+
+ Sord::URI machina_SelectorNode(_rdf_world, MACHINA_NS_SelectorNode);
+ Sord::URI machina_duration(_rdf_world, MACHINA_NS_duration);
+ Sord::URI machina_edge(_rdf_world, MACHINA_NS_arc);
+ Sord::URI machina_head(_rdf_world, MACHINA_NS_head);
+ Sord::URI machina_node(_rdf_world, MACHINA_NS_node);
+ Sord::URI machina_onEnter(_rdf_world, MACHINA_NS_onEnter);
+ Sord::URI machina_onExit(_rdf_world, MACHINA_NS_onExit);
+ Sord::URI machina_probability(_rdf_world, MACHINA_NS_probability);
+ Sord::URI machina_start(_rdf_world, MACHINA_NS_start);
+ Sord::URI machina_tail(_rdf_world, MACHINA_NS_tail);
+ Sord::URI rdf_type(_rdf_world, MACHINA_URI_RDF "type");
+
+ Sord::Node subject = base_uri;
+
+ // Get start node ID (but re-use existing start node)
+ Sord::Iter i = model.find(subject, machina_start, nil);
+ if (i.end()) {
+ cerr << "error: Machine has no start node" << std::endl;
+ }
+ created[i.get_object()] = machine->initial_node();
+
+ // Get remaining nodes
+ for (Sord::Iter i = model.find(subject, machina_node, nil); !i.end(); ++i) {
+ const Sord::Node& id = i.get_object();
+ if (created.find(id) != created.end()) {
+ cerr << "warning: Machine lists the same node twice" << std::endl;
+ continue;
+ }
+
+ // Create node
+ Sord::Iter d = model.find(id, machina_duration, nil);
+ SPtr<Node> node(new Node(TimeStamp(beats, d.get_object().to_float())));
+ machine->add_node(node);
+ created[id] = node;
+
+ node->set_enter_action(
+ load_action(model, model.get(id, machina_onEnter, nil)));
+ node->set_exit_action(
+ load_action(model, model.get(id, machina_onExit, nil)));
+ }
+
+ // Get arcs
+ for (Sord::Iter i = model.find(subject, machina_edge, nil); !i.end(); ++i) {
+ Sord::Node edge = i.get_object();
+ Sord::Iter t = model.find(edge, machina_tail, nil);
+ Sord::Iter h = model.find(edge, machina_head, nil);
+ Sord::Iter p = model.find(edge, machina_probability, nil);
+
+ Sord::Node tail = t.get_object();
+ Sord::Node head = h.get_object();
+ Sord::Node probability = p.get_object();
+
+ float prob = probability.to_float();
+
+ Created::iterator tail_i = created.find(tail);
+ Created::iterator head_i = created.find(head);
+
+ if (tail_i != created.end() && head_i != created.end()) {
+ const SPtr<Node> tail = tail_i->second;
+ const SPtr<Node> head = head_i->second;
+ tail->add_edge(SPtr<Edge>(new Edge(tail, head, prob)));
+ } else {
+ cerr << "warning: Ignored edge between unknown nodes "
+ << tail << " -> " << head << endl;
+ }
+ }
+
+ if (machine && !machine->nodes().empty()) {
+ machine->reset(NULL, machine->time());
+ return machine;
+ } else {
+ return SPtr<Machine>();
+ }
+}
+
+SPtr<Machine>
+Loader::load_midi(const std::string& uri,
+ double q,
+ Raul::TimeDuration dur)
+{
+ SPtr<SMFDriver> file_driver(new SMFDriver(_forge, dur.unit()));
+ return file_driver->learn(uri, q, dur);
+}
+
+} // namespace machina
diff --git a/src/engine/MIDISink.hpp b/src/engine/MIDISink.hpp
new file mode 100644
index 0000000..983dbba
--- /dev/null
+++ b/src/engine/MIDISink.hpp
@@ -0,0 +1,37 @@
+/*
+ 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/>.
+*/
+
+#ifndef MACHINA_MIDI_SINK_HPP
+#define MACHINA_MIDI_SINK_HPP
+
+#include "raul/Deletable.hpp"
+#include "raul/TimeStamp.hpp"
+
+namespace machina {
+
+/** Pure virtual base for anything you can write MIDI to. */
+class MIDISink
+ : public Raul::Deletable
+{
+public:
+ virtual void write_event(Raul::TimeStamp time,
+ size_t ev_size,
+ const uint8_t* ev) = 0;
+};
+
+} // namespace machina
+
+#endif // MACHINA_MIDI_SINK_HPP
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 (&copy == 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
diff --git a/src/engine/MachineBuilder.cpp b/src/engine/MachineBuilder.cpp
new file mode 100644
index 0000000..d812b06
--- /dev/null
+++ b/src/engine/MachineBuilder.cpp
@@ -0,0 +1,325 @@
+/*
+ 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 "lv2/lv2plug.in/ns/ext/midi/midi.h"
+
+#include "machina/Machine.hpp"
+#include "machina/types.hpp"
+
+#include "Edge.hpp"
+#include "MachineBuilder.hpp"
+#include "MidiAction.hpp"
+#include "Node.hpp"
+#include "quantize.hpp"
+
+using namespace std;
+using namespace Raul;
+
+namespace machina {
+
+MachineBuilder::MachineBuilder(SPtr<Machine> machine, double q, bool step)
+ : _quantization(q)
+ , _time(machine->time().unit()) // = 0
+ , _machine(machine)
+ , _initial_node(machine->initial_node()) // duration 0
+ , _connect_node(_initial_node)
+ , _connect_node_end_time(_time) // = 0
+ , _step_duration(_time.unit(), q)
+ , _step(step)
+{}
+
+void
+MachineBuilder::reset()
+{
+ _time = TimeStamp(_machine->time().unit()); // = 0
+ _connect_node = _initial_node;
+ _connect_node_end_time = _time; // = 0
+}
+
+bool
+MachineBuilder::is_delay_node(SPtr<Node> node) const
+{
+ return node != _initial_node &&
+ !node->enter_action() &&
+ !node->exit_action();
+}
+
+/** Set the duration of a node, with quantization.
+ */
+void
+MachineBuilder::set_node_duration(SPtr<Node> node,
+ Raul::TimeDuration d) const
+{
+ if (_step) {
+ node->set_duration(_step_duration);
+ return;
+ }
+
+ Raul::TimeStamp q_dur = quantize(TimeStamp(d.unit(), _quantization), d);
+
+ // Never quantize a note to duration 0
+ if (q_dur.is_zero() && (node->enter_action() || node->exit_action())) {
+ q_dur = _quantization; // Round up
+ }
+ node->set_duration(q_dur);
+}
+
+/** Connect two nodes, inserting a delay node between them if necessary.
+ *
+ * If a delay node is added to the machine, it is returned.
+ */
+SPtr<Node>
+MachineBuilder::connect_nodes(SPtr<Machine> m,
+ SPtr<Node> tail,
+ Raul::TimeStamp tail_end_time,
+ SPtr<Node> head,
+ Raul::TimeStamp head_start_time)
+{
+ SPtr<Node> delay_node;
+ if (tail == head) {
+ return delay_node;
+ }
+
+ 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);
+ tail->add_edge(SPtr<Edge>(new Edge(tail, head)));
+ } else if (_step || (head_start_time == tail_end_time)) {
+ // Connect directly
+ tail->add_edge(SPtr<Edge>(new Edge(tail, head)));
+ } else {
+ // Need to actually create a delay node
+ delay_node = SPtr<Node>(new Node(head_start_time - tail_end_time));
+ tail->add_edge(SPtr<Edge>(new Edge(tail, delay_node)));
+ delay_node->add_edge(SPtr<Edge>(new Edge(delay_node, head)));
+ m->add_node(delay_node);
+ }
+
+ return delay_node;
+}
+
+void
+MachineBuilder::note_on(Raul::TimeStamp t, size_t ev_size, uint8_t* buf)
+{
+ SPtr<Node> node;
+ if (_step && _poly_nodes.empty() && is_delay_node(_connect_node)) {
+ /* Stepping and the connect node is the merge node after a polyphonic
+ group. Re-use it to avoid creating delay nodes in step mode. */
+ node = _connect_node;
+ node->set_duration(_step_duration);
+ } else {
+ node = SPtr<Node>(new Node(default_duration()));
+ }
+
+ node->set_enter_action(SPtr<Action>(new MidiAction(ev_size, buf)));
+
+ if (_step && _poly_nodes.empty()) {
+ t = _time = _time + _step_duration; // Advance time one step
+ }
+
+ SPtr<Node> this_connect_node;
+ Raul::TimeStamp this_connect_node_end_time(t.unit());
+
+ /* If currently polyphonic, use a poly node with no successors as connect
+ node, for more sensible patterns like what a human would build. */
+ if (!_poly_nodes.empty()) {
+ for (PolyList::iterator j = _poly_nodes.begin();
+ j != _poly_nodes.end(); ++j) {
+ if (j->second->edges().empty()) {
+ this_connect_node = j->second;
+ this_connect_node_end_time = j->first + j->second->duration();
+ break;
+ }
+ }
+ }
+
+ /* Currently monophonic, or didn't find a poly node, so use _connect_node
+ which is maintained below on note off events. */
+ if (!this_connect_node) {
+ this_connect_node = _connect_node;
+ this_connect_node_end_time = _connect_node_end_time;
+ }
+
+ SPtr<Node> delay_node = connect_nodes(
+ _machine, this_connect_node, this_connect_node_end_time, node, t);
+
+ if (delay_node) {
+ _connect_node = delay_node;
+ _connect_node_end_time = t;
+ }
+
+ node->enter(NULL, t);
+ _active_nodes.push_back(node);
+}
+
+void
+MachineBuilder::resolve_note(Raul::TimeStamp time,
+ size_t ev_size,
+ uint8_t* buf,
+ SPtr<Node> resolved)
+{
+ resolved->set_exit_action(SPtr<Action>(new MidiAction(ev_size, buf)));
+
+ if (_active_nodes.size() == 1) {
+ if (_step) {
+ time = _time = _time + _step_duration;
+ }
+
+ // Last active note
+ _connect_node_end_time = time;
+
+ if (!_poly_nodes.empty()) {
+ // Finish a polyphonic section
+ _connect_node = SPtr<Node>(new Node(TimeStamp(_time.unit(), 0, 0)));
+ _machine->add_node(_connect_node);
+
+ connect_nodes(_machine, resolved, time, _connect_node, time);
+
+ for (PolyList::iterator j = _poly_nodes.begin();
+ j != _poly_nodes.end(); ++j) {
+ _machine->add_node(j->second);
+ if (j->second->edges().empty()) {
+ connect_nodes(_machine, j->second,
+ j->first + j->second->duration(),
+ _connect_node, time);
+ }
+ }
+ _poly_nodes.clear();
+
+ _machine->add_node(resolved);
+
+ } else {
+ // Just monophonic
+ if (is_delay_node(_connect_node)
+ && _connect_node->duration().is_zero()
+ && (_connect_node->edges().size() == 1)
+ && ((*_connect_node->edges().begin())->head() == resolved)) {
+ // Trim useless delay node if possible (after poly sections)
+
+ _connect_node->edges().clear();
+ _connect_node->set_enter_action(resolved->enter_action());
+ _connect_node->set_exit_action(resolved->exit_action());
+ resolved->set_enter_action(SPtr<Action>());
+ resolved->set_exit_action(SPtr<Action>());
+ set_node_duration(_connect_node, resolved->duration());
+ resolved = _connect_node;
+ _machine->add_node(_connect_node);
+
+ } else {
+ _connect_node = resolved;
+ _machine->add_node(resolved);
+ }
+ }
+
+ } else {
+ // Polyphonic, add this node to poly list
+ _poly_nodes.push_back(make_pair(resolved->enter_time(), resolved));
+ _connect_node = resolved;
+ _connect_node_end_time = _time;
+ }
+
+ if (resolved->is_active()) {
+ resolved->exit(NULL, _time);
+ }
+}
+
+void
+MachineBuilder::event(Raul::TimeStamp time,
+ size_t ev_size,
+ uint8_t* buf)
+{
+ if (ev_size == 0) {
+ return;
+ }
+
+ if (!_step) {
+ _time = time;
+ }
+
+ if ((buf[0] & 0xF0) == LV2_MIDI_MSG_NOTE_ON) {
+ note_on(time, ev_size, buf);
+ } else if ((buf[0] & 0xF0) == LV2_MIDI_MSG_NOTE_OFF) {
+ for (ActiveList::iterator i = _active_nodes.begin();
+ i != _active_nodes.end(); ++i) {
+ SPtr<MidiAction> action = dynamic_ptr_cast<MidiAction>(
+ (*i)->enter_action());
+ if (!action) {
+ continue;
+ }
+
+ const size_t ev_size = action->event_size();
+ const uint8_t* ev = action->event();
+
+ if ((ev[0] & 0xF0) == LV2_MIDI_MSG_NOTE_ON &&
+ (ev[0] & 0x0F) == (buf[0] & 0x0F) &&
+ ev[1] == buf[1]) {
+ // Same channel and note as on event
+ resolve_note(time, ev_size, buf, *i);
+ _active_nodes.erase(i);
+ break;
+ }
+ }
+ }
+}
+
+/** Finish the constructed machine and prepare it for use.
+ * Resolve any stuck notes, quantize, etc.
+ */
+void
+MachineBuilder::resolve()
+{
+ // Resolve stuck notes
+ if (!_active_nodes.empty()) {
+ for (list<SPtr<Node> >::iterator i = _active_nodes.begin();
+ i != _active_nodes.end(); ++i) {
+ cerr << "WARNING: Resolving stuck note." << endl;
+ SPtr<MidiAction> action = dynamic_ptr_cast<MidiAction>(
+ (*i)->enter_action());
+ if (!action) {
+ continue;
+ }
+
+ const size_t ev_size = action->event_size();
+ const uint8_t* ev = action->event();
+ if (ev_size == 3 && (ev[0] & 0xF0) == LV2_MIDI_MSG_NOTE_ON) {
+ uint8_t st((LV2_MIDI_MSG_NOTE_OFF & 0xF0) | (ev[0] & 0x0F));
+ const uint8_t note_off[3] = { st, ev[1], 0x40 };
+ (*i)->set_exit_action(
+ SPtr<Action>(new MidiAction(3, note_off)));
+ set_node_duration((*i), _time - (*i)->enter_time());
+ (*i)->exit(NULL, _time);
+ _machine->add_node((*i));
+ }
+ }
+ _active_nodes.clear();
+ }
+
+ // Add initial node if necessary
+ if (!_machine->nodes().empty()) {
+ _machine->add_node(_initial_node);
+ }
+}
+
+SPtr<Machine>
+MachineBuilder::finish()
+{
+ resolve();
+
+ return _machine;
+}
+
+} // namespace machina
diff --git a/src/engine/MachineBuilder.hpp b/src/engine/MachineBuilder.hpp
new file mode 100644
index 0000000..83baf78
--- /dev/null
+++ b/src/engine/MachineBuilder.hpp
@@ -0,0 +1,87 @@
+/*
+ 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/>.
+*/
+
+#ifndef MACHINA_MACHINEBUILDER_HPP
+#define MACHINA_MACHINEBUILDER_HPP
+
+#include <stdint.h>
+
+#include <list>
+
+#include "machina/types.hpp"
+#include "raul/TimeStamp.hpp"
+
+namespace machina {
+
+class Machine;
+class Node;
+
+class MachineBuilder
+{
+public:
+ MachineBuilder(SPtr<Machine> machine,
+ double quantization,
+ bool step);
+
+ void event(Raul::TimeStamp time, size_t size, unsigned char* buf);
+
+ void set_step(bool step) { _step = step; }
+
+ void reset();
+ void resolve();
+
+ SPtr<Machine> finish();
+
+private:
+ bool is_delay_node(SPtr<Node> node) const;
+ void set_node_duration(SPtr<Node> node, Raul::TimeDuration d) const;
+
+ void note_on(Raul::TimeStamp t, size_t ev_size, uint8_t* buf);
+
+ void resolve_note(Raul::TimeStamp t,
+ size_t ev_size,
+ uint8_t* buf,
+ SPtr<Node> resolved);
+
+ SPtr<Node>connect_nodes(SPtr<Machine> m,
+ SPtr<Node> tail,
+ Raul::TimeStamp tail_end_time,
+ SPtr<Node> head,
+ Raul::TimeStamp head_start_time);
+
+ Raul::TimeStamp default_duration() {
+ return _step ? _step_duration : Raul::TimeStamp(_time.unit(), 0, 0);
+ }
+
+ typedef std::list<SPtr<Node> > ActiveList;
+ ActiveList _active_nodes;
+
+ typedef std::list<std::pair<Raul::TimeStamp, SPtr<Node> > > PolyList;
+ PolyList _poly_nodes;
+
+ double _quantization;
+ Raul::TimeStamp _time;
+ SPtr<Machine> _machine;
+ SPtr<Node> _initial_node;
+ SPtr<Node> _connect_node;
+ Raul::TimeStamp _connect_node_end_time;
+ Raul::TimeStamp _step_duration;
+ bool _step;
+};
+
+} // namespace machina
+
+#endif // MACHINA_MACHINEBUILDER_HPP
diff --git a/src/engine/MidiAction.cpp b/src/engine/MidiAction.cpp
new file mode 100644
index 0000000..b5df155
--- /dev/null
+++ b/src/engine/MidiAction.cpp
@@ -0,0 +1,103 @@
+/*
+ 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 "lv2/lv2plug.in/ns/ext/midi/midi.h"
+#include "machina/Atom.hpp"
+#include "machina/URIs.hpp"
+#include "machina/types.hpp"
+
+#include "MIDISink.hpp"
+#include "MidiAction.hpp"
+
+namespace machina {
+
+/** Create a MIDI action.
+ *
+ * Creating a NULL MidiAction is okay, pass event=NULL and the action will
+ * simply do nothing until a set_event (for MIDI learning).
+ */
+MidiAction::MidiAction(size_t size,
+ const byte* event)
+ : _size(0)
+ , _max_size(size)
+{
+ _event = new byte[_max_size];
+ set_event(size, event);
+}
+
+MidiAction::~MidiAction()
+{
+ delete[] _event.load();
+}
+
+bool
+MidiAction::set_event(size_t size, const byte* new_event)
+{
+ byte* const event = _event.load();
+ if (size <= _max_size) {
+ _event = NULL;
+ if (size > 0 && new_event) {
+ memcpy(event, new_event, size);
+ }
+ _size = size;
+ _event = event;
+ return true;
+ } else {
+ return false;
+ }
+}
+
+void
+MidiAction::execute(MIDISink* sink, Raul::TimeStamp time)
+{
+ const byte* const ev = _event.load();
+ if (ev && sink) {
+ sink->write_event(time, _size, ev);
+ }
+}
+
+void
+MidiAction::write_state(Sord::Model& model)
+{
+ const uint8_t* ev = event();
+ const uint8_t type = (ev[0] & 0xF0);
+ if (type == LV2_MIDI_MSG_NOTE_ON) {
+ model.add_statement(
+ rdf_id(model.world()),
+ Sord::URI(model.world(), MACHINA_URI_RDF "type"),
+ Sord::URI(model.world(), LV2_MIDI__NoteOn));
+ } else if (type == LV2_MIDI_MSG_NOTE_OFF) {
+ model.add_statement(
+ rdf_id(model.world()),
+ Sord::URI(model.world(), MACHINA_URI_RDF "type"),
+ Sord::URI(model.world(), LV2_MIDI__NoteOff));
+ } else {
+ std::cerr << "warning: Unable to serialise MIDI event" << std::endl;
+ }
+
+ model.add_statement(
+ rdf_id(model.world()),
+ Sord::URI(model.world(), LV2_MIDI__noteNumber),
+ Sord::Literal::integer(model.world(), (int)ev[1]));
+ if (ev[2] != 64) {
+ model.add_statement(
+ rdf_id(model.world()),
+ Sord::URI(model.world(), LV2_MIDI__velocity),
+ Sord::Literal::integer(model.world(), (int)ev[2]));
+ }
+}
+
+} // namespace machina
diff --git a/src/engine/MidiAction.hpp b/src/engine/MidiAction.hpp
new file mode 100644
index 0000000..64a2c65
--- /dev/null
+++ b/src/engine/MidiAction.hpp
@@ -0,0 +1,57 @@
+/*
+ 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/>.
+*/
+
+#ifndef MACHINA_MIDIACTION_HPP
+#define MACHINA_MIDIACTION_HPP
+
+#include <atomic>
+
+#include "raul/TimeSlice.hpp"
+
+#include "machina/types.hpp"
+
+#include "Action.hpp"
+
+namespace machina {
+
+class MIDISink;
+
+class MidiAction : public Action
+{
+public:
+ ~MidiAction();
+
+ MidiAction(size_t size,
+ const unsigned char* event);
+
+ size_t event_size() { return _size; }
+ byte* event() { return _event.load(); }
+
+ bool set_event(size_t size, const byte* event);
+
+ void execute(MIDISink* sink, Raul::TimeStamp time);
+
+ virtual void write_state(Sord::Model& model);
+
+private:
+ size_t _size;
+ const size_t _max_size;
+ std::atomic<byte*> _event;
+};
+
+} // namespace machina
+
+#endif // MACHINA_MIDIACTION_HPP
diff --git a/src/engine/Mutation.cpp b/src/engine/Mutation.cpp
new file mode 100644
index 0000000..fed2ad4
--- /dev/null
+++ b/src/engine/Mutation.cpp
@@ -0,0 +1,190 @@
+/*
+ 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 <iostream>
+#include <cstdlib>
+
+#include "machina/Machine.hpp"
+#include "machina/Mutation.hpp"
+
+#include "ActionFactory.hpp"
+#include "Edge.hpp"
+#include "MidiAction.hpp"
+
+using namespace std;
+
+namespace machina {
+namespace Mutation {
+
+void
+Compress::mutate(Random& rng, Machine& machine)
+{
+ //cout << "COMPRESS" << endl;
+
+ // Trim disconnected nodes
+ for (Machine::Nodes::iterator i = machine.nodes().begin();
+ i != machine.nodes().end(); ) {
+ Machine::Nodes::iterator next = i;
+ ++next;
+
+ if ((*i)->edges().empty()) {
+ machine.remove_node(*i);
+ }
+
+ i = next;
+ }
+}
+
+void
+AddNode::mutate(Random& rng, Machine& machine)
+{
+ //cout << "ADD NODE" << endl;
+
+ // Create random node
+ SPtr<Node> node(new Node(Raul::TimeDuration{machine.time().unit()}));
+ node->set_selector(true);
+
+ SPtr<Node> note_node = machine.random_node();
+ if (!note_node) {
+ return;
+ }
+
+ uint8_t note = rand() % 128;
+
+ SPtr<MidiAction> enter_action = dynamic_ptr_cast<MidiAction>(
+ note_node->enter_action());
+ if (enter_action) {
+ note = enter_action->event()[1];
+ }
+
+ node->set_enter_action(ActionFactory::note_on(note));
+ node->set_exit_action(ActionFactory::note_off(note));
+ machine.add_node(node);
+
+ // Insert after some node
+ SPtr<Node> tail = machine.random_node();
+ if (tail && (tail != node) /* && !node->connected_to(tail)*/) {
+ tail->add_edge(SPtr<Edge>(new Edge(tail, node)));
+ }
+
+ // Insert before some other node
+ SPtr<Node> head = machine.random_node();
+ if (head && (head != node) /* && !head->connected_to(node)*/) {
+ node->add_edge(SPtr<Edge>(new Edge(node, head)));
+ }
+}
+
+void
+RemoveNode::mutate(Random& rng, Machine& machine)
+{
+ //cout << "REMOVE NODE" << endl;
+
+ SPtr<Node> node = machine.random_node();
+ if (node && !node->is_initial()) {
+ machine.remove_node(node);
+ }
+}
+
+void
+AdjustNode::mutate(Random& rng, Machine& machine)
+{
+ //cout << "ADJUST NODE" << endl;
+
+ SPtr<Node> node = machine.random_node();
+ if (node) {
+ SPtr<MidiAction> enter_action = dynamic_ptr_cast<MidiAction>(
+ node->enter_action());
+ SPtr<MidiAction> exit_action = dynamic_ptr_cast<MidiAction>(
+ node->exit_action());
+ if (enter_action && exit_action) {
+ const uint8_t note = rand() % 128;
+ enter_action->event()[1] = note;
+ exit_action->event()[1] = note;
+ }
+ node->set_changed();
+ }
+}
+
+void
+SwapNodes::mutate(Random& rng, Machine& machine)
+{
+ //cout << "SWAP NODE" << endl;
+
+ if (machine.nodes().size() <= 1) {
+ return;
+ }
+
+ SPtr<Node> a = machine.random_node();
+ SPtr<Node> b = machine.random_node();
+ while (b == a) {
+ b = machine.random_node();
+ }
+
+ SPtr<MidiAction> a_enter = dynamic_ptr_cast<MidiAction>(a->enter_action());
+ SPtr<MidiAction> a_exit = dynamic_ptr_cast<MidiAction>(a->exit_action());
+ SPtr<MidiAction> b_enter = dynamic_ptr_cast<MidiAction>(b->enter_action());
+ SPtr<MidiAction> b_exit = dynamic_ptr_cast<MidiAction>(b->exit_action());
+
+ uint8_t note_a = a_enter->event()[1];
+ uint8_t note_b = b_enter->event()[1];
+
+ a_enter->event()[1] = note_b;
+ a_exit->event()[1] = note_b;
+ b_enter->event()[1] = note_a;
+ b_exit->event()[1] = note_a;
+}
+
+void
+AddEdge::mutate(Random& rng, Machine& machine)
+{
+ //cout << "ADJUST EDGE" << endl;
+
+ SPtr<Node> tail = machine.random_node();
+ SPtr<Node> head = machine.random_node();
+
+ if (tail && head && tail != head) {
+ // && !tail->connected_to(head) && !head->connected_to(tail)
+ SPtr<Edge> edge(new Edge(tail, head));
+ edge->set_probability(rand() / (float)RAND_MAX);
+ tail->add_edge(SPtr<Edge>(new Edge(tail, head)));
+ }
+}
+
+void
+RemoveEdge::mutate(Random& rng, Machine& machine)
+{
+ //cout << "REMOVE EDGE" << endl;
+
+ SPtr<Node> tail = machine.random_node();
+ if (tail && !(tail->is_initial() && tail->edges().size() == 1)) {
+ tail->remove_edge(tail->random_edge());
+ }
+}
+
+void
+AdjustEdge::mutate(Random& rng, Machine& machine)
+{
+ //cout << "ADJUST EDGE" << endl;
+
+ SPtr<Edge> edge = machine.random_edge();
+ if (edge) {
+ edge->set_probability(rand() / (float)RAND_MAX);
+ edge->tail().lock()->edges_changed();
+ }
+}
+
+} // namespace Mutation
+} // namespace machina
diff --git a/src/engine/Node.cpp b/src/engine/Node.cpp
new file mode 100644
index 0000000..7d8a870
--- /dev/null
+++ b/src/engine/Node.cpp
@@ -0,0 +1,271 @@
+/*
+ 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 <cassert>
+#include <iostream>
+
+#include "sord/sordmm.hpp"
+
+#include "machina/Atom.hpp"
+#include "machina/URIs.hpp"
+
+#include "ActionFactory.hpp"
+#include "Edge.hpp"
+#include "Node.hpp"
+
+using namespace Raul;
+using namespace std;
+
+namespace machina {
+
+Node::Node(TimeDuration duration, bool initial)
+ : _enter_time(duration.unit())
+ , _duration(duration)
+ , _is_initial(initial)
+ , _is_selector(false)
+ , _is_active(false)
+{}
+
+Node::Node(const Node& copy)
+ : Stateful() // don't copy RDF ID
+ , _enter_time(copy._enter_time)
+ , _duration(copy._duration)
+ , _enter_action(ActionFactory::copy(copy._enter_action))
+ , _exit_action(ActionFactory::copy(copy._exit_action))
+ , _is_initial(copy._is_initial)
+ , _is_selector(copy._is_selector)
+ , _is_active(false)
+{
+ for (Edges::const_iterator i = copy._edges.begin(); i != copy._edges.end();
+ ++i) {
+ SPtr<Edge> edge(new Edge(*i->get()));
+ _edges.insert(edge);
+ }
+}
+
+static inline bool
+action_equals(SPtr<const Action> a, SPtr<const Action> b)
+{
+ return (a == b) || (a && b && *a.get() == *b.get());
+}
+
+bool
+Node::operator==(const Node& rhs) const
+{
+ return _duration == rhs._duration &&
+ _is_initial == rhs._is_initial &&
+ _is_selector == rhs._is_selector &&
+ _is_active == rhs._is_active &&
+ action_equals(_enter_action, rhs.enter_action()) &&
+ action_equals(_exit_action, rhs.exit_action());
+ // TODO: compare edges
+}
+
+/** Always returns an edge, unless there are none */
+SPtr<Edge>
+Node::random_edge()
+{
+ SPtr<Edge> ret;
+ if (_edges.empty()) {
+ return ret;
+ }
+
+ size_t i = rand() % _edges.size();
+
+ // FIXME: O(n) worst case :(
+ for (Edges::const_iterator e = _edges.begin(); e != _edges.end(); ++e,
+ --i) {
+ if (i == 0) {
+ ret = *e;
+ break;
+ }
+ }
+
+ return ret;
+}
+
+void
+Node::edges_changed()
+{
+ if (!_is_selector) {
+ return;
+ }
+
+ // Normalize edge probabilities if we're a selector
+ double prob_sum = 0;
+
+ for (Edges::iterator i = _edges.begin(); i != _edges.end(); ++i) {
+ prob_sum += (*i)->probability();
+ }
+
+ for (Edges::iterator i = _edges.begin(); i != _edges.end(); ++i) {
+ (*i)->set_probability((*i)->probability() / prob_sum);
+ }
+
+ _changed = true;
+}
+
+void
+Node::set_selector(bool yn)
+{
+ _is_selector = yn;
+
+ if (yn) {
+ edges_changed();
+ }
+
+ _changed = true;
+}
+
+void
+Node::set_enter_action(SPtr<Action> action)
+{
+ _enter_action = action;
+ _changed = true;
+}
+
+void
+Node::set_exit_action(SPtr<Action> action)
+{
+ _exit_action = action;
+ _changed = true;
+}
+
+void
+Node::enter(MIDISink* sink, TimeStamp time)
+{
+ if (!_is_active) {
+ _changed = true;
+ _is_active = true;
+ _enter_time = time;
+
+ if (sink && _enter_action) {
+ _enter_action->execute(sink, time);
+ }
+ }
+}
+
+void
+Node::exit(MIDISink* sink, TimeStamp time)
+{
+ if (_is_active) {
+ if (sink && _exit_action) {
+ _exit_action->execute(sink, time);
+ }
+
+ _changed = true;
+ _is_active = false;
+ _enter_time = 0;
+ }
+}
+
+SPtr<Edge>
+Node::edge_to(SPtr<Node> head) const
+{
+ // TODO: Make logarithmic
+ for (Edges::const_iterator i = _edges.begin(); i != _edges.end(); ++i) {
+ if ((*i)->head() == head) {
+ return *i;
+ }
+ }
+ return SPtr<Edge>();
+}
+
+void
+Node::add_edge(SPtr<Edge> edge)
+{
+ assert(edge->tail().lock().get() == this);
+ if (edge_to(edge->head())) {
+ return;
+ }
+
+ _edges.insert(edge);
+ edges_changed();
+}
+
+void
+Node::remove_edge(SPtr<Edge> edge)
+{
+ _edges.erase(edge);
+ edges_changed();
+}
+
+bool
+Node::connected_to(SPtr<Node> node)
+{
+ return bool(edge_to(node));
+}
+
+SPtr<Edge>
+Node::remove_edge_to(SPtr<Node> node)
+{
+ SPtr<Edge> edge = edge_to(node);
+ if (edge) {
+ _edges.erase(edge); // TODO: avoid double search
+ edges_changed();
+ }
+ return edge;
+}
+
+void
+Node::set(URIInt key, const Atom& value)
+{
+ if (key == URIs::instance().machina_initial) {
+ std::cerr << "error: Attempt to change node initial state" << std::endl;
+ } else if (key == URIs::instance().machina_selector) {
+ set_selector(value.get<int32_t>());
+ }
+}
+
+void
+Node::write_state(Sord::Model& model)
+{
+ using namespace Raul;
+
+ const Sord::Node& rdf_id = this->rdf_id(model.world());
+
+ if (_is_selector)
+ model.add_statement(
+ rdf_id,
+ Sord::URI(model.world(), MACHINA_URI_RDF "type"),
+ Sord::URI(model.world(), MACHINA_NS_SelectorNode));
+ else
+ model.add_statement(
+ rdf_id,
+ Sord::URI(model.world(), MACHINA_URI_RDF "type"),
+ Sord::URI(model.world(), MACHINA_NS_Node));
+
+ model.add_statement(
+ rdf_id,
+ Sord::URI(model.world(), MACHINA_NS_duration),
+ Sord::Literal::decimal(model.world(), _duration.to_double(), 7));
+
+ if (_enter_action) {
+ _enter_action->write_state(model);
+ model.add_statement(rdf_id,
+ Sord::URI(model.world(), MACHINA_NS_onEnter),
+ _enter_action->rdf_id(model.world()));
+ }
+
+ if (_exit_action) {
+ _exit_action->write_state(model);
+ model.add_statement(rdf_id,
+ Sord::URI(model.world(), MACHINA_NS_onExit),
+ _exit_action->rdf_id(model.world()));
+ }
+}
+
+} // namespace machina
diff --git a/src/engine/Node.hpp b/src/engine/Node.hpp
new file mode 100644
index 0000000..cae75f5
--- /dev/null
+++ b/src/engine/Node.hpp
@@ -0,0 +1,116 @@
+/*
+ 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/>.
+*/
+
+#ifndef MACHINA_NODE_HPP
+#define MACHINA_NODE_HPP
+
+#include <set>
+
+#include "machina/types.hpp"
+
+#include "Action.hpp"
+#include "MIDISink.hpp"
+#include "Schrodinbit.hpp"
+#include "Stateful.hpp"
+
+namespace machina {
+
+class Edge;
+using Raul::TimeDuration;
+using Raul::TimeStamp;
+using Raul::TimeUnit;
+
+/** A node is a state (as in a FSM diagram), or "note".
+ *
+ * It contains a action, as well as a duration and pointers to its
+ * successors (states/nodes that (may) follow it).
+ *
+ * Initial nodes do not have enter actions (since they are entered at
+ * an undefined point in time <= 0).
+ */
+class Node : public Stateful
+{
+public:
+ Node(TimeDuration duration, bool initial=false);
+ Node(const Node& copy);
+
+ bool operator==(const Node& rhs) const;
+
+ void set_enter_action(SPtr<Action> action);
+ void set_exit_action(SPtr<Action> action);
+
+ SPtr<const Action> enter_action() const { return _enter_action; }
+ SPtr<Action> enter_action() { return _enter_action; }
+ SPtr<const Action> exit_action() const { return _exit_action; }
+ SPtr<Action> exit_action() { return _exit_action; }
+
+ void enter(MIDISink* sink, TimeStamp time);
+ void exit(MIDISink* sink, TimeStamp time);
+
+ void edges_changed();
+
+ void add_edge(SPtr<Edge> edge);
+ void remove_edge(SPtr<Edge> edge);
+ SPtr<Edge> remove_edge_to(SPtr<Node> node);
+ bool connected_to(SPtr<Node> node);
+
+ void set(URIInt key, const Atom& value);
+ void write_state(Sord::Model& model);
+
+ bool is_initial() const { return _is_initial; }
+ 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; }
+ bool is_selector() const { return _is_selector; }
+ void set_selector(bool i);
+
+ inline bool changed() { return _changed; }
+ inline void set_changed() { _changed = true; }
+
+ struct EdgeHeadOrder {
+ inline bool operator()(const SPtr<const Edge>& a,
+ const SPtr<const Edge>& b) {
+ return a.get() < b.get();
+ }
+ };
+
+ SPtr<Edge> edge_to(SPtr<Node> head) const;
+
+ typedef std::set<SPtr<Edge>, EdgeHeadOrder> Edges;
+
+ Edges& edges() { return _edges; }
+
+ SPtr<Edge> random_edge();
+
+private:
+ Node& operator=(const Node& other); // undefined
+
+ TimeStamp _enter_time; ///< valid iff _is_active
+ TimeDuration _duration;
+ SPtr<Action> _enter_action;
+ SPtr<Action> _exit_action;
+ Edges _edges;
+ Schrodinbit _changed;
+ bool _is_initial;
+ bool _is_selector;
+ bool _is_active;
+};
+
+} // namespace machina
+
+#endif // MACHINA_NODE_HPP
diff --git a/src/engine/Problem.cpp b/src/engine/Problem.cpp
new file mode 100644
index 0000000..3b05fed
--- /dev/null
+++ b/src/engine/Problem.cpp
@@ -0,0 +1,383 @@
+/*
+ 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/>.
+*/
+
+#define __STDC_LIMIT_MACROS 1
+
+#include <stdint.h>
+
+#include <set>
+#include <vector>
+#include <iostream>
+
+#include "lv2/lv2plug.in/ns/ext/midi/midi.h"
+
+#include "eugene/Problem.hpp"
+
+#include "machina/Machine.hpp"
+
+#include "ActionFactory.hpp"
+#include "Edge.hpp"
+#include "MidiAction.hpp"
+#include "Problem.hpp"
+#include "SMFReader.hpp"
+#include "machina_config.h"
+
+using namespace std;
+
+namespace machina {
+
+Problem::Problem(TimeUnit unit,
+ const std::string& target_midi,
+ SPtr<Machine> seed)
+ : _unit(unit)
+ , _target(*this)
+ , _seed(new Machine(*seed.get()))
+{
+ SMFReader smf;
+ const bool opened = smf.open(target_midi);
+ assert(opened);
+
+ smf.seek_to_track(2); // FIXME: ?
+
+ uint8_t buf[4];
+ uint32_t ev_size;
+ uint32_t delta_time;
+ while (smf.read_event(4, buf, &ev_size, &delta_time) >= 0) {
+ // time ignored
+ _target.write_event(TimeStamp(_unit, 0.0), ev_size, buf);
+#if 0
+ //_target._length += delta_time / (double)smf.ppqn();
+ if ((buf[0] & 0xF0) == MIDI_CMD_NOTE_ON) {
+ const uint8_t note = buf[1];
+ /*++_target._note_frequency[note];
+ ++_target.n_notes();*/
+ ++_target._counts[note];
+ _target._notes.push_back(note);
+ }
+#endif
+ }
+
+ cout << "Target notes: " << _target.n_notes() << endl;
+
+ _target.compute();
+}
+
+float
+Problem::evaluate(const Machine& const_machine) const
+{
+ #if 0
+ //cout << "(";
+
+ // kluuudge
+ Machine& machine = const_cast<Machine&>(const_machine);
+
+ map<Machine*, float>::const_iterator cached = _fitness.find(&machine);
+ if (cached != _fitness.end()) {
+ return cached->second;
+ }
+
+ SPtr<Evaluator> eval(new Evaluator(*this));
+
+ //machine.reset();
+ machine.set_sink(eval);
+
+ // FIXME: timing stuff here isn't right at all...
+
+ static const unsigned ppqn = MACHINA_PPQN;
+ Raul::TimeSlice time(ppqn, ppqn, 120.0);
+ time.set_slice(TimeStamp(_unit, 0, 0), TimeDuration(_unit, 2 * ppqn));
+
+ machine.run(time);
+ if (eval->n_notes() == 0) {
+ return 0.0f; // bad dog
+ }
+ TimeStamp end(_unit, time.start_ticks().ticks() + 2 * ppqn);
+ time.set_slice(end, TimeStamp(_unit, 0, 0));
+
+ while (eval->n_notes() < _target.n_notes()) {
+ machine.run(time);
+ if (machine.is_finished()) {
+ machine.reset(time.start_ticks());
+ }
+ time.set_slice(end, TimeStamp(end.unit(), 0, 0));
+ }
+
+ eval->compute();
+
+ // count
+#if 0
+ float f = 0;
+
+ for (uint8_t i = 0; i < 128; ++i) {
+ /*if (eval->_note_frequency[i] <= _target._note_frequency[i])
+ f += eval->_note_frequency[i];
+ else
+ f -= _target._note_frequency[i] - eval->_note_frequency[i];*/
+ //f -= fabs(eval->_note_frequency[i] - _target._note_frequency[i]);
+ }
+#endif
+ //cout << ")";
+
+ // distance
+ //float f = distance(eval->_notes, _target._notes);
+
+ float f = 0.0;
+
+ for (Evaluator::Patterns::const_iterator i = eval->_patterns.begin();
+ i != eval->_patterns.end(); ++i) {
+ // Reward for matching patterns
+ if (_target._patterns.find(i->first) != _target._patterns.end()) {
+ Evaluator::Patterns::const_iterator c = _target._patterns.find(
+ i->first);
+ const uint32_t cnt
+ = (c == _target._patterns.end()) ? 1 : c->second;
+ f += min(i->second, cnt) * (i->first.length());
+
+ } else {
+ // Punish for bad patterns
+ const uint32_t invlen = (eval->_order - i->first.length() + 1);
+ f -= (i->second / (float)eval->_patterns.size()
+ * (float)(invlen * invlen * invlen)) * 4;
+ }
+ }
+
+ // Punish for missing patterns
+ for (Evaluator::Patterns::const_iterator i = _target._patterns.begin();
+ i != _target._patterns.end(); ++i) {
+ if (eval->_patterns.find(i->first) == _target._patterns.end()) {
+ f -= i->second / (float)_target.n_notes()
+ * (float)(eval->_order - i->first.length() + 1);
+ }
+ }
+
+ //cout << "f = " << f << endl;
+
+ _fitness[&machine] = f;
+
+ return f;
+ #endif
+ return 0.0f;
+}
+
+void
+Problem::Evaluator::write_event(Raul::TimeStamp time,
+ size_t ev_size,
+ const uint8_t* ev) throw (std::logic_error)
+{
+ if ((ev[0] & 0xF0) == LV2_MIDI_MSG_NOTE_ON) {
+
+ const uint8_t note = ev[1];
+
+ if (_first_note == 0) {
+ _first_note = note;
+ }
+
+ /*++_note_frequency[note];
+ ++n_notes();*/
+ //_notes.push_back(note);
+ if (_read.length() == 0) {
+ _read = note;
+ return;
+ }
+ if (_read.length() == _order) {
+ _read = _read.substr(1);
+ }
+
+ _read = _read + (char)note;
+
+ for (size_t i = 0; i < _read.length(); ++i) {
+ const string pattern = _read.substr(i);
+ Patterns::iterator p = _patterns.find(pattern);
+ if (p != _patterns.end()) {
+ ++(p->second);
+ } else {
+ _patterns[pattern] = 1;
+ }
+ }
+
+ ++_counts[note];
+ ++_n_notes;
+
+ }
+}
+
+void
+Problem::Evaluator::compute()
+{
+ /*
+ for (uint8_t i=0; i < 128; ++i) {
+ if (_note_frequency[i] > 0) {
+ _note_frequency[i] /= (float)n_notes();
+ //cout << (int)i << ":\t" << _note_frequency[i] << endl;
+ }
+ }*/
+}
+
+void
+Problem::initial_population(eugene::Random& rng,
+ Population& pop,
+ size_t gene_size,
+ size_t pop_size) const
+{
+ // FIXME: ignores _seed and builds based on MIDI
+ // evolution of the visible machine would be nice..
+ SPtr<Machine> base = SPtr<Machine>(new Machine(_unit));
+ for (uint8_t i = 0; i < 128; ++i) {
+ if (_target._counts[i] > 0) {
+ //cout << "Initial note: " << (int)i << endl;
+ SPtr<Node> node(new Node(TimeDuration(_unit, 1 / 2.0)));
+ node->set_enter_action(ActionFactory::note_on(i));
+ node->set_exit_action(ActionFactory::note_off(i));
+ node->set_selector(true);
+ base->add_node(node);
+ }
+ }
+
+ for (size_t i = 0; i < pop_size; ++i) {
+ // FIXME: double copy
+ Machine m(*base.get());
+
+ set< SPtr<Node> > unreachable;
+
+ for (Machine::Nodes::iterator i = m.nodes().begin(); i != m.nodes().end();
+ ++i) {
+ if (dynamic_ptr_cast<MidiAction>((*i)->enter_action())->event()[1] ==
+ _target.first_note()) {
+ m.initial_node()->add_edge(
+ SPtr<Edge>(new Edge(m.initial_node(), *i)));
+ } else {
+ unreachable.insert(*i);
+ }
+ }
+
+ SPtr<Node> cur = m.initial_node();
+ unreachable.erase(cur);
+ SPtr<Node> head;
+
+ while (!unreachable.empty()) {
+ if (rand() % 2) {
+ head = m.random_node();
+ } else {
+ head = *unreachable.begin();
+ }
+
+ if (!head->connected_to(head) ) {
+ cur->add_edge(SPtr<Edge>(new Edge(cur, head)));
+ unreachable.erase(head);
+ cur = head;
+ }
+ }
+
+ pop.push_back(m);
+
+ /*cout << "initial # nodes: " << m.nodes().size();
+ cout << "initial fitness: " << fitness(m) << endl;*/
+ }
+}
+
+/** levenshtein distance (edit distance) */
+size_t
+Problem::distance(const std::vector<uint8_t>& source,
+ const std::vector<uint8_t>& target) const
+{
+ // Derived from http://www.merriampark.com/ldcpp.htm
+
+ assert(source.size() < UINT16_MAX);
+ assert(target.size() < UINT16_MAX);
+
+ // Step 1
+
+ const uint16_t n = source.size();
+ const uint16_t m = target.size();
+ if (n == 0) {
+ return m;
+ }
+ if (m == 0) {
+ return n;
+ }
+
+ _matrix.resize(n + 1);
+ for (uint16_t i = 0; i <= n; i++) {
+ _matrix[i].resize(m + 1);
+ }
+
+ // Step 2
+
+ for (uint16_t i = 0; i <= n; i++) {
+ _matrix[i][0] = i;
+ }
+
+ for (uint16_t j = 0; j <= m; j++) {
+ _matrix[0][j] = j;
+ }
+
+ // Step 3
+
+ for (uint16_t i = 1; i <= n; i++) {
+
+ const uint8_t s_i = source[i - 1];
+
+ // Step 4
+
+ for (uint16_t j = 1; j <= m; j++) {
+
+ const uint8_t t_j = target[j - 1];
+
+ // Step 5
+
+ uint16_t cost;
+ if (s_i == t_j) {
+ cost = 0;
+ } else {
+ cost = 1;
+ }
+
+ // Step 6
+
+ const uint16_t above = _matrix[i - 1][j];
+ const uint16_t left = _matrix[i][j - 1];
+ const uint16_t diag = _matrix[i - 1][j - 1];
+ uint16_t cell = min(above + 1, min(left + 1, diag + cost));
+
+ // Step 6A: Cover transposition, in addition to deletion,
+ // insertion and substitution. This step is taken from:
+ // Berghel, Hal ; Roach, David : "An Extension of Ukkonen's
+ // Enhanced Dynamic Programming ASM Algorithm"
+ // (http://www.acm.org/~hlb/publications/asm/asm.html)
+
+ if (i > 2 && j > 2) {
+ uint16_t trans = _matrix[i - 2][j - 2] + 1;
+ if (source[i - 2] != t_j) {
+ trans++;
+ }
+ if (s_i != target[j - 2]) {
+ trans++;
+ }
+ if (cell > trans) {
+ cell = trans;
+ }
+ }
+
+ _matrix[i][j] = cell;
+ }
+ }
+
+ // Step 7
+
+ return _matrix[n][m];
+}
+
+} // namespace machina
diff --git a/src/engine/Problem.hpp b/src/engine/Problem.hpp
new file mode 100644
index 0000000..81e67d7
--- /dev/null
+++ b/src/engine/Problem.hpp
@@ -0,0 +1,135 @@
+/*
+ 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/>.
+*/
+
+#ifndef MACHINA_PROBLEM_HPP
+#define MACHINA_PROBLEM_HPP
+
+#include <stdint.h>
+
+#include <map>
+#include <stdexcept>
+
+#include "machina/Machine.hpp"
+#include "eugene/Problem.hpp"
+
+#include "MIDISink.hpp"
+
+namespace machina {
+
+class Problem : public eugene::Problem<Machine> {
+public:
+ Problem(TimeUnit unit,
+ const std::string& target_midi,
+ SPtr<Machine> seed = SPtr<Machine>());
+ virtual ~Problem() {}
+
+ void seed(SPtr<Machine> parent) { _seed = parent; }
+
+ float evaluate(const Machine& machine) const;
+
+ bool fitness_less_than(float a, float b) const { return a < b; }
+
+ void clear_fitness_cache() { _fitness.clear(); }
+
+ void initial_population(eugene::Random& rng,
+ Population& pop,
+ size_t gene_size,
+ size_t pop_size) const;
+
+private:
+ size_t distance(const std::vector<uint8_t>& source,
+ const std::vector<uint8_t>& target) const;
+
+ // count
+ /*struct FreqEvaluator : public Raul::MIDISink {
+ Evaluator(const Problem& problem)
+ : _problem(problem), _n_notes(0), _length(0) {
+ for (uint8_t i=0; i < 128; ++i)
+ _note_frequency[i] = 0;
+ }
+ void write_event(Raul::BeatTime time,
+ size_t ev_size,
+ const uint8_t* ev) throw (std::logic_error);
+ void compute();
+ const Problem& _problem;
+
+ size_t n_notes() const { return _n_notes; }
+
+ float _note_frequency[128];
+ size_t _n_notes;
+ double _length;
+ };*/
+
+ // distance
+ /*struct Evaluator : public Raul::MIDISink {
+ Evaluator(const Problem& problem) : _problem(problem) {}
+ void write_event(Raul::BeatTime time,
+ size_t ev_size,
+ const uint8_t* ev) throw (std::logic_error);
+ void compute();
+ const Problem& _problem;
+
+ size_t n_notes() const { return _notes.size(); }
+
+ std::vector<uint8_t> _notes;
+ float _counts[128];
+ };*/
+
+ struct Evaluator : public MIDISink {
+ explicit Evaluator(const Problem& problem)
+ : _problem(problem)
+ , _order(4)
+ , _n_notes(0)
+ , _first_note(0)
+ {
+ for (uint8_t i=0; i < 128; ++i)
+ _counts[i] = 0;
+ }
+ void write_event(TimeStamp time,
+ size_t ev_size,
+ const uint8_t* ev) throw (std::logic_error);
+ void compute();
+ const Problem& _problem;
+
+ size_t n_notes() const { return _n_notes; }
+ uint8_t first_note() const { return _first_note; }
+
+ const uint32_t _order;
+
+ std::string _read;
+
+ typedef std::map<std::string, uint32_t> Patterns;
+ Patterns _patterns;
+ uint32_t _counts[128];
+ size_t _n_notes;
+ uint8_t _first_note;
+ };
+
+ TimeUnit _unit;
+
+ Evaluator _target;
+ SPtr<Machine> _seed;
+
+ /// for levenshtein distance
+ mutable std::vector< std::vector<uint16_t> > _matrix;
+
+ /// memoization
+ mutable std::map<Machine*, float> _fitness;
+};
+
+} // namespace machina
+
+#endif // MACHINA_PROBLEM_HPP
diff --git a/src/engine/Recorder.cpp b/src/engine/Recorder.cpp
new file mode 100644
index 0000000..21dfd4b
--- /dev/null
+++ b/src/engine/Recorder.cpp
@@ -0,0 +1,69 @@
+/*
+ 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 <ios>
+#include <iostream>
+
+#include "MachineBuilder.hpp"
+#include "Recorder.hpp"
+
+using namespace std;
+using namespace Raul;
+
+namespace machina {
+
+Recorder::Recorder(Forge& forge,
+ size_t buffer_size,
+ TimeUnit unit,
+ double q,
+ bool step)
+ : _unit(unit)
+ , _record_buffer(buffer_size)
+ , _builder(new MachineBuilder(SPtr<Machine>(new Machine(unit)), q, step))
+{}
+
+void
+Recorder::_whipped()
+{
+ TimeStamp t(_unit);
+ size_t size;
+ unsigned char buf[4];
+
+ while (true) {
+ bool success = _record_buffer.read(sizeof(TimeStamp), (uint8_t*)&t);
+ if (success) {
+ success = _record_buffer.read(sizeof(size_t), (uint8_t*)&size);
+ }
+ if (success) {
+ success = _record_buffer.read(size, buf);
+ }
+ if (success) {
+ _builder->event(t, size, buf);
+ } else {
+ break;
+ }
+ }
+}
+
+SPtr<Machine>
+Recorder::finish()
+{
+ SPtr<Machine> machine = _builder->finish();
+ _builder.reset();
+ return machine;
+}
+
+}
diff --git a/src/engine/Recorder.hpp b/src/engine/Recorder.hpp
new file mode 100644
index 0000000..c98c42b
--- /dev/null
+++ b/src/engine/Recorder.hpp
@@ -0,0 +1,68 @@
+/*
+ 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/>.
+*/
+
+#ifndef MACHINA_RECORDER_HPP
+#define MACHINA_RECORDER_HPP
+
+#include "raul/RingBuffer.hpp"
+
+#include "machina/Machine.hpp"
+#include "machina/types.hpp"
+
+#include "Slave.hpp"
+
+namespace machina {
+
+class MachineBuilder;
+
+class Recorder
+ : public Slave
+{
+public:
+ Recorder(Forge& forge,
+ size_t buffer_size,
+ TimeUnit unit,
+ double q,
+ bool step);
+
+ inline void write(Raul::TimeStamp time, size_t size,
+ const unsigned char* buf) {
+ if (_record_buffer.write_space() <
+ (sizeof(TimeStamp) + sizeof(size_t) + size)) {
+ std::cerr << "Record buffer overflow" << std::endl;
+ return;
+ } else {
+ _record_buffer.write(sizeof(TimeStamp), (uint8_t*)&time);
+ _record_buffer.write(sizeof(size_t), (uint8_t*)&size);
+ _record_buffer.write(size, buf);
+ }
+ }
+
+ SPtr<MachineBuilder> builder() { return _builder; }
+
+ SPtr<Machine> finish();
+
+private:
+ virtual void _whipped();
+
+ TimeUnit _unit;
+ Raul::RingBuffer _record_buffer;
+ SPtr<MachineBuilder> _builder;
+};
+
+} // namespace machina
+
+#endif // MACHINA_RECORDER_HPP
diff --git a/src/engine/SMFDriver.cpp b/src/engine/SMFDriver.cpp
new file mode 100644
index 0000000..3dc51e2
--- /dev/null
+++ b/src/engine/SMFDriver.cpp
@@ -0,0 +1,162 @@
+/*
+ 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 <iostream>
+#include <list>
+
+#include "machina/Context.hpp"
+#include "machina/Machine.hpp"
+#include "machina/types.hpp"
+
+#include "Edge.hpp"
+#include "SMFDriver.hpp"
+#include "SMFReader.hpp"
+#include "SMFWriter.hpp"
+#include "quantize.hpp"
+
+using namespace std;
+
+namespace machina {
+
+SMFDriver::SMFDriver(Forge& forge, Raul::TimeUnit unit)
+ : Driver(forge, SPtr<Machine>())
+{
+ _writer = SPtr<SMFWriter>(new SMFWriter(unit));
+}
+
+/** Learn a single track from the MIDI file at `uri`
+ *
+ * @param track The track of the MIDI file to import, starting from 1.
+ *
+ * Currently only file:// URIs are supported.
+ * @return the resulting machine.
+ */
+SPtr<Machine>
+SMFDriver::learn(const string& filename,
+ unsigned track,
+ double q,
+ Raul::TimeDuration max_duration)
+{
+ //assert(q.unit() == max_duration.unit());
+ SPtr<Machine> m(new Machine(max_duration.unit()));
+ SPtr<MachineBuilder> builder = SPtr<MachineBuilder>(
+ new MachineBuilder(m, q, false));
+ SMFReader reader;
+
+ if (!reader.open(filename)) {
+ cerr << "Unable to open MIDI file " << filename << endl;
+ return SPtr<Machine>();
+ }
+
+ if (track > reader.num_tracks()) {
+ return SPtr<Machine>();
+ } else {
+ learn_track(builder, reader, track, q, max_duration);
+ }
+
+ m->reset(NULL, m->time());
+
+ if (m->nodes().size() > 1) {
+ return m;
+ } else {
+ return SPtr<Machine>();
+ }
+}
+
+/** Learn all tracks from a MIDI file into a single machine.
+ *
+ * This will result in one disjoint subgraph in the machine for each track.
+ */
+SPtr<Machine>
+SMFDriver::learn(const string& filename, double q, Raul::TimeStamp max_duration)
+{
+ SPtr<Machine> m(new Machine(max_duration.unit()));
+ SPtr<MachineBuilder> builder = SPtr<MachineBuilder>(
+ new MachineBuilder(m, q, false));
+ SMFReader reader;
+
+ if (!reader.open(filename)) {
+ cerr << "Unable to open MIDI file " << filename << endl;
+ return SPtr<Machine>();
+ }
+
+ for (unsigned t = 1; t <= reader.num_tracks(); ++t) {
+ builder->reset();
+ learn_track(builder, reader, t, q, max_duration);
+ }
+
+ m->reset(NULL, m->time());
+
+ if (m->nodes().size() > 1) {
+ return m;
+ } else {
+ return SPtr<Machine>();
+ }
+}
+
+void
+SMFDriver::learn_track(SPtr<MachineBuilder> builder,
+ SMFReader& reader,
+ unsigned track,
+ double q,
+ Raul::TimeDuration max_duration)
+{
+ const bool found_track = reader.seek_to_track(track);
+ if (!found_track) {
+ return;
+ }
+
+ uint8_t buf[4];
+ uint32_t ev_size;
+ uint32_t ev_delta_time;
+
+ Raul::TimeUnit unit = Raul::TimeUnit(TimeUnit::BEATS, MACHINA_PPQN);
+
+ uint64_t t = 0;
+ while (reader.read_event(4, buf, &ev_size, &ev_delta_time) >= 0) {
+ t += ev_delta_time;
+
+ const uint32_t beats = t / (uint32_t)reader.ppqn();
+ const uint32_t smf_ticks = t % (uint32_t)reader.ppqn();
+ const double frac = smf_ticks / (double)reader.ppqn();
+ const uint32_t ticks = frac * MACHINA_PPQN;
+
+ if (!max_duration.is_zero() && t > max_duration.to_double()) {
+ break;
+ }
+
+ if (ev_size > 0) {
+ // TODO: quantize
+ builder->event(TimeStamp(unit, beats, ticks), ev_size, buf);
+ }
+ }
+
+ builder->resolve();
+}
+
+void
+SMFDriver::run(SPtr<Machine> machine, Raul::TimeStamp max_time)
+{
+ // FIXME: unit kludge (tempo only)
+ Context context(_forge, machine->time().unit().ppt(),
+ _writer->unit().ppt(), 120.0);
+ context.set_sink(this);
+ context.time().set_slice(TimeStamp(max_time.unit(), 0, 0),
+ context.time().beats_to_ticks(max_time));
+ machine->run(context, SPtr<Raul::RingBuffer>());
+}
+
+} // namespace machina
diff --git a/src/engine/SMFDriver.hpp b/src/engine/SMFDriver.hpp
new file mode 100644
index 0000000..e8b9a4b
--- /dev/null
+++ b/src/engine/SMFDriver.hpp
@@ -0,0 +1,67 @@
+/*
+ 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/>.
+*/
+
+#ifndef MACHINA_SMFDRIVER_HPP
+#define MACHINA_SMFDRIVER_HPP
+
+#include "machina/Driver.hpp"
+#include "machina/types.hpp"
+
+#include "MachineBuilder.hpp"
+#include "SMFReader.hpp"
+#include "SMFWriter.hpp"
+
+namespace machina {
+
+class Node;
+class Machine;
+
+class SMFDriver : public Driver
+{
+public:
+ SMFDriver(Forge& forge, Raul::TimeUnit unit);
+
+ SPtr<Machine> learn(const std::string& filename,
+ double q,
+ Raul::TimeDuration max_duration);
+
+ SPtr<Machine> learn(const std::string& filename,
+ unsigned track,
+ double q,
+ Raul::TimeDuration max_duration);
+
+ void run(SPtr<Machine> machine, Raul::TimeStamp max_time);
+
+ void write_event(Raul::TimeStamp time,
+ size_t ev_size,
+ const unsigned char* ev)
+ { _writer->write_event(time, ev_size, ev); }
+
+ SPtr<SMFWriter> writer() { return _writer; }
+
+private:
+ SPtr<SMFWriter> _writer;
+
+ void learn_track(SPtr<MachineBuilder> builder,
+ SMFReader& reader,
+ unsigned track,
+ double q,
+ Raul::TimeDuration max_duration);
+};
+
+} // namespace machina
+
+#endif // MACHINA_SMFDRIVER_HPP
diff --git a/src/engine/SMFReader.cpp b/src/engine/SMFReader.cpp
new file mode 100644
index 0000000..5976f75
--- /dev/null
+++ b/src/engine/SMFReader.cpp
@@ -0,0 +1,323 @@
+/*
+ This file is part of Machina.
+ Copyright 2007-2013 David Robillard <http://drobilla.net>
+
+ Raul 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.
+
+ Raul 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 Raul. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <arpa/inet.h>
+#include <cassert>
+#include <cstdio>
+#include <cstring>
+#include <string>
+
+#include "lv2/lv2plug.in/ns/ext/midi/midi.h"
+
+#include "SMFReader.hpp"
+
+using std::endl;
+
+namespace machina {
+
+/** Return the size of the given event NOT including the status byte,
+ * or -1 if unknown (eg sysex)
+ */
+static int
+midi_event_size(unsigned char status)
+{
+ if (status >= 0x80 && status <= 0xE0) {
+ status &= 0xF0; // mask off the channel
+ }
+
+ switch (status) {
+ case LV2_MIDI_MSG_NOTE_OFF:
+ case LV2_MIDI_MSG_NOTE_ON:
+ case LV2_MIDI_MSG_NOTE_PRESSURE:
+ case LV2_MIDI_MSG_CONTROLLER:
+ case LV2_MIDI_MSG_BENDER:
+ case LV2_MIDI_MSG_SONG_POS:
+ return 2;
+
+ case LV2_MIDI_MSG_PGM_CHANGE:
+ case LV2_MIDI_MSG_CHANNEL_PRESSURE:
+ case LV2_MIDI_MSG_MTC_QUARTER:
+ case LV2_MIDI_MSG_SONG_SELECT:
+ return 1;
+
+ case LV2_MIDI_MSG_TUNE_REQUEST:
+ case LV2_MIDI_MSG_CLOCK:
+ case LV2_MIDI_MSG_START:
+ case LV2_MIDI_MSG_CONTINUE:
+ case LV2_MIDI_MSG_STOP:
+ case LV2_MIDI_MSG_ACTIVE_SENSE:
+ case LV2_MIDI_MSG_RESET:
+ return 0;
+
+ case LV2_MIDI_MSG_SYSTEM_EXCLUSIVE:
+ return -1;
+ }
+
+ return -1;
+}
+
+SMFReader::SMFReader(const std::string filename)
+ : _fd(NULL)
+ , _ppqn(0)
+ , _track(0)
+ , _track_size(0)
+{
+ if (filename.length() > 0) {
+ open(filename);
+ }
+}
+
+SMFReader::~SMFReader()
+{
+ if (_fd) {
+ close();
+ }
+}
+
+bool
+SMFReader::open(const std::string& filename)
+{
+ if (_fd) {
+ throw std::logic_error(
+ "Attempt to start new read while write in progress.");
+ }
+
+ std::cout << "Opening SMF file " << filename << " for reading." << endl;
+
+ _fd = fopen(filename.c_str(), "r+");
+
+ if (_fd) {
+ // Read type (bytes 8..9)
+ fseek(_fd, 0, SEEK_SET);
+ char mthd[5];
+ mthd[4] = '\0';
+ fread(mthd, 1, 4, _fd);
+ if (strcmp(mthd, "MThd")) {
+ std::cerr << filename << " is not an SMF file, aborting." << endl;
+ fclose(_fd);
+ _fd = NULL;
+ return false;
+ }
+
+ // Read type (bytes 8..9)
+ fseek(_fd, 8, SEEK_SET);
+ uint16_t type_be = 0;
+ fread(&type_be, 2, 1, _fd);
+ _type = ntohs(type_be);
+
+ // Read number of tracks (bytes 10..11)
+ uint16_t num_tracks_be = 0;
+ fread(&num_tracks_be, 2, 1, _fd);
+ _num_tracks = ntohs(num_tracks_be);
+
+ // Read PPQN (bytes 12..13)
+ uint16_t ppqn_be = 0;
+ fread(&ppqn_be, 2, 1, _fd);
+ _ppqn = ntohs(ppqn_be);
+
+ // TODO: Absolute (SMPTE seconds) time support
+ if ((_ppqn & 0x8000) != 0) {
+ throw UnsupportedTime();
+ }
+
+ seek_to_track(1);
+
+ return true;
+ } else {
+ return false;
+ }
+}
+
+/** Seek to the start of a given track, starting from 1.
+ * Returns true if specified track was found.
+ */
+bool
+SMFReader::seek_to_track(unsigned track)
+{
+ if (track == 0) {
+ throw std::logic_error("Seek to track 0 out of range (must be >= 1)");
+ }
+
+ if (!_fd) {
+ throw std::logic_error("Attempt to seek to track on unopened SMF file.");
+ }
+
+ unsigned track_pos = 0;
+
+ fseek(_fd, 14, SEEK_SET);
+ char id[5];
+ id[4] = '\0';
+ uint32_t chunk_size = 0;
+
+ while (!feof(_fd)) {
+ fread(id, 1, 4, _fd);
+
+ if (!strcmp(id, "MTrk")) {
+ ++track_pos;
+ } else {
+ std::cerr << "Unknown chunk ID " << id << endl;
+ }
+
+ uint32_t chunk_size_be;
+ fread(&chunk_size_be, 4, 1, _fd);
+ chunk_size = ntohl(chunk_size_be);
+
+ if (track_pos == track) {
+ break;
+ }
+
+ fseek(_fd, chunk_size, SEEK_CUR);
+ }
+
+ if (!feof(_fd) && track_pos == track) {
+ _track = track;
+ _track_size = chunk_size;
+ return true;
+ } else {
+ return false;
+ }
+}
+
+/** Read an event from the current position in file.
+ *
+ * File position MUST be at the beginning of a delta time, or this will die very messily.
+ * ev.buffer must be of size ev.size, and large enough for the event. The returned event
+ * will have it's time field set to it's delta time (so it's the caller's responsibility
+ * to keep track of delta time, even for ignored events).
+ *
+ * Returns event length (including status byte) on success, 0 if event was
+ * skipped (eg a meta event), or -1 on EOF (or end of track).
+ *
+ * If `buf` is not large enough to hold the event, 0 will be returned, but ev_size
+ * set to the actual size of the event.
+ */
+int
+SMFReader::read_event(size_t buf_len,
+ uint8_t* buf,
+ uint32_t* ev_size,
+ uint32_t* delta_time)
+{
+ if (_track == 0) {
+ throw std::logic_error("Attempt to read from unopened SMF file");
+ }
+
+ if (!_fd || feof(_fd)) {
+ return -1;
+ }
+
+ assert(buf_len > 0);
+ assert(buf);
+ assert(ev_size);
+ assert(delta_time);
+
+ // Running status state
+ static uint8_t last_status = 0;
+ static uint32_t last_size = 0;
+
+ *delta_time = read_var_len(_fd);
+ int status = fgetc(_fd);
+ if (status == EOF) {
+ throw PrematureEOF();
+ } else if (status > 0xFF) {
+ throw CorruptFile();
+ }
+
+ if (status < 0x80) {
+ if (last_status == 0) {
+ throw CorruptFile();
+ }
+ status = last_status;
+ *ev_size = last_size;
+ fseek(_fd, -1, SEEK_CUR);
+ } else {
+ last_status = status;
+ *ev_size = midi_event_size(status) + 1;
+ last_size = *ev_size;
+ }
+
+ buf[0] = static_cast<uint8_t>(status);
+
+ if (status == 0xFF) {
+ *ev_size = 0;
+ if (feof(_fd)) {
+ throw PrematureEOF();
+ }
+ uint8_t type = fgetc(_fd);
+ const uint32_t size = read_var_len(_fd);
+
+ if (type == 0x2F) {
+ return -1; // we hit the logical EOF anyway...
+ } else {
+ fseek(_fd, size, SEEK_CUR);
+ return 0;
+ }
+ }
+
+ if ((*ev_size > buf_len) || (*ev_size == 0) || feof(_fd)) {
+ // Skip event, return 0
+ fseek(_fd, *ev_size - 1, SEEK_CUR);
+ return 0;
+ } else {
+ // Read event, return size
+ if (ferror(_fd)) {
+ throw CorruptFile();
+ }
+
+ fread(buf + 1, 1, *ev_size - 1, _fd);
+
+ if (((buf[0] & 0xF0) == 0x90) && (buf[2] == 0) ) {
+ buf[0] = (0x80 | (buf[0] & 0x0F));
+ buf[2] = 0x40;
+ }
+
+ return *ev_size;
+ }
+}
+
+void
+SMFReader::close()
+{
+ if (_fd) {
+ fclose(_fd);
+ }
+
+ _fd = NULL;
+}
+
+uint32_t
+SMFReader::read_var_len(FILE* fd)
+{
+ if (feof(fd)) {
+ throw PrematureEOF();
+ }
+
+ uint32_t value;
+ uint8_t c;
+
+ if ((value = getc(fd)) & 0x80) {
+ value &= 0x7F;
+ do {
+ if (feof(fd)) {
+ throw PrematureEOF();
+ }
+ value = (value << 7) + ((c = getc(fd)) & 0x7F);
+ } while (c & 0x80);
+ }
+
+ return value;
+}
+
+} // namespace machina
diff --git a/src/engine/SMFReader.hpp b/src/engine/SMFReader.hpp
new file mode 100644
index 0000000..7147f38
--- /dev/null
+++ b/src/engine/SMFReader.hpp
@@ -0,0 +1,87 @@
+/*
+ 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/>.
+*/
+
+#ifndef MACHINA_SMF_READER_HPP
+#define MACHINA_SMF_READER_HPP
+
+#include <stdexcept>
+#include <string>
+#include <inttypes.h>
+#include "raul/TimeStamp.hpp"
+
+namespace machina {
+
+/** Standard Midi File (Type 0) Reader
+ *
+ * Currently this only reads SMF files with tempo-based timing.
+ * \ingroup raul
+ */
+class SMFReader
+{
+public:
+ class PrematureEOF
+ : public std::exception
+ {
+ const char* what() const throw() { return "Unexpected end of file"; }
+ };
+ class CorruptFile
+ : public std::exception
+ {
+ const char* what() const throw() { return "Corrupted file"; }
+ };
+ class UnsupportedTime
+ : public std::exception
+ {
+ const char* what() const throw() { return
+ "Unsupported time stamp type (SMPTE)"; }
+ };
+
+ explicit SMFReader(const std::string filename = "");
+ ~SMFReader();
+
+ bool open(const std::string& filename);
+
+ bool seek_to_track(unsigned track);
+
+ uint16_t type() const { return _type; }
+ uint16_t ppqn() const { return _ppqn; }
+ size_t num_tracks() { return _num_tracks; }
+
+ int read_event(size_t buf_len,
+ uint8_t* buf,
+ uint32_t* ev_size,
+ uint32_t* ev_delta_time);
+
+ void close();
+
+ static uint32_t read_var_len(FILE* fd);
+
+protected:
+ /** size of SMF header, including MTrk chunk header */
+ static const uint32_t HEADER_SIZE = 22;
+
+ std::string _filename;
+ FILE* _fd;
+ uint16_t _type;
+ uint16_t _ppqn;
+ uint16_t _num_tracks;
+ uint32_t _track;
+ uint32_t _track_size;
+};
+
+} // namespace machina
+
+#endif // MACHINA_SMF_READER_HPP
diff --git a/src/engine/SMFWriter.cpp b/src/engine/SMFWriter.cpp
new file mode 100644
index 0000000..25d25c9
--- /dev/null
+++ b/src/engine/SMFWriter.cpp
@@ -0,0 +1,241 @@
+/*
+ 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 <arpa/inet.h>
+#include <stdint.h>
+#include <stdio.h>
+
+#include <cassert>
+#include <cstdio>
+#include <cstring>
+#include <limits>
+#include <string>
+
+#include "SMFWriter.hpp"
+
+using std::endl;
+
+namespace machina {
+
+/** Create a new SMF writer.
+ *
+ * @param unit Must match the time stamp of ALL events passed to write, or
+ * terrible things will happen.
+ *
+ * *** NOTE: Only beat time is implemented currently.
+ */
+SMFWriter::SMFWriter(Raul::TimeUnit unit)
+ : _fd(NULL)
+ , _unit(unit)
+ , _start_time(unit, 0, 0)
+ , _last_ev_time(unit, 0, 0)
+ , _track_size(0)
+ , _header_size(0)
+{
+ if (unit.type() == Raul::TimeUnit::BEATS) {
+ assert(unit.ppt() < std::numeric_limits<uint16_t>::max());
+ }
+}
+
+SMFWriter::~SMFWriter()
+{
+ if (_fd) {
+ finish();
+ }
+}
+
+/** Start a write to an SMF file.
+ *
+ * @param filename Filename to write to.
+ * @param start_time Beat time corresponding to t=0 in the file (timestamps
+ * passed to write_event will have this value subtracted before writing).
+ */
+bool
+SMFWriter::start(const std::string& filename,
+ Raul::TimeStamp start_time)
+{
+ if (_fd) {
+ throw std::logic_error(
+ "Attempt to start new write while write in progress.");
+ }
+
+ std::cout << "Opening SMF file " << filename << " for writing." << endl;
+
+ _fd = fopen(filename.c_str(), "w+");
+
+ if (_fd) {
+ _track_size = 0;
+ _filename = filename;
+ _start_time = start_time;
+ _last_ev_time = 0;
+ // write a tentative header to pad file out so writing starts at the right offset
+ write_header();
+ }
+
+ return (_fd == 0) ? false : true;
+}
+
+/** Write an event at the end of the file.
+ *
+ * @param time The absolute time of the event, relative to the start of the
+ * file (the start_time parameter to start). Must be monotonically increasing
+ * on successive calls to this method.
+ */
+void
+SMFWriter::write_event(Raul::TimeStamp time,
+ size_t ev_size,
+ const unsigned char* ev)
+{
+ if (time < _start_time) {
+ throw std::logic_error("Event time is before file start time");
+ } else if (time < _last_ev_time) {
+ throw std::logic_error("Event time not monotonically increasing");
+ } else if (time.unit() != _unit) {
+ throw std::logic_error("Event has unexpected time unit");
+ }
+
+ Raul::TimeStamp delta_time = time;
+ delta_time -= _start_time;
+
+ fseek(_fd, 0, SEEK_END);
+
+ uint64_t delta_ticks = delta_time.ticks() * _unit.ppt()
+ + delta_time.subticks();
+ size_t stamp_size = 0;
+
+ /* If delta time is too long (i.e. overflows), write out empty
+ * "proprietary" events to reach the desired time.
+ * Any SMF reading application should interpret this correctly
+ * (by accumulating the delta time and ignoring the event) */
+ while (delta_ticks > VAR_LEN_MAX) {
+ static unsigned char null_event[] = { 0xFF, 0x7F, 0x0 };
+ stamp_size = write_var_len(VAR_LEN_MAX);
+ fwrite(null_event, 1, 3, _fd);
+ _track_size += stamp_size + 3;
+ delta_ticks -= VAR_LEN_MAX;
+ }
+
+ assert(delta_ticks <= VAR_LEN_MAX);
+ stamp_size = write_var_len(static_cast<uint32_t>(delta_ticks));
+ fwrite(ev, 1, ev_size, _fd);
+
+ _last_ev_time = time;
+ _track_size += stamp_size + ev_size;
+}
+
+void
+SMFWriter::flush()
+{
+ if (_fd) {
+ fflush(_fd);
+ }
+}
+
+void
+SMFWriter::finish()
+{
+ if (!_fd) {
+ throw std::logic_error(
+ "Attempt to finish write with no write in progress.");
+ }
+
+ write_footer();
+ fclose(_fd);
+ _fd = NULL;
+}
+
+void
+SMFWriter::write_header()
+{
+ std::cout << "SMF Flushing header\n";
+
+ const uint16_t type = htons(0); // SMF Type 0 (single track)
+ const uint16_t ntracks = htons(1); // Number of tracks (always 1 for Type 0)
+ const uint16_t division = htons(_unit.ppt()); // PPQN
+
+ char data[6];
+ memcpy(data, &type, 2);
+ memcpy(data + 2, &ntracks, 2);
+ memcpy(data + 4, &division, 2);
+ //data[4] = _ppqn & 0xF0;
+ //data[5] = _ppqn & 0x0F;
+
+ _fd = freopen(_filename.c_str(), "r+", _fd);
+ assert(_fd);
+ fseek(_fd, 0, 0);
+ write_chunk("MThd", 6, data);
+ write_chunk_header("MTrk", _track_size);
+}
+
+void
+SMFWriter::write_footer()
+{
+ std::cout << "Writing EOT\n";
+
+ fseek(_fd, 0, SEEK_END);
+ write_var_len(1); // whatever...
+ static const unsigned char eot[4] = { 0xFF, 0x2F, 0x00 }; // end-of-track meta-event
+ fwrite(eot, 1, 4, _fd);
+}
+
+void
+SMFWriter::write_chunk_header(const char id[4], uint32_t length)
+{
+ const uint32_t length_be = htonl(length);
+
+ fwrite(id, 1, 4, _fd);
+ fwrite(&length_be, 4, 1, _fd);
+}
+
+void
+SMFWriter::write_chunk(const char id[4], uint32_t length, void* data)
+{
+ write_chunk_header(id, length);
+
+ fwrite(data, 1, length, _fd);
+}
+
+/** Write an SMF variable length value.
+ *
+ * @return size (in bytes) of the value written.
+ */
+size_t
+SMFWriter::write_var_len(uint32_t value)
+{
+ size_t ret = 0;
+
+ uint32_t buffer = value & 0x7F;
+
+ while ((value >>= 7)) {
+ buffer <<= 8;
+ buffer |= ((value & 0x7F) | 0x80);
+ }
+
+ while (true) {
+ //printf("Writing var len byte %X\n", (unsigned char)buffer);
+ ++ret;
+ fputc(buffer, _fd);
+ if (buffer & 0x80) {
+ buffer >>= 8;
+ } else {
+ break;
+ }
+ }
+
+ return ret;
+}
+
+} // namespace machina
diff --git a/src/engine/SMFWriter.hpp b/src/engine/SMFWriter.hpp
new file mode 100644
index 0000000..18bf3ec
--- /dev/null
+++ b/src/engine/SMFWriter.hpp
@@ -0,0 +1,73 @@
+/*
+ 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/>.
+*/
+
+#ifndef MACHINA_SMF_WRITER_HPP
+#define MACHINA_SMF_WRITER_HPP
+
+#include <stdexcept>
+#include <string>
+
+#include "raul/TimeStamp.hpp"
+
+#include "MIDISink.hpp"
+
+namespace machina {
+
+/** Standard Midi File (Type 0) Writer
+ * \ingroup raul
+ */
+class SMFWriter
+ : public MIDISink
+{
+public:
+ explicit SMFWriter(Raul::TimeUnit unit);
+ ~SMFWriter();
+
+ bool start(const std::string& filename,
+ Raul::TimeStamp start_time);
+
+ Raul::TimeUnit unit() const { return _unit; }
+
+ void write_event(Raul::TimeStamp time,
+ size_t ev_size,
+ const unsigned char* ev);
+
+ void flush();
+
+ void finish();
+
+protected:
+ static const uint32_t VAR_LEN_MAX = 0x0FFFFFFF;
+
+ void write_header();
+ void write_footer();
+
+ void write_chunk_header(const char id[4], uint32_t length);
+ void write_chunk(const char id[4], uint32_t length, void* data);
+ size_t write_var_len(uint32_t val);
+
+ std::string _filename;
+ FILE* _fd;
+ Raul::TimeUnit _unit;
+ Raul::TimeStamp _start_time;
+ Raul::TimeStamp _last_ev_time; ///< Time last event was written relative to _start_time
+ uint32_t _track_size;
+ uint32_t _header_size; ///< size of SMF header, including MTrk chunk header
+};
+
+} // namespace machina
+
+#endif // MACHINA_SMF_WRITER_HPP
diff --git a/src/engine/Schrodinbit.hpp b/src/engine/Schrodinbit.hpp
new file mode 100644
index 0000000..0721eb9
--- /dev/null
+++ b/src/engine/Schrodinbit.hpp
@@ -0,0 +1,42 @@
+/*
+ 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/>.
+*/
+
+#ifndef SCHRODINBIT_HPP
+#define SCHRODINBIT_HPP
+
+/** A flag which becomes false when its value is observed
+ */
+class Schrodinbit
+{
+public:
+ Schrodinbit() : _flag(false) {}
+
+ inline operator bool() {
+ const bool ret = _flag;
+ _flag = false;
+ return ret;
+ }
+
+ inline bool operator=(bool flag) {
+ _flag = flag;
+ return flag;
+ }
+
+private:
+ bool _flag;
+};
+
+#endif // SCHRODINBIT_HPP
diff --git a/src/engine/Slave.hpp b/src/engine/Slave.hpp
new file mode 100644
index 0000000..74ec06b
--- /dev/null
+++ b/src/engine/Slave.hpp
@@ -0,0 +1,73 @@
+/*
+ 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/>.
+*/
+
+#ifndef MACHINA_SLAVE_HPP
+#define MACHINA_SLAVE_HPP
+
+#include <thread>
+
+#include "raul/Semaphore.hpp"
+
+namespace machina {
+
+/** Thread driven by (realtime safe) signals.
+ *
+ * Use this to perform some task in a separate thread you want to 'drive'
+ * from a realtime (or otherwise) thread.
+ */
+class Slave
+{
+public:
+ Slave()
+ : _whip(0)
+ , _exit_flag(false)
+ , _thread(&Slave::_run, this)
+ {}
+
+ virtual ~Slave() {
+ _exit_flag = true;
+ _whip.post();
+ _thread.join();
+ }
+
+ /** Tell the slave to do whatever work it does. Realtime safe. */
+ inline void whip() { _whip.post(); }
+
+protected:
+ /** Worker method.
+ *
+ * This is called once from this thread every time whip() is called.
+ * Implementations likely want to put a single (non loop) chunk of code
+ * here, e.g. to process an event.
+ */
+ virtual void _whipped() = 0;
+
+ Raul::Semaphore _whip;
+
+private:
+ inline void _run() {
+ while (_whip.wait() && !_exit_flag) {
+ _whipped();
+ }
+ }
+
+ bool _exit_flag;
+ std::thread _thread;
+};
+
+} // namespace machina
+
+#endif // MACHINA_SLAVE_HPP
diff --git a/src/engine/Stateful.cpp b/src/engine/Stateful.cpp
new file mode 100644
index 0000000..7608a06
--- /dev/null
+++ b/src/engine/Stateful.cpp
@@ -0,0 +1,39 @@
+/*
+ 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 "Stateful.hpp"
+
+namespace machina {
+
+uint64_t Stateful::_next_id = 1;
+
+Stateful::Stateful()
+ : _id(next_id())
+{}
+
+const Sord::Node&
+Stateful::rdf_id(Sord::World& world) const
+{
+ if (!_rdf_id.is_valid()) {
+ std::ostringstream ss;
+ ss << "b" << _id;
+ _rdf_id = Sord::Node(world, Sord::Node::BLANK, ss.str());
+ }
+
+ return _rdf_id;
+}
+
+} // namespace machina
diff --git a/src/engine/Stateful.hpp b/src/engine/Stateful.hpp
new file mode 100644
index 0000000..b15d863
--- /dev/null
+++ b/src/engine/Stateful.hpp
@@ -0,0 +1,70 @@
+/*
+ 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/>.
+*/
+
+#ifndef MACHINA_STATEFUL_HPP
+#define MACHINA_STATEFUL_HPP
+
+#include <stdint.h>
+
+#include "sord/sordmm.hpp"
+
+#include "machina/Atom.hpp"
+#include "machina/types.hpp"
+
+namespace Raul {
+class Atom;
+}
+
+namespace machina {
+
+class URIs;
+
+class Stateful
+{
+public:
+ Stateful();
+
+ virtual ~Stateful() {}
+
+ virtual void set(URIInt key, const Atom& value) {}
+ virtual void write_state(Sord::Model& model) = 0;
+
+ uint64_t id() const { return _id; }
+ const Sord::Node& rdf_id(Sord::World& world) const;
+
+ static uint64_t next_id() { return _next_id++; }
+
+protected:
+ explicit Stateful(uint64_t id) : _id(id) {}
+
+private:
+ static uint64_t _next_id;
+
+ uint64_t _id;
+ mutable Sord::Node _rdf_id;
+};
+
+class StatefulKey : public Stateful
+{
+public:
+ explicit StatefulKey(uint64_t id) : Stateful(id) {}
+
+ void write_state(Sord::Model& model) {}
+};
+
+} // namespace machina
+
+#endif // MACHINA_STATEFUL_HPP
diff --git a/src/engine/URIs.cpp b/src/engine/URIs.cpp
new file mode 100644
index 0000000..2b43b81
--- /dev/null
+++ b/src/engine/URIs.cpp
@@ -0,0 +1,23 @@
+/*
+ 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 "machina/URIs.hpp"
+
+namespace machina {
+
+URIs* URIs::_instance = NULL;
+
+} // namespace machina
diff --git a/src/engine/Updates.cpp b/src/engine/Updates.cpp
new file mode 100644
index 0000000..ce655e4
--- /dev/null
+++ b/src/engine/Updates.cpp
@@ -0,0 +1,66 @@
+/*
+ This file is part of Machina.
+ Copyright 2007-2014 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 "lv2/lv2plug.in/ns/ext/atom/atom.h"
+
+#include "machina/Atom.hpp"
+#include "machina/Updates.hpp"
+#include "machina/types.hpp"
+#include "raul/RingBuffer.hpp"
+
+namespace machina {
+
+void
+write_set(SPtr<Raul::RingBuffer> buf,
+ uint64_t subject,
+ URIInt key,
+ const Atom& value)
+{
+ const uint32_t update_type = UPDATE_SET;
+ buf->write(sizeof(update_type), &update_type);
+ buf->write(sizeof(subject), &subject);
+ buf->write(sizeof(key), &key);
+
+ const LV2_Atom atom = { value.size(), value.type() };
+ buf->write(sizeof(LV2_Atom), &atom);
+ buf->write(value.size(), value.get_body());
+}
+
+uint32_t
+read_set(SPtr<Raul::RingBuffer> buf,
+ uint64_t* subject,
+ URIInt* key,
+ Atom* value)
+{
+ uint32_t update_type = 0;
+ buf->read(sizeof(update_type), &update_type);
+ if (update_type != UPDATE_SET) {
+ return 0;
+ }
+
+ buf->read(sizeof(*subject), subject);
+ buf->read(sizeof(*key), key);
+
+ LV2_Atom atom = { 0, 0 };
+ buf->read(sizeof(LV2_Atom), &atom);
+ *value = Atom(atom.size, atom.type, NULL);
+ buf->read(atom.size, value->get_body());
+
+ return sizeof(update_type) + sizeof(*subject) + sizeof(*key)
+ + sizeof(LV2_Atom) + atom.size;
+}
+
+}
diff --git a/src/engine/machina/Atom.hpp b/src/engine/machina/Atom.hpp
new file mode 100644
index 0000000..710cc26
--- /dev/null
+++ b/src/engine/machina/Atom.hpp
@@ -0,0 +1,217 @@
+/*
+ This file is part of Machina.
+ Copyright 2007-2013 David Robillard <http://drobilla.net>
+
+ Raul 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.
+
+ Raul 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 Raul. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef MACHINA_ATOM_HPP
+#define MACHINA_ATOM_HPP
+
+#include <stdint.h>
+
+#include <cassert>
+#include <cstdlib>
+#include <cstring>
+#include <string>
+
+namespace machina {
+
+class Forge;
+
+/**
+ A generic typed data container.
+
+ An Atom holds a value with some type and size, both specified by a uint32_t.
+ Values with size less than sizeof(void*) are stored inline: no dynamic
+ allocation occurs so Atoms may be created in hard real-time threads.
+ Otherwise, if the size is larger than sizeof(void*), the value will be
+ dynamically allocated in a separate chunk of memory.
+*/
+class Atom {
+public:
+ Atom() : _size(0), _type(0) { _val._blob = NULL; }
+ ~Atom() { dealloc(); }
+
+ typedef uint32_t TypeID;
+
+ /** Contruct a raw atom.
+ *
+ * Typically this is not used directly, use Forge methods to make atoms.
+ */
+ Atom(uint32_t size, TypeID type, const void* body)
+ : _size(size)
+ , _type(type)
+ {
+ if (is_reference()) {
+ _val._blob = malloc(size);
+ }
+ if (body) {
+ memcpy(get_body(), body, size);
+ }
+ }
+
+ Atom(const Atom& copy)
+ : _size(copy._size)
+ , _type(copy._type)
+ {
+ if (is_reference()) {
+ _val._blob = malloc(_size);
+ memcpy(_val._blob, copy._val._blob, _size);
+ } else {
+ memcpy(&_val, &copy._val, _size);
+ }
+ }
+
+ Atom& operator=(const Atom& other) {
+ if (&other == this) {
+ return *this;
+ }
+ dealloc();
+ _size = other._size;
+ _type = other._type;
+ if (is_reference()) {
+ _val._blob = malloc(_size);
+ memcpy(_val._blob, other._val._blob, _size);
+ } else {
+ memcpy(&_val, &other._val, _size);
+ }
+ return *this;
+ }
+
+ inline bool operator==(const Atom& other) const {
+ if (_type == other.type() && _size == other.size()) {
+ if (is_reference()) {
+ return !memcmp(_val._blob, other._val._blob, _size);
+ } else {
+ return !memcmp(&_val, &other._val, _size);
+ }
+ }
+ return false;
+ }
+
+ inline bool operator!=(const Atom& other) const {
+ return !operator==(other);
+ }
+
+ inline bool operator<(const Atom& other) const {
+ if (_type == other.type()) {
+ if (is_reference()) {
+ return memcmp(_val._blob, other._val._blob, _size) < 0;
+ } else {
+ return memcmp(&_val, &other._val, _size) < 0;
+ }
+ }
+ return _type < other.type();
+ }
+
+ inline uint32_t size() const { return _size; }
+ inline bool is_valid() const { return _type; }
+ inline TypeID type() const { return _type; }
+
+ inline const void* get_body() const {
+ return is_reference() ? _val._blob : &_val;
+ }
+
+ inline void* get_body() {
+ return is_reference() ? _val._blob : &_val;
+ }
+
+ template <typename T> const T& get() const {
+ assert(size() == sizeof(T));
+ return *static_cast<const T*>(get_body());
+ }
+
+ template <typename T> const T* ptr() const {
+ return static_cast<const T*>(get_body());
+ }
+
+private:
+ friend class Forge;
+
+ /** Free dynamically allocated value, if applicable. */
+ inline void dealloc() {
+ if (is_reference()) {
+ free(_val._blob);
+ }
+ }
+
+ /** Return true iff this value is dynamically allocated. */
+ inline bool is_reference() const {
+ return _size > sizeof(_val);
+ }
+
+ uint32_t _size;
+ TypeID _type;
+
+ union {
+ intptr_t _val;
+ void* _blob;
+ } _val;
+};
+
+class Forge {
+public:
+ Forge()
+ : Int(1)
+ , Float(2)
+ , Bool(3)
+ , URI(4)
+ , URID(5)
+ , String(6)
+ {}
+
+ virtual ~Forge() {}
+
+ Atom make() { return Atom(); }
+ Atom make(int32_t v) { return Atom(sizeof(v), Int, &v); }
+ Atom make(float v) { return Atom(sizeof(v), Float, &v); }
+ Atom make(bool v) {
+ const int32_t iv = v ? 1 : 0;
+ return Atom(sizeof(int32_t), Bool, &iv);
+ }
+
+ Atom make_urid(int32_t v) { return Atom(sizeof(int32_t), URID, &v); }
+
+ Atom alloc(uint32_t size, uint32_t type, const void* val) {
+ return Atom(size, type, val);
+ }
+
+ Atom alloc(const char* v) {
+ const size_t len = strlen(v);
+ return Atom(len + 1, String, v);
+ }
+
+ Atom alloc(const std::string& v) {
+ return Atom(v.length() + 1, String, v.c_str());
+ }
+
+ Atom alloc_uri(const char* v) {
+ const size_t len = strlen(v);
+ return Atom(len + 1, URI, v);
+ }
+
+ Atom alloc_uri(const std::string& v) {
+ return Atom(v.length() + 1, URI, v.c_str());
+ }
+
+ Atom::TypeID Int;
+ Atom::TypeID Float;
+ Atom::TypeID Bool;
+ Atom::TypeID URI;
+ Atom::TypeID URID;
+ Atom::TypeID String;
+};
+
+} // namespace machina
+
+#endif // MACHINA_ATOM_HPP
diff --git a/src/engine/machina/Context.hpp b/src/engine/machina/Context.hpp
new file mode 100644
index 0000000..766975b
--- /dev/null
+++ b/src/engine/machina/Context.hpp
@@ -0,0 +1,51 @@
+/*
+ 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/>.
+*/
+
+#ifndef MACHINA_CONTEXT_HPP
+#define MACHINA_CONTEXT_HPP
+
+#include "machina/Atom.hpp"
+#include "machina/types.hpp"
+#include "raul/TimeSlice.hpp"
+
+namespace machina {
+
+class MIDISink;
+
+class Context
+{
+public:
+ Context(Forge& forge, uint32_t rate, uint32_t ppqn, double bpm)
+ : _forge(forge)
+ , _time(rate, ppqn, bpm)
+ {}
+
+ void set_sink(MIDISink* sink) { _sink = sink; }
+
+ Forge& forge() { return _forge; }
+ const Raul::TimeSlice& time() const { return _time; }
+ Raul::TimeSlice& time() { return _time; }
+ MIDISink* sink() { return _sink; }
+
+private:
+ Forge& _forge;
+ Raul::TimeSlice _time;
+ MIDISink* _sink;
+};
+
+} // namespace machina
+
+#endif // MACHINA_CONTEXT_HPP
diff --git a/src/engine/machina/Controller.hpp b/src/engine/machina/Controller.hpp
new file mode 100644
index 0000000..833b5b2
--- /dev/null
+++ b/src/engine/machina/Controller.hpp
@@ -0,0 +1,80 @@
+/*
+ This file is part of Machina.
+ Copyright 2007-2014 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/>.
+*/
+
+#ifndef MACHINA_CONTROLLER_HPP
+#define MACHINA_CONTROLLER_HPP
+
+#include <stdint.h>
+
+#include <set>
+
+#include "raul/RingBuffer.hpp"
+#include "raul/Maid.hpp"
+
+#include "machina/Model.hpp"
+#include "machina/URIs.hpp"
+#include "machina/types.hpp"
+
+#include "Stateful.hpp"
+
+namespace Raul {
+class Atom;
+}
+
+namespace machina {
+
+class Engine;
+class Machine;
+
+class Controller
+{
+public:
+ Controller(SPtr<Engine> engine, Model& model);
+
+ uint64_t create(const Properties& properties);
+ uint64_t connect(uint64_t tail_id, uint64_t head_id);
+
+ void set_property(uint64_t object_id, URIInt key, const Atom& value);
+
+ void learn(SPtr<Raul::Maid> maid, uint64_t node_id);
+ void disconnect(uint64_t tail_id, uint64_t head_id);
+ void erase(uint64_t id);
+
+ void announce(SPtr<Machine> machine);
+
+ void process_updates();
+
+private:
+ SPtr<Stateful> find(uint64_t id);
+
+ struct StatefulComparator {
+ inline bool operator()(SPtr<Stateful> a, SPtr<Stateful> b) const {
+ return a->id() < b->id();
+ }
+ };
+
+ typedef std::set<SPtr<Stateful>, StatefulComparator> Objects;
+ Objects _objects;
+
+ SPtr<Engine> _engine;
+ Model& _model;
+
+ SPtr<Raul::RingBuffer> _updates;
+};
+
+}
+
+#endif // MACHINA_CONTROLLER_HPP
diff --git a/src/engine/machina/Driver.hpp b/src/engine/machina/Driver.hpp
new file mode 100644
index 0000000..546c4d6
--- /dev/null
+++ b/src/engine/machina/Driver.hpp
@@ -0,0 +1,86 @@
+/*
+ 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/>.
+*/
+
+#ifndef MACHINA_DRIVER_HPP
+#define MACHINA_DRIVER_HPP
+
+#include "raul/RingBuffer.hpp"
+
+#include "machina/types.hpp"
+
+#include "MIDISink.hpp"
+
+namespace machina {
+
+class Machine;
+
+class Driver : public MIDISink
+{
+public:
+ Driver(Forge& forge, SPtr<Machine> machine)
+ : _forge(forge)
+ , _machine(machine)
+ , _play_state(PlayState::STOPPED)
+ , _bpm(120.0)
+ , _quantization(0.125)
+ , _quantize_record(0)
+ {}
+
+ enum class PlayState {
+ STOPPED,
+ PLAYING,
+ RECORDING,
+ STEP_RECORDING
+ };
+
+ virtual ~Driver() {}
+
+ SPtr<Machine> machine() { return _machine; }
+
+ virtual void set_machine(SPtr<Machine> machine) {
+ _machine = machine;
+ }
+
+ SPtr<Raul::RingBuffer> update_sink() { return _updates; }
+
+ void set_update_sink(SPtr<Raul::RingBuffer> b) {
+ _updates = b;
+ }
+
+ virtual void set_bpm(double bpm) { _bpm = bpm; }
+ virtual void set_quantization(double q) { _quantization = q; }
+ virtual void set_quantize_record(bool q) { _quantize_record = q; }
+ virtual void set_play_state(PlayState state) { _play_state = state; }
+
+ virtual bool is_activated() const { return false; }
+ virtual void activate() {}
+ virtual void deactivate() {}
+
+ PlayState play_state() const { return _play_state; }
+
+protected:
+ Forge& _forge;
+ SPtr<Machine> _machine;
+ SPtr<Raul::RingBuffer> _updates;
+ PlayState _play_state;
+ double _bpm;
+ double _quantization;
+ bool _quantize_record;
+};
+
+} // namespace machina
+
+#endif // MACHINA_JACKDRIVER_HPP
diff --git a/src/engine/machina/Engine.hpp b/src/engine/machina/Engine.hpp
new file mode 100644
index 0000000..dd40f42
--- /dev/null
+++ b/src/engine/machina/Engine.hpp
@@ -0,0 +1,68 @@
+/*
+ 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/>.
+*/
+
+#ifndef MACHINA_ENGINE_HPP
+#define MACHINA_ENGINE_HPP
+
+#include <string>
+
+#include "machina/Atom.hpp"
+#include "machina/Driver.hpp"
+#include "machina/Loader.hpp"
+#include "machina/types.hpp"
+
+namespace machina {
+
+class Machine;
+
+class Engine
+{
+public:
+ Engine(Forge& forge,
+ SPtr<Driver> driver,
+ Sord::World& rdf_world);
+
+ Sord::World& rdf_world() { return _rdf_world; }
+
+ static SPtr<Driver> new_driver(Forge& forge,
+ const std::string& name,
+ SPtr<Machine> machine);
+
+ SPtr<Driver> driver() { return _driver; }
+ SPtr<Machine> machine() { return _driver->machine(); }
+ Forge& forge() { return _forge; }
+
+ SPtr<Machine> load_machine(const std::string& uri);
+ SPtr<Machine> load_machine_midi(const std::string& uri,
+ double q,
+ Raul::TimeDuration dur);
+
+ void export_midi(const std::string& filename,
+ Raul::TimeDuration dur);
+
+ void set_bpm(double bpm);
+ void set_quantization(double beat_fraction);
+
+private:
+ SPtr<Driver> _driver;
+ Sord::World& _rdf_world;
+ Loader _loader;
+ Forge _forge;
+};
+
+} // namespace machina
+
+#endif // MACHINA_ENGINE_HPP
diff --git a/src/engine/machina/Evolver.hpp b/src/engine/machina/Evolver.hpp
new file mode 100644
index 0000000..400c177
--- /dev/null
+++ b/src/engine/machina/Evolver.hpp
@@ -0,0 +1,72 @@
+/*
+ This file is part of Machina.
+ Copyright 2007-2017 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/>.
+*/
+
+#ifndef MACHINA_EVOLVER_HPP
+#define MACHINA_EVOLVER_HPP
+
+#include <atomic>
+#include <memory>
+#include <thread>
+
+#include "eugene/GA.hpp"
+#include "eugene/Random.hpp"
+#include "machina/types.hpp"
+#include "raul/TimeStamp.hpp"
+
+#include "Machine.hpp"
+#include "Schrodinbit.hpp"
+
+namespace eugene {
+template<typename G> class HybridMutation;
+}
+
+namespace machina {
+
+class Problem;
+
+class Evolver
+{
+public:
+ Evolver(Raul::TimeUnit unit,
+ const std::string& target_midi,
+ SPtr<Machine> seed);
+
+ void seed(SPtr<Machine> parent);
+ bool improvement() { return _improvement; }
+
+ void start();
+ void join();
+
+ const Machine& best() { return _ga->best(); }
+
+ typedef eugene::GA<Machine> MachinaGA;
+
+private:
+ void run();
+
+ eugene::Random _rng;
+ SPtr<MachinaGA> _ga;
+ SPtr<Problem> _problem;
+ float _seed_fitness;
+ Schrodinbit _improvement;
+ std::atomic<bool> _exit_flag;
+
+ std::unique_ptr<std::thread> _thread;
+};
+
+} // namespace machina
+
+#endif // MACHINA_EVOLVER_HPP
diff --git a/src/engine/machina/Loader.hpp b/src/engine/machina/Loader.hpp
new file mode 100644
index 0000000..3fa66ff
--- /dev/null
+++ b/src/engine/machina/Loader.hpp
@@ -0,0 +1,49 @@
+/*
+ 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/>.
+*/
+
+#ifndef MACHINA_LOADER_HPP
+#define MACHINA_LOADER_HPP
+
+#include "machina/Atom.hpp"
+#include "machina/types.hpp"
+#include "raul/TimeStamp.hpp"
+#include "sord/sordmm.hpp"
+
+using Sord::Namespaces;
+
+namespace machina {
+
+class Machine;
+
+class Loader
+{
+public:
+ Loader(Forge& forge, Sord::World& rdf_world);
+
+ SPtr<Machine> load(const std::string& filename);
+
+ SPtr<Machine> load_midi(const std::string& filename,
+ double q,
+ Raul::TimeDuration dur);
+
+private:
+ Forge& _forge;
+ Sord::World& _rdf_world;
+};
+
+} // namespace machina
+
+#endif // MACHINA_LOADER_HPP
diff --git a/src/engine/machina/Machine.hpp b/src/engine/machina/Machine.hpp
new file mode 100644
index 0000000..7c9855d
--- /dev/null
+++ b/src/engine/machina/Machine.hpp
@@ -0,0 +1,132 @@
+/*
+ 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/>.
+*/
+
+#ifndef MACHINA_MACHINE_HPP
+#define MACHINA_MACHINE_HPP
+
+#include <vector>
+#include <set>
+
+#include "machina/types.hpp"
+#include "machina/Atom.hpp"
+#include "raul/RingBuffer.hpp"
+#include "raul/TimeSlice.hpp"
+#include "sord/sordmm.hpp"
+
+#include "types.hpp"
+#include "Node.hpp"
+
+namespace machina {
+
+class Context;
+class LearnRequest;
+
+/** A (Finite State) Machine.
+ */
+class Machine : public Stateful
+{
+public:
+ explicit 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);
+
+ /** Completely replace this machine's contents with a deep copy. */
+ Machine& operator=(const Machine& copy);
+
+ bool operator==(const Machine& rhs) const;
+
+ /** Merge another machine into this machine. */
+ void merge(const Machine& machine);
+
+ bool is_empty() { return _nodes.empty(); }
+ bool is_finished() { return _is_finished; }
+
+ void add_node(SPtr<Node> node);
+ void remove_node(SPtr<Node> node);
+ void learn(SPtr<Raul::Maid> maid, SPtr<Node> node);
+
+ void write_state(Sord::Model& model);
+
+ /** 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<Raul::RingBuffer> updates);
+
+ // Any context
+ inline Raul::TimeStamp time() const { return _time; }
+
+ SPtr<LearnRequest> pending_learn() { return _pending_learn; }
+ void clear_pending_learn() { _pending_learn.reset(); }
+
+ typedef std::set< SPtr<Node> > Nodes;
+ Nodes& nodes() { return _nodes; }
+ const Nodes& nodes() const { return _nodes; }
+
+ SPtr<Node> initial_node() const { return _initial_node; }
+
+ SPtr<Node> random_node();
+ SPtr<Edge> random_edge();
+
+ float fitness; // For GA
+
+private:
+ /** Return the active Node with the earliest exit time. */
+ SPtr<Node> earliest_node() const;
+
+ void assign(const Machine& other);
+
+ /** Enter a node at the current time (called by run()).
+ *
+ * @return true if node was entered, otherwise voics are exhausted.
+ */
+ bool enter_node(Context& context,
+ SPtr<Node> node,
+ SPtr<Raul::RingBuffer> updates);
+
+ /** Exit a node at the current time (called by run()). */
+ void exit_node(Context& context,
+ SPtr<Node> node,
+ SPtr<Raul::RingBuffer> updates);
+
+ static const size_t MAX_ACTIVE_NODES = 128;
+
+ SPtr<Node> _initial_node;
+ std::vector< SPtr<Node> > _active_nodes;
+
+ SPtr<LearnRequest> _pending_learn;
+ Nodes _nodes;
+ Raul::TimeStamp _time;
+
+ bool _is_finished;
+};
+
+} // namespace machina
+
+#endif // MACHINA_MACHINE_HPP
diff --git a/src/engine/machina/Model.hpp b/src/engine/machina/Model.hpp
new file mode 100644
index 0000000..32a332e
--- /dev/null
+++ b/src/engine/machina/Model.hpp
@@ -0,0 +1,44 @@
+/*
+ This file is part of Machina.
+ Copyright 2014 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/>.
+*/
+
+#ifndef MACHINA_MODEL_HPP
+#define MACHINA_MODEL_HPP
+
+#include <stdint.h>
+
+#include <map>
+
+#include "machina/Atom.hpp"
+#include "machina/types.hpp"
+
+namespace machina {
+
+class Model
+{
+public:
+ virtual ~Model() {}
+
+ virtual void new_object(uint64_t id, const Properties& properties) = 0;
+
+ virtual void erase_object(uint64_t id) = 0;
+
+ virtual void set(uint64_t id, URIInt key, const Atom& value) = 0;
+ virtual const Atom& get(uint64_t id, URIInt key) const = 0;
+};
+
+} // namespace machina
+
+#endif // MACHINA_MODEL_HPP
diff --git a/src/engine/machina/Mutation.hpp b/src/engine/machina/Mutation.hpp
new file mode 100644
index 0000000..69f5ee4
--- /dev/null
+++ b/src/engine/machina/Mutation.hpp
@@ -0,0 +1,60 @@
+/*
+ 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/>.
+*/
+
+#ifndef MACHINA_MACHINE_MUTATION_HPP
+#define MACHINA_MACHINE_MUTATION_HPP
+
+#include "machina_config.h"
+
+#ifdef HAVE_EUGENE
+# include "eugene/Mutation.hpp"
+# define SUPER : public eugene::Mutation<Machine>
+#else
+# define SUPER : public Mutation
+#endif
+
+namespace machina {
+
+#ifdef HAVE_EUGENE
+typedef eugene::Random Random;
+#else
+struct Random {};
+#endif
+
+class Machine;
+
+namespace Mutation {
+
+struct Mutation {
+ virtual ~Mutation() {}
+
+ virtual void mutate(Random& rng, Machine& machine) = 0;
+};
+
+struct Compress SUPER { void mutate(Random& rng, Machine& machine); };
+struct AddNode SUPER { void mutate(Random& rng, Machine& machine); };
+struct RemoveNode SUPER { void mutate(Random& rng, Machine& machine); };
+struct AdjustNode SUPER { void mutate(Random& rng, Machine& machine); };
+struct SwapNodes SUPER { void mutate(Random& rng, Machine& machine); };
+struct AddEdge SUPER { void mutate(Random& rng, Machine& machine); };
+struct RemoveEdge SUPER { void mutate(Random& rng, Machine& machine); };
+struct AdjustEdge SUPER { void mutate(Random& rng, Machine& machine); };
+
+} // namespace Mutation
+
+} // namespace machina
+
+#endif // MACHINA_MACHINE_MUTATION_HPP
diff --git a/src/engine/machina/URIs.hpp b/src/engine/machina/URIs.hpp
new file mode 100644
index 0000000..f770723
--- /dev/null
+++ b/src/engine/machina/URIs.hpp
@@ -0,0 +1,93 @@
+/*
+ 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/>.
+*/
+
+#ifndef MACHINA_URIS_HPP
+#define MACHINA_URIS_HPP
+
+#include <stdint.h>
+
+#include "machina/Atom.hpp"
+#include "machina/types.hpp"
+
+#define MACHINA_URI_RDF "http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+
+#define MACHINA_NS "http://drobilla.net/ns/machina#"
+
+#define MACHINA_NS_Machine MACHINA_NS "Machine"
+#define MACHINA_NS_Node MACHINA_NS "Node"
+#define MACHINA_NS_SelectorNode MACHINA_NS "SelectorNode"
+#define MACHINA_NS_arc MACHINA_NS "arc"
+#define MACHINA_NS_duration MACHINA_NS "duration"
+#define MACHINA_NS_head MACHINA_NS "head"
+#define MACHINA_NS_node MACHINA_NS "node"
+#define MACHINA_NS_onEnter MACHINA_NS "onEnter"
+#define MACHINA_NS_onExit MACHINA_NS "onExit"
+#define MACHINA_NS_probability MACHINA_NS "probability"
+#define MACHINA_NS_start MACHINA_NS "start"
+#define MACHINA_NS_tail MACHINA_NS "tail"
+
+namespace machina {
+
+class URIs
+{
+public:
+ static void init() { _instance = new URIs(); }
+
+ static inline const URIs& instance() { assert(_instance); return *_instance; }
+
+ URIInt machina_Edge;
+ URIInt machina_MidiAction;
+ URIInt machina_Node;
+ URIInt machina_active;
+ URIInt machina_canvas_x;
+ URIInt machina_canvas_y;
+ URIInt machina_duration;
+ URIInt machina_enter_action;
+ URIInt machina_exit_action;
+ URIInt machina_head_id;
+ URIInt machina_initial;
+ URIInt machina_note_number;
+ URIInt machina_probability;
+ URIInt machina_selector;
+ URIInt machina_tail_id;
+ URIInt rdf_type;
+
+private:
+ URIs()
+ : machina_Edge(100)
+ , machina_MidiAction(101)
+ , machina_Node(102)
+ , machina_active(1)
+ , machina_canvas_x(2)
+ , machina_canvas_y(3)
+ , machina_duration(4)
+ , machina_enter_action(11)
+ , machina_exit_action(12)
+ , machina_head_id(5)
+ , machina_initial(6)
+ , machina_note_number(13)
+ , machina_probability(7)
+ , machina_selector(8)
+ , machina_tail_id(9)
+ , rdf_type(10)
+ {}
+
+ static URIs* _instance;
+};
+
+} // namespace machina
+
+#endif // MACHINA_URIS_HPP
diff --git a/src/engine/machina/Updates.hpp b/src/engine/machina/Updates.hpp
new file mode 100644
index 0000000..ff09af9
--- /dev/null
+++ b/src/engine/machina/Updates.hpp
@@ -0,0 +1,46 @@
+/*
+ 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/>.
+*/
+
+#ifndef MACHINA_UPDATES_HPP
+#define MACHINA_UPDATES_HPP
+
+#include <stdint.h>
+
+#include "machina/Atom.hpp"
+#include "machina/types.hpp"
+#include "raul/RingBuffer.hpp"
+
+namespace machina {
+
+enum UpdateType {
+ UPDATE_SET = 1
+};
+
+void
+write_set(SPtr<Raul::RingBuffer> buf,
+ uint64_t subject,
+ URIInt key,
+ const Atom& value);
+
+uint32_t
+read_set(SPtr<Raul::RingBuffer> buf,
+ uint64_t* subject,
+ URIInt* key,
+ Atom* value);
+
+} // namespace machina
+
+#endif // MACHINA_UPDATES_HPP
diff --git a/src/engine/machina/types.hpp b/src/engine/machina/types.hpp
new file mode 100644
index 0000000..61137b7
--- /dev/null
+++ b/src/engine/machina/types.hpp
@@ -0,0 +1,69 @@
+/*
+ This file is part of Machina.
+ Copyright 2007-2014 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/>.
+*/
+
+#ifndef MACHINA_TYPES_HPP
+#define MACHINA_TYPES_HPP
+
+#include <map>
+#include <memory>
+
+#include "raul/Maid.hpp"
+
+namespace machina {
+
+typedef unsigned char byte;
+
+typedef uint32_t URIInt;
+
+class Atom;
+typedef std::map<URIInt, Atom> Properties;
+
+#if __cplusplus >= 201103L
+template <class T>
+using SPtr = std::shared_ptr<T>;
+
+template <class T>
+using WPtr = std::weak_ptr<T>;
+
+template <class T>
+using MPtr = Raul::managed_ptr<T>;
+#else
+#define SPtr std::shared_ptr
+#define WPtr std::weak_ptr
+#define MPtr Raul::managed_ptr
+#endif
+
+template <class T>
+void NullDeleter(T* ptr) {}
+
+template<class T, class U>
+SPtr<T> static_ptr_cast(const SPtr<U>& r) {
+ return std::static_pointer_cast<T>(r);
+}
+
+template<class T, class U>
+SPtr<T> dynamic_ptr_cast(const SPtr<U>& r) {
+ return std::dynamic_pointer_cast<T>(r);
+}
+
+template<class T, class U>
+SPtr<T> const_ptr_cast(const SPtr<U>& r) {
+ return std::const_pointer_cast<T>(r);
+}
+
+} // namespace machina
+
+#endif // MACHINA_TYPES_HPP
diff --git a/src/engine/quantize.hpp b/src/engine/quantize.hpp
new file mode 100644
index 0000000..9cf939a
--- /dev/null
+++ b/src/engine/quantize.hpp
@@ -0,0 +1,46 @@
+/*
+ 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/>.
+*/
+
+#ifndef MACHINA_QUANTIZE_HPP
+#define MACHINA_QUANTIZE_HPP
+
+#include <cmath>
+
+#include "raul/TimeStamp.hpp"
+
+namespace machina {
+
+inline TimeStamp
+quantize(TimeStamp q, TimeStamp t)
+{
+ assert(q.unit() == t.unit());
+ // FIXME: Precision problem? Should probably stay in discrete domain
+ const double qd = q.to_double();
+ const double td = t.to_double();
+ return TimeStamp(t.unit(), (qd > 0) ? lrint(td / qd) * qd : td);
+}
+
+inline double
+quantize(double q, double t)
+{
+ return (q > 0)
+ ? lrint(t / q) * q
+ : t;
+}
+
+} // namespace machina
+
+#endif // MACHINA_QUANTIZE_HPP
diff --git a/src/engine/quantize_test.cpp b/src/engine/quantize_test.cpp
new file mode 100644
index 0000000..5410489
--- /dev/null
+++ b/src/engine/quantize_test.cpp
@@ -0,0 +1,41 @@
+/*
+ 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 <iostream>
+
+#include "quantize.hpp"
+
+using namespace std;
+using namespace machina;
+
+int
+main()
+{
+ TimeStamp q(TimeUnit(TimeUnit::BEATS, 19200), 0.25);
+
+ for (double in = 0.0; in < 32; in += 0.23) {
+ TimeStamp beats(TimeUnit(TimeUnit::BEATS, 19200), in);
+
+ /*cout << "Q(" << in << ", 1/4) = "
+ << quantize(q, beats) << endl;*/
+
+ if (quantize(q, beats).subticks() % (19200 / 4) != 0) {
+ return 1;
+ }
+ }
+
+ return 0;
+}
diff --git a/src/engine/smf_test.cpp b/src/engine/smf_test.cpp
new file mode 100644
index 0000000..d98a696
--- /dev/null
+++ b/src/engine/smf_test.cpp
@@ -0,0 +1,84 @@
+/*
+ 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 <iostream>
+#include <string>
+#include "raul/log.hpp"
+#include "raul/SMFReader.hpp"
+#include "raul/SMFWriter.hpp"
+
+using namespace std;
+using namespace machina;
+
+int
+main(int argc, char** argv)
+{
+#define CHECK(cond) \
+ do { if (!(cond)) { \
+ error << "Test at " << __FILE__ << ":" << __LINE__ \
+ << " failed: " << __STRING(cond) << endl; \
+ return 1; \
+ } \
+ } \
+ while (0)
+
+ static const uint16_t ppqn = 19200;
+
+ const char* filename = NULL;
+
+ if (argc < 2) {
+ filename = "./test.mid";
+ SMFWriter writer(TimeUnit(TimeUnit::BEATS, ppqn));
+ writer.start(string(filename), TimeStamp(writer.unit(), 0, 0));
+ writer.finish();
+ } else {
+ filename = argv[1];
+ }
+
+ SMFReader reader;
+ bool opened = reader.open(filename);
+
+ if (!opened) {
+ cerr << "Unable to open SMF file " << filename << endl;
+ return -1;
+ }
+
+ CHECK(reader.type() == 0);
+ CHECK(reader.num_tracks() == 1);
+ CHECK(reader.ppqn() == ppqn);
+
+ for (unsigned t = 1; t <= reader.num_tracks(); ++t) {
+ reader.seek_to_track(t);
+
+ unsigned char buf[4];
+ uint32_t ev_size;
+ uint32_t ev_delta_time;
+ while (reader.read_event(4, buf, &ev_size, &ev_delta_time) >= 0) {
+
+ cout << t << ": Event, size = " << ev_size << ", time = "
+ << ev_delta_time;
+ cout << ":\t";
+ cout.flags(ios::hex);
+ for (uint32_t i = 0; i < ev_size; ++i) {
+ cout << "0x" << static_cast<int>(buf[i]) << " ";
+ }
+ cout.flags(ios::dec);
+ cout << endl;
+ }
+ }
+
+ return 0;
+}
diff --git a/src/engine/wscript b/src/engine/wscript
new file mode 100644
index 0000000..1ec63a2
--- /dev/null
+++ b/src/engine/wscript
@@ -0,0 +1,45 @@
+#!/usr/bin/env python
+from waflib.extras import autowaf as autowaf
+
+def build(bld):
+ core_source = '''
+ ActionFactory.cpp
+ Controller.cpp
+ Edge.cpp
+ Engine.cpp
+ JackDriver.cpp
+ LearnRequest.cpp
+ Loader.cpp
+ Machine.cpp
+ MachineBuilder.cpp
+ MidiAction.cpp
+ Mutation.cpp
+ Node.cpp
+ Recorder.cpp
+ SMFDriver.cpp
+ SMFReader.cpp
+ SMFWriter.cpp
+ Stateful.cpp
+ Updates.cpp
+ URIs.cpp
+ '''
+ if bld.env.HAVE_EUGENE:
+ core_source += '''
+ Evolver.cpp
+ Problem.cpp
+ '''
+ obj = bld(features = 'cxx cxxshlib')
+ obj.source = core_source
+ obj.export_includes = ['.']
+ obj.includes = ['.', '..', '../..']
+ obj.name = 'libmachina_engine'
+ obj.target = 'machina_engine'
+ obj.cxxflags = '-pthread'
+ if bld.env.CXX_NAME != 'clang':
+ obj.linkflags = '-pthread'
+ core_libs = 'RAUL SERD SORD JACK LV2'
+ if bld.env.HAVE_EUGENE:
+ core_libs += ' EUGENE '
+ autowaf.use_lib(bld, obj, core_libs)
+
+ bld.add_post_fun(autowaf.run_ldconfig)