aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDavid Robillard <d@drobilla.net>2007-03-25 03:01:32 +0000
committerDavid Robillard <d@drobilla.net>2007-03-25 03:01:32 +0000
commitf9c827a15fda4a0ce2ca3cfdc64ec1448385f149 (patch)
tree2a84884814207da7c5ec8f8d7b6902b8fc979910
parent2bcf45ee765a733cd5b0a606ed44a7d311eb9783 (diff)
downloadmachina-f9c827a15fda4a0ce2ca3cfdc64ec1448385f149.tar.gz
machina-f9c827a15fda4a0ce2ca3cfdc64ec1448385f149.tar.bz2
machina-f9c827a15fda4a0ce2ca3cfdc64ec1448385f149.zip
Added node properties window.
Improved learning algorithm. git-svn-id: http://svn.drobilla.net/lad/machina@375 a436a847-0d15-0410-975c-d299462d15a1
-rw-r--r--src/engine/LearnRequest.cpp4
-rw-r--r--src/engine/Loader.cpp4
-rw-r--r--src/engine/Machine.cpp8
-rw-r--r--src/engine/Node.cpp16
-rw-r--r--src/engine/SMFDriver.cpp136
-rw-r--r--src/engine/machina/Machine.hpp1
-rw-r--r--src/engine/machina/Node.hpp14
-rw-r--r--src/engine/machina/SMFDriver.hpp7
-rw-r--r--src/gui/MachinaCanvas.cpp4
-rw-r--r--src/gui/MachinaGUI.cpp27
-rw-r--r--src/gui/Makefile.am5
-rw-r--r--src/gui/NodePropertiesWindow.cpp99
-rw-r--r--src/gui/NodePropertiesWindow.hpp51
-rw-r--r--src/gui/NodeView.cpp22
-rw-r--r--src/gui/NodeView.hpp5
-rw-r--r--src/gui/machina.glade181
-rw-r--r--src/gui/main.cpp14
17 files changed, 522 insertions, 76 deletions
diff --git a/src/engine/LearnRequest.cpp b/src/engine/LearnRequest.cpp
index 6f311cb..019f7a2 100644
--- a/src/engine/LearnRequest.cpp
+++ b/src/engine/LearnRequest.cpp
@@ -25,8 +25,8 @@ namespace Machina {
void
LearnRequest::finish(BeatTime time)
{
- _node->add_enter_action(_enter_action);
- _node->add_exit_action(_exit_action);
+ _node->set_enter_action(_enter_action);
+ _node->set_exit_action(_exit_action);
double duration = Raul::Quantizer::quantize(_quantization, time - _start_time);
diff --git a/src/engine/Loader.cpp b/src/engine/Loader.cpp
index b58d150..02aff51 100644
--- a/src/engine/Loader.cpp
+++ b/src/engine/Loader.cpp
@@ -157,8 +157,8 @@ Loader::load(const Glib::ustring& uri)
SharedPtr<Node> node = node_i->second;
const long note_num = strtol(note.c_str(), NULL, 10);
if (note_num >= 0 && note_num <= 127) {
- node->add_enter_action(ActionFactory::note_on((unsigned char)note_num));
- node->add_exit_action(ActionFactory::note_off((unsigned char)note_num));
+ node->set_enter_action(ActionFactory::note_on((unsigned char)note_num));
+ node->set_exit_action(ActionFactory::note_off((unsigned char)note_num));
} else {
cerr << "WARNING: MIDI note number out of range, ignoring." << endl;
}
diff --git a/src/engine/Machine.cpp b/src/engine/Machine.cpp
index f2aa845..91dcfe4 100644
--- a/src/engine/Machine.cpp
+++ b/src/engine/Machine.cpp
@@ -55,10 +55,18 @@ Machine::set_sink(SharedPtr<Raul::MIDISink> sink)
void
Machine::add_node(SharedPtr<Node> node)
{
+ cerr << "ADDING NODE " << node.get() << endl;
_nodes.push_back(node);
}
+void
+Machine::remove_node(SharedPtr<Node> node)
+{
+ _nodes.erase(_nodes.find(node));
+}
+
+
/** Exit all active states and reset time to 0.
*/
void
diff --git a/src/engine/Node.cpp b/src/engine/Node.cpp
index 4a1ded0..d94e0bc 100644
--- a/src/engine/Node.cpp
+++ b/src/engine/Node.cpp
@@ -33,14 +33,14 @@ Node::Node(BeatCount duration, bool initial)
void
-Node::add_enter_action(SharedPtr<Action> action)
+Node::set_enter_action(SharedPtr<Action> action)
{
_enter_action = action;
}
void
-Node::remove_enter_action(SharedPtr<Action> /*action*/)
+Node::remove_enter_action()
{
_enter_action.reset();
}
@@ -48,14 +48,14 @@ Node::remove_enter_action(SharedPtr<Action> /*action*/)
void
-Node::add_exit_action(SharedPtr<Action> action)
+Node::set_exit_action(SharedPtr<Action> action)
{
_exit_action = action;
}
void
-Node::remove_exit_action(SharedPtr<Action> /*action*/)
+Node::remove_exit_action()
{
_exit_action.reset();
}
@@ -65,7 +65,9 @@ Node::remove_exit_action(SharedPtr<Action> /*action*/)
void
Node::enter(SharedPtr<Raul::MIDISink> sink, BeatTime time)
{
- //cerr << "ENTER " << time << endl;
+ assert(!_is_active);
+ //std::cerr << "ENTER " << time << std::endl;
+
_is_active = true;
_enter_time = time;
if (sink && _enter_action)
@@ -76,7 +78,9 @@ Node::enter(SharedPtr<Raul::MIDISink> sink, BeatTime time)
void
Node::exit(SharedPtr<Raul::MIDISink> sink, BeatTime time)
{
- //cerr << "EXIT " << time << endl;
+ assert(_is_active);
+ //std::cerr << "EXIT " << time << std::endl;
+
if (sink && _exit_action)
_exit_action->execute(sink, time);
_is_active = false;
diff --git a/src/engine/SMFDriver.cpp b/src/engine/SMFDriver.cpp
index e14e04c..ff55e00 100644
--- a/src/engine/SMFDriver.cpp
+++ b/src/engine/SMFDriver.cpp
@@ -96,6 +96,54 @@ SMFDriver::learn(const string& filename, double q, Raul::BeatTime max_duration)
}
+bool
+SMFDriver::is_delay_node(SharedPtr<Node> node) const
+{
+ if (node->enter_action() || node->exit_action())
+ return false;
+ else
+ return true;
+}
+
+
+/** Connect two nodes, inserting a delay node between them if necessary.
+ *
+ * If a delay node is added to the machine, it is returned.
+ */
+SharedPtr<Node>
+SMFDriver::connect_nodes(SharedPtr<Machine> m,
+ SharedPtr<Node> tail, Raul::BeatTime tail_end_time,
+ SharedPtr<Node> head, Raul::BeatTime head_start_time)
+{
+ assert(tail != head);
+ assert(head_start_time >= tail_end_time);
+
+ SharedPtr<Node> delay_node;
+
+ cout << "Connect nodes durations: " << tail->duration() << " .. " << head->duration() << endl;
+ cout << "Connect nodes times: " << tail_end_time << " .. " << head_start_time << endl;
+
+ if (head_start_time == tail_end_time) {
+ // Connect directly
+ tail->add_outgoing_edge(SharedPtr<Edge>(new Edge(tail, head)));
+ } else if (is_delay_node(tail)) {
+ // Tail is a delay node, just accumulate the time difference into it
+ tail->set_duration(tail->duration() + head_start_time - tail_end_time);
+ tail->add_outgoing_edge(SharedPtr<Edge>(new Edge(tail, head)));
+ } else {
+ // Need to actually create a delay node
+ cerr << "Adding delay node for " << tail_end_time << " .. " << head_start_time << endl;
+ delay_node = SharedPtr<Node>(new Node());
+ delay_node->set_duration(head_start_time - tail_end_time);
+ tail->add_outgoing_edge(SharedPtr<Edge>(new Edge(tail, delay_node)));
+ delay_node->add_outgoing_edge(SharedPtr<Edge>(new Edge(delay_node, head)));
+ m->add_node(delay_node);
+ }
+
+ return delay_node;
+}
+
+
void
SMFDriver::learn_track(SharedPtr<Machine> m,
Raul::SMFReader& reader,
@@ -107,7 +155,11 @@ SMFDriver::learn_track(SharedPtr<Machine> m,
if (!found_track)
return;
- list<SharedPtr<Node> > active_nodes;
+ typedef list<SharedPtr<Node> > ActiveList;
+ ActiveList active_nodes;
+
+ typedef list<pair<Raul::BeatTime, SharedPtr<Node> > > PolyList;
+ PolyList poly_nodes;
SharedPtr<Node> initial_node(new Node());
initial_node->set_initial(true);
@@ -135,28 +187,21 @@ SMFDriver::learn_track(SharedPtr<Machine> m,
if ((buf[0] & 0xF0) == MIDI_CMD_NOTE_ON) {
//cerr << "NOTE ON: " << (int)buf[1] << ", channel = " << (int)(buf[0] & 0x0F) << endl;
SharedPtr<Node> node(new Node());
- node->add_enter_action(SharedPtr<Action>(new MidiAction(ev_size, buf)));
- assert(connect_node_end_time <= t);
- if (t == connect_node_end_time) {
- connect_node->add_outgoing_edge(SharedPtr<Edge>(new Edge(connect_node, node)));
- } else {
- SharedPtr<Node> delay_node(new Node());
- delay_node->set_duration(t - connect_node_end_time);
- connect_node->add_outgoing_edge(SharedPtr<Edge>(new Edge(connect_node, delay_node)));
- delay_node->add_outgoing_edge(SharedPtr<Edge>(new Edge(delay_node, node)));
- m->add_node(delay_node);
- ++added_nodes;
+ node->set_enter_action(SharedPtr<Action>(new MidiAction(ev_size, buf)));
+
+ SharedPtr<Node> delay_node = connect_nodes(m, connect_node, connect_node_end_time, node, t);
+ if (delay_node) {
connect_node = delay_node;
connect_node_end_time = t;
}
node->enter(SharedPtr<Raul::MIDISink>(), t);
active_nodes.push_back(node);
+
} else if ((buf[0] & 0xF0) == MIDI_CMD_NOTE_OFF) {
//cerr << "NOTE OFF: " << (int)buf[1] << endl;
- for (list<SharedPtr<Node> >::iterator i = active_nodes.begin();
- i != active_nodes.end(); ++i) {
+ for (ActiveList::iterator i = active_nodes.begin(); i != active_nodes.end(); ++i) {
SharedPtr<MidiAction> action = PtrCast<MidiAction>((*i)->enter_action());
if (!action)
continue;
@@ -167,17 +212,64 @@ SMFDriver::learn_track(SharedPtr<Machine> m,
&& (ev[0] & 0x0F) == (buf[0] & 0x0F) // same channel
&& ev[1] == buf[1]) // same note
{
- //cerr << "FOUND MATCHING NOTE ON!\n";
- (*i)->add_exit_action(SharedPtr<Action>(new MidiAction(ev_size, buf)));
- (*i)->set_duration(t - (*i)->enter_time());
- (*i)->exit(SharedPtr<Raul::MIDISink>(), t);
- m->add_node((*i));
+ //cerr << "FOUND MATCHING NOTE OFF!\n";
+
+ SharedPtr<Node> resolved = *i;
+
+ resolved->set_exit_action(SharedPtr<Action>(new MidiAction(ev_size, buf)));
+ resolved->set_duration(t - resolved->enter_time());
+
++added_nodes;
+
+ connect_node_end_time = t;
+
if (active_nodes.size() == 1) {
- connect_node = (*i);
- connect_node_end_time = t;
+ if (poly_nodes.size() > 0) {
+
+ connect_node = SharedPtr<Node>(new Node());
+ m->add_node(connect_node);
+
+ connect_nodes(m, resolved, t, connect_node, t);
+
+ for (PolyList::iterator j = poly_nodes.begin();
+ j != poly_nodes.end(); ++j) {
+ m->add_node(j->second);
+ connect_nodes(m, j->second, j->first + j->second->duration(),
+ connect_node, t);
+ }
+ poly_nodes.clear();
+
+ m->add_node(resolved);
+
+ } else {
+ // Trim useless delay node, if possible
+ // (these happen after polyphonic sections)
+ if (is_delay_node(connect_node) && connect_node->duration() == 0
+ && connect_node->outgoing_edges().size() == 1
+ && (*connect_node->outgoing_edges().begin())->dst() == resolved) {
+ connect_node->outgoing_edges().clear();
+ assert(connect_node->outgoing_edges().empty());
+ connect_node->set_enter_action(resolved->enter_action());
+ connect_node->set_exit_action(resolved->exit_action());
+ resolved->remove_enter_action();
+ resolved->remove_exit_action();
+ connect_node->set_duration(resolved->duration());
+ resolved = connect_node;
+ } else {
+ connect_node = resolved;
+ m->add_node(resolved);
+ }
+ }
+
+ } else {
+ poly_nodes.push_back(make_pair(resolved->enter_time(), resolved));
}
+
+ if (resolved->is_active())
+ resolved->exit(SharedPtr<Raul::MIDISink>(), t);
+
active_nodes.erase(i);
+
break;
}
}
@@ -197,7 +289,7 @@ SMFDriver::learn_track(SharedPtr<Machine> m,
const unsigned char* ev = action->event();
if (ev_size == 3 && (ev[0] & 0xF0) == MIDI_CMD_NOTE_ON) {
unsigned char note_off[3] = { ((MIDI_CMD_NOTE_OFF & 0xF0) | (ev[0] & 0x0F)), ev[1], 0x40 };
- (*i)->add_exit_action(SharedPtr<Action>(new MidiAction(3, note_off)));
+ (*i)->set_exit_action(SharedPtr<Action>(new MidiAction(3, note_off)));
(*i)->set_duration(t - (*i)->enter_time());
(*i)->exit(SharedPtr<Raul::MIDISink>(), t);
m->add_node((*i));
diff --git a/src/engine/machina/Machine.hpp b/src/engine/machina/Machine.hpp
index b8e371d..e5bef23 100644
--- a/src/engine/machina/Machine.hpp
+++ b/src/engine/machina/Machine.hpp
@@ -43,6 +43,7 @@ public:
bool is_finished() { return _is_finished; }
void add_node(SharedPtr<Node> node);
+ void remove_node(SharedPtr<Node> node);
void learn(SharedPtr<LearnRequest> learn);
void write_state(Raul::RDFWriter& writer);
diff --git a/src/engine/machina/Node.hpp b/src/engine/machina/Node.hpp
index 2f9c2e4..5181149 100644
--- a/src/engine/machina/Node.hpp
+++ b/src/engine/machina/Node.hpp
@@ -46,11 +46,11 @@ public:
Node(BeatCount duration=0, bool initial=false);
- void add_enter_action(SharedPtr<Action> action);
- void remove_enter_action(SharedPtr<Action> action);
+ void set_enter_action(SharedPtr<Action> action);
+ void remove_enter_action();
- void add_exit_action(SharedPtr<Action> action);
- void remove_exit_action(SharedPtr<Action> action);
+ void set_exit_action(SharedPtr<Action> action);
+ void remove_exit_action();
SharedPtr<Action> enter_action() { return _enter_action; }
SharedPtr<Action> exit_action() { return _exit_action; }
@@ -67,13 +67,13 @@ public:
bool is_initial() const { return _is_initial; }
void set_initial(bool i) { _is_initial = i; }
bool is_active() const { return _is_active; }
- BeatTime enter_time() const { return _enter_time; }
- BeatTime exit_time() const { return _enter_time + _duration; }
+ BeatTime enter_time() const { assert(_is_active); return _enter_time; }
+ BeatTime exit_time() const { assert(_is_active); return _enter_time + _duration; }
BeatCount duration() { return _duration; }
void set_duration(BeatCount d) { _duration = d; }
typedef Raul::List<SharedPtr<Edge> > Edges;
- const Edges& outgoing_edges() const { return _outgoing_edges; }
+ Edges& outgoing_edges() { return _outgoing_edges; }
private:
bool _is_initial;
diff --git a/src/engine/machina/SMFDriver.hpp b/src/engine/machina/SMFDriver.hpp
index 090eb02..7fec256 100644
--- a/src/engine/machina/SMFDriver.hpp
+++ b/src/engine/machina/SMFDriver.hpp
@@ -55,6 +55,13 @@ public:
private:
SharedPtr<Raul::SMFWriter> _writer;
+ bool is_delay_node(SharedPtr<Node> node) const;
+
+ SharedPtr<Node>
+ connect_nodes(SharedPtr<Machine> m,
+ SharedPtr<Node> tail, Raul::BeatTime tail_end_time,
+ SharedPtr<Node> head, Raul::BeatTime head_start_time);
+
void learn_track(SharedPtr<Machine> machine,
Raul::SMFReader& reader,
unsigned track,
diff --git a/src/gui/MachinaCanvas.cpp b/src/gui/MachinaCanvas.cpp
index 6fdbc86..e11532e 100644
--- a/src/gui/MachinaCanvas.cpp
+++ b/src/gui/MachinaCanvas.cpp
@@ -98,7 +98,7 @@ MachinaCanvas::canvas_event(GdkEvent* event)
SharedPtr<Machina::Node> node(new Machina::Node(1.0, false));
//node->add_enter_action(SharedPtr<Machina::Action>(new Machina::PrintAction(name)));
- SharedPtr<NodeView> view(new NodeView(node, shared_from_this(),
+ SharedPtr<NodeView> view(new NodeView(_app->window(), shared_from_this(), node,
name, x, y));
view->signal_clicked.connect(sigc::bind<0>(sigc::mem_fun(this,
@@ -173,7 +173,7 @@ MachinaCanvas::disconnect_node(boost::shared_ptr<NodeView> src,
SharedPtr<NodeView>
MachinaCanvas::create_node_view(SharedPtr<Machina::Node> node)
{
- SharedPtr<NodeView> view(new NodeView(node, shared_from_this(),
+ SharedPtr<NodeView> view(new NodeView(_app->window(), shared_from_this(), node,
"", 10, 10));
if ( ! node->enter_action() && ! node->exit_action() )
diff --git a/src/gui/MachinaGUI.cpp b/src/gui/MachinaGUI.cpp
index fdd75c6..381d8f4 100644
--- a/src/gui/MachinaGUI.cpp
+++ b/src/gui/MachinaGUI.cpp
@@ -24,6 +24,7 @@
#include <raul/RDFWriter.h>
#include <machina/Machine.hpp>
#include <machina/SMFDriver.hpp>
+#include "GladeXml.hpp"
#include "MachinaGUI.hpp"
#include "MachinaCanvas.hpp"
#include "NodeView.hpp"
@@ -42,31 +43,7 @@ MachinaGUI::MachinaGUI(SharedPtr<Machina::Engine> engine)
_canvas = boost::shared_ptr<MachinaCanvas>(new MachinaCanvas(this, 1600*2, 1200*2));
- Glib::RefPtr<Gnome::Glade::Xml> xml;
-
- // Check for the .glade file in current directory
- string glade_filename = "./machina.glade";
- ifstream fs(glade_filename.c_str());
- if (fs.fail()) { // didn't find it, check PKGDATADIR
- fs.clear();
- glade_filename = PKGDATADIR;
- glade_filename += "/machina.glade";
-
- fs.open(glade_filename.c_str());
- if (fs.fail()) {
- cerr << "Unable to find machina.glade in current directory or "
- << PKGDATADIR << "." << endl;
- exit(EXIT_FAILURE);
- }
- fs.close();
- }
-
- try {
- xml = Gnome::Glade::Xml::create(glade_filename);
- } catch(const Gnome::Glade::XmlError& ex) {
- std::cerr << ex.what() << std::endl;
- throw;
- }
+ Glib::RefPtr<Gnome::Glade::Xml> xml = GladeXml::create();
xml->get_widget("machina_win", _main_window);
xml->get_widget("about_win", _about_window);
diff --git a/src/gui/Makefile.am b/src/gui/Makefile.am
index 1d6390d..3a204f2 100644
--- a/src/gui/Makefile.am
+++ b/src/gui/Makefile.am
@@ -15,6 +15,7 @@ machina_gui_LDADD = @FLOWCANVAS_LIBS@ @LIBGLADEMM_LIBS@ @GNOMECANVASMM_LIBS@ @JA
bin_PROGRAMS = machina_gui
machina_gui_SOURCES = \
main.cpp \
+ GladeXml.hpp \
MachinaGUI.hpp \
MachinaGUI.cpp \
MachinaCanvas.hpp \
@@ -22,6 +23,8 @@ machina_gui_SOURCES = \
NodeView.hpp \
NodeView.cpp \
EdgeView.hpp \
- EdgeView.cpp
+ EdgeView.cpp \
+ NodePropertiesWindow.hpp \
+ NodePropertiesWindow.cpp
endif
diff --git a/src/gui/NodePropertiesWindow.cpp b/src/gui/NodePropertiesWindow.cpp
new file mode 100644
index 0000000..151c602
--- /dev/null
+++ b/src/gui/NodePropertiesWindow.cpp
@@ -0,0 +1,99 @@
+/* This file is part of Machina.
+ * Copyright (C) 2007 Dave 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 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 details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <string>
+#include "machina/MidiAction.hpp"
+#include "NodePropertiesWindow.hpp"
+#include "GladeXml.hpp"
+
+using namespace std;
+using namespace Machina;
+
+
+NodePropertiesWindow* NodePropertiesWindow::_instance = NULL;
+
+
+NodePropertiesWindow::NodePropertiesWindow(BaseObjectType* cobject, const Glib::RefPtr<Gnome::Glade::Xml>& xml)
+ : Gtk::Dialog(cobject)
+{
+ 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_cancel_button", _cancel_button);
+ xml->get_widget("node_properties_ok_button", _ok_button);
+
+ _ok_button->signal_clicked().connect(sigc::mem_fun(this, &NodePropertiesWindow::ok_clicked));
+ _cancel_button->signal_clicked().connect(sigc::mem_fun(this, &NodePropertiesWindow::cancel_clicked));
+}
+
+
+NodePropertiesWindow::~NodePropertiesWindow()
+{
+}
+
+
+void
+NodePropertiesWindow::ok_clicked()
+{
+ assert(this == _instance);
+ delete _instance;
+ _instance = NULL;
+}
+
+
+void
+NodePropertiesWindow::cancel_clicked()
+{
+ assert(this == _instance);
+ delete _instance;
+ _instance = NULL;
+}
+
+
+void
+NodePropertiesWindow::set_node(SharedPtr<Machina::Node> node)
+{
+ _node = node;
+ SharedPtr<MidiAction> enter_action = PtrCast<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 {
+ _note_spinbutton->hide();
+ }
+ _duration_spinbutton->set_value(node->duration());
+}
+
+
+void
+NodePropertiesWindow::present(Gtk::Window* parent, SharedPtr<Machina::Node> node)
+{
+ if (!_instance) {
+ Glib::RefPtr<Gnome::Glade::Xml> xml = GladeXml::create();
+
+ xml->get_widget_derived("node_properties_dialog", _instance);
+
+ if (parent)
+ _instance->set_transient_for(*parent);
+ }
+
+ _instance->set_node(node);
+ _instance->show();
+}
+
diff --git a/src/gui/NodePropertiesWindow.hpp b/src/gui/NodePropertiesWindow.hpp
new file mode 100644
index 0000000..461b51b
--- /dev/null
+++ b/src/gui/NodePropertiesWindow.hpp
@@ -0,0 +1,51 @@
+/* This file is part of Machina.
+ * Copyright (C) 2007 Dave 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 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 details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef NODEPROPERTIESWINDOW_H
+#define NODEPROPERTIESWINDOW_H
+
+#include <gtkmm.h>
+#include <libglademm/xml.h>
+#include "machina/Node.hpp"
+
+class NodePropertiesWindow : public Gtk::Dialog
+{
+public:
+ static void present(Gtk::Window* parent, SharedPtr<Machina::Node> node);
+
+private:
+ friend class Gnome::Glade::Xml;
+ NodePropertiesWindow(BaseObjectType* cobject, const Glib::RefPtr<Gnome::Glade::Xml>& glade_xml);
+ ~NodePropertiesWindow();
+
+ void set_node(SharedPtr<Machina::Node> node);
+
+ void ok_clicked();
+ void cancel_clicked();
+
+ static NodePropertiesWindow* _instance;
+
+ SharedPtr<Machina::Node> _node;
+
+ Gtk::SpinButton* _note_spinbutton;
+ Gtk::SpinButton* _duration_spinbutton;
+ Gtk::Button* _cancel_button;
+ Gtk::Button* _ok_button;
+};
+
+
+#endif // NODEPROPERTIESWINDOW_H
diff --git a/src/gui/NodeView.cpp b/src/gui/NodeView.cpp
index d675f1d..be40047 100644
--- a/src/gui/NodeView.cpp
+++ b/src/gui/NodeView.cpp
@@ -16,14 +16,17 @@
*/
#include "NodeView.hpp"
-#include <iostream>
+#include "NodePropertiesWindow.hpp"
-NodeView::NodeView(SharedPtr<Machina::Node> node,
+
+NodeView::NodeView(Gtk::Window* window,
SharedPtr<LibFlowCanvas::FlowCanvas> canvas,
+ SharedPtr<Machina::Node> node,
const std::string& name,
double x,
double y)
: LibFlowCanvas::Ellipse(canvas, name, x, y, 20, 20, false)
+ , _window(window)
, _node(node)
{
//signal_double_clicked.connect(sigc::mem_fun(this, &NodeView::on_double_click));
@@ -33,9 +36,18 @@ NodeView::NodeView(SharedPtr<Machina::Node> node,
void
NodeView::on_double_click(GdkEventButton*)
{
- bool is_initial = _node->is_initial();
- _node->set_initial( ! is_initial );
- set_border_width(is_initial ? 1.0 : 2.0);
+ NodePropertiesWindow::present(_window, _node);
+}
+
+
+void
+NodeView::on_click(GdkEventButton* event)
+{
+ if (event->button == 3) {
+ bool is_initial = _node->is_initial();
+ _node->set_initial( ! is_initial );
+ set_border_width(is_initial ? 1.0 : 2.0);
+ }
}
diff --git a/src/gui/NodeView.hpp b/src/gui/NodeView.hpp
index ae5c34e..4641787 100644
--- a/src/gui/NodeView.hpp
+++ b/src/gui/NodeView.hpp
@@ -24,8 +24,9 @@
class NodeView : public LibFlowCanvas::Ellipse {
public:
- NodeView(SharedPtr<Machina::Node> node,
+ NodeView(Gtk::Window* window,
SharedPtr<LibFlowCanvas::FlowCanvas> canvas,
+ SharedPtr<Machina::Node> node,
const std::string& name,
double x,
double y);
@@ -35,8 +36,10 @@ public:
void update_state();
private:
+ void on_click(GdkEventButton* ev);
void on_double_click(GdkEventButton* ev);
+ Gtk::Window* _window;
SharedPtr<Machina::Node> _node;
uint32_t _old_color;
};
diff --git a/src/gui/machina.glade b/src/gui/machina.glade
index 8536e8f..df73a85 100644
--- a/src/gui/machina.glade
+++ b/src/gui/machina.glade
@@ -672,4 +672,185 @@ Connect some nodes up and double click one. You'll get it.</property>
</child>
</widget>
+<widget class="GtkDialog" id="node_properties_dialog">
+ <property name="border_width">8</property>
+ <property name="title" translatable="yes">Node Properties</property>
+ <property name="type">GTK_WINDOW_TOPLEVEL</property>
+ <property name="window_position">GTK_WIN_POS_NONE</property>
+ <property name="modal">False</property>
+ <property name="resizable">False</property>
+ <property name="destroy_with_parent">True</property>
+ <property name="decorated">True</property>
+ <property name="skip_taskbar_hint">True</property>
+ <property name="skip_pager_hint">True</property>
+ <property name="type_hint">GDK_WINDOW_TYPE_HINT_DIALOG</property>
+ <property name="gravity">GDK_GRAVITY_NORTH_WEST</property>
+ <property name="focus_on_map">True</property>
+ <property name="urgency_hint">False</property>
+ <property name="has_separator">True</property>
+
+ <child internal-child="vbox">
+ <widget class="GtkVBox" id="dialog-vbox2">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">8</property>
+
+ <child internal-child="action_area">
+ <widget class="GtkHButtonBox" id="dialog-action_area2">
+ <property name="visible">True</property>
+ <property name="layout_style">GTK_BUTTONBOX_END</property>
+
+ <child>
+ <widget class="GtkButton" id="node_properties_cancel_button">
+ <property name="visible">True</property>
+ <property name="can_default">True</property>
+ <property name="can_focus">True</property>
+ <property name="label">gtk-cancel</property>
+ <property name="use_stock">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ <property name="focus_on_click">True</property>
+ <property name="response_id">-6</property>
+ </widget>
+ </child>
+
+ <child>
+ <widget class="GtkButton" id="node_properties_ok_button">
+ <property name="visible">True</property>
+ <property name="can_default">True</property>
+ <property name="can_focus">True</property>
+ <property name="label">gtk-ok</property>
+ <property name="use_stock">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ <property name="focus_on_click">True</property>
+ <property name="response_id">-5</property>
+ </widget>
+ </child>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="pack_type">GTK_PACK_END</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkTable" id="table1">
+ <property name="visible">True</property>
+ <property name="n_rows">2</property>
+ <property name="n_columns">2</property>
+ <property name="homogeneous">False</property>
+ <property name="row_spacing">8</property>
+ <property name="column_spacing">4</property>
+
+ <child>
+ <widget class="GtkSpinButton" id="node_properties_note_spinbutton">
+ <property name="visible">True</property>
+ <property name="sensitive">False</property>
+ <property name="can_focus">True</property>
+ <property name="climb_rate">1</property>
+ <property name="digits">0</property>
+ <property name="numeric">True</property>
+ <property name="update_policy">GTK_UPDATE_ALWAYS</property>
+ <property name="snap_to_ticks">False</property>
+ <property name="wrap">False</property>
+ <property name="adjustment">60 0 127 1 10 10</property>
+ </widget>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">0</property>
+ <property name="bottom_attach">1</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkLabel" id="label6">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Note: </property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </widget>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="right_attach">1</property>
+ <property name="top_attach">0</property>
+ <property name="bottom_attach">1</property>
+ <property name="x_options">fill</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkLabel" id="label7">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Duration: </property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </widget>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="right_attach">1</property>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ <property name="x_options">fill</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkSpinButton" id="node_properties_duration_spinbutton">
+ <property name="visible">True</property>
+ <property name="sensitive">False</property>
+ <property name="can_focus">True</property>
+ <property name="climb_rate">1</property>
+ <property name="digits">2</property>
+ <property name="numeric">True</property>
+ <property name="update_policy">GTK_UPDATE_ALWAYS</property>
+ <property name="snap_to_ticks">False</property>
+ <property name="wrap">False</property>
+ <property name="adjustment">1 0 999999 1 10 10</property>
+ </widget>
+ <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"></property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ </widget>
+ </child>
+</widget>
+
</glade-interface>
diff --git a/src/gui/main.cpp b/src/gui/main.cpp
index 6f1fc4d..741c801 100644
--- a/src/gui/main.cpp
+++ b/src/gui/main.cpp
@@ -38,11 +38,19 @@ main(int argc, char** argv)
SharedPtr<Machina::Machine> machine;
// Load machine, if given
- if (argc == 2) {
- string filename = argv[1];
+ if (argc >= 2) {
+ const string filename = argv[1];
cout << "Building machine from MIDI file " << filename << endl;
SharedPtr<Machina::SMFDriver> file_driver(new Machina::SMFDriver());
- machine = file_driver->learn(filename);
+
+ if (argc >= 3) {
+ float q = strtof(argv[2], NULL);
+ cout << "Quantization: " << q << endl;
+ machine = file_driver->learn(filename, q);
+ } else {
+ cout << "No quantization." << endl;
+ machine = file_driver->learn(filename);
+ }
}
if (!machine)