From 33e9991326a1cd90a4956f3221f5a48d03d5af89 Mon Sep 17 00:00:00 2001 From: David Robillard Date: Thu, 6 Dec 2007 19:06:54 +0000 Subject: Added missing files. git-svn-id: http://svn.drobilla.net/lad/machina@956 a436a847-0d15-0410-975c-d299462d15a1 --- src/engine/ActionFactory.cpp | 10 +++ src/engine/Machine.cpp | 19 +++++ src/engine/Makefile.am | 4 +- src/engine/Node.cpp | 18 ++++ src/engine/Problem.cpp | 112 +++++++++++++++++++++++++ src/engine/machina/ActionFactory.hpp | 1 + src/engine/machina/Edge.hpp | 2 +- src/engine/machina/LearnRequest.hpp | 6 +- src/engine/machina/Machine.hpp | 4 +- src/engine/machina/MidiAction.hpp | 11 --- src/engine/machina/Node.hpp | 4 +- src/engine/machina/Problem.hpp | 56 +++++++++++++ src/gui/MachinaGUI.cpp | 156 +++++++++++++++++++---------------- src/gui/MachinaGUI.hpp | 14 ++-- src/gui/machina.glade | 22 +++++ 15 files changed, 340 insertions(+), 99 deletions(-) create mode 100644 src/engine/Problem.cpp create mode 100644 src/engine/machina/Problem.hpp diff --git a/src/engine/ActionFactory.cpp b/src/engine/ActionFactory.cpp index 4ca1a3e..6632b3c 100644 --- a/src/engine/ActionFactory.cpp +++ b/src/engine/ActionFactory.cpp @@ -20,6 +20,16 @@ namespace Machina { +SharedPtr +ActionFactory::copy(SharedPtr copy) +{ + SharedPtr ma = PtrCast(copy); + if (ma) + return SharedPtr(new MidiAction(ma->event_size(), ma->event())); + else + return SharedPtr(); +} + SharedPtr ActionFactory::note_on(unsigned char note) diff --git a/src/engine/Machine.cpp b/src/engine/Machine.cpp index 44e8446..fbda9ad 100644 --- a/src/engine/Machine.cpp +++ b/src/engine/Machine.cpp @@ -38,6 +38,25 @@ Machine::Machine() } +/** Copy a Machine. + * + * Creates a deep copy which is the 'same' machine, but with + * fresh state (deactivated, rewound) + */ +Machine::Machine(const Machine& copy) + : Raul::Stateful() // don't copy RDF ID + , _is_activated(false) + , _is_finished(false) + , _time(0) + , _sink(copy._sink) +{ + for (Nodes::const_iterator i = copy._nodes.begin(); i != copy._nodes.end(); ++i) { + SharedPtr node(new Machina::Node(*i->get())); + _nodes.push_back(node); + } +} + + Machine::~Machine() { } diff --git a/src/engine/Makefile.am b/src/engine/Makefile.am index 879c00d..c46ffed 100644 --- a/src/engine/Makefile.am +++ b/src/engine/Makefile.am @@ -2,8 +2,8 @@ SUBDIRS = machina lib_LTLIBRARIES = libmachina.la -libmachina_la_CXXFLAGS = @REDLANDMM_CFLAGS@ @RAUL_CFLAGS@ @JACK_CFLAGS@ @GLIBMM_CFLAGS@ -libmachina_la_LIBADD = @REDLANDMM_LIBS@ @RAUL_LIBS@ @JACK_LIBS@ @GLIBMM_LIBS@ +libmachina_la_CXXFLAGS = @EUGENE_CFLAGS@ @REDLANDMM_CFLAGS@ @RAUL_CFLAGS@ @JACK_CFLAGS@ @GLIBMM_CFLAGS@ +libmachina_la_LIBADD = @EUGENE_LIBS@ @RAUL_LIBS@ @JACK_LIBS@ @GLIBMM_LIBS@ libmachina_la_SOURCES = \ Action.cpp \ diff --git a/src/engine/Node.cpp b/src/engine/Node.cpp index 0eca0e6..f965aff 100644 --- a/src/engine/Node.cpp +++ b/src/engine/Node.cpp @@ -21,6 +21,7 @@ #include #include #include +#include namespace Machina { @@ -35,6 +36,23 @@ Node::Node(BeatCount duration, bool initial) } +Node::Node(const Node& copy) + : Raul::Stateful() // don't copy RDF ID + , _is_initial(copy._is_initial) + , _is_selector(copy._is_selector) + , _is_active(false) + , _enter_time(0) + , _duration(copy._duration) + , _enter_action(ActionFactory::copy(copy._enter_action)) + , _exit_action(ActionFactory::copy(copy._exit_action)) +{ + for (Edges::const_iterator i = copy._edges.begin(); i != copy._edges.end(); ++i) { + SharedPtr edge(new Edge(*i->get())); + _edges.push_back(edge); + } +} + + /** Always returns an edge, unless there are none */ SharedPtr Node::random_edge() diff --git a/src/engine/Problem.cpp b/src/engine/Problem.cpp new file mode 100644 index 0000000..f57aa15 --- /dev/null +++ b/src/engine/Problem.cpp @@ -0,0 +1,112 @@ +/* This file is part of Machina. + * Copyright (C) 2007 Dave Robillard + * + * 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 +#include +#include +#include +#include + +using namespace std; + +namespace Machina { + + +Problem::Problem(const std::string& target_midi) + : _target(*this) +{ + Raul::SMFReader smf; + const bool opened = smf.open(target_midi); + assert(opened); + + smf.seek_to_track(2); // FIXME: kluge + + uint8_t buf[4]; + uint32_t ev_size; + uint32_t delta_time; + while (smf.read_event(4, buf, &ev_size, &delta_time) >= 0) { + if ((buf[0] & 0xF0) == MIDI_CMD_NOTE_ON) { + const uint8_t note = buf[1]; + ++_target._note_frequency[note]; + ++_target._n_notes; + } + } + + _target.compute(); +} + + +float +Problem::fitness(Machine& machine) +{ + SharedPtr eval(new Evaluator(*this)); + + machine.reset(0.0f); + machine.set_sink(eval); + + // FIXME: timing stuff here isn't right at all... + + unsigned ppqn = 19200; + Raul::TimeSlice time(ppqn, 120); + time.set_start(0); + time.set_length(ppqn); + + while (time.start_ticks() < _target._n_notes * ppqn) { + machine.run(time); + time.set_start(time.start_ticks() + ppqn); + } + + eval->compute(); + + float f = 0; + + // Punish for frequency differences. + // Optimal fitness = 0 (max) + for (uint8_t i=0; i < 128; ++i) + f -= fabs(_target._note_frequency[i] - eval->_note_frequency[i]); + + return f; +} + + +void +Problem::Evaluator::write_event(Raul::BeatTime time, + size_t ev_size, + const uint8_t* ev) throw (std::logic_error) +{ + if ((ev[0] & 0xF0) == MIDI_CMD_NOTE_ON) { + const uint8_t note = ev[1]; + ++_note_frequency[note]; + ++_n_notes; + } +} + + +void +Problem::Evaluator::compute() +{ + for (uint8_t i=0; i < 128; ++i) { + if (_note_frequency[i] > 0) { + _note_frequency[i] /= (float)_n_notes; + //cout << (int)i << ":\t" << _note_frequency[i] << endl; + } + } +} + + +} // namespace Machina + diff --git a/src/engine/machina/ActionFactory.hpp b/src/engine/machina/ActionFactory.hpp index b0aa1f9..f47a626 100644 --- a/src/engine/machina/ActionFactory.hpp +++ b/src/engine/machina/ActionFactory.hpp @@ -26,6 +26,7 @@ class Action; namespace ActionFactory { + SharedPtr copy(SharedPtr copy); SharedPtr note_on(unsigned char note); SharedPtr note_off(unsigned char note); } diff --git a/src/engine/machina/Edge.hpp b/src/engine/machina/Edge.hpp index 3f564ef..1b52dc7 100644 --- a/src/engine/machina/Edge.hpp +++ b/src/engine/machina/Edge.hpp @@ -31,7 +31,7 @@ namespace Machina { class Node; -class Edge : public Raul::Stateful, public boost::noncopyable { +class Edge : public Raul::Stateful { public: Edge(WeakPtr tail, SharedPtr head) diff --git a/src/engine/machina/LearnRequest.hpp b/src/engine/machina/LearnRequest.hpp index 4ec22ff..f6bc60f 100644 --- a/src/engine/machina/LearnRequest.hpp +++ b/src/engine/machina/LearnRequest.hpp @@ -58,9 +58,11 @@ private: LearnRequest(SharedPtr maid, SharedPtr node) : _started(false) , _node(node) - , _enter_action(MidiAction::create(maid, 4, NULL)) - , _exit_action(MidiAction::create(maid, 4, NULL)) + , _enter_action(new MidiAction(4, NULL)) + , _exit_action(new MidiAction(4, NULL)) { + maid->manage(_enter_action); + maid->manage(_exit_action); } bool _started; diff --git a/src/engine/machina/Machine.hpp b/src/engine/machina/Machine.hpp index 889dc1a..4544090 100644 --- a/src/engine/machina/Machine.hpp +++ b/src/engine/machina/Machine.hpp @@ -20,6 +20,7 @@ #include #include +#include #include #include #include @@ -32,9 +33,10 @@ namespace Machina { /** A (Finite State) Machine. */ -class Machine : public Raul::Stateful, public boost::noncopyable { +class Machine : public Raul::Stateful { public: Machine(); + Machine(const Machine& copy); ~Machine(); // Main context diff --git a/src/engine/machina/MidiAction.hpp b/src/engine/machina/MidiAction.hpp index 5639532..1a3f176 100644 --- a/src/engine/machina/MidiAction.hpp +++ b/src/engine/machina/MidiAction.hpp @@ -18,8 +18,6 @@ #ifndef MACHINA_MIDIACTION_HPP #define MACHINA_MIDIACTION_HPP -#include -#include #include #include #include "types.hpp" @@ -37,15 +35,6 @@ public: MidiAction(size_t size, const unsigned char* event); - static SharedPtr - create(SharedPtr maid, - size_t size, const unsigned char* event) - { - SharedPtr ret(new MidiAction(size, event)); - maid->manage(ret); - return ret; - } - size_t event_size() { return _size; } byte* event() { return _event.get(); } diff --git a/src/engine/machina/Node.hpp b/src/engine/machina/Node.hpp index 5926a50..1f0face 100644 --- a/src/engine/machina/Node.hpp +++ b/src/engine/machina/Node.hpp @@ -18,7 +18,6 @@ #ifndef MACHINA_NODE_HPP #define MACHINA_NODE_HPP -#include #include #include #include @@ -41,11 +40,12 @@ using Raul::BeatTime; * Initial nodes do not have enter actions (since they are entered at * an undefined point in time <= 0). */ -class Node : public Raul::Stateful, public boost::noncopyable { +class Node : public Raul::Stateful { public: typedef std::string ID; Node(BeatCount duration=0, bool initial=false); + Node(const Node& copy); void set_enter_action(SharedPtr action); void set_exit_action(SharedPtr action); diff --git a/src/engine/machina/Problem.hpp b/src/engine/machina/Problem.hpp new file mode 100644 index 0000000..d221e5c --- /dev/null +++ b/src/engine/machina/Problem.hpp @@ -0,0 +1,56 @@ +/* This file is part of Machina. + * Copyright (C) 2007 Dave Robillard + * + * 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 MACHINA_PROBLEM_HPP +#define MACHINA_PROBLEM_HPP + +#include + +namespace Machina { + +class Machine; + + +class Problem { +public: + Problem(const std::string& target_midi); + + float fitness(Machine& machine); + +private: + struct Evaluator : public Raul::MIDISink { + Evaluator(Problem& problem) : _problem(problem), _n_notes(0) { + for (uint8_t i=0; i < 128; ++i) + _note_frequency[i] = 0; + } + void write_event(Raul::BeatTime time, + size_t ev_size, + const uint8_t* ev) throw (std::logic_error); + void compute(); + Problem& _problem; + + float _note_frequency[128]; + size_t _n_notes; + }; + + Evaluator _target; +}; + + +} // namespace Machina + +#endif // MACHINA_PROBLEM_HPP diff --git a/src/gui/MachinaGUI.cpp b/src/gui/MachinaGUI.cpp index 8e0b68a..42cb4ce 100644 --- a/src/gui/MachinaGUI.cpp +++ b/src/gui/MachinaGUI.cpp @@ -36,9 +36,10 @@ using namespace Machina; MachinaGUI::MachinaGUI(SharedPtr engine) -: _refresh(false), - _engine(engine), - _maid(new Raul::Maid(32)) + : _refresh(false) + , _evolve(false) + , _engine(engine) + , _maid(new Raul::Maid(32)) { _canvas = boost::shared_ptr(new MachinaCanvas(this, 1600*2, 1200*2)); @@ -71,6 +72,7 @@ MachinaGUI::MachinaGUI(SharedPtr engine) xml->get_widget("zoom_normal_but", _zoom_normal_button); xml->get_widget("zoom_full_but", _zoom_full_button); xml->get_widget("arrange_but", _arrange_button); + 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); @@ -128,22 +130,24 @@ MachinaGUI::MachinaGUI(SharedPtr engine) _quantize_spinbutton->signal_changed().connect( sigc::mem_fun(this, &MachinaGUI::quantize_changed)); - _mutate_button->signal_clicked().connect( - sigc::mem_fun(this, &MachinaGUI::mutate)); - _compress_button->signal_clicked().connect( - sigc::mem_fun(this, &MachinaGUI::compress)); - _add_node_button->signal_clicked().connect( - sigc::mem_fun(this, &MachinaGUI::add_node)); - _remove_node_button->signal_clicked().connect( - sigc::mem_fun(this, &MachinaGUI::remove_node)); - _adjust_node_button->signal_clicked().connect( - sigc::mem_fun(this, &MachinaGUI::adjust_node)); - _add_edge_button->signal_clicked().connect( - sigc::mem_fun(this, &MachinaGUI::add_edge)); - _remove_edge_button->signal_clicked().connect( - sigc::mem_fun(this, &MachinaGUI::remove_edge)); - _adjust_edge_button->signal_clicked().connect( - sigc::mem_fun(this, &MachinaGUI::adjust_edge)); + _evolve_button->signal_clicked().connect( + sigc::mem_fun(this, &MachinaGUI::evolve)); + _mutate_button->signal_clicked().connect(sigc::bind( + sigc::mem_fun(this, &MachinaGUI::random_mutation), SharedPtr())); + _compress_button->signal_clicked().connect(sigc::hide_return(sigc::bind( + sigc::mem_fun(this, &MachinaGUI::mutate), SharedPtr(), 0))); + _add_node_button->signal_clicked().connect(sigc::bind( + sigc::mem_fun(this, &MachinaGUI::mutate), SharedPtr(), 1)); + _remove_node_button->signal_clicked().connect(sigc::bind( + sigc::mem_fun(this, &MachinaGUI::mutate), SharedPtr(), 2)); + _adjust_node_button->signal_clicked().connect(sigc::bind( + sigc::mem_fun(this, &MachinaGUI::mutate), SharedPtr(), 3)); + _add_edge_button->signal_clicked().connect(sigc::bind( + sigc::mem_fun(this, &MachinaGUI::mutate), SharedPtr(), 4)); + _remove_edge_button->signal_clicked().connect(sigc::bind( + sigc::mem_fun(this, &MachinaGUI::mutate), SharedPtr(), 5)); + _adjust_edge_button->signal_clicked().connect(sigc::bind( + sigc::mem_fun(this, &MachinaGUI::mutate), SharedPtr(), 6)); connect_widgets(); @@ -165,6 +169,9 @@ MachinaGUI::MachinaGUI(SharedPtr engine) // Idle callback to update node states Glib::signal_timeout().connect(sigc::mem_fun(this, &MachinaGUI::idle_callback), 100); + // Periodic mutation callback (FIXME: temporary kludge, thread this) + Glib::signal_timeout().connect(sigc::mem_fun(this, &MachinaGUI::evolve_callback), 1000); + _canvas->build(engine->machine()); } @@ -174,6 +181,20 @@ MachinaGUI::~MachinaGUI() } +bool +MachinaGUI::evolve_callback() +{ + if (_evolve) { + // Single greedy mutation + SharedPtr copy(new Machine(*_engine->machine().get())); + random_mutation(copy); + _canvas->build(copy); + } + + return true; +} + + bool MachinaGUI::idle_callback() { @@ -223,73 +244,64 @@ MachinaGUI::arrange() void -MachinaGUI::mutate() +MachinaGUI::evolve() { - switch (rand() % 7) { - case 0: compress(); break; - case 1: add_node(); break; - case 2: remove_node(); break; - case 3: adjust_node(); break; - case 4: add_edge(); break; - case 5: remove_edge(); break; - case 6: adjust_edge(); default: break; - } + if (_evolve_button->get_active()) + _evolve = true; + else + _evolve = false; } void -MachinaGUI::compress() +MachinaGUI::random_mutation(SharedPtr machine) { - Mutation::Compress::mutate(*_engine->machine().get()); - _canvas->build(_engine->machine()); -} - + if (!machine) + machine = _engine->machine(); -void -MachinaGUI::add_node() -{ - Mutation::AddNode::mutate(*_engine->machine().get()); - _canvas->build(_engine->machine()); + mutate(machine, rand() % 7); } void -MachinaGUI::remove_node() +MachinaGUI::mutate(SharedPtr machine, unsigned mutation) { - Mutation::RemoveNode::mutate(*_engine->machine().get()); - _canvas->build(_engine->machine()); -} + if (!machine) + machine = _engine->machine(); - -void -MachinaGUI::adjust_node() -{ - Mutation::AdjustNode::mutate(*_engine->machine().get()); - idle_callback(); // update nodes -} + using namespace Mutation; - -void -MachinaGUI::add_edge() -{ - Mutation::AddEdge::mutate(*_engine->machine().get()); - _canvas->build(_engine->machine()); -} - - -void -MachinaGUI::remove_edge() -{ - Mutation::RemoveEdge::mutate(*_engine->machine().get()); - _canvas->build(_engine->machine()); -} - - -void -MachinaGUI::adjust_edge() -{ - Mutation::AdjustEdge::mutate(*_engine->machine().get()); - _canvas->update_edges(); + switch (mutation) { + case 0: + Compress::mutate(*machine.get()); + _canvas->build(machine); + break; + case 1: + AddNode::mutate(*machine.get()); + _canvas->build(machine); + break; + case 2: + RemoveNode::mutate(*machine.get()); + _canvas->build(machine); + break; + case 3: + AdjustNode::mutate(*machine.get()); + idle_callback(); // update nodes + break; + case 4: + AddEdge::mutate(*machine.get()); + _canvas->build(machine); + break; + case 5: + RemoveEdge::mutate(*machine.get()); + _canvas->build(machine); + break; + case 6: + AdjustEdge::mutate(*machine.get()); + _canvas->update_edges(); + break; + default: throw; + } } diff --git a/src/gui/MachinaGUI.hpp b/src/gui/MachinaGUI.hpp index c82cd01..bbba67f 100644 --- a/src/gui/MachinaGUI.hpp +++ b/src/gui/MachinaGUI.hpp @@ -63,16 +63,12 @@ protected: void menu_help_about(); void menu_help_help(); void arrange(); - void mutate(); - void compress(); - void add_node(); - void remove_node(); - void adjust_node(); - void add_edge(); - void remove_edge(); - void adjust_edge(); + void evolve(); + void random_mutation(SharedPtr machine); + void mutate(SharedPtr machine, unsigned mutation); void zoom(double z); void update_toolbar(); + bool evolve_callback(); bool idle_callback(); bool scrolled_window_event(GdkEvent* ev); @@ -84,6 +80,7 @@ protected: void tempo_changed(); bool _refresh; + bool _evolve; string _save_uri; @@ -123,6 +120,7 @@ protected: Gtk::ToolButton* _zoom_normal_button; Gtk::ToolButton* _zoom_full_button; Gtk::ToolButton* _arrange_button; + Gtk::ToggleToolButton* _evolve_button; Gtk::ToolButton* _mutate_button; Gtk::ToolButton* _compress_button; Gtk::ToolButton* _add_node_button; diff --git a/src/gui/machina.glade b/src/gui/machina.glade index 2153908..e91553d 100644 --- a/src/gui/machina.glade +++ b/src/gui/machina.glade @@ -403,6 +403,27 @@ False GTK_ICON_SIZE_SMALL_TOOLBAR True + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + + + False + False + + + + + True + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK + Evolve machine (towards target MIDI) + gtk-execute + + + False + + True @@ -432,6 +453,7 @@ False + False -- cgit v1.2.1