From 0123cdeacc9acc7ca16fa8b0a9dee7a5d916b7df Mon Sep 17 00:00:00 2001 From: David Robillard Date: Thu, 5 Apr 2007 05:50:53 +0000 Subject: Selector states. Togglable edge and state labels. Builder fixes. GUI/input/etc fixes. git-svn-id: http://svn.drobilla.net/lad/machina@398 a436a847-0d15-0410-975c-d299462d15a1 --- src/engine/Machine.cpp | 46 ++++++++++++++++++++++------ src/engine/MachineBuilder.cpp | 55 ++++++++++++++++++++++++++++----- src/engine/Node.cpp | 16 ++++++++++ src/engine/machina/MachineBuilder.hpp | 1 + src/engine/machina/MidiAction.hpp | 1 - src/engine/machina/Node.hpp | 4 +-- src/gui/MachinaCanvas.cpp | 4 +++ src/gui/MachinaGUI.cpp | 6 ++++ src/gui/NodeView.cpp | 45 +++++++++++++++++++++++---- src/gui/NodeView.hpp | 4 ++- src/gui/machina.glade | 57 +++++------------------------------ 11 files changed, 162 insertions(+), 77 deletions(-) diff --git a/src/engine/Machine.cpp b/src/engine/Machine.cpp index c61bd4f..38e6e11 100644 --- a/src/engine/Machine.cpp +++ b/src/engine/Machine.cpp @@ -65,6 +65,9 @@ void Machine::remove_node(SharedPtr node) { _nodes.erase(_nodes.find(node)); + + for (Nodes::const_iterator n = _nodes.begin(); n != _nodes.end(); ++n) + (*n)->remove_outgoing_edges_to(node); } @@ -164,21 +167,44 @@ Machine::exit_node(SharedPtr sink, const SharedPtr node) } } - // Activate all successors to this node + // Activate successors to this node // (that aren't aready active right now) - for (Node::Edges::const_iterator s = node->outgoing_edges().begin(); - s != node->outgoing_edges().end(); ++s) { - - assert((*s)->head() != node); // no loops + + if (node->is_selector()) { const double rand_normal = rand() / (double)RAND_MAX; // [0, 1] - - if (rand_normal <= (*s)->probability()) { - SharedPtr head = (*s)->head(); + double range_min = 0; + + for (Node::Edges::const_iterator s = node->outgoing_edges().begin(); + s != node->outgoing_edges().end(); ++s) { + + if (!(*s)->head()->is_active() + && rand_normal > range_min + && rand_normal < range_min + (*s)->probability()) { + + enter_node(sink, (*s)->head()); + break; + + } else { + range_min += (*s)->probability(); + } + } + + } else { + + for (Node::Edges::const_iterator s = node->outgoing_edges().begin(); + s != node->outgoing_edges().end(); ++s) { - if (!head->is_active()) - enter_node(sink, head); + const double rand_normal = rand() / (double)RAND_MAX; // [0, 1] + + if (rand_normal <= (*s)->probability()) { + SharedPtr head = (*s)->head(); + + if ( ! head->is_active()) + enter_node(sink, head); + } } + } } diff --git a/src/engine/MachineBuilder.cpp b/src/engine/MachineBuilder.cpp index 03d20b9..b62e90e 100644 --- a/src/engine/MachineBuilder.cpp +++ b/src/engine/MachineBuilder.cpp @@ -62,6 +62,21 @@ MachineBuilder::is_delay_node(SharedPtr node) const } +/** Set the duration of a node, with quantization. + */ +void +MachineBuilder::set_node_duration(SharedPtr node, Raul::BeatTime d) const +{ + Raul::BeatTime q_dur = Quantizer::quantize(_quantization, d); + + // Never quantize a note to duration 0 + if (q_dur == 0 && ( node->enter_action() || node->exit_action() )) + q_dur = _quantization; // Round up + + node->set_duration(q_dur); +} + + /** Connect two nodes, inserting a delay node between them if necessary. * * If a delay node is added to the machine, it is returned. @@ -83,7 +98,7 @@ MachineBuilder::connect_nodes(SharedPtr m, if (is_delay_node(tail) && tail->outgoing_edges().size() == 0) { // Tail is a delay node, just accumulate the time difference into it //cerr << "Accumulating delay " << tail_end_time << " .. " << head_start_time << endl; - tail->set_duration(tail->duration() + head_start_time - tail_end_time); + set_node_duration(tail, tail->duration() + head_start_time - tail_end_time); tail->add_outgoing_edge(SharedPtr(new Edge(tail, head))); } else if (head_start_time == tail_end_time) { // Connect directly @@ -93,7 +108,7 @@ MachineBuilder::connect_nodes(SharedPtr m, // Need to actually create a delay node //cerr << "Adding delay node for " << tail_end_time << " .. " << head_start_time << endl; delay_node = SharedPtr(new Node()); - delay_node->set_duration(head_start_time - tail_end_time); + set_node_duration(delay_node, head_start_time - tail_end_time); tail->add_outgoing_edge(SharedPtr(new Edge(tail, delay_node))); delay_node->add_outgoing_edge(SharedPtr(new Edge(delay_node, head))); m->add_node(delay_node); @@ -181,7 +196,7 @@ MachineBuilder::event(Raul::BeatTime time_offset, SharedPtr resolved = *i; resolved->set_exit_action(SharedPtr(new MidiAction(ev_size, buf))); - resolved->set_duration(t - resolved->enter_time()); + set_node_duration(resolved, t - resolved->enter_time()); // Last active note if (_active_nodes.size() == 1) { @@ -227,7 +242,7 @@ MachineBuilder::event(Raul::BeatTime time_offset, _connect_node->set_exit_action(resolved->exit_action()); resolved->remove_enter_action(); resolved->remove_exit_action(); - _connect_node->set_duration(resolved->duration()); + set_node_duration(_connect_node, resolved->duration()); resolved = _connect_node; if (_machine->nodes().find(_connect_node) == _machine->nodes().end()) _machine->add_node(_connect_node); @@ -281,19 +296,20 @@ MachineBuilder::resolve() 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)->set_exit_action(SharedPtr(new MidiAction(3, note_off))); - (*i)->set_duration(_time - (*i)->enter_time()); + set_node_duration((*i), _time - (*i)->enter_time()); (*i)->exit(SharedPtr(), _time); _machine->add_node((*i)); } } _active_nodes.clear(); } - + // Add initial note if necessary if (_machine->nodes().size() > 0 && _machine->nodes().find(_initial_node) == _machine->nodes().end()) _machine->add_node(_initial_node); - + +#if 0 // Quantize if (_quantization > 0) { for (Machine::Nodes::iterator i = _machine->nodes().begin(); @@ -305,6 +321,31 @@ MachineBuilder::resolve() (*i)->set_duration(q_dur); } } + + // Remove any useless states + for (Machine::Nodes::iterator i = _machine->nodes().begin(); + i != _machine->nodes().end() ; ) { + + if (!(*i)->enter_action() && !(*i)->exit_action() && (*i)->duration() == 0 + && (*i)->outgoing_edges().size() <= 1) { + + // Connect any predecessors to the successor + if ((*i)->outgoing_edges().size() == 1) { + SharedPtr successor = *((*i)->outgoing_edges().begin())->head(); + + for (Machine::Nodes::iterator i = _machine->nodes().begin(); + i != _machine->nodes().end() ; ) { + + + Machine::Nodes::iterator next = i; + ++next; + + _machine->remove_node(*i); + + i = next; + } + } +#endif } diff --git a/src/engine/Node.cpp b/src/engine/Node.cpp index e51e33d..b55619d 100644 --- a/src/engine/Node.cpp +++ b/src/engine/Node.cpp @@ -33,6 +33,22 @@ Node::Node(BeatCount duration, bool initial) } +void +Node::set_selector(bool yn) +{ + _is_selector = yn; + + if (yn) { + double prob_sum = 0; + for (Edges::iterator i = _outgoing_edges.begin(); i != _outgoing_edges.end(); ++i) + prob_sum += (*i)->probability(); + + for (Edges::iterator i = _outgoing_edges.begin(); i != _outgoing_edges.end(); ++i) + (*i)->set_probability((*i)->probability() / prob_sum); + } +} + + void Node::set_enter_action(SharedPtr action) { diff --git a/src/engine/machina/MachineBuilder.hpp b/src/engine/machina/MachineBuilder.hpp index ac446c9..25104e8 100644 --- a/src/engine/machina/MachineBuilder.hpp +++ b/src/engine/machina/MachineBuilder.hpp @@ -44,6 +44,7 @@ public: private: bool is_delay_node(SharedPtr node) const; + void set_node_duration(SharedPtr node, Raul::BeatTime d) const; SharedPtr connect_nodes(SharedPtr m, diff --git a/src/engine/machina/MidiAction.hpp b/src/engine/machina/MidiAction.hpp index 60d9189..510c0d0 100644 --- a/src/engine/machina/MidiAction.hpp +++ b/src/engine/machina/MidiAction.hpp @@ -57,7 +57,6 @@ public: private: - size_t _size; const size_t _max_size; Raul::AtomicPtr _event; diff --git a/src/engine/machina/Node.hpp b/src/engine/machina/Node.hpp index b4c72e2..66d1b2d 100644 --- a/src/engine/machina/Node.hpp +++ b/src/engine/machina/Node.hpp @@ -66,13 +66,13 @@ public: bool is_initial() const { return _is_initial; } void set_initial(bool i) { _is_initial = i; } - bool is_selector() const { return _is_selector; } - void set_selector(bool i) { _is_selector = i; } bool is_active() const { return _is_active; } 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; } + bool is_selector() const { return _is_selector; } + void set_selector(bool i); typedef Raul::List > Edges; Edges& outgoing_edges() { return _outgoing_edges; } diff --git a/src/gui/MachinaCanvas.cpp b/src/gui/MachinaCanvas.cpp index 134b885..716a40d 100644 --- a/src/gui/MachinaCanvas.cpp +++ b/src/gui/MachinaCanvas.cpp @@ -52,6 +52,9 @@ MachinaCanvas::node_clicked(WeakPtr item, GdkEventButton* event) if (!node) return; + if (event->state & GDK_CONTROL_MASK) + return; + // Middle click, learn if (event->button == 2) { _app->machine()->learn(Machina::LearnRequest::create(_app->maid(), node->node())); @@ -192,6 +195,7 @@ void MachinaCanvas::build(SharedPtr machine) { destroy(); + _last_clicked.reset(); if (!machine) return; diff --git a/src/gui/MachinaGUI.cpp b/src/gui/MachinaGUI.cpp index 3cf38bd..e5e6d90 100644 --- a/src/gui/MachinaGUI.cpp +++ b/src/gui/MachinaGUI.cpp @@ -608,6 +608,12 @@ void MachinaGUI::show_labels_toggled() { const bool show = _menu_view_labels->get_active(); + + for (ItemList::iterator i = _canvas->items().begin(); i != _canvas->items().end(); ++i) { + const SharedPtr nv = PtrCast(*i); + if (nv) + nv->show_label(show); + } for (ConnectionList::iterator c = _canvas->connections().begin(); c != _canvas->connections().end(); ++c) { diff --git a/src/gui/NodeView.cpp b/src/gui/NodeView.cpp index 6ff21ee..d38e228 100644 --- a/src/gui/NodeView.cpp +++ b/src/gui/NodeView.cpp @@ -15,6 +15,7 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA */ +#include #include "NodeView.hpp" #include "NodePropertiesWindow.hpp" @@ -29,7 +30,8 @@ NodeView::NodeView(Gtk::Window* window, , _window(window) , _node(node) { - //signal_double_clicked.connect(sigc::mem_fun(this, &NodeView::on_double_click)); + signal_clicked.connect(sigc::mem_fun(this, &NodeView::handle_click)); + update_state(); } @@ -41,27 +43,50 @@ NodeView::on_double_click(GdkEventButton*) void -NodeView::on_click(GdkEventButton* event) +NodeView::handle_click(GdkEventButton* event) { if (event->state & GDK_CONTROL_MASK) { if (event->button == 1) { bool is_initial = _node->is_initial(); _node->set_initial( ! is_initial ); - set_border_width(is_initial ? 1.0 : 6.0); + update_state(); } else if (event->button == 3) { bool is_selector = _node->is_selector(); _node->set_selector( ! is_selector ); - set_border_width(is_selector ? 1.0 : 3.0); + update_state(); } } } void -NodeView::update_state() +NodeView::show_label(bool show) { - static const uint32_t active_color = 0x80808AFF; + char str[4]; + + SharedPtr action + = PtrCast(_node->enter_action()); + + if (show) { + if (action && action->event_size() >= 2 + && (action->event()[0] & 0xF0) == 0x90) { + + unsigned char note = action->event()[1]; + snprintf(str, 4, "%d", (int)note); + + set_name(str); + } + } else { + set_name(""); + } +} + +void +NodeView::update_state() +{ + static const uint32_t active_color = 0xA0A0AAFF; + if (_node->is_active()) { if (_color != active_color) { _old_color = _color; @@ -70,5 +95,13 @@ NodeView::update_state() } else if (_color == active_color) { set_base_color(_old_color); } + + if (_node->is_selector()) + if (_node->is_active()) + set_base_color(0x00FF00FF); + else + set_base_color(0x00A000FF); + + set_border_width(_node->is_initial() ? 4.0 : 1.0); } diff --git a/src/gui/NodeView.hpp b/src/gui/NodeView.hpp index 4641787..41ff45f 100644 --- a/src/gui/NodeView.hpp +++ b/src/gui/NodeView.hpp @@ -32,11 +32,13 @@ public: double y); SharedPtr node() { return _node; } + + void show_label(bool show); void update_state(); private: - void on_click(GdkEventButton* ev); + void handle_click(GdkEventButton* ev); void on_double_click(GdkEventButton* ev); Gtk::Window* _window; diff --git a/src/gui/machina.glade b/src/gui/machina.glade index b5349be..61096f1 100644 --- a/src/gui/machina.glade +++ b/src/gui/machina.glade @@ -316,51 +316,6 @@ - - - True - Add selected states - gtk-add - True - True - False - - - False - True - - - - - - True - Multiply (merge) selected states - - True - gtk-convert - True - True - False - - - False - True - - - - - - True - True - True - True - - - False - False - - - True @@ -732,7 +687,8 @@ True Machina © 2007 Dave Robillard <http://drobilla.net> - It's a machine + A MIDI sequencer based on + probabilistic finite-state automata 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 @@ -808,14 +764,15 @@ along with Machina; if not, write to the Free Software Foundation, Inc., True - Interface is still a bit arbitrary: + Initial nodes are shown with a thick border. +Selector nodes are shown in green. - Middle click canvas to create a new node - Middle click nodes to learn a MIDI note for that node - Right click two nodes in succession to connect nodes -- Double click a node to make it an initial node - -Connect some nodes up and double click one. You'll get it. +- Double click a node to show its properties dialog +- Ctrl+Left click a node to make it an initial node +- Ctrl+Right click a node to make it a selector node False False GTK_JUSTIFY_LEFT -- cgit v1.2.1