diff options
Diffstat (limited to 'src/gui')
-rw-r--r-- | src/gui/EdgeView.cpp | 142 | ||||
-rw-r--r-- | src/gui/EdgeView.hpp | 59 | ||||
-rw-r--r-- | src/gui/MachinaCanvas.cpp | 211 | ||||
-rw-r--r-- | src/gui/MachinaCanvas.hpp | 66 | ||||
-rw-r--r-- | src/gui/MachinaGUI.cpp | 750 | ||||
-rw-r--r-- | src/gui/MachinaGUI.hpp | 190 | ||||
-rw-r--r-- | src/gui/NodePropertiesWindow.cpp | 136 | ||||
-rw-r--r-- | src/gui/NodePropertiesWindow.hpp | 67 | ||||
-rw-r--r-- | src/gui/NodeView.cpp | 203 | ||||
-rw-r--r-- | src/gui/NodeView.hpp | 76 | ||||
-rw-r--r-- | src/gui/WidgetFactory.hpp | 65 | ||||
-rw-r--r-- | src/gui/machina.gladep | 9 | ||||
-rw-r--r-- | src/gui/machina.svg | 66 | ||||
-rw-r--r-- | src/gui/machina.ui | 1128 | ||||
-rw-r--r-- | src/gui/main.cpp | 100 | ||||
-rw-r--r-- | src/gui/wscript | 42 |
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 <http://drobilla.net></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 <d@drobilla.net></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"><big><b>Nodes</b></big> +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 + +<big><b>Arcs</b></big> +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 + +<big><b>Recording</b></big> +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. + +<big><b>Connecting</b></big> +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') |