aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/client/ClientModel.cpp94
-rw-r--r--src/client/ClientModel.hpp67
-rw-r--r--src/client/ClientObject.cpp56
-rw-r--r--src/client/ClientObject.hpp71
-rw-r--r--src/client/wscript17
-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
-rw-r--r--src/gui/EdgeView.cpp142
-rw-r--r--src/gui/EdgeView.hpp59
-rw-r--r--src/gui/MachinaCanvas.cpp211
-rw-r--r--src/gui/MachinaCanvas.hpp66
-rw-r--r--src/gui/MachinaGUI.cpp750
-rw-r--r--src/gui/MachinaGUI.hpp190
-rw-r--r--src/gui/NodePropertiesWindow.cpp136
-rw-r--r--src/gui/NodePropertiesWindow.hpp67
-rw-r--r--src/gui/NodeView.cpp203
-rw-r--r--src/gui/NodeView.hpp76
-rw-r--r--src/gui/WidgetFactory.hpp65
-rw-r--r--src/gui/machina.gladep9
-rw-r--r--src/gui/machina.svg66
-rw-r--r--src/gui/machina.ui1128
-rw-r--r--src/gui/main.cpp100
-rw-r--r--src/gui/wscript42
-rw-r--r--src/machina.cpp86
-rw-r--r--src/midi2machina.cpp81
78 files changed, 10235 insertions, 0 deletions
diff --git a/src/client/ClientModel.cpp b/src/client/ClientModel.cpp
new file mode 100644
index 0000000..3a8770f
--- /dev/null
+++ b/src/client/ClientModel.cpp
@@ -0,0 +1,94 @@
+/*
+ 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 "ClientModel.hpp"
+
+namespace machina {
+namespace client {
+
+SPtr<ClientObject>
+ClientModel::find(uint64_t id)
+{
+ SPtr<ClientObject> key(new ClientObjectKey(id));
+ Objects::iterator i = _objects.find(key);
+ if (i != _objects.end()) {
+ return *i;
+ } else {
+ return SPtr<ClientObject>();
+ }
+}
+
+SPtr<const ClientObject>
+ClientModel::find(uint64_t id) const
+{
+ SPtr<ClientObject> key(new ClientObjectKey(id));
+ Objects::const_iterator i = _objects.find(key);
+ if (i != _objects.end()) {
+ return *i;
+ } else {
+ return SPtr<ClientObject>();
+ }
+}
+
+void
+ClientModel::new_object(uint64_t id, const Properties& properties)
+{
+ SPtr<ClientObject> key(new ClientObjectKey(id));
+ Objects::iterator i = _objects.find(key);
+ if (i == _objects.end()) {
+ SPtr<ClientObject> object(new ClientObject(id, properties));
+ _objects.insert(object);
+ signal_new_object.emit(object);
+ } else {
+ for (const auto& p : properties) {
+ (*i)->set(p.first, p.second);
+ }
+ }
+}
+
+void
+ClientModel::erase_object(uint64_t id)
+{
+ SPtr<ClientObject> key(new ClientObjectKey(id));
+ Objects::iterator i = _objects.find(key);
+ if (i == _objects.end()) {
+ return;
+ }
+
+ signal_erase_object.emit(*i);
+ (*i)->set_view(NULL);
+ _objects.erase(i);
+}
+
+void
+ClientModel::set(uint64_t id, URIInt key, const Atom& value)
+{
+ SPtr<ClientObject> object = find(id);
+ if (object) {
+ object->set(key, value);
+ }
+}
+
+const Atom&
+ClientModel::get(uint64_t id, URIInt key) const
+{
+ static const Atom null_atom;
+ SPtr<const ClientObject> object = find(id);
+ return object ? object->get(key) : null_atom;
+}
+
+}
+}
diff --git a/src/client/ClientModel.hpp b/src/client/ClientModel.hpp
new file mode 100644
index 0000000..e65fb93
--- /dev/null
+++ b/src/client/ClientModel.hpp
@@ -0,0 +1,67 @@
+/*
+ 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_CLIENTMODEL_HPP
+#define MACHINA_CLIENTMODEL_HPP
+
+#include <set>
+
+#include <sigc++/sigc++.h>
+
+#include "machina/Model.hpp"
+
+#include "ClientObject.hpp"
+
+namespace Raul {
+class Atom;
+}
+
+namespace machina {
+namespace client {
+
+class ClientModel : public Model
+{
+public:
+ void new_object(uint64_t id, const Properties& properties);
+
+ void erase_object(uint64_t id);
+
+ SPtr<ClientObject> find(uint64_t id);
+ SPtr<const ClientObject> find(uint64_t id) const;
+
+ void set(uint64_t id, URIInt key, const Atom& value);
+ const Atom& get(uint64_t id, URIInt key) const;
+
+ sigc::signal< void, SPtr<ClientObject> > signal_new_object;
+ sigc::signal< void, SPtr<ClientObject> > signal_erase_object;
+
+private:
+ struct ClientObjectComparator {
+ inline bool operator()(SPtr<ClientObject> lhs,
+ SPtr<ClientObject> rhs) const {
+ return lhs->id() < rhs->id();
+ }
+
+ };
+
+ typedef std::set<SPtr<ClientObject>, ClientObjectComparator> Objects;
+ Objects _objects;
+};
+
+}
+}
+
+#endif // MACHINA_CLIENTMODEL_HPP
diff --git a/src/client/ClientObject.cpp b/src/client/ClientObject.cpp
new file mode 100644
index 0000000..20b50a2
--- /dev/null
+++ b/src/client/ClientObject.cpp
@@ -0,0 +1,56 @@
+/*
+ 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 <iostream>
+
+#include "ClientObject.hpp"
+
+namespace machina {
+namespace client {
+
+ClientObject::ClientObject(uint64_t id, const Properties& properties)
+ : _id(id)
+ , _view(NULL)
+ , _properties(properties)
+{}
+
+ClientObject::ClientObject(const ClientObject& copy, uint64_t id)
+ : _id(id)
+ , _view(NULL)
+ , _properties(copy._properties)
+{}
+
+void
+ClientObject::set(URIInt key, const Atom& value)
+{
+ _properties[key] = value;
+ signal_property.emit(key, value);
+}
+
+const Atom&
+ClientObject::get(URIInt key) const
+{
+ static const Atom null_atom;
+ Properties::const_iterator i = _properties.find(key);
+ if (i != _properties.end()) {
+ return i->second;
+ } else {
+ return null_atom;
+ }
+}
+
+}
+}
diff --git a/src/client/ClientObject.hpp b/src/client/ClientObject.hpp
new file mode 100644
index 0000000..6520b11
--- /dev/null
+++ b/src/client/ClientObject.hpp
@@ -0,0 +1,71 @@
+/*
+ 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_CLIENTOBJECT_HPP
+#define MACHINA_CLIENTOBJECT_HPP
+
+#include <map>
+
+#include <sigc++/sigc++.h>
+
+#include "machina/Atom.hpp"
+#include "machina/types.hpp"
+
+namespace machina {
+namespace client {
+
+class ClientObject
+{
+public:
+ explicit ClientObject(uint64_t id, const Properties& properties={});
+ ClientObject(const ClientObject& copy, uint64_t id);
+
+ inline uint64_t id() const { return _id; }
+
+ void set(URIInt key, const Atom& value);
+ const Atom& get(URIInt key) const;
+
+ sigc::signal<void, URIInt, Atom> signal_property;
+
+ class View
+ {
+ public:
+ virtual ~View() {}
+ };
+
+ typedef std::map<URIInt, Atom> Properties;
+ const Properties& properties() { return _properties; }
+
+ View* view() const { return _view; }
+ void set_view(View* view) { _view = view; }
+
+private:
+ uint64_t _id;
+ View* _view;
+
+ Properties _properties;
+};
+
+/** Stub client object to use as a search key. */
+struct ClientObjectKey : public ClientObject
+{
+ explicit ClientObjectKey(uint64_t id) : ClientObject(id) {}
+};
+
+}
+}
+
+#endif // MACHINA_CLIENTOBJECT_HPP
diff --git a/src/client/wscript b/src/client/wscript
new file mode 100644
index 0000000..8333b43
--- /dev/null
+++ b/src/client/wscript
@@ -0,0 +1,17 @@
+#!/usr/bin/env python
+from waflib.extras import autowaf as autowaf
+
+def build(bld):
+ obj = bld(features = 'cxx cxxshlib')
+ obj.source = '''
+ ClientModel.cpp
+ ClientObject.cpp
+ '''
+ obj.export_includes = ['.']
+ obj.includes = ['.', '..', '../..']
+ obj.name = 'libmachina_client'
+ obj.target = 'machina_client'
+ obj.use = 'libmachina_engine'
+ autowaf.use_lib(bld, obj, 'GLIBMM GTKMM RAUL LV2')
+
+ bld.add_post_fun(autowaf.run_ldconfig)
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..776142d
--- /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(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)
diff --git a/src/gui/EdgeView.cpp b/src/gui/EdgeView.cpp
new file mode 100644
index 0000000..b4c6099
--- /dev/null
+++ b/src/gui/EdgeView.cpp
@@ -0,0 +1,142 @@
+/*
+ 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 "ganv/Canvas.hpp"
+
+#include "machina/Controller.hpp"
+#include "machina/types.hpp"
+
+#include "EdgeView.hpp"
+#include "MachinaCanvas.hpp"
+#include "MachinaGUI.hpp"
+#include "NodeView.hpp"
+
+namespace machina {
+namespace gui {
+
+/* probability colour stuff */
+
+#define RGB_TO_UINT(r, g, b) ((((guint)(r)) << 16) | (((guint)(g)) << 8) | ((guint)(b)))
+#define RGB_TO_RGBA(x, a) (((x) << 8) | ((((guint)a) & 0xff)))
+#define RGBA_TO_UINT(r, g, b, a) RGB_TO_RGBA(RGB_TO_UINT(r, g, b), a)
+
+#define UINT_RGBA_R(x) (((uint32_t)(x)) >> 24)
+#define UINT_RGBA_G(x) ((((uint32_t)(x)) >> 16) & 0xff)
+#define UINT_RGBA_B(x) ((((uint32_t)(x)) >> 8) & 0xff)
+#define UINT_RGBA_A(x) (((uint32_t)(x)) & 0xff)
+
+#define MONO_INTERPOLATE(v1, v2, t) ((int)rint((v2) * (t) + (v1) * (1 - (t))))
+
+#define UINT_INTERPOLATE(c1, c2, t) \
+ RGBA_TO_UINT(MONO_INTERPOLATE(UINT_RGBA_R(c1), UINT_RGBA_R(c2), t), \
+ MONO_INTERPOLATE(UINT_RGBA_G(c1), UINT_RGBA_G(c2), t), \
+ MONO_INTERPOLATE(UINT_RGBA_B(c1), UINT_RGBA_B(c2), t), \
+ MONO_INTERPOLATE(UINT_RGBA_A(c1), UINT_RGBA_A(c2), t) )
+
+inline static uint32_t edge_color(float prob)
+{
+ static const uint32_t min = 0xFF4444FF;
+ static const uint32_t mid = 0xFFFF44FF;
+ static const uint32_t max = 0x44FF44FF;
+
+ if (prob <= 0.5) {
+ return UINT_INTERPOLATE(min, mid, prob * 2.0);
+ } else {
+ return UINT_INTERPOLATE(mid, max, (prob - 0.5) * 2.0);
+ }
+}
+
+/* end probability colour stuff */
+
+using namespace Ganv;
+
+EdgeView::EdgeView(Canvas& canvas,
+ NodeView* src,
+ NodeView* dst,
+ SPtr<machina::client::ClientObject> edge)
+ : Ganv::Edge(canvas, src, dst, 0x9FA0A0FF, true, false)
+ , _edge(edge)
+{
+ set_color(edge_color(probability()));
+
+ edge->signal_property.connect(
+ sigc::mem_fun(this, &EdgeView::on_property));
+
+ signal_event().connect(
+ sigc::mem_fun(this, &EdgeView::on_event));
+}
+
+EdgeView::~EdgeView()
+{
+ _edge->set_view(NULL);
+}
+
+float
+EdgeView::probability() const
+{
+ return _edge->get(URIs::instance().machina_probability).get<float>();
+}
+
+double
+EdgeView::length_hint() const
+{
+ NodeView* tail = dynamic_cast<NodeView*>(get_tail());
+ return tail->node()->get(URIs::instance().machina_duration).get<float>()
+ * 10.0;
+}
+
+void
+EdgeView::show_label(bool show)
+{
+ set_color(edge_color(probability()));
+}
+
+bool
+EdgeView::on_event(GdkEvent* ev)
+{
+ MachinaCanvas* canvas = dynamic_cast<MachinaCanvas*>(this->canvas());
+ Forge& forge = canvas->app()->forge();
+
+ if (ev->type == GDK_BUTTON_PRESS) {
+ if (ev->button.state & GDK_CONTROL_MASK) {
+ if (ev->button.button == 1) {
+ canvas->app()->controller()->set_property(
+ _edge->id(),
+ URIs::instance().machina_probability,
+ forge.make(float(probability() - 0.1f)));
+ return true;
+ } else if (ev->button.button == 3) {
+ canvas->app()->controller()->set_property(
+ _edge->id(),
+ URIs::instance().machina_probability,
+ forge.make(float(probability() + 0.1f)));
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+void
+EdgeView::on_property(machina::URIInt key, const Atom& value)
+{
+ if (key == URIs::instance().machina_probability) {
+ set_color(edge_color(value.get<float>()));
+ }
+}
+
+} // namespace machina
+} // namespace gui
diff --git a/src/gui/EdgeView.hpp b/src/gui/EdgeView.hpp
new file mode 100644
index 0000000..1ad2dd0
--- /dev/null
+++ b/src/gui/EdgeView.hpp
@@ -0,0 +1,59 @@
+/*
+ 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_EDGEVIEW_HPP
+#define MACHINA_EDGEVIEW_HPP
+
+#include "ganv/Edge.hpp"
+
+#include "client/ClientObject.hpp"
+
+#include "machina/types.hpp"
+
+namespace machina {
+namespace gui {
+
+class NodeView;
+
+class EdgeView
+ : public Ganv::Edge
+ , public machina::client::ClientObject::View
+{
+public:
+ EdgeView(Ganv::Canvas& canvas,
+ NodeView* src,
+ NodeView* dst,
+ SPtr<machina::client::ClientObject> edge);
+
+ ~EdgeView();
+
+ void show_label(bool show);
+
+ virtual double length_hint() const;
+
+private:
+ bool on_event(GdkEvent* ev);
+ void on_property(machina::URIInt key, const Atom& value);
+
+ float probability() const;
+
+ SPtr<machina::client::ClientObject> _edge;
+};
+
+} // namespace machina
+} // namespace gui
+
+#endif // MACHINA_EDGEVIEW_HPP
diff --git a/src/gui/MachinaCanvas.cpp b/src/gui/MachinaCanvas.cpp
new file mode 100644
index 0000000..a9e51c4
--- /dev/null
+++ b/src/gui/MachinaCanvas.cpp
@@ -0,0 +1,211 @@
+/*
+ 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 <map>
+
+#include "raul/TimeStamp.hpp"
+
+#include "client/ClientModel.hpp"
+#include "client/ClientObject.hpp"
+#include "machina/Controller.hpp"
+#include "machina/Engine.hpp"
+#include "machina/types.hpp"
+
+#include "EdgeView.hpp"
+#include "MachinaCanvas.hpp"
+#include "MachinaGUI.hpp"
+#include "NodeView.hpp"
+
+using namespace Raul;
+using namespace Ganv;
+
+namespace machina {
+namespace gui {
+
+MachinaCanvas::MachinaCanvas(MachinaGUI* app, int width, int height)
+ : Canvas(width, height)
+ , _app(app)
+ , _connect_node(NULL)
+ , _did_connect(false)
+{
+ widget().grab_focus();
+
+ signal_event.connect(sigc::mem_fun(this, &MachinaCanvas::on_event));
+}
+
+void
+MachinaCanvas::connect_nodes(GanvNode* node, void* data)
+{
+ MachinaCanvas* canvas = (MachinaCanvas*)data;
+ NodeView* view = dynamic_cast<NodeView*>(Glib::wrap(node));
+ if (!view || view == canvas->_connect_node) {
+ return;
+ }
+ if (canvas->get_edge(view, canvas->_connect_node)) {
+ canvas->action_disconnect(view, canvas->_connect_node);
+ canvas->_did_connect = true;
+ } else if (!canvas->get_edge(canvas->_connect_node, view)) {
+ canvas->action_connect(view, canvas->_connect_node);
+ canvas->_did_connect = true;
+ }
+}
+
+bool
+MachinaCanvas::node_clicked(NodeView* node, GdkEventButton* event)
+{
+ if (event->state & (GDK_CONTROL_MASK|GDK_SHIFT_MASK)) {
+ return false;
+
+ } else if (event->button == 2) {
+ // Middle click: learn
+ _app->controller()->learn(_app->maid(), node->node()->id());
+ return false;
+
+ } else if (event->button == 1) {
+ // Left click: connect/disconnect
+ _connect_node = node;
+ for_each_selected_node(connect_nodes, this);
+ const bool handled = _did_connect;
+ _connect_node = NULL;
+ _did_connect = false;
+ if (_app->chain_mode()) {
+ return false; // Cause Ganv to select as usual
+ } else {
+ return handled; // If we did something, stop event here
+ }
+ }
+
+ return false;
+}
+
+bool
+MachinaCanvas::on_event(GdkEvent* event)
+{
+ if (event->type == GDK_BUTTON_RELEASE
+ && event->button.button == 3
+ && !(event->button.state & (GDK_CONTROL_MASK))) {
+
+ action_create_node(event->button.x, event->button.y);
+ return true;
+ }
+ return false;
+}
+
+void
+MachinaCanvas::on_new_object(SPtr<client::ClientObject> object)
+{
+ const machina::URIs& uris = URIs::instance();
+ const Atom& type = object->get(uris.rdf_type);
+ if (!type.is_valid()) {
+ return;
+ }
+
+ if (type.get<URIInt>() == uris.machina_Node) {
+ const Atom& node_x = object->get(uris.machina_canvas_x);
+ const Atom& node_y = object->get(uris.machina_canvas_y);
+ float x, y;
+ if (node_x.type() == _app->forge().Float &&
+ node_y.type() == _app->forge().Float) {
+ x = node_x.get<float>();
+ y = node_y.get<float>();
+ } else {
+ int scroll_x, scroll_y;
+ get_scroll_offsets(scroll_x, scroll_y);
+ x = scroll_x + 128.0;
+ y = scroll_y + 128.0;
+ }
+
+ NodeView* view = new NodeView(_app->window(), *this, object, x, y);
+
+ //if ( ! node->enter_action() && ! node->exit_action() )
+ // view->set_base_color(0x101010FF);
+
+ view->signal_clicked().connect(
+ sigc::bind<0>(sigc::mem_fun(this, &MachinaCanvas::node_clicked),
+ view));
+
+ object->set_view(view);
+
+ } else if (type.get<URIInt>() == uris.machina_Edge) {
+ SPtr<machina::client::ClientObject> tail = _app->client_model()->find(
+ object->get(uris.machina_tail_id).get<int32_t>());
+ SPtr<machina::client::ClientObject> head = _app->client_model()->find(
+ object->get(uris.machina_head_id).get<int32_t>());
+
+ if (!tail || !head) {
+ std::cerr << "Invalid arc "
+ << object->get(uris.machina_tail_id).get<int32_t>()
+ << " => "
+ << object->get(uris.machina_head_id).get<int32_t>()
+ << std::endl;
+ return;
+ }
+
+ NodeView* tail_view = dynamic_cast<NodeView*>(tail->view());
+ NodeView* head_view = dynamic_cast<NodeView*>(head->view());
+
+ object->set_view(new EdgeView(*this, tail_view, head_view, object));
+
+ } else {
+ std::cerr << "Unknown object type " << type.get<URIInt>() << std::endl;
+ }
+}
+
+void
+MachinaCanvas::on_erase_object(SPtr<client::ClientObject> object)
+{
+ const Atom& type = object->get(URIs::instance().rdf_type);
+ if (type.get<URIInt>() == URIs::instance().machina_Node) {
+ delete object->view();
+ object->set_view(NULL);
+ } else if (type.get<URIInt>() == URIs::instance().machina_Edge) {
+ remove_edge(dynamic_cast<Ganv::Edge*>(object->view()));
+ object->set_view(NULL);
+ } else {
+ std::cerr << "Unknown object type" << std::endl;
+ }
+}
+
+void
+MachinaCanvas::action_create_node(double x, double y)
+{
+ const Properties props = {
+ { URIs::instance().rdf_type,
+ _app->forge().make_urid(URIs::instance().machina_Node) },
+ { URIs::instance().machina_canvas_x,
+ _app->forge().make((float)x) },
+ { URIs::instance().machina_canvas_y,
+ _app->forge().make((float)y) },
+ { URIs::instance().machina_duration,
+ _app->forge().make((float)_app->default_length()) } };
+
+ _app->controller()->create(props);
+}
+
+void
+MachinaCanvas::action_connect(NodeView* tail, NodeView* head)
+{
+ _app->controller()->connect(tail->node()->id(), head->node()->id());
+}
+
+void
+MachinaCanvas::action_disconnect(NodeView* tail, NodeView* head)
+{
+ _app->controller()->disconnect(tail->node()->id(), head->node()->id());
+}
+
+} // namespace machina
+} // namespace gui
diff --git a/src/gui/MachinaCanvas.hpp b/src/gui/MachinaCanvas.hpp
new file mode 100644
index 0000000..f32e6cb
--- /dev/null
+++ b/src/gui/MachinaCanvas.hpp
@@ -0,0 +1,66 @@
+/*
+ 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_CANVAS_HPP_HPP
+#define MACHINA_CANVAS_HPP_HPP
+
+#include <string>
+
+#include "ganv/Canvas.hpp"
+#include "machina/types.hpp"
+
+using namespace Ganv;
+
+namespace machina {
+
+namespace client { class ClientObject; }
+
+namespace gui {
+
+class MachinaGUI;
+class NodeView;
+
+class MachinaCanvas : public Canvas
+{
+public:
+ MachinaCanvas(MachinaGUI* app, int width, int height);
+
+ void on_new_object(SPtr<machina::client::ClientObject> object);
+ void on_erase_object(SPtr<machina::client::ClientObject> object);
+
+ MachinaGUI* app() { return _app; }
+
+protected:
+ bool on_event(GdkEvent* event);
+
+ bool node_clicked(NodeView* node, GdkEventButton* ev);
+
+private:
+ void action_create_node(double x, double y);
+ void action_connect(NodeView* tail, NodeView* head);
+ void action_disconnect(NodeView* tail, NodeView* head);
+
+ static void connect_nodes(GanvNode* node, void* data);
+
+ MachinaGUI* _app;
+ NodeView* _connect_node;
+ bool _did_connect;
+};
+
+} // namespace machina
+} // namespace gui
+
+#endif // MACHINA_CANVAS_HPP_HPP
diff --git a/src/gui/MachinaGUI.cpp b/src/gui/MachinaGUI.cpp
new file mode 100644
index 0000000..ff71ca7
--- /dev/null
+++ b/src/gui/MachinaGUI.cpp
@@ -0,0 +1,750 @@
+/*
+ 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_config.h"
+
+#include <cmath>
+#include <fstream>
+#include <limits.h>
+#include <pthread.h>
+#include "sord/sordmm.hpp"
+#include "machina/Controller.hpp"
+#include "machina/Engine.hpp"
+#include "machina/Machine.hpp"
+#include "machina/Mutation.hpp"
+#include "machina/Updates.hpp"
+#include "client/ClientModel.hpp"
+#include "WidgetFactory.hpp"
+#include "MachinaGUI.hpp"
+#include "MachinaCanvas.hpp"
+#include "NodeView.hpp"
+#include "EdgeView.hpp"
+
+#ifdef HAVE_EUGENE
+#include "machina/Evolver.hpp"
+#endif
+
+namespace machina {
+namespace gui {
+
+MachinaGUI::MachinaGUI(SPtr<machina::Engine> engine)
+ : _unit(TimeUnit::BEATS, 19200)
+ , _engine(engine)
+ , _client_model(new machina::client::ClientModel())
+ , _controller(new machina::Controller(_engine, *_client_model.get()))
+ , _maid(new Raul::Maid())
+ , _refresh(false)
+ , _evolve(false)
+ , _chain_mode(true)
+{
+ _canvas = SPtr<MachinaCanvas>(new MachinaCanvas(this, 1600*2, 1200*2));
+
+ Glib::RefPtr<Gtk::Builder> xml = WidgetFactory::create();
+
+ xml->get_widget("machina_win", _main_window);
+ xml->get_widget("about_win", _about_window);
+ xml->get_widget("help_dialog", _help_dialog);
+ xml->get_widget("toolbar", _toolbar);
+ xml->get_widget("open_menuitem", _menu_file_open);
+ xml->get_widget("save_menuitem", _menu_file_save);
+ xml->get_widget("save_as_menuitem", _menu_file_save_as);
+ xml->get_widget("quit_menuitem", _menu_file_quit);
+ xml->get_widget("zoom_in_menuitem", _menu_zoom_in);
+ xml->get_widget("zoom_out_menuitem", _menu_zoom_out);
+ xml->get_widget("zoom_normal_menuitem", _menu_zoom_normal);
+ xml->get_widget("arrange_menuitem", _menu_view_arrange);
+ xml->get_widget("import_midi_menuitem", _menu_import_midi);
+ xml->get_widget("export_midi_menuitem", _menu_export_midi);
+ xml->get_widget("export_graphviz_menuitem", _menu_export_graphviz);
+ xml->get_widget("view_toolbar_menuitem", _menu_view_toolbar);
+ xml->get_widget("view_labels_menuitem", _menu_view_labels);
+ xml->get_widget("help_about_menuitem", _menu_help_about);
+ xml->get_widget("help_help_menuitem", _menu_help_help);
+ xml->get_widget("canvas_scrolledwindow", _canvas_scrolledwindow);
+ xml->get_widget("bpm_spinbutton", _bpm_spinbutton);
+ xml->get_widget("quantize_checkbutton", _quantize_checkbutton);
+ xml->get_widget("quantize_spinbutton", _quantize_spinbutton);
+ xml->get_widget("stop_but", _stop_button);
+ xml->get_widget("play_but", _play_button);
+ xml->get_widget("record_but", _record_button);
+ xml->get_widget("step_record_but", _step_record_button);
+ xml->get_widget("chain_but", _chain_button);
+ xml->get_widget("fan_but", _fan_button);
+ xml->get_widget("load_target_but", _load_target_button);
+ xml->get_widget("evolve_toolbar", _evolve_toolbar);
+ xml->get_widget("evolve_but", _evolve_button);
+ xml->get_widget("mutate_but", _mutate_button);
+ xml->get_widget("compress_but", _compress_button);
+ xml->get_widget("add_node_but", _add_node_button);
+ xml->get_widget("remove_node_but", _remove_node_button);
+ xml->get_widget("adjust_node_but", _adjust_node_button);
+ xml->get_widget("add_edge_but", _add_edge_button);
+ xml->get_widget("remove_edge_but", _remove_edge_button);
+ xml->get_widget("adjust_edge_but", _adjust_edge_button);
+
+ _canvas_scrolledwindow->add(_canvas->widget());
+ _canvas_scrolledwindow->signal_event().connect(sigc::mem_fun(this,
+ &MachinaGUI::scrolled_window_event));
+
+ _canvas_scrolledwindow->property_hadjustment().get_value()->set_step_increment(10);
+ _canvas_scrolledwindow->property_vadjustment().get_value()->set_step_increment(10);
+
+ _stop_button->signal_toggled().connect(
+ sigc::mem_fun(this, &MachinaGUI::stop_toggled));
+ _play_button->signal_toggled().connect(
+ sigc::mem_fun(this, &MachinaGUI::play_toggled));
+ _record_button->signal_toggled().connect(
+ sigc::mem_fun(this, &MachinaGUI::record_toggled));
+ _step_record_button->signal_toggled().connect(
+ sigc::mem_fun(this, &MachinaGUI::step_record_toggled));
+
+ _chain_button->signal_toggled().connect(
+ sigc::mem_fun(this, &MachinaGUI::chain_toggled));
+ _fan_button->signal_toggled().connect(
+ sigc::mem_fun(this, &MachinaGUI::fan_toggled));
+
+ _menu_file_open->signal_activate().connect(
+ sigc::mem_fun(this, &MachinaGUI::menu_file_open));
+ _menu_file_save->signal_activate().connect(
+ sigc::mem_fun(this, &MachinaGUI::menu_file_save));
+ _menu_file_save_as->signal_activate().connect(
+ sigc::mem_fun(this, &MachinaGUI::menu_file_save_as));
+ _menu_file_quit->signal_activate().connect(
+ sigc::mem_fun(this, &MachinaGUI::menu_file_quit));
+ _menu_zoom_in->signal_activate().connect(
+ sigc::mem_fun(this, &MachinaGUI::on_zoom_in));
+ _menu_zoom_out->signal_activate().connect(
+ sigc::mem_fun(this, &MachinaGUI::on_zoom_out));
+ _menu_zoom_normal->signal_activate().connect(
+ sigc::mem_fun(this, &MachinaGUI::on_zoom_normal));
+ _menu_view_arrange->signal_activate().connect(
+ sigc::mem_fun(this, &MachinaGUI::arrange));
+ _menu_import_midi->signal_activate().connect(
+ sigc::mem_fun(this, &MachinaGUI::menu_import_midi));
+ _menu_export_midi->signal_activate().connect(
+ sigc::mem_fun(this, &MachinaGUI::menu_export_midi));
+ _menu_export_graphviz->signal_activate().connect(
+ sigc::mem_fun(this, &MachinaGUI::menu_export_graphviz));
+ _menu_view_toolbar->signal_toggled().connect(
+ sigc::mem_fun(this, &MachinaGUI::show_toolbar_toggled));
+ _menu_view_labels->signal_toggled().connect(
+ sigc::mem_fun(this, &MachinaGUI::show_labels_toggled));
+ _menu_help_about->signal_activate().connect(
+ sigc::mem_fun(this, &MachinaGUI::menu_help_about));
+ _menu_help_help->signal_activate().connect(
+ sigc::mem_fun(this, &MachinaGUI::menu_help_help));
+ _bpm_spinbutton->signal_changed().connect(
+ sigc::mem_fun(this, &MachinaGUI::tempo_changed));
+ _quantize_checkbutton->signal_toggled().connect(
+ sigc::mem_fun(this, &MachinaGUI::quantize_record_changed));
+ _quantize_spinbutton->signal_changed().connect(
+ sigc::mem_fun(this, &MachinaGUI::quantize_changed));
+
+ _mutate_button->signal_clicked().connect(
+ sigc::bind(sigc::mem_fun(this, &MachinaGUI::random_mutation),
+ SPtr<Machine>()));
+ _compress_button->signal_clicked().connect(
+ sigc::hide_return(sigc::bind(sigc::mem_fun(this, &MachinaGUI::mutate),
+ SPtr<Machine>(), 0)));
+ _add_node_button->signal_clicked().connect(
+ sigc::bind(sigc::mem_fun(this, &MachinaGUI::mutate),
+ SPtr<Machine>(), 1));
+ _remove_node_button->signal_clicked().connect(
+ sigc::bind(sigc::mem_fun(this, &MachinaGUI::mutate),
+ SPtr<Machine>(), 2));
+ _adjust_node_button->signal_clicked().connect(
+ sigc::bind(sigc::mem_fun(this, &MachinaGUI::mutate),
+ SPtr<Machine>(), 3));
+ _add_edge_button->signal_clicked().connect(
+ sigc::bind(sigc::mem_fun(this, &MachinaGUI::mutate),
+ SPtr<Machine>(), 4));
+ _remove_edge_button->signal_clicked().connect(
+ sigc::bind(sigc::mem_fun(this, &MachinaGUI::mutate),
+ SPtr<Machine>(), 5));
+ _adjust_edge_button->signal_clicked().connect(
+ sigc::bind(sigc::mem_fun(this, &MachinaGUI::mutate),
+ SPtr<Machine>(), 6));
+
+ _canvas->widget().show();
+
+ _main_window->present();
+
+ _quantize_checkbutton->set_active(false);
+ update_toolbar();
+
+ // Idle callback to drive the maid (collect garbage)
+ Glib::signal_timeout().connect(
+ sigc::bind_return(sigc::mem_fun(_maid.get(), &Raul::Maid::cleanup),
+ true),
+ 1000);
+
+ // Idle callback to update node states
+ Glib::signal_timeout().connect(
+ sigc::mem_fun(this, &MachinaGUI::idle_callback), 100);
+
+#ifdef HAVE_EUGENE
+ _load_target_button->signal_clicked().connect(
+ sigc::mem_fun(this, &MachinaGUI::load_target_clicked));
+ _evolve_button->signal_clicked().connect(
+ sigc::mem_fun(this, &MachinaGUI::evolve_toggled));
+ Glib::signal_timeout().connect(
+ sigc::mem_fun(this, &MachinaGUI::evolve_callback), 1000);
+#else
+ _evolve_toolbar->hide();
+#endif
+
+ _client_model->signal_new_object.connect(
+ sigc::mem_fun(this, &MachinaGUI::on_new_object));
+ _client_model->signal_erase_object.connect(
+ sigc::mem_fun(this, &MachinaGUI::on_erase_object));
+
+ rebuild_canvas();
+}
+
+MachinaGUI::~MachinaGUI()
+{
+}
+
+#ifdef HAVE_EUGENE
+bool
+MachinaGUI::evolve_callback()
+{
+ if (_evolve && _evolver->improvement()) {
+ _engine->driver()->set_machine(
+ SPtr<Machine>(new Machine(_evolver->best())));
+ _controller->announce(_engine->machine());
+ }
+
+ return true;
+}
+#endif
+
+bool
+MachinaGUI::idle_callback()
+{
+ _controller->process_updates();
+ return true;
+}
+
+static void
+destroy_edge(GanvEdge* edge, void* data)
+{
+ MachinaGUI* gui = (MachinaGUI*)data;
+ EdgeView* view = dynamic_cast<EdgeView*>(Glib::wrap(edge));
+ if (view) {
+ NodeView* tail = dynamic_cast<NodeView*>(view->get_tail());
+ NodeView* head = dynamic_cast<NodeView*>(view->get_head());
+ gui->controller()->disconnect(tail->node()->id(), head->node()->id());
+ }
+}
+
+static void
+destroy_node(GanvNode* node, void* data)
+{
+ MachinaGUI* gui = (MachinaGUI*)data;
+ NodeView* view = dynamic_cast<NodeView*>(Glib::wrap(GANV_NODE(node)));
+ if (view) {
+ const SPtr<client::ClientObject> node = view->node();
+ gui->canvas()->for_each_edge_on(
+ GANV_NODE(view->gobj()), destroy_edge, gui);
+ gui->controller()->erase(node->id());
+ }
+}
+
+bool
+MachinaGUI::scrolled_window_event(GdkEvent* event)
+{
+ if (event->type == GDK_KEY_PRESS) {
+ if (event->key.keyval == GDK_Delete) {
+ _canvas->for_each_selected_node(destroy_node, this);
+ return true;
+ }
+ }
+
+ return false;
+}
+
+void
+MachinaGUI::arrange()
+{
+ _canvas->arrange();
+}
+
+void
+MachinaGUI::load_target_clicked()
+{
+ Gtk::FileChooserDialog dialog(*_main_window,
+ "Load MIDI file for evolution", Gtk::FILE_CHOOSER_ACTION_OPEN);
+ dialog.set_local_only(false);
+ dialog.add_button(Gtk::Stock::CANCEL, Gtk::RESPONSE_CANCEL);
+ dialog.add_button(Gtk::Stock::OPEN, Gtk::RESPONSE_OK);
+
+ Gtk::FileFilter filt;
+ filt.add_pattern("*.mid");
+ filt.set_name("MIDI Files");
+ dialog.set_filter(filt);
+
+ const int result = dialog.run();
+
+ if (result == Gtk::RESPONSE_OK)
+ _target_filename = dialog.get_filename();
+}
+
+#ifdef HAVE_EUGENE
+void
+MachinaGUI::evolve_toggled()
+{
+ if (_evolve_button->get_active()) {
+ _evolver = SPtr<Evolver>(
+ new Evolver(_unit, _target_filename, _engine->machine()));
+ _evolve = true;
+ stop_toggled();
+ _engine->driver()->set_machine(SPtr<Machine>());
+ _evolver->start();
+ } else {
+ _evolver->join();
+ _evolve = false;
+ SPtr<Machine> new_machine = SPtr<Machine>(
+ new Machine(_evolver->best()));
+ _engine->driver()->set_machine(new_machine);
+ _controller->announce(_engine->machine());
+ _engine->driver()->activate();
+ }
+}
+#endif
+
+void
+MachinaGUI::random_mutation(SPtr<Machine> machine)
+{
+ if (!machine)
+ machine = _engine->machine();
+
+ mutate(machine, machine->nodes().size() < 2 ? 1 : rand() % 7);
+}
+
+void
+MachinaGUI::mutate(SPtr<Machine> machine, unsigned mutation)
+{
+ #if 0
+ if (!machine)
+ machine = _engine->machine();
+
+ using namespace Mutation;
+
+ switch (mutation) {
+ case 0:
+ Compress().mutate(*machine.get());
+ _canvas->build(machine, _menu_view_labels->get_active());
+ break;
+ case 1:
+ AddNode().mutate(*machine.get());
+ _canvas->build(machine, _menu_view_labels->get_active());
+ break;
+ case 2:
+ RemoveNode().mutate(*machine.get());
+ _canvas->build(machine, _menu_view_labels->get_active());
+ break;
+ case 3:
+ AdjustNode().mutate(*machine.get());
+ idle_callback(); // update nodes
+ break;
+ case 4:
+ AddEdge().mutate(*machine.get());
+ _canvas->build(machine, _menu_view_labels->get_active());
+ break;
+ case 5:
+ RemoveEdge().mutate(*machine.get());
+ _canvas->build(machine, _menu_view_labels->get_active());
+ break;
+ case 6:
+ AdjustEdge().mutate(*machine.get());
+ _canvas->update_edges();
+ break;
+ default: throw;
+ }
+ #endif
+}
+
+void
+MachinaGUI::update_toolbar()
+{
+ const Driver::PlayState state = _engine->driver()->play_state();
+ _record_button->set_active(state == Driver::PlayState::RECORDING);
+ _step_record_button->set_active(state == Driver::PlayState::STEP_RECORDING);
+ _play_button->set_active(state == Driver::PlayState::PLAYING);
+}
+
+void
+MachinaGUI::rebuild_canvas()
+{
+ _controller->announce(_engine->machine());
+ _canvas->arrange();
+}
+
+void
+MachinaGUI::quantize_record_changed()
+{
+ _engine->driver()->set_quantize_record(_quantize_checkbutton->get_active());
+}
+
+void
+MachinaGUI::quantize_changed()
+{
+ _engine->set_quantization(1.0 / _quantize_spinbutton->get_value());
+}
+
+void
+MachinaGUI::tempo_changed()
+{
+ _engine->set_bpm(_bpm_spinbutton->get_value_as_int());
+}
+
+void
+MachinaGUI::menu_file_quit()
+{
+ _main_window->hide();
+}
+
+void
+MachinaGUI::menu_file_open()
+{
+ Gtk::FileChooserDialog dialog(*_main_window, "Open Machine", Gtk::FILE_CHOOSER_ACTION_OPEN);
+ dialog.set_local_only(false);
+ dialog.add_button(Gtk::Stock::CANCEL, Gtk::RESPONSE_CANCEL);
+ dialog.add_button(Gtk::Stock::OPEN, Gtk::RESPONSE_OK);
+
+ Gtk::FileFilter filt;
+ filt.add_pattern("*.machina.ttl");
+ filt.set_name("Machina Machines (Turtle/RDF)");
+ dialog.set_filter(filt);
+
+ const int result = dialog.run();
+
+ if (result == Gtk::RESPONSE_OK) {
+ SPtr<machina::Machine> new_machine = _engine->load_machine(dialog.get_uri());
+ if (new_machine) {
+ rebuild_canvas();
+ _save_uri = dialog.get_uri();
+ }
+ }
+}
+
+void
+MachinaGUI::menu_file_save()
+{
+ if (_save_uri == "" || _save_uri.substr(0, 5) != "file:") {
+ menu_file_save_as();
+ } else {
+ if (_save_uri.substr(0, 5) != "file:")
+ menu_file_save_as();
+
+ Sord::Model model(_engine->rdf_world(), _save_uri);
+ _engine->machine()->write_state(model);
+ model.write_to_file(_save_uri, SERD_TURTLE);
+ }
+}
+
+void
+MachinaGUI::menu_file_save_as()
+{
+ Gtk::FileChooserDialog dialog(*_main_window, "Save Machine",
+ Gtk::FILE_CHOOSER_ACTION_SAVE);
+
+ dialog.add_button(Gtk::Stock::CANCEL, Gtk::RESPONSE_CANCEL);
+ dialog.add_button(Gtk::Stock::SAVE, Gtk::RESPONSE_OK);
+
+ if (_save_uri.length() > 0)
+ dialog.set_uri(_save_uri);
+
+ const int result = dialog.run();
+
+ assert(result == Gtk::RESPONSE_OK
+ || result == Gtk::RESPONSE_CANCEL
+ || result == Gtk::RESPONSE_NONE);
+
+ if (result == Gtk::RESPONSE_OK) {
+ string filename = dialog.get_filename();
+
+ if (filename.length() < 13 || filename.substr(filename.length()-12) != ".machina.ttl")
+ filename += ".machina.ttl";
+
+ const std::string uri = Glib::filename_to_uri(filename);
+
+ bool confirm = false;
+ std::fstream fin;
+ fin.open(filename.c_str(), std::ios::in);
+ if (fin.is_open()) { // File exists
+ string msg = "A file named \"";
+ msg += filename + "\" already exists.\n\nDo you want to replace it?";
+ Gtk::MessageDialog confirm_dialog(dialog,
+ msg, false, Gtk::MESSAGE_WARNING, Gtk::BUTTONS_YES_NO, true);
+ if (confirm_dialog.run() == Gtk::RESPONSE_YES)
+ confirm = true;
+ else
+ confirm = false;
+ } else { // File doesn't exist
+ confirm = true;
+ }
+ fin.close();
+
+ if (confirm) {
+ _save_uri = uri;
+ Sord::Model model(_engine->rdf_world(), _save_uri);
+ _engine->machine()->write_state(model);
+ model.write_to_file(_save_uri, SERD_TURTLE);
+ }
+ }
+}
+
+void
+MachinaGUI::menu_import_midi()
+{
+ Gtk::FileChooserDialog dialog(*_main_window, "Learn from MIDI file",
+ Gtk::FILE_CHOOSER_ACTION_OPEN);
+ dialog.add_button(Gtk::Stock::CANCEL, Gtk::RESPONSE_CANCEL);
+ dialog.add_button(Gtk::Stock::OPEN, Gtk::RESPONSE_OK);
+
+ Gtk::FileFilter filt;
+ filt.add_pattern("*.mid");
+ filt.set_name("MIDI Files");
+ dialog.set_filter(filt);
+
+ Gtk::HBox* extra_widget = Gtk::manage(new Gtk::HBox());
+ Gtk::SpinButton* length_sb = Gtk::manage(new Gtk::SpinButton());
+ length_sb->set_increments(1, 10);
+ length_sb->set_range(0, INT_MAX);
+ length_sb->set_value(0);
+ extra_widget->pack_start(*Gtk::manage(new Gtk::Label("")), true, true);
+ extra_widget->pack_start(*Gtk::manage(new Gtk::Label("Maximum Length (0 = unlimited): ")), false, false);
+ extra_widget->pack_start(*length_sb, false, false);
+ dialog.set_extra_widget(*extra_widget);
+ extra_widget->show_all();
+
+ const int result = dialog.run();
+
+ if (result == Gtk::RESPONSE_OK) {
+ const double length_dbl = length_sb->get_value_as_int();
+ const Raul::TimeStamp length(_unit, length_dbl);
+
+ SPtr<machina::Machine> machine = _engine->load_machine_midi(
+ dialog.get_filename(), 0.0, length);
+
+ if (machine) {
+ dialog.hide();
+ machine->reset(NULL, machine->time());
+ _engine->driver()->set_machine(machine);
+ _canvas->clear();
+ rebuild_canvas();
+ } else {
+ Gtk::MessageDialog msg_dialog(dialog, "Error loading MIDI file",
+ false, Gtk::MESSAGE_ERROR, Gtk::BUTTONS_OK, true);
+ msg_dialog.run();
+ }
+ }
+}
+
+void
+MachinaGUI::menu_export_midi()
+{
+ Gtk::FileChooserDialog dialog(*_main_window, "Export to a MIDI file",
+ Gtk::FILE_CHOOSER_ACTION_SAVE);
+ dialog.add_button(Gtk::Stock::CANCEL, Gtk::RESPONSE_CANCEL);
+ dialog.add_button(Gtk::Stock::SAVE, Gtk::RESPONSE_OK);
+
+ Gtk::FileFilter filt;
+ filt.add_pattern("*.mid");
+ filt.set_name("MIDI Files");
+ dialog.set_filter(filt);
+
+ Gtk::HBox* extra_widget = Gtk::manage(new Gtk::HBox());
+ Gtk::SpinButton* dur_sb = Gtk::manage(new Gtk::SpinButton());
+ dur_sb->set_increments(1, 10);
+ dur_sb->set_range(0, INT_MAX);
+ dur_sb->set_value(0);
+ extra_widget->pack_start(*Gtk::manage(new Gtk::Label("")), true, true);
+ extra_widget->pack_start(*Gtk::manage(new Gtk::Label("Duration (beats): ")), false, false);
+ extra_widget->pack_start(*dur_sb, false, false);
+ dialog.set_extra_widget(*extra_widget);
+ extra_widget->show_all();
+
+ const int result = dialog.run();
+
+ if (result == Gtk::RESPONSE_OK) {
+ const double dur_dbl = dur_sb->get_value_as_int();
+ const Raul::TimeStamp dur(_unit, dur_dbl);
+ _engine->export_midi(dialog.get_filename(), dur);
+ }
+}
+
+void
+MachinaGUI::menu_export_graphviz()
+{
+ Gtk::FileChooserDialog dialog(*_main_window, "Export to a GraphViz DOT file",
+ Gtk::FILE_CHOOSER_ACTION_SAVE);
+ dialog.add_button(Gtk::Stock::CANCEL, Gtk::RESPONSE_CANCEL);
+ dialog.add_button(Gtk::Stock::SAVE, Gtk::RESPONSE_OK);
+
+ const int result = dialog.run();
+
+ if (result == Gtk::RESPONSE_OK)
+ _canvas->export_dot(dialog.get_filename().c_str());
+}
+
+void
+MachinaGUI::show_toolbar_toggled()
+{
+ if (_menu_view_toolbar->get_active())
+ _toolbar->show();
+ else
+ _toolbar->hide();
+}
+
+void
+MachinaGUI::on_zoom_in()
+{
+ _canvas->set_font_size(_canvas->get_font_size() + 1.0);
+}
+
+void
+MachinaGUI::on_zoom_out()
+{
+ _canvas->set_font_size(_canvas->get_font_size() - 1.0);
+}
+
+void
+MachinaGUI::on_zoom_normal()
+{
+ _canvas->set_zoom(1.0);
+}
+
+static void
+show_node_label(GanvNode* node, void* data)
+{
+ bool show = *(bool*)data;
+ Ganv::Node* nodemm = Glib::wrap(node);
+ NodeView* const nv = dynamic_cast<NodeView*>(nodemm);
+ if (nv) {
+ nv->show_label(show);
+ }
+}
+
+static void
+show_edge_label(GanvEdge* edge, void* data)
+{
+ bool show = *(bool*)data;
+ Ganv::Edge* edgemm = Glib::wrap(edge);
+ EdgeView* const ev = dynamic_cast<EdgeView*>(edgemm);
+ if (ev) {
+ ev->show_label(show);
+ }
+}
+
+void
+MachinaGUI::show_labels_toggled()
+{
+ bool show = _menu_view_labels->get_active();
+
+ _canvas->for_each_node(show_node_label, &show);
+ _canvas->for_each_edge(show_edge_label, &show);
+}
+
+void
+MachinaGUI::menu_help_about()
+{
+ _about_window->set_transient_for(*_main_window);
+ _about_window->show();
+}
+
+void
+MachinaGUI::menu_help_help()
+{
+ _help_dialog->set_transient_for(*_main_window);
+ _help_dialog->run();
+ _help_dialog->hide();
+}
+
+void
+MachinaGUI::stop_toggled()
+{
+ if (_stop_button->get_active()) {
+ const Driver::PlayState old_state = _engine->driver()->play_state();
+ _engine->driver()->set_play_state(Driver::PlayState::STOPPED);
+ if (old_state == Driver::PlayState::RECORDING ||
+ old_state == Driver::PlayState::STEP_RECORDING) {
+ rebuild_canvas();
+ }
+ }
+}
+
+void
+MachinaGUI::play_toggled()
+{
+ if (_play_button->get_active()) {
+ const Driver::PlayState old_state = _engine->driver()->play_state();
+ _engine->driver()->set_play_state(Driver::PlayState::PLAYING);
+ if (old_state == Driver::PlayState::RECORDING ||
+ old_state == Driver::PlayState::STEP_RECORDING) {
+ rebuild_canvas();
+ }
+ }
+}
+
+void
+MachinaGUI::record_toggled()
+{
+ if (_record_button->get_active()) {
+ _engine->driver()->set_play_state(Driver::PlayState::RECORDING);
+ }
+}
+
+void
+MachinaGUI::step_record_toggled()
+{
+ if (_step_record_button->get_active()) {
+ _engine->driver()->set_play_state(Driver::PlayState::STEP_RECORDING);
+ }
+}
+
+void
+MachinaGUI::chain_toggled()
+{
+ if (_chain_button->get_active()) {
+ _chain_mode = true;
+ }
+}
+
+void
+MachinaGUI::fan_toggled()
+{
+ if (_fan_button->get_active()) {
+ _chain_mode = false;
+ }
+}
+
+void
+MachinaGUI::on_new_object(SPtr<client::ClientObject> object)
+{
+ _canvas->on_new_object(object);
+}
+
+void
+MachinaGUI::on_erase_object(SPtr<client::ClientObject> object)
+{
+ _canvas->on_erase_object(object);
+}
+
+} // namespace machina
+} // namespace gui
diff --git a/src/gui/MachinaGUI.hpp b/src/gui/MachinaGUI.hpp
new file mode 100644
index 0000000..bb111a2
--- /dev/null
+++ b/src/gui/MachinaGUI.hpp
@@ -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/>.
+*/
+
+#ifndef MACHINA_GUI_HPP
+#define MACHINA_GUI_HPP
+
+#include <string>
+
+#include <gtkmm.h>
+
+#include "raul/Maid.hpp"
+#include "raul/TimeStamp.hpp"
+
+#include "machina/types.hpp"
+#include "machina_config.h"
+
+using namespace std;
+
+namespace machina {
+
+class Machine;
+class Engine;
+class Evolver;
+class Controller;
+
+namespace client {
+class ClientModel;
+class ClientObject;
+}
+
+namespace gui {
+
+class MachinaCanvas;
+
+class MachinaGUI
+{
+public:
+ explicit MachinaGUI(SPtr<machina::Engine> engine);
+ ~MachinaGUI();
+
+ SPtr<MachinaCanvas> canvas() { return _canvas; }
+ SPtr<machina::Engine> engine() { return _engine; }
+ SPtr<machina::Controller> controller() { return _controller; }
+ Forge& forge() { return _forge; }
+ SPtr<Raul::Maid> maid() { return _maid; }
+ Gtk::Window* window() { return _main_window; }
+
+ void attach();
+ void quit() { _main_window->hide(); }
+
+ bool chain_mode() const { return _chain_mode; }
+
+ double default_length() const {
+ return 1 / (double)_quantize_spinbutton->get_value();
+ }
+
+ inline void queue_refresh() { _refresh = true; }
+
+ void on_new_object(SPtr<machina::client::ClientObject> object);
+ void on_erase_object(SPtr<machina::client::ClientObject> object);
+
+ SPtr<machina::client::ClientModel> client_model() {
+ return _client_model;
+ }
+
+protected:
+ void menu_file_quit();
+ void menu_file_open();
+ void menu_file_save();
+ void menu_file_save_as();
+ void menu_import_midi();
+ void menu_export_midi();
+ void menu_export_graphviz();
+ void on_zoom_in();
+ void on_zoom_out();
+ void on_zoom_normal();
+ void show_toolbar_toggled();
+ void show_labels_toggled();
+ void menu_help_about();
+ void menu_help_help();
+ void arrange();
+ void load_target_clicked();
+
+ void random_mutation(SPtr<machina::Machine> machine);
+ void mutate(SPtr<machina::Machine> machine, unsigned mutation);
+ void update_toolbar();
+ void rebuild_canvas();
+
+ bool scrolled_window_event(GdkEvent* ev);
+ bool idle_callback();
+
+#ifdef HAVE_EUGENE
+ void evolve_toggled();
+ bool evolve_callback();
+#endif
+
+ void stop_toggled();
+ void play_toggled();
+ void record_toggled();
+ void step_record_toggled();
+
+ void chain_toggled();
+ void fan_toggled();
+
+ void quantize_record_changed();
+ void quantize_changed();
+ void tempo_changed();
+
+ string _save_uri;
+ string _target_filename;
+
+ Raul::TimeUnit _unit;
+
+ SPtr<MachinaCanvas> _canvas;
+ SPtr<machina::Engine> _engine;
+ SPtr<machina::client::ClientModel> _client_model;
+ SPtr<machina::Controller> _controller;
+
+ SPtr<Raul::Maid> _maid;
+ SPtr<machina::Evolver> _evolver;
+
+ Forge _forge;
+
+ Gtk::Main* _gtk_main;
+
+ Gtk::Window* _main_window;
+ Gtk::Dialog* _help_dialog;
+ Gtk::AboutDialog* _about_window;
+ Gtk::Toolbar* _toolbar;
+ Gtk::MenuItem* _menu_file_open;
+ Gtk::MenuItem* _menu_file_save;
+ Gtk::MenuItem* _menu_file_save_as;
+ Gtk::MenuItem* _menu_file_quit;
+ Gtk::MenuItem* _menu_zoom_in;
+ Gtk::MenuItem* _menu_zoom_out;
+ Gtk::MenuItem* _menu_zoom_normal;
+ Gtk::MenuItem* _menu_view_arrange;
+ Gtk::MenuItem* _menu_import_midi;
+ Gtk::MenuItem* _menu_export_midi;
+ Gtk::MenuItem* _menu_export_graphviz;
+ Gtk::MenuItem* _menu_help_about;
+ Gtk::CheckMenuItem* _menu_view_labels;
+ Gtk::CheckMenuItem* _menu_view_toolbar;
+ Gtk::MenuItem* _menu_help_help;
+ Gtk::ScrolledWindow* _canvas_scrolledwindow;
+ Gtk::TextView* _status_text;
+ Gtk::Expander* _messages_expander;
+ Gtk::SpinButton* _bpm_spinbutton;
+ Gtk::CheckButton* _quantize_checkbutton;
+ Gtk::SpinButton* _quantize_spinbutton;
+ Gtk::ToggleToolButton* _stop_button;
+ Gtk::ToggleToolButton* _play_button;
+ Gtk::ToggleToolButton* _record_button;
+ Gtk::ToggleToolButton* _step_record_button;
+ Gtk::RadioButton* _chain_button;
+ Gtk::RadioButton* _fan_button;
+ Gtk::ToolButton* _load_target_button;
+ Gtk::Toolbar* _evolve_toolbar;
+ Gtk::ToggleToolButton* _evolve_button;
+ Gtk::ToolButton* _mutate_button;
+ Gtk::ToolButton* _compress_button;
+ Gtk::ToolButton* _add_node_button;
+ Gtk::ToolButton* _remove_node_button;
+ Gtk::ToolButton* _adjust_node_button;
+ Gtk::ToolButton* _add_edge_button;
+ Gtk::ToolButton* _remove_edge_button;
+ Gtk::ToolButton* _adjust_edge_button;
+
+ bool _refresh;
+ bool _evolve;
+ bool _chain_mode;
+};
+
+} // namespace machina
+} // namespace gui
+
+#endif // MACHINA_GUI_HPP
diff --git a/src/gui/NodePropertiesWindow.cpp b/src/gui/NodePropertiesWindow.cpp
new file mode 100644
index 0000000..f22eb57
--- /dev/null
+++ b/src/gui/NodePropertiesWindow.cpp
@@ -0,0 +1,136 @@
+/*
+ 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 <string>
+
+#include "client/ClientObject.hpp"
+#include "machina/URIs.hpp"
+#include "raul/TimeStamp.hpp"
+
+#include "MachinaGUI.hpp"
+#include "NodePropertiesWindow.hpp"
+#include "WidgetFactory.hpp"
+
+using namespace std;
+
+namespace machina {
+namespace gui {
+
+NodePropertiesWindow* NodePropertiesWindow::_instance = NULL;
+
+NodePropertiesWindow::NodePropertiesWindow(
+ BaseObjectType* cobject,
+ const Glib::RefPtr<Gtk::Builder>& xml)
+ : Gtk::Dialog(cobject)
+ , _gui(NULL)
+{
+ property_visible() = false;
+
+ xml->get_widget("node_properties_note_spinbutton", _note_spinbutton);
+ xml->get_widget("node_properties_duration_spinbutton", _duration_spinbutton);
+ xml->get_widget("node_properties_apply_button", _apply_button);
+ xml->get_widget("node_properties_cancel_button", _cancel_button);
+ xml->get_widget("node_properties_ok_button", _ok_button);
+
+ _apply_button->signal_clicked().connect(
+ sigc::mem_fun(this, &NodePropertiesWindow::apply_clicked));
+ _cancel_button->signal_clicked().connect(
+ sigc::mem_fun(this, &NodePropertiesWindow::cancel_clicked));
+ _ok_button->signal_clicked().connect(
+ sigc::mem_fun(this, &NodePropertiesWindow::ok_clicked));
+}
+
+NodePropertiesWindow::~NodePropertiesWindow()
+{}
+
+void
+NodePropertiesWindow::apply_clicked()
+{
+#if 0
+ const uint8_t note = _note_spinbutton->get_value();
+ if (!_node->enter_action()) {
+ _node->set_enter_action(ActionFactory::note_on(note));
+ _node->set_exit_action(ActionFactory::note_off(note));
+ } else {
+ SPtr<MidiAction> action = dynamic_ptr_cast<MidiAction>(_node->enter_action());
+ action->event()[1] = note;
+ action = dynamic_ptr_cast<MidiAction>(_node->exit_action());
+ action->event()[1] = note;
+ }
+#endif
+ _node->set(URIs::instance().machina_duration,
+ _gui->forge().make(float(_duration_spinbutton->get_value())));
+}
+
+void
+NodePropertiesWindow::cancel_clicked()
+{
+ assert(this == _instance);
+ delete _instance;
+ _instance = NULL;
+}
+
+void
+NodePropertiesWindow::ok_clicked()
+{
+ apply_clicked();
+ cancel_clicked();
+}
+
+void
+NodePropertiesWindow::set_node(MachinaGUI* gui,
+ SPtr<machina::client::ClientObject> node)
+{
+ _gui = gui;
+ _node = node;
+ #if 0
+ SPtr<MidiAction> enter_action = dynamic_ptr_cast<MidiAction>(node->enter_action());
+ if (enter_action && ( enter_action->event_size() > 1)
+ && ( (enter_action->event()[0] & 0xF0) == 0x90) ) {
+ _note_spinbutton->set_value(enter_action->event()[1]);
+ _note_spinbutton->show();
+ } else if (!enter_action) {
+ _note_spinbutton->set_value(60);
+ _note_spinbutton->show();
+ } else {
+ _note_spinbutton->hide();
+ }
+ #endif
+ _duration_spinbutton->set_value(
+ node->get(URIs::instance().machina_duration).get<float>());
+}
+
+void
+NodePropertiesWindow::present(MachinaGUI* gui,
+ Gtk::Window* parent,
+ SPtr<machina::client::ClientObject> node)
+{
+ if (!_instance) {
+ Glib::RefPtr<Gtk::Builder> xml = WidgetFactory::create();
+
+ xml->get_widget_derived("node_properties_dialog", _instance);
+
+ if (parent) {
+ _instance->set_transient_for(*parent);
+ }
+ }
+
+ _instance->set_node(gui, node);
+ _instance->show();
+}
+
+} // namespace machina
+} // namespace gui
diff --git a/src/gui/NodePropertiesWindow.hpp b/src/gui/NodePropertiesWindow.hpp
new file mode 100644
index 0000000..a999004
--- /dev/null
+++ b/src/gui/NodePropertiesWindow.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 NODEPROPERTIESWINDOW_HPP
+#define NODEPROPERTIESWINDOW_HPP
+
+#include <gtkmm.h>
+
+#include "machina/types.hpp"
+
+namespace machina {
+
+namespace client { class ClientObject; }
+
+namespace gui {
+
+class MachinaGUI;
+
+class NodePropertiesWindow : public Gtk::Dialog
+{
+public:
+ NodePropertiesWindow(BaseObjectType* cobject,
+ const Glib::RefPtr<Gtk::Builder>& xml);
+
+ ~NodePropertiesWindow();
+
+ static void present(MachinaGUI* gui,
+ Gtk::Window* parent,
+ SPtr<machina::client::ClientObject> node);
+
+private:
+ void set_node(MachinaGUI* gui,
+ SPtr<machina::client::ClientObject> node);
+
+ void apply_clicked();
+ void cancel_clicked();
+ void ok_clicked();
+
+ static NodePropertiesWindow* _instance;
+
+ MachinaGUI* _gui;
+ SPtr<machina::client::ClientObject> _node;
+
+ Gtk::SpinButton* _note_spinbutton;
+ Gtk::SpinButton* _duration_spinbutton;
+ Gtk::Button* _apply_button;
+ Gtk::Button* _cancel_button;
+ Gtk::Button* _ok_button;
+};
+
+} // namespace machina
+} // namespace gui
+
+#endif // NODEPROPERTIESWINDOW_HPP
diff --git a/src/gui/NodeView.cpp b/src/gui/NodeView.cpp
new file mode 100644
index 0000000..a1b96e4
--- /dev/null
+++ b/src/gui/NodeView.cpp
@@ -0,0 +1,203 @@
+/*
+ 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/Controller.hpp"
+#include "machina/URIs.hpp"
+#include "machina/types.hpp"
+
+#include "client/ClientModel.hpp"
+
+#include "MachinaCanvas.hpp"
+#include "MachinaGUI.hpp"
+#include "NodePropertiesWindow.hpp"
+#include "NodeView.hpp"
+
+using namespace std;
+
+namespace machina {
+namespace gui {
+
+NodeView::NodeView(Gtk::Window* window,
+ Ganv::Canvas& canvas,
+ SPtr<machina::client::ClientObject> node,
+ double x,
+ double y)
+ : Ganv::Circle(canvas, "", x, y)
+ , _window(window)
+ , _node(node)
+ , _default_border_color(get_border_color())
+ , _default_fill_color(get_fill_color())
+{
+ set_fit_label(false);
+ set_radius_ems(1.25);
+
+ signal_event().connect(sigc::mem_fun(this, &NodeView::on_event));
+
+ MachinaCanvas* mcanvas = dynamic_cast<MachinaCanvas*>(&canvas);
+ if (is(mcanvas->app()->forge(), URIs::instance().machina_initial)) {
+ set_border_width(4.0);
+ set_is_source(true);
+ const uint8_t alpha[] = { 0xCE, 0xB1, 0 };
+ set_label((const char*)alpha);
+ }
+
+ node->signal_property.connect(sigc::mem_fun(this, &NodeView::on_property));
+
+ for (const auto& p : node->properties()) {
+ on_property(p.first, p.second);
+ }
+}
+
+NodeView::~NodeView()
+{
+ _node->set_view(NULL);
+}
+
+bool
+NodeView::on_double_click(GdkEventButton*)
+{
+ MachinaCanvas* canvas = dynamic_cast<MachinaCanvas*>(this->canvas());
+ NodePropertiesWindow::present(canvas->app(), _window, _node);
+ return true;
+}
+
+bool
+NodeView::is(Forge& forge, machina::URIInt key)
+{
+ const Atom& value = _node->get(key);
+ return value.type() == forge.Bool && value.get<int32_t>();
+}
+
+bool
+NodeView::on_event(GdkEvent* event)
+{
+ MachinaCanvas* canvas = dynamic_cast<MachinaCanvas*>(this->canvas());
+ Forge& forge = canvas->app()->forge();
+ if (event->type == GDK_BUTTON_PRESS) {
+ if (event->button.state & GDK_CONTROL_MASK) {
+ if (event->button.button == 1) {
+ canvas->app()->controller()->set_property(
+ _node->id(),
+ URIs::instance().machina_selector,
+ forge.make(!is(forge, URIs::instance().machina_selector)));
+ return true;
+ }
+ } else {
+ return _signal_clicked.emit(&event->button);
+ }
+ } else if (event->type == GDK_2BUTTON_PRESS) {
+ return on_double_click(&event->button);
+ }
+ return false;
+}
+
+static void
+midi_note_name(uint8_t num, uint8_t buf[8])
+{
+ static const char* notes = "CCDDEFFGGAAB";
+ static const bool is_sharp[] = { 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0 };
+
+ const uint8_t octave = num / 12;
+ const uint8_t id = num - octave * 12;
+ const uint8_t sub[] = { 0xE2, 0x82, uint8_t(0x80 + octave) };
+ const uint8_t sharp[] = { 0xE2, 0x99, 0xAF };
+
+ int b = 0;
+ buf[b++] = notes[id];
+ if (is_sharp[id]) {
+ for (unsigned s = 0; s < sizeof(sharp); ++s) {
+ buf[b++] = sharp[s];
+ }
+ }
+ for (unsigned s = 0; s < sizeof(sub); ++s) {
+ buf[b++] = sub[s];
+ }
+ buf[b++] = 0;
+}
+
+void
+NodeView::show_label(bool show)
+{
+ if (show && _enter_action) {
+ Atom note_number = _enter_action->get(
+ URIs::instance().machina_note_number);
+ if (note_number.is_valid()) {
+ uint8_t buf[8];
+ midi_note_name(note_number.get<int32_t>(), buf);
+ set_label((const char*)buf);
+ return;
+ }
+ }
+
+ set_label("");
+}
+
+void
+NodeView::on_property(machina::URIInt key, const Atom& value)
+{
+ static const uint32_t active_color = 0x408040FF;
+ static const uint32_t active_border_color = 0x00FF00FF;
+
+ if (key == URIs::instance().machina_selector) {
+ if (value.get<int32_t>()) {
+ set_dash_length(4.0);
+ } else {
+ set_dash_length(0.0);
+ }
+ } else if (key == URIs::instance().machina_initial) {
+ set_border_width(value.get<int32_t>() ? 4.0 : 1.0);
+ set_is_source(value.get<int32_t>());
+ } else if (key == URIs::instance().machina_active) {
+ if (value.get<int32_t>()) {
+ if (get_fill_color() != active_color) {
+ set_fill_color(active_color);
+ set_border_color(active_border_color);
+ }
+ } else if (get_fill_color() == active_color) {
+ set_default_colors();
+ }
+ } else if (key == URIs::instance().machina_enter_action) {
+ const uint64_t action_id = value.get<int32_t>();
+ MachinaCanvas* canvas = dynamic_cast<MachinaCanvas*>(this->canvas());
+ _enter_action_connection.disconnect();
+ _enter_action = canvas->app()->client_model()->find(action_id);
+ if (_enter_action) {
+ _enter_action_connection = _enter_action->signal_property.connect(
+ sigc::mem_fun(this, &NodeView::on_action_property));
+ for (auto i : _enter_action->properties()) {
+ on_action_property(i.first, i.second);
+ }
+ }
+ }
+}
+
+void
+NodeView::on_action_property(machina::URIInt key, const Atom& value)
+{
+ if (key == URIs::instance().machina_note_number) {
+ show_label(true);
+ }
+}
+
+void
+NodeView::set_default_colors()
+{
+ set_fill_color(_default_fill_color);
+ set_border_color(_default_border_color);
+}
+
+} // namespace machina
+} // namespace gui
diff --git a/src/gui/NodeView.hpp b/src/gui/NodeView.hpp
new file mode 100644
index 0000000..6e0681c
--- /dev/null
+++ b/src/gui/NodeView.hpp
@@ -0,0 +1,76 @@
+/*
+ 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_NODEVIEW_HPP
+#define MACHINA_NODEVIEW_HPP
+
+#include "ganv/Circle.hpp"
+
+#include "client/ClientObject.hpp"
+
+#include "machina/types.hpp"
+
+namespace machina {
+namespace gui {
+
+class NodeView
+ : public Ganv::Circle
+ , public machina::client::ClientObject::View
+{
+public:
+ NodeView(Gtk::Window* window,
+ Canvas& canvas,
+ SPtr<machina::client::ClientObject> node,
+ double x,
+ double y);
+
+ ~NodeView();
+
+ SPtr<machina::client::ClientObject> node() { return _node; }
+
+ void show_label(bool show);
+
+ void update_state(bool show_labels);
+
+ void set_default_colors();
+
+ sigc::signal<bool, GdkEventButton*>& signal_clicked() {
+ return _signal_clicked;
+ }
+
+private:
+ bool on_event(GdkEvent* ev);
+ bool on_double_click(GdkEventButton* ev);
+ void on_property(machina::URIInt key, const Atom& value);
+ void on_action_property(machina::URIInt key, const Atom& value);
+
+ bool is(Forge& forge, machina::URIInt key);
+
+ Gtk::Window* _window;
+ SPtr<machina::client::ClientObject> _node;
+ uint32_t _default_border_color;
+ uint32_t _default_fill_color;
+
+ SPtr<machina::client::ClientObject> _enter_action;
+ sigc::connection _enter_action_connection;
+
+ sigc::signal<bool, GdkEventButton*> _signal_clicked;
+};
+
+} // namespace machina
+} // namespace gui
+
+#endif // MACHINA_NODEVIEW_HPP
diff --git a/src/gui/WidgetFactory.hpp b/src/gui/WidgetFactory.hpp
new file mode 100644
index 0000000..08118dc
--- /dev/null
+++ b/src/gui/WidgetFactory.hpp
@@ -0,0 +1,65 @@
+/*
+ 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 <fstream>
+#include <iostream>
+#include <string>
+
+#include <gtkmm.h>
+
+#include "machina_config.h"
+
+namespace machina {
+namespace gui {
+
+class WidgetFactory
+{
+public:
+ static Glib::RefPtr<Gtk::Builder> create() {
+ Glib::RefPtr<Gtk::Builder> xml;
+
+ // Check for the .ui file in current directory
+ std::string ui_filename = "./machina.ui";
+ std::ifstream fs(ui_filename.c_str());
+ if (fs.fail()) {
+ // didn't find it, check MACHINA_DATA_DIR
+ fs.clear();
+ ui_filename = MACHINA_DATA_DIR;
+ ui_filename += "/machina.ui";
+
+ fs.open(ui_filename.c_str());
+ if (fs.fail()) {
+ std::cerr << "No machina.ui in current directory or "
+ << MACHINA_DATA_DIR << "." << std::endl;
+ exit(EXIT_FAILURE);
+ }
+ fs.close();
+ }
+
+ try {
+ xml = Gtk::Builder::create_from_file(ui_filename);
+ } catch (const Gtk::BuilderError& ex) {
+ std::cerr << ex.what() << std::endl;
+ throw ex;
+ }
+
+ return xml;
+ }
+
+};
+
+} // namespace machina
+} // namespace gui
diff --git a/src/gui/machina.gladep b/src/gui/machina.gladep
new file mode 100644
index 0000000..c27832f
--- /dev/null
+++ b/src/gui/machina.gladep
@@ -0,0 +1,9 @@
+<?xml version="1.0" standalone="no"?> <!--*- mode: xml -*-->
+<!DOCTYPE glade-project SYSTEM "http://glade.gnome.org/glade-project-2.0.dtd">
+
+<glade-project>
+ <name>Machina</name>
+ <program_name>machina</program_name>
+ <language>C++</language>
+ <gnome_support>FALSE</gnome_support>
+</glade-project>
diff --git a/src/gui/machina.svg b/src/gui/machina.svg
new file mode 100644
index 0000000..39f7be0
--- /dev/null
+++ b/src/gui/machina.svg
@@ -0,0 +1,66 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://web.resource.org/cc/"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ id="svg2"
+ sodipodi:version="0.32"
+ inkscape:version="0.45pre1"
+ width="256"
+ height="256"
+ version="1.0"
+ sodipodi:docbase="/home/dave/code/lad/machina/src/gui"
+ sodipodi:docname="machina-icon.svg"
+ inkscape:output_extension="org.inkscape.output.svg.inkscape"
+ sodipodi:modified="true">
+ <metadata
+ id="metadata7">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <defs
+ id="defs5" />
+ <sodipodi:namedview
+ inkscape:window-height="771"
+ inkscape:window-width="1183"
+ inkscape:pageshadow="2"
+ inkscape:pageopacity="0.0"
+ guidetolerance="10.0"
+ gridtolerance="10.0"
+ objecttolerance="10.0"
+ borderopacity="1.0"
+ bordercolor="#666666"
+ pagecolor="#ffffff"
+ id="base"
+ inkscape:zoom="1.4142136"
+ inkscape:cx="310.87907"
+ inkscape:cy="40.263308"
+ inkscape:window-x="293"
+ inkscape:window-y="52"
+ inkscape:current-layer="svg2"
+ width="256px"
+ height="256px" />
+ <path
+ style="fill:#000000"
+ id="path2189"
+ d="M 9.0278551,221.40628 C 22.572047,206.65805 31.269455,188.04472 39.259152,169.8919 C 51.644822,140.58208 56.041288,109.13092 58.849953,77.700978 C 59.962909,56.867589 61.443535,35.864275 59.495794,15.031194 C 63.699947,13.959444 67.938586,10.617016 72.108252,11.815945 C 77.429885,13.346107 71.833885,22.901101 72.179608,28.42755 C 73.082013,42.852664 75.819667,56.648771 78.944682,70.727057 C 85.857753,99.135712 98.546656,125.08352 122.5541,142.3916 C 149.02904,155.16169 159.76491,132.36389 173.41824,112.97967 C 189.89026,87.286307 201.67487,59.107084 212.79124,30.80732 C 238.81023,-0.42720544 213.47911,64.070958 211.76716,70.33904 C 202.71922,108.04829 200.75405,146.89462 201.60034,185.52025 C 202.59785,202.30805 203.03716,219.51338 208.40658,235.60545 C 209.82998,239.98783 211.97855,244.0493 214.19967,248.06447 L 201.80123,255.17231 C 199.63995,251.03059 197.23968,246.9206 196.00991,242.38503 C 191.48082,225.58243 192.01187,207.89788 191.55783,190.64995 C 191.61755,152.05096 193.33386,113.23415 201.28376,75.349455 C 205.86472,56.717664 207.3011,28.642568 227.34088,20.082503 C 229.12544,19.320222 225.62336,23.562881 224.76461,25.303071 C 211.8352,53.436232 199.91298,82.147117 183.57547,108.55509 C 167.9424,131.6754 143.08989,165.0319 111.95557,149.10157 C 88.367319,131.04024 76.047527,104.6256 68.72284,76.231983 C 66.537387,67.180737 52.831088,26.99333 59.36089,18.303914 C 62.551525,14.058035 67.78841,11.838043 72.002169,8.6051067 C 71.542368,29.947073 70.679719,51.279977 69.135855,72.573834 C 66.386515,103.98497 62.160375,135.44301 50.178851,164.87632 C 42.654028,182.74324 34.458191,200.83743 22.235303,216.04826 L 9.0278551,221.40628 z " />
+ <path
+ style="fill:#000000"
+ id="path2191"
+ d="M 177.4044,227.49356 C 186.73664,226.83386 194.85497,230.46984 202.63542,235.17331 C 208.34182,239.12994 205.55279,237.07748 211.00405,241.32859 L 199.24569,248.73751 C 193.86468,244.57093 196.63478,246.5644 190.93236,242.7613 C 183.22589,238.37114 175.24676,235.06127 166.16497,235.78874 L 177.4044,227.49356 z " />
+ <path
+ style="fill:#000000"
+ id="path2193"
+ d="M 202.904,253.28805 C 206.25526,244.218 211.55226,236.16787 216.96397,228.23361 C 217.73013,227.17184 218.49629,226.11007 219.26244,225.0483 L 232.24972,219.44712 C 231.4278,220.51566 230.60588,221.58419 229.78396,222.65273 C 224.30649,230.41756 219.07262,238.3307 215.72758,247.29568 L 202.904,253.28805 z " />
+</svg>
diff --git a/src/gui/machina.ui b/src/gui/machina.ui
new file mode 100644
index 0000000..89404fc
--- /dev/null
+++ b/src/gui/machina.ui
@@ -0,0 +1,1128 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <requires lib="gtk+" version="2.16"/>
+ <!-- interface-naming-policy toplevel-contextual -->
+ <object class="GtkRadioAction" id="play_action">
+ <property name="stock_id">gtk-media-play</property>
+ <property name="draw_as_radio">True</property>
+ <property name="group">record_action</property>
+ </object>
+ <object class="GtkRadioAction" id="step_record_action">
+ <property name="label" translatable="yes">Step record</property>
+ <property name="stock_id">gtk-add</property>
+ <property name="draw_as_radio">True</property>
+ <property name="group">record_action</property>
+ </object>
+ <object class="GtkRadioAction" id="stop_action">
+ <property name="stock_id">gtk-media-stop</property>
+ <property name="draw_as_radio">True</property>
+ <property name="group">record_action</property>
+ </object>
+ <object class="GtkAboutDialog" id="about_win">
+ <property name="can_focus">False</property>
+ <property name="destroy_with_parent">True</property>
+ <property name="type_hint">normal</property>
+ <property name="program_name">Machina</property>
+ <property name="copyright" translatable="yes">© 2013 David Robillard &lt;http://drobilla.net&gt;</property>
+ <property name="comments" translatable="yes">A MIDI sequencer based on
+ probabilistic finite-state automata</property>
+ <property name="website">http://drobilla.net/software/machina</property>
+ <property name="license" translatable="yes">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 2 of the License, or
+(at your option) any later version.
+
+Machina is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Machina; if not, write to the Free Software Foundation, Inc.,
+51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+</property>
+ <property name="authors">David Robillard &lt;d@drobilla.net&gt;</property>
+ <property name="translator_credits" translatable="yes" comments="TRANSLATORS: Replace this string with your names, one name per line.">translator-credits</property>
+ <property name="logo">machina.svg</property>
+ <child internal-child="vbox">
+ <object class="GtkVBox" id="dialog-vbox1">
+ <property name="can_focus">False</property>
+ <child internal-child="action_area">
+ <object class="GtkHButtonBox" id="dialog-action_area1">
+ <property name="can_focus">False</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="pack_type">end</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ <object class="GtkAdjustment" id="bpm_adjustment">
+ <property name="lower">1</property>
+ <property name="upper">480</property>
+ <property name="value">120</property>
+ <property name="step_increment">1</property>
+ <property name="page_increment">10</property>
+ </object>
+ <object class="GtkAdjustment" id="duration_adjustment">
+ <property name="upper">64</property>
+ <property name="value">0.25</property>
+ <property name="step_increment">1</property>
+ <property name="page_increment">4</property>
+ </object>
+ <object class="GtkDialog" id="help_dialog">
+ <property name="can_focus">False</property>
+ <property name="border_width">8</property>
+ <property name="title" translatable="yes">Machina Help</property>
+ <property name="resizable">False</property>
+ <property name="window_position">center-on-parent</property>
+ <property name="icon_name">gtk-help</property>
+ <property name="type_hint">dialog</property>
+ <child internal-child="vbox">
+ <object class="GtkVBox" id="dialog-vbox2">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child internal-child="action_area">
+ <object class="GtkHButtonBox" id="dialog-action_area2">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="layout_style">end</property>
+ <child>
+ <object class="GtkButton" id="closebutton1">
+ <property name="label">gtk-close</property>
+ <property name="use_action_appearance">False</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="can_default">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_stock">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="pack_type">end</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label5">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0</property>
+ <property name="label" translatable="yes">&lt;big&gt;&lt;b&gt;Nodes&lt;/b&gt;&lt;/big&gt;
+Nodes represent notes, which have a pitch and duration.
+When a node is highlighted green, it is playing.
+
+Play begins at the initial node α. Whenever all nodes become
+inactive or stop is pressed, play returns to the initial node.
+
+Nodes with dashed borders are selectors. Only one successor is
+played after a selector, i.e. only one outgoing arc is traversed.
+
+ • Right click the canvas to create a new node
+ • Middle click a node to learn a MIDI note
+ • Double click a node to edit its properties
+ • Ctrl+Left click a node to make it a selector
+
+&lt;big&gt;&lt;b&gt;Arcs&lt;/b&gt;&lt;/big&gt;
+When a node is finished playing, play travels along outgoing
+arcs, depending on their probability. The colour of an arc
+indicates its probability, where green is high and red is low.
+
+ • Ctrl+Left click an arc to decrease its probability
+ • Ctrl+Right click an arc to increase its probability
+
+&lt;big&gt;&lt;b&gt;Recording&lt;/b&gt;&lt;/big&gt;
+A machine can be built by recording MIDI input. To record, press
+the record button and play some MIDI notes. To finish recording,
+press stop or play and the new nodes will be added to the machine.
+
+Normal recording inserts delay nodes to reproduce the timing of
+the input. To avoid this, use step recording which directly connects
+nodes to their successors with no delays in-between.
+
+&lt;big&gt;&lt;b&gt;Connecting&lt;/b&gt;&lt;/big&gt;
+Connecting nodes is based on the selection. If there is a selection,
+clicking a node will connect the selection to that node.
+
+There are two modes: chain and fan. In chain mode, the selection is
+moved to the clicked node for quickly connecting long chains of nodes.
+In fan mode, the selection is unchanged for quickly connecting the
+selection to many nodes.</property>
+ <property name="use_markup">True</property>
+ <property name="justify">fill</property>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="padding">8</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ <action-widgets>
+ <action-widget response="-7">closebutton1</action-widget>
+ </action-widgets>
+ </object>
+ <object class="GtkAdjustment" id="length_adjustment">
+ <property name="lower">1</property>
+ <property name="upper">256</property>
+ <property name="value">8</property>
+ <property name="step_increment">1</property>
+ <property name="page_increment">8</property>
+ </object>
+ <object class="GtkWindow" id="machina_win">
+ <property name="can_focus">False</property>
+ <property name="border_width">1</property>
+ <property name="title" translatable="yes">Machina</property>
+ <property name="default_width">640</property>
+ <property name="default_height">480</property>
+ <property name="icon">machina.svg</property>
+ <child>
+ <object class="GtkVBox" id="main_vbox">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkMenuBar" id="menubar">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkMenuItem" id="file_menu">
+ <property name="use_action_appearance">False</property>
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">_File</property>
+ <property name="use_underline">True</property>
+ <child type="submenu">
+ <object class="GtkMenu" id="file_menu_menu">
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkImageMenuItem" id="open_menuitem">
+ <property name="label">gtk-open</property>
+ <property name="use_action_appearance">False</property>
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="use_underline">True</property>
+ <property name="use_stock">True</property>
+ <signal name="activate" handler="on_open_session_menuitem_activate" swapped="no"/>
+ </object>
+ </child>
+ <child>
+ <object class="GtkImageMenuItem" id="save_menuitem">
+ <property name="label">gtk-save</property>
+ <property name="use_action_appearance">False</property>
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="use_underline">True</property>
+ <property name="use_stock">True</property>
+ <signal name="activate" handler="on_save_session_menuitem_activate" swapped="no"/>
+ </object>
+ </child>
+ <child>
+ <object class="GtkImageMenuItem" id="save_as_menuitem">
+ <property name="label">gtk-save-as</property>
+ <property name="use_action_appearance">False</property>
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="use_underline">True</property>
+ <property name="use_stock">True</property>
+ <signal name="activate" handler="on_save_session_as_menuitem_activate" swapped="no"/>
+ </object>
+ </child>
+ <child>
+ <object class="GtkSeparatorMenuItem" id="separator5">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkImageMenuItem" id="import_midi_menuitem">
+ <property name="label">_Import MIDI...</property>
+ <property name="use_action_appearance">False</property>
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="use_underline">True</property>
+ <property name="use_stock">True</property>
+ <accelerator key="I" signal="activate" modifiers="GDK_CONTROL_MASK"/>
+ <signal name="activate" handler="on_learn_midi_menuitem_activate" swapped="no"/>
+ </object>
+ </child>
+ <child>
+ <object class="GtkImageMenuItem" id="export_midi_menuitem">
+ <property name="label">_Export MIDI...</property>
+ <property name="use_action_appearance">False</property>
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="use_underline">True</property>
+ <property name="use_stock">True</property>
+ <accelerator key="E" signal="activate" modifiers="GDK_CONTROL_MASK"/>
+ <signal name="activate" handler="on_export_midi_menuitem_activate" swapped="no"/>
+ </object>
+ </child>
+ <child>
+ <object class="GtkSeparatorMenuItem" id="separator7">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkImageMenuItem" id="export_graphviz_menuitem">
+ <property name="label">Export _GraphViz...</property>
+ <property name="use_action_appearance">False</property>
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="use_underline">True</property>
+ <property name="use_stock">True</property>
+ <accelerator key="d" signal="activate" modifiers="GDK_CONTROL_MASK"/>
+ <signal name="activate" handler="on_export_graphviz_menuitem_activate" swapped="no"/>
+ </object>
+ </child>
+ <child>
+ <object class="GtkSeparatorMenuItem" id="separator6">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkImageMenuItem" id="quit_menuitem">
+ <property name="label">gtk-quit</property>
+ <property name="use_action_appearance">False</property>
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="use_underline">True</property>
+ <property name="use_stock">True</property>
+ <signal name="activate" handler="on_quit1_activate" swapped="no"/>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkMenuItem" id="view_menu">
+ <property name="use_action_appearance">False</property>
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">_View</property>
+ <property name="use_underline">True</property>
+ <child type="submenu">
+ <object class="GtkMenu" id="view_menu_menu">
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkCheckMenuItem" id="view_labels_menuitem">
+ <property name="use_action_appearance">False</property>
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">_Labels</property>
+ <property name="use_underline">True</property>
+ <accelerator key="L" signal="activate" modifiers="GDK_CONTROL_MASK"/>
+ <signal name="activate" handler="on_view_edge_labels_menuitem_activate" swapped="no"/>
+ </object>
+ </child>
+ <child>
+ <object class="GtkCheckMenuItem" id="view_toolbar_menuitem">
+ <property name="use_action_appearance">False</property>
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">_Toolbar</property>
+ <property name="use_underline">True</property>
+ <property name="active">True</property>
+ <accelerator key="T" signal="activate" modifiers="GDK_CONTROL_MASK"/>
+ <signal name="activate" handler="on_toolbar2_activate" swapped="no"/>
+ </object>
+ </child>
+ <child>
+ <object class="GtkSeparatorMenuItem" id="separatormenuitem3">
+ <property name="use_action_appearance">False</property>
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkImageMenuItem" id="zoom_in_menuitem">
+ <property name="label">gtk-zoom-in</property>
+ <property name="use_action_appearance">False</property>
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="use_underline">True</property>
+ <property name="use_stock">True</property>
+ <accelerator key="plus" signal="activate" modifiers="GDK_CONTROL_MASK"/>
+ <accelerator key="equal" signal="activate" modifiers="GDK_CONTROL_MASK"/>
+ </object>
+ </child>
+ <child>
+ <object class="GtkImageMenuItem" id="zoom_out_menuitem">
+ <property name="label">gtk-zoom-out</property>
+ <property name="use_action_appearance">False</property>
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="use_underline">True</property>
+ <property name="use_stock">True</property>
+ <accelerator key="minus" signal="activate" modifiers="GDK_CONTROL_MASK"/>
+ </object>
+ </child>
+ <child>
+ <object class="GtkImageMenuItem" id="zoom_normal_menuitem">
+ <property name="label">gtk-zoom-100</property>
+ <property name="use_action_appearance">False</property>
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="use_underline">True</property>
+ <property name="use_stock">True</property>
+ <accelerator key="0" signal="activate" modifiers="GDK_CONTROL_MASK"/>
+ </object>
+ </child>
+ <child>
+ <object class="GtkSeparatorMenuItem" id="separatormenuitem1">
+ <property name="use_action_appearance">False</property>
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkMenuItem" id="arrange_menuitem">
+ <property name="use_action_appearance">False</property>
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Arrange</property>
+ <property name="use_underline">True</property>
+ <accelerator key="g" signal="activate" modifiers="GDK_CONTROL_MASK"/>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkMenuItem" id="help_menu">
+ <property name="use_action_appearance">False</property>
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">_Help</property>
+ <property name="use_underline">True</property>
+ <child type="submenu">
+ <object class="GtkMenu" id="help_menu_menu">
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkImageMenuItem" id="help_help_menuitem">
+ <property name="label">gtk-help</property>
+ <property name="use_action_appearance">False</property>
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="use_underline">True</property>
+ <property name="use_stock">True</property>
+ <signal name="activate" handler="on_help_about_menuitem_activate" swapped="no"/>
+ </object>
+ </child>
+ <child>
+ <object class="GtkImageMenuItem" id="help_about_menuitem">
+ <property name="label">gtk-about</property>
+ <property name="use_action_appearance">False</property>
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="use_underline">True</property>
+ <property name="use_stock">True</property>
+ <signal name="activate" handler="on_about1_activate" swapped="no"/>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkHBox" id="hbox2">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+ <child>
+ <object class="GtkToolbar" id="toolbar">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="toolbar_style">icons</property>
+ <property name="show_arrow">False</property>
+ <property name="icon_size">2</property>
+ <property name="icon_size_set">True</property>
+ <child>
+ <object class="GtkToggleToolButton" id="stop_but">
+ <property name="related_action">stop_action</property>
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="tooltip_text" translatable="yes">Stop</property>
+ <property name="use_underline">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="homogeneous">True</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkToggleToolButton" id="play_but">
+ <property name="related_action">play_action</property>
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="tooltip_text" translatable="yes">Play</property>
+ <property name="active">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="homogeneous">True</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkToggleToolButton" id="record_but">
+ <property name="related_action">record_action</property>
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="tooltip_text" translatable="yes">Record</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="homogeneous">True</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkToggleToolButton" id="step_record_but">
+ <property name="related_action">step_record_action</property>
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+ <property name="tooltip_text" translatable="yes">Step record</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="homogeneous">True</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkSeparatorToolItem" id="separatortoolitem1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkToolItem" id="toolitem1">
+ <property name="use_action_appearance">False</property>
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkHBox" id="hbox3">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkSpinButton" id="bpm_spinbutton">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="tooltip_text" translatable="yes">Playback tempo</property>
+ <property name="max_length">3</property>
+ <property name="invisible_char">•</property>
+ <property name="width_chars">3</property>
+ <property name="primary_icon_activatable">False</property>
+ <property name="secondary_icon_activatable">False</property>
+ <property name="primary_icon_sensitive">True</property>
+ <property name="secondary_icon_sensitive">True</property>
+ <property name="adjustment">bpm_adjustment</property>
+ <property name="climb_rate">1</property>
+ <property name="numeric">True</property>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label8">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes"> BPM</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkSeparatorToolItem" id="separatortoolitem2">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkToolItem" id="toolitem3">
+ <property name="use_action_appearance">False</property>
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkHBox" id="hbox4">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="border_width">4</property>
+ <child>
+ <object class="GtkCheckButton" id="quantize_checkbutton">
+ <property name="label" translatable="yes">Quantize 1/</property>
+ <property name="use_action_appearance">False</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="tooltip_text" translatable="yes">Quantize recorded notes</property>
+ <property name="use_underline">True</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkSpinButton" id="quantize_spinbutton">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="tooltip_text" translatable="yes">Note type for quantization</property>
+ <property name="primary_icon_activatable">False</property>
+ <property name="secondary_icon_activatable">False</property>
+ <property name="primary_icon_sensitive">True</property>
+ <property name="secondary_icon_sensitive">True</property>
+ <property name="adjustment">quantize_adjustment</property>
+ <property name="climb_rate">1</property>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkSeparatorToolItem" id="separatortoolitem3">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkToolItem" id="toolitem2">
+ <property name="use_action_appearance">False</property>
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkHBox" id="hbox1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="border_width">4</property>
+ <child>
+ <object class="GtkRadioButton" id="chain_but">
+ <property name="label" translatable="yes">Chain</property>
+ <property name="use_action_appearance">False</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="tooltip_text" translatable="yes">Move selection to head node after connection</property>
+ <property name="active">True</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkRadioButton" id="fan_but">
+ <property name="label" translatable="yes">Fan</property>
+ <property name="use_action_appearance">False</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="tooltip_text" translatable="yes">Keep selection on tail node after connection</property>
+ <property name="active">True</property>
+ <property name="draw_indicator">True</property>
+ <property name="group">chain_but</property>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkToolbar" id="evolve_toolbar">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+ <property name="toolbar_style">icons</property>
+ <property name="show_arrow">False</property>
+ <property name="icon_size">2</property>
+ <property name="icon_size_set">True</property>
+ <child>
+ <object class="GtkSeparatorToolItem" id="toolbutton7">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkToolButton" id="load_target_but">
+ <property name="use_action_appearance">False</property>
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+ <property name="stock_id">gtk-open</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="homogeneous">True</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkSeparatorToolItem" id="toolbutton5">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkToggleToolButton" id="evolve_but">
+ <property name="use_action_appearance">False</property>
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+ <property name="label" translatable="yes">Evolve machine (towards target MIDI)</property>
+ <property name="stock_id">gtk-execute</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="homogeneous">True</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkSeparatorToolItem" id="toolbutton4">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkToolButton" id="mutate_but">
+ <property name="use_action_appearance">False</property>
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+ <property name="stock_id">gtk-dialog-warning</property>
+ <accelerator key="m" signal="clicked" modifiers="GDK_CONTROL_MASK"/>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="homogeneous">True</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkSeparatorToolItem" id="toolbutton3">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkToolButton" id="compress_but">
+ <property name="use_action_appearance">False</property>
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+ <property name="stock_id">gtk-convert</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="homogeneous">True</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkSeparatorToolItem" id="toolbutton2">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkToolButton" id="add_node_but">
+ <property name="use_action_appearance">False</property>
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+ <property name="label" translatable="yes">Add Node</property>
+ <property name="stock_id">gtk-new</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="homogeneous">True</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkToolButton" id="remove_node_but">
+ <property name="use_action_appearance">False</property>
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+ <property name="label" translatable="yes">Delete Node</property>
+ <property name="stock_id">gtk-delete</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="homogeneous">True</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkToolButton" id="adjust_node_but">
+ <property name="use_action_appearance">False</property>
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+ <property name="label" translatable="yes">Adjust Node</property>
+ <property name="stock_id">gtk-edit</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="homogeneous">True</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkSeparatorToolItem" id="toolbutton1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkToolButton" id="add_edge_but">
+ <property name="use_action_appearance">False</property>
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+ <property name="label" translatable="yes">Add Edge</property>
+ <property name="stock_id">gtk-connect</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="homogeneous">True</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkToolButton" id="remove_edge_but">
+ <property name="use_action_appearance">False</property>
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+ <property name="label" translatable="yes">Remove Edge</property>
+ <property name="stock_id">gtk-disconnect</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="homogeneous">True</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkToolButton" id="adjust_edge_but">
+ <property name="use_action_appearance">False</property>
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+ <property name="label" translatable="yes">Adjust Edge</property>
+ <property name="stock_id">gtk-select-color</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="homogeneous">True</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkScrolledWindow" id="canvas_scrolledwindow">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="has_focus">True</property>
+ <property name="can_default">True</property>
+ <property name="has_default">True</property>
+ <property name="shadow_type">in</property>
+ <child>
+ <placeholder/>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="padding">2</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ <object class="GtkAdjustment" id="note_num_adjustment">
+ <property name="upper">127</property>
+ <property name="value">64</property>
+ <property name="step_increment">1</property>
+ <property name="page_increment">10</property>
+ </object>
+ <object class="GtkRadioAction" id="record_action">
+ <property name="stock_id">gtk-media-record</property>
+ <property name="draw_as_radio">True</property>
+ </object>
+ <object class="GtkDialog" id="node_properties_dialog">
+ <property name="can_focus">False</property>
+ <property name="border_width">8</property>
+ <property name="title" translatable="yes">dialog1</property>
+ <property name="resizable">False</property>
+ <property name="icon">machina.svg</property>
+ <property name="type_hint">dialog</property>
+ <property name="skip_taskbar_hint">True</property>
+ <property name="skip_pager_hint">True</property>
+ <child internal-child="vbox">
+ <object class="GtkVBox" id="dialog-vbox3">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="spacing">8</property>
+ <child internal-child="action_area">
+ <object class="GtkHButtonBox" id="dialog-action_area3">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="layout_style">end</property>
+ <child>
+ <object class="GtkButton" id="node_properties_apply_button">
+ <property name="label">gtk-apply</property>
+ <property name="use_action_appearance">False</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="can_default">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_stock">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="node_properties_cancel_button">
+ <property name="label">gtk-cancel</property>
+ <property name="use_action_appearance">False</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="can_default">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_stock">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="node_properties_ok_button">
+ <property name="label">gtk-ok</property>
+ <property name="use_action_appearance">False</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="can_default">True</property>
+ <property name="has_default">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_stock">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="pack_type">end</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkTable" id="table1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="n_rows">2</property>
+ <property name="n_columns">2</property>
+ <property name="column_spacing">4</property>
+ <property name="row_spacing">8</property>
+ <child>
+ <object class="GtkSpinButton" id="node_properties_duration_spinbutton">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="has_focus">True</property>
+ <property name="primary_icon_activatable">False</property>
+ <property name="secondary_icon_activatable">False</property>
+ <property name="primary_icon_sensitive">True</property>
+ <property name="secondary_icon_sensitive">True</property>
+ <property name="adjustment">duration_adjustment</property>
+ <property name="digits">3</property>
+ <property name="numeric">True</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ <property name="y_options"/>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label7">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes">Duration: </property>
+ </object>
+ <packing>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ <property name="x_options">GTK_FILL</property>
+ <property name="y_options"/>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label6">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes">Note: </property>
+ </object>
+ <packing>
+ <property name="x_options">GTK_FILL</property>
+ <property name="y_options"/>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkSpinButton" id="node_properties_note_spinbutton">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="primary_icon_activatable">False</property>
+ <property name="secondary_icon_activatable">False</property>
+ <property name="primary_icon_sensitive">True</property>
+ <property name="secondary_icon_sensitive">True</property>
+ <property name="adjustment">note_num_adjustment</property>
+ <property name="climb_rate">1</property>
+ <property name="numeric">True</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="y_options"/>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ <action-widgets>
+ <action-widget response="-10">node_properties_apply_button</action-widget>
+ <action-widget response="-6">node_properties_cancel_button</action-widget>
+ <action-widget response="-5">node_properties_ok_button</action-widget>
+ </action-widgets>
+ </object>
+ <object class="GtkAdjustment" id="quantize_adjustment">
+ <property name="lower">1</property>
+ <property name="upper">256</property>
+ <property name="value">1</property>
+ <property name="step_increment">1</property>
+ <property name="page_increment">8</property>
+ </object>
+</interface>
diff --git a/src/gui/main.cpp b/src/gui/main.cpp
new file mode 100644
index 0000000..547966f
--- /dev/null
+++ b/src/gui/main.cpp
@@ -0,0 +1,100 @@
+/*
+ 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 <signal.h>
+
+#include <iostream>
+#include <string>
+
+#include "lv2/lv2plug.in/ns/ext/midi/midi.h"
+#include "machina/Engine.hpp"
+#include "machina/Loader.hpp"
+#include "machina/Machine.hpp"
+#include "machina/URIs.hpp"
+#include "machina_config.h"
+#include "sord/sordmm.hpp"
+
+#include "MachinaGUI.hpp"
+
+using namespace std;
+using namespace machina;
+
+int
+main(int argc, char** argv)
+{
+ Glib::thread_init();
+ machina::URIs::init();
+
+ Sord::World rdf_world;
+ rdf_world.add_prefix("", MACHINA_NS);
+ rdf_world.add_prefix("midi", LV2_MIDI_PREFIX);
+
+ Forge forge;
+ SPtr<machina::Machine> machine;
+
+ Raul::TimeUnit beats(TimeUnit::BEATS, MACHINA_PPQN);
+
+ // Load machine, if given
+ if (argc >= 2) {
+ const string filename = argv[1];
+ const string ext = filename.substr(filename.length() - 4);
+
+ if (ext == ".ttl") {
+ cout << "Loading machine from " << filename << endl;
+ machine = Loader(forge, rdf_world).load(filename);
+
+ } else if (ext == ".mid") {
+ cout << "Building machine from MIDI file " << filename << endl;
+
+ double q = 0.0;
+ if (argc >= 3) {
+ q = strtod(argv[2], NULL);
+ cout << "Quantization: " << q << endl;
+ }
+
+ machine = Loader(forge, rdf_world).load_midi(
+ filename, q, Raul::TimeDuration(beats, 0, 0));
+ }
+
+ if (!machine) {
+ cerr << "Failed to load machine, exiting" << std::endl;
+ return 1;
+ }
+ }
+
+
+ if (!machine) {
+ machine = SPtr<Machine>(new Machine(beats));
+ }
+
+ // Create driver
+ SPtr<Driver> driver(Engine::new_driver(forge, "jack", machine));
+ if (!driver) {
+ cerr << "warning: Failed to create Jack driver, using SMF" << endl;
+ driver = SPtr<Driver>(Engine::new_driver(forge, "smf", machine));
+ }
+
+ SPtr<Engine> engine(new Engine(forge, driver, rdf_world));
+
+ Gtk::Main app(argc, argv);
+
+ driver->activate();
+ gui::MachinaGUI gui(engine);
+
+ app.run(*gui.window());
+
+ return 0;
+}
diff --git a/src/gui/wscript b/src/gui/wscript
new file mode 100644
index 0000000..0eef312
--- /dev/null
+++ b/src/gui/wscript
@@ -0,0 +1,42 @@
+#!/usr/bin/env python
+from waflib.extras import autowaf as autowaf
+
+def build(bld):
+ obj = bld(features = 'cxx cxxshlib')
+ obj.source = '''
+ EdgeView.cpp
+ MachinaCanvas.cpp
+ MachinaGUI.cpp
+ NodePropertiesWindow.cpp
+ NodeView.cpp
+ '''
+
+ obj.includes = ['.', '..', '../..', '../engine']
+ obj.export_includes = ['.']
+ obj.name = 'libmachina_gui'
+ obj.target = 'machina_gui'
+ obj.use = 'libmachina_engine libmachina_client'
+ autowaf.use_lib(bld, obj, '''
+ GANV
+ GLADEMM
+ GLIBMM
+ GNOMECANVAS
+ GTKMM
+ RAUL
+ SORD
+ SIGCPP
+ EUGENE
+ LV2
+ ''')
+
+ # GUI runtime files
+ bld.install_files('${DATADIR}/machina', 'machina.ui')
+ bld.install_files('${DATADIR}/machina', 'machina.svg')
+
+ # Executable
+ obj = bld(features = 'cxx cxxprogram')
+ obj.target = 'machina_gui'
+ obj.source = 'main.cpp'
+ obj.includes = ['.', '../..', '../engine']
+ obj.use = 'libmachina_engine libmachina_gui'
+ autowaf.use_lib(bld, obj, 'GTHREAD GLIBMM GTKMM SORD RAUL MACHINA EUGENE GANV LV2')
diff --git a/src/machina.cpp b/src/machina.cpp
new file mode 100644
index 0000000..32eab05
--- /dev/null
+++ b/src/machina.cpp
@@ -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/>.
+*/
+
+#include "machina_config.h"
+
+#include <iostream>
+#include <signal.h>
+#include "machina/Action.hpp"
+#include "machina/Edge.hpp"
+#include "machina/Engine.hpp"
+#include "machina/Machine.hpp"
+#include "machina/MidiAction.hpp"
+#ifdef HAVE_EUGENE
+# include "machina/Problem.hpp"
+#endif
+
+#include "JackDriver.hpp"
+
+using namespace std;
+using namespace machina;
+
+bool quit = false;
+
+void
+catch_int(int)
+{
+ signal(SIGINT, catch_int);
+ signal(SIGTERM, catch_int);
+
+ std::cout << "Interrupted" << std::endl;
+
+ quit = true;
+}
+
+int
+main(int argc, char** argv)
+{
+ if (argc != 2) {
+ cout << "Usage: " << argv[0] << " FILE" << endl;
+ return -1;
+ }
+
+ if (!Glib::thread_supported()) {
+ Glib::thread_init();
+ }
+
+ SPtr<JackDriver> driver(new JackDriver());
+
+ Sord::World rdf_world;
+ Engine engine(driver, rdf_world);
+
+ /* FIXME: Would be nice if this could take URIs on the cmd line
+ char* uri = (char*)calloc(6 + strlen(argv[1]), sizeof(char));
+ strcpy(uri, "file:");
+ strcat(uri, argv[1]);
+ engine.load_machine(uri);
+ free(uri);
+ */
+ engine.load_machine(argv[1]);
+
+ driver->attach("machina");
+
+ signal(SIGINT, catch_int);
+ signal(SIGTERM, catch_int);
+
+ while (!quit) {
+ sleep(1);
+ }
+
+ driver->detach();
+
+ return 0;
+}
diff --git a/src/midi2machina.cpp b/src/midi2machina.cpp
new file mode 100644
index 0000000..fdff2f7
--- /dev/null
+++ b/src/midi2machina.cpp
@@ -0,0 +1,81 @@
+/*
+ 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 <signal.h>
+#include "sord/sordmm.hpp"
+#include "machina/Engine.hpp"
+#include "machina/Machine.hpp"
+#include "machina/Action.hpp"
+#include "machina/Edge.hpp"
+#include "machina/SMFDriver.hpp"
+#include "machina/MidiAction.hpp"
+
+using namespace std;
+using namespace machina;
+
+bool quit = false;
+
+void
+catch_int(int)
+{
+ signal(SIGINT, catch_int);
+ signal(SIGTERM, catch_int);
+
+ std::cout << "Interrupted" << std::endl;
+
+ quit = true;
+}
+
+int
+main(int argc, char** argv)
+{
+ if (argc != 3) {
+ cout << "Usage: midi2machina QUANTIZATION FILE" << endl;
+ cout << "Specify quantization in beats, e.g. 1.0, or 0 for none"
+ << endl;
+ return -1;
+ }
+
+ SPtr<SMFDriver> driver(new SMFDriver());
+
+ SPtr<Machine> machine = driver->learn(argv[2], strtof(argv[1], NULL));
+
+ if (!machine) {
+ cout << "Failed to load MIDI file." << endl;
+ return -1;
+ }
+
+ /*
+ string out_filename = argv[1];
+ if (out_filename.find_last_of("/") != string::npos)
+ out_filename = out_filename.substr(out_filename.find_last_of("/")+1);
+
+ const string::size_type last_dot = out_filename.find_last_of(".");
+ if (last_dot != string::npos && last_dot != 0)
+ out_filename = out_filename.substr(0, last_dot);
+ out_filename += ".machina";
+
+ cout << "Writing output to " << out_filename << endl;
+ */
+
+ Sord::World world;
+ Sord::Model model(world);
+ machine->write_state(model);
+ model.serialise_to_file_handle(stdout);
+
+ return 0;
+}