aboutsummaryrefslogtreecommitdiffstats
path: root/src/gui
diff options
context:
space:
mode:
Diffstat (limited to 'src/gui')
-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
16 files changed, 3310 insertions, 0 deletions
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')