diff options
Diffstat (limited to 'src/engine')
55 files changed, 6453 insertions, 0 deletions
diff --git a/src/engine/Action.hpp b/src/engine/Action.hpp new file mode 100644 index 0000000..2916099 --- /dev/null +++ b/src/engine/Action.hpp @@ -0,0 +1,60 @@ +/* + 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_ACTION_HPP +#define MACHINA_ACTION_HPP + +#include <string> +#include <iostream> + +#include "raul/Maid.hpp" +#include "raul/TimeSlice.hpp" + +#include "machina/types.hpp" + +#include "MIDISink.hpp" +#include "Stateful.hpp" + +namespace machina { + +/** An Action, executed on entering or exiting of a state. + */ +struct Action + : public Raul::Maid::Disposable + , public Stateful +{ + bool operator==(const Action& rhs) const { return false; } + + virtual void execute(MIDISink* sink, Raul::TimeStamp time) = 0; + + virtual void write_state(Sord::Model& model) {} +}; + +class PrintAction : public Action +{ +public: + explicit PrintAction(const std::string& msg) : _msg(msg) {} + + void execute(MIDISink* sink, Raul::TimeStamp time) + { std::cout << "t=" << time << ": " << _msg << std::endl; } + +private: + std::string _msg; +}; + +} // namespace machina + +#endif // MACHINA_ACTION_HPP diff --git a/src/engine/ActionFactory.cpp b/src/engine/ActionFactory.cpp new file mode 100644 index 0000000..f031149 --- /dev/null +++ b/src/engine/ActionFactory.cpp @@ -0,0 +1,55 @@ +/* + 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 "ActionFactory.hpp" +#include "MidiAction.hpp" + +namespace machina { + +SPtr<Action> +ActionFactory::copy(SPtr<Action> copy) +{ + SPtr<MidiAction> ma = dynamic_ptr_cast<MidiAction>(copy); + if (ma) { + return SPtr<Action>(new MidiAction(ma->event_size(), ma->event())); + } else { + return SPtr<Action>(); + } +} + +SPtr<Action> +ActionFactory::note_on(uint8_t note, uint8_t velocity) +{ + uint8_t buf[3]; + buf[0] = 0x90; + buf[1] = note; + buf[2] = velocity; + + return SPtr<Action>(new MidiAction(3, buf)); +} + +SPtr<Action> +ActionFactory::note_off(uint8_t note, uint8_t velocity) +{ + uint8_t buf[3]; + buf[0] = 0x80; + buf[1] = note; + buf[2] = velocity; + + return SPtr<Action>(new MidiAction(3, buf)); +} + +} // namespace Machine diff --git a/src/engine/ActionFactory.hpp b/src/engine/ActionFactory.hpp new file mode 100644 index 0000000..db7aa68 --- /dev/null +++ b/src/engine/ActionFactory.hpp @@ -0,0 +1,34 @@ +/* + 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_ACTIONFACTORY_HPP +#define MACHINA_ACTIONFACTORY_HPP + +#include "machina/types.hpp" + +namespace machina { + +struct Action; + +namespace ActionFactory { +SPtr<Action> copy(SPtr<Action> copy); +SPtr<Action> note_on(uint8_t note, uint8_t velocity=64); +SPtr<Action> note_off(uint8_t note, uint8_t velocity=64); +} + +} // namespace machina + +#endif // MACHINA_ACTIONFACTORY_HPP diff --git a/src/engine/Controller.cpp b/src/engine/Controller.cpp new file mode 100644 index 0000000..8f9e1a2 --- /dev/null +++ b/src/engine/Controller.cpp @@ -0,0 +1,228 @@ +/* + 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 "machina/Controller.hpp" +#include "machina/Engine.hpp" +#include "machina/Machine.hpp" +#include "machina/Updates.hpp" + +#include "Edge.hpp" +#include "MidiAction.hpp" + +namespace machina { + +Controller::Controller(SPtr<Engine> engine, + Model& model) + : _engine(engine) + , _model(model) + , _updates(new Raul::RingBuffer(4096)) +{ + _engine->driver()->set_update_sink(_updates); +} + +uint64_t +Controller::create(const Properties& properties) +{ + std::map<URIInt, Atom>::const_iterator d = properties.find( + URIs::instance().machina_duration); + + double duration = 0.0; + if (d != properties.end()) { + duration = d->second.get<float>(); + } else { + std::cerr << "warning: new object has no duration" << std::endl; + } + + TimeDuration dur(_engine->machine()->time().unit(), duration); + SPtr<Node> node(new Node(dur)); + _objects.insert(node); + _model.new_object(node->id(), properties); + _engine->machine()->add_node(node); + return node->id(); +} + +void +Controller::announce(SPtr<Machine> machine) +{ + if (!machine) { + return; + } + + Forge& forge = _engine->forge(); + + // Announce nodes + for (const auto& n : machine->nodes()) { + Properties properties { + { URIs::instance().rdf_type, + forge.make_urid(URIs::instance().machina_Node) }, + { URIs::instance().machina_duration, + forge.make(float(n->duration().to_double())) } }; + if (n->is_initial()) { + properties.insert({ URIs::instance().machina_initial, + forge.make(true) }); + } + + SPtr<MidiAction> midi_action = dynamic_ptr_cast<MidiAction>( + n->enter_action()); + if (midi_action) { + Properties action_properties { + { URIs::instance().machina_note_number, + forge.make((int32_t)midi_action->event()[1]) } }; + + _model.new_object(midi_action->id(), action_properties); + properties.insert({ URIs::instance().machina_enter_action, + forge.make(int32_t(n->enter_action()->id())) }); + } + + _objects.insert(n); + _model.new_object(n->id(), properties); + } + + // Announce edges + for (const auto& n : machine->nodes()) { + for (const auto& e : n->edges()) { + const Properties properties { + { URIs::instance().rdf_type, + forge.make_urid(URIs::instance().machina_Edge) }, + { URIs::instance().machina_probability, + forge.make(e->probability()) }, + { URIs::instance().machina_tail_id, + forge.make((int32_t)n->id()) }, + { URIs::instance().machina_head_id, + forge.make((int32_t)e->head()->id()) } }; + + _objects.insert(e); + _model.new_object(e->id(), properties); + } + } +} + +SPtr<Stateful> +Controller::find(uint64_t id) +{ + SPtr<StatefulKey> key(new StatefulKey(id)); + Objects::iterator i = _objects.find(key); + if (i != _objects.end()) { + return *i; + } + return SPtr<Stateful>(); +} + +void +Controller::learn(SPtr<Raul::Maid> maid, uint64_t node_id) +{ + SPtr<Node> node = dynamic_ptr_cast<Node>(find(node_id)); + if (node) { + _engine->machine()->learn(maid, node); + } else { + std::cerr << "Failed to find node " << node_id << " for learn" + << std::endl; + } +} + +void +Controller::set_property(uint64_t object_id, + URIInt key, + const Atom& value) +{ + SPtr<Stateful> object = find(object_id); + if (object) { + object->set(key, value); + _model.set(object_id, key, value); + } +} + +uint64_t +Controller::connect(uint64_t tail_id, uint64_t head_id) +{ + SPtr<Node> tail = dynamic_ptr_cast<Node>(find(tail_id)); + SPtr<Node> head = dynamic_ptr_cast<Node>(find(head_id)); + + if (!tail) { + std::cerr << "error: tail node " << tail_id << " not found" << std::endl; + return 0; + } else if (!head) { + std::cerr << "error: head node " << head_id << " not found" << std::endl; + return 0; + } + + SPtr<Edge> edge(new Edge(tail, head)); + tail->add_edge(edge); + _objects.insert(edge); + + Forge& forge = _engine->forge(); + + const Properties properties = { + { URIs::instance().rdf_type, + forge.make_urid(URIs::instance().machina_Edge) }, + { URIs::instance().machina_probability, forge.make(1.0f) }, + { URIs::instance().machina_tail_id, + forge.make((int32_t)tail->id()) }, + { URIs::instance().machina_head_id, + forge.make((int32_t)head->id()) } }; + + _model.new_object(edge->id(), properties); + + return edge->id(); +} + +void +Controller::disconnect(uint64_t tail_id, uint64_t head_id) +{ + SPtr<Node> tail = dynamic_ptr_cast<Node>(find(tail_id)); + SPtr<Node> head = dynamic_ptr_cast<Node>(find(head_id)); + + SPtr<Edge> edge = tail->remove_edge_to(head); + if (edge) { + _model.erase_object(edge->id()); + } else { + std::cerr << "Edge not found" << std::endl; + } +} + +void +Controller::erase(uint64_t id) +{ + SPtr<StatefulKey> key(new StatefulKey(id)); + Objects::iterator i = _objects.find(key); + if (i == _objects.end()) { + return; + } + + SPtr<Node> node = dynamic_ptr_cast<Node>(*i); + if (node) { + _engine->machine()->remove_node(node); + } + + _model.erase_object((*i)->id()); + _objects.erase(i); +} + +void +Controller::process_updates() +{ + const uint32_t read_space = _updates->read_space(); + + uint64_t subject; + URIInt key; + Atom value; + for (uint32_t i = 0; i < read_space; ) { + i += read_set(_updates, &subject, &key, &value); + _model.set(subject, key, value); + } +} + +} diff --git a/src/engine/Edge.cpp b/src/engine/Edge.cpp new file mode 100644 index 0000000..7c206f9 --- /dev/null +++ b/src/engine/Edge.cpp @@ -0,0 +1,64 @@ +/* + 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/Atom.hpp" +#include "machina/URIs.hpp" +#include "sord/sordmm.hpp" + +#include "Edge.hpp" +#include "Node.hpp" + +namespace machina { + +void +Edge::set(URIInt key, const Atom& value) +{ + if (key == URIs::instance().machina_probability) { + _probability = value.get<float>(); + } +} + +void +Edge::write_state(Sord::Model& model) +{ + using namespace Raul; + + const Sord::Node& rdf_id = this->rdf_id(model.world()); + + SPtr<Node> tail = _tail.lock(); + SPtr<Node> head = _head; + + if (!tail || !head) + return; + + assert(tail->rdf_id(model.world()).is_valid() + && head->rdf_id(model.world()).is_valid()); + + model.add_statement(rdf_id, + Sord::URI(model.world(), MACHINA_NS_tail), + tail->rdf_id(model.world())); + + model.add_statement(rdf_id, + Sord::URI(model.world(), MACHINA_NS_head), + head->rdf_id(model.world())); + + model.add_statement( + rdf_id, + Sord::URI(model.world(), MACHINA_NS_probability), + Sord::Literal::decimal(model.world(), _probability, 7)); +} + +} // namespace machina diff --git a/src/engine/Edge.hpp b/src/engine/Edge.hpp new file mode 100644 index 0000000..a04dd73 --- /dev/null +++ b/src/engine/Edge.hpp @@ -0,0 +1,60 @@ +/* + 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_EDGE_HPP +#define MACHINA_EDGE_HPP + +#include <list> + +#include "machina/types.hpp" + +#include "Action.hpp" +#include "Stateful.hpp" + +namespace machina { + +class Node; + +class Edge : public Stateful +{ +public: + Edge(WPtr<Node> tail, SPtr<Node> head, float probability=1.0f) + : _tail(tail) + , _head(head) + , _probability(probability) + {} + + void set(URIInt key, const Atom& value); + void write_state(Sord::Model& model); + + WPtr<Node> tail() { return _tail; } + SPtr<Node> head() { return _head; } + + void set_tail(WPtr<Node> tail) { _tail = tail; } + void set_head(SPtr<Node> head) { _head = head; } + + inline float probability() const { return _probability; } + inline void set_probability(float p) { _probability = p; } + +private: + WPtr<Node> _tail; + SPtr<Node> _head; + float _probability; +}; + +} // namespace machina + +#endif // MACHINA_EDGE_HPP diff --git a/src/engine/Engine.cpp b/src/engine/Engine.cpp new file mode 100644 index 0000000..1aed7b6 --- /dev/null +++ b/src/engine/Engine.cpp @@ -0,0 +1,126 @@ +/* + 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 "machina_config.h" +#include "machina/Engine.hpp" +#include "machina/Loader.hpp" +#include "machina/Machine.hpp" +#include "SMFDriver.hpp" +#ifdef HAVE_JACK +#include "JackDriver.hpp" +#endif + +namespace machina { + +Engine::Engine(Forge& forge, + SPtr<Driver> driver, + Sord::World& rdf_world) + : _driver(driver) + , _rdf_world(rdf_world) + , _loader(_forge, _rdf_world) + , _forge(forge) +{} + +SPtr<Driver> +Engine::new_driver(Forge& forge, + const std::string& name, + SPtr<Machine> machine) +{ + if (name == "jack") { +#ifdef HAVE_JACK + JackDriver* driver = new JackDriver(forge, machine); + driver->attach("machina"); + return SPtr<Driver>(driver); +#else + return SPtr<Driver>(); +#endif + } + if (name == "smf") { + return SPtr<Driver>(new SMFDriver(forge, machine->time().unit())); + } + + std::cerr << "Error: Unknown driver type `" << name << "'" << std::endl; + return SPtr<Driver>(); +} + +/** Load the machine at `uri`, and run it (replacing current machine). + * Safe to call while engine is processing. + */ +SPtr<Machine> +Engine::load_machine(const std::string& uri) +{ + SPtr<Machine> machine = _loader.load(uri); + SPtr<Machine> old_machine = _driver->machine(); // Keep reference + if (machine) { + _driver->set_machine(machine); // Switch driver to new machine and wait + } + + // Drop (possibly last) reference to old_machine in this thread + return machine; +} + +/** Build a machine from the MIDI at `uri`, and run it (replacing current machine). + * Safe to call while engine is processing. + */ +SPtr<Machine> +Engine::load_machine_midi(const std::string& uri, + double q, + Raul::TimeDuration dur) +{ + SPtr<Machine> machine = _loader.load_midi(uri, q, dur); + SPtr<Machine> old_machine = _driver->machine(); // Keep reference + if (machine) { + _driver->set_machine(machine); // Switch driver to new machine and wait + } + + // Drop (possibly last) reference to old_machine in this thread + return machine; +} + +void +Engine::export_midi(const std::string& filename, Raul::TimeDuration dur) +{ + SPtr<Machine> machine = _driver->machine(); + SPtr<machina::SMFDriver> file_driver( + new machina::SMFDriver(_forge, dur.unit())); + + const bool activated = _driver->is_activated(); + if (activated) { + _driver->deactivate(); // FIXME: disable instead + } + file_driver->writer()->start(filename, TimeStamp(dur.unit(), 0.0)); + file_driver->run(machine, dur); + machine->reset(NULL, machine->time()); + file_driver->writer()->finish(); + + if (activated) { + _driver->activate(); + } +} + +void +Engine::set_bpm(double bpm) +{ + _driver->set_bpm(bpm); +} + +void +Engine::set_quantization(double q) +{ + _driver->set_quantization(q); +} + +} // namespace machina diff --git a/src/engine/Evolver.cpp b/src/engine/Evolver.cpp new file mode 100644 index 0000000..973c9c8 --- /dev/null +++ b/src/engine/Evolver.cpp @@ -0,0 +1,139 @@ +/* + 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 <iostream> + +#include "eugene/HybridMutation.hpp" +#include "eugene/Mutation.hpp" +#include "eugene/TournamentSelection.hpp" + +#include "machina/Evolver.hpp" +#include "machina/Mutation.hpp" + +#include "Problem.hpp" + +using namespace std; +using namespace eugene; + +namespace machina { + +Evolver::Evolver(TimeUnit unit, + const string& target_midi, + SPtr<Machine> seed) + : _rng(0) + , _problem(new Problem(unit, target_midi, seed)) + , _seed_fitness(-FLT_MAX) + , _exit_flag(false) +{ + SPtr<eugene::HybridMutation<Machine> > m(new HybridMutation<Machine>()); + + m->append_mutation(1 / 6.0f, std::shared_ptr< eugene::Mutation<Machine> >( + new Mutation::Compress())); + m->append_mutation(1 / 6.0f, std::shared_ptr< eugene::Mutation<Machine> >( + new Mutation::AddNode())); + //m->append_mutation(1/6.0f, std::shared_ptr< eugene::Mutation<Machine> >( + // new Mutation::RemoveNode())); + //m->append_mutation(1/6.0f, std::shared_ptr< eugene::Mutation<Machine> >( + // new Mutation::AdjustNode())); + m->append_mutation(1 / 6.0f, std::shared_ptr< eugene::Mutation<Machine> >( + new Mutation::SwapNodes())); + m->append_mutation(1 / 6.0f, std::shared_ptr< eugene::Mutation<Machine> >( + new Mutation::AddEdge())); + m->append_mutation(1 / 6.0f, std::shared_ptr< eugene::Mutation<Machine> >( + new Mutation::RemoveEdge())); + m->append_mutation(1 / 6.0f, std::shared_ptr< eugene::Mutation<Machine> >( + new Mutation::AdjustEdge())); + + std::shared_ptr< Selection<Machine> > s( + new TournamentSelection<Machine>(*_problem.get(), 3, 0.8)); + std::shared_ptr< Crossover<Machine> > crossover; + size_t gene_length = 20; // FIXME + Problem::Population pop; + _ga = SPtr<MachinaGA>( + new MachinaGA(_rng, + _problem, + s, + crossover, + m, + pop, + gene_length, + 20, + 2, + 1.0, + 0.0)); +} + +void +Evolver::start() +{ + if (!_thread) { + _thread = std::unique_ptr<std::thread>( + new std::thread(&Evolver::run, this)); + } +} + +void +Evolver::join() +{ + if (_thread && _thread->joinable()) { + _exit_flag = true; + _thread->join(); + } +} + +void +Evolver::seed(SPtr<Machine> parent) +{ + /*_best = SPtr<Machine>(new Machine(*parent.get())); + _best_fitness = _problem->fitness(*_best.get());*/ + _problem->seed(parent); + _seed_fitness = _problem->evaluate(*parent.get()); +} + +void +Evolver::run() +{ + float old_best = _ga->best_fitness(); + + //cout << "ORIGINAL BEST: " << _ga->best_fitness() << endl; + + _improvement = true; + + while (!_exit_flag) { + //cout << "{" << endl; + _problem->clear_fitness_cache(); + _ga->iteration(); + + float new_best = _ga->best_fitness(); + + /*cout << _problem->fitness_less(old_best, *_ga->best().get()) << endl; + cout << "best: " << _ga->best().get() << endl; + cout << "best fitness: " << _problem->fitness(*_ga->best().get()) << endl; + cout << "old best: " << old_best << endl; + cout << "new best: " << new_best << endl;*/ + cout << "generation best: " << new_best << endl; + + if (_problem->fitness_less_than(old_best, new_best)) { + _improvement = true; + old_best = new_best; + cout << "*** NEW BEST: " << new_best << endl; + } + + //cout << "}" << endl; + } +} + +} // namespace machina diff --git a/src/engine/JackDriver.cpp b/src/engine/JackDriver.cpp new file mode 100644 index 0000000..e7f2e97 --- /dev/null +++ b/src/engine/JackDriver.cpp @@ -0,0 +1,479 @@ +/* + 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 <iostream> + +#include "machina/Context.hpp" +#include "machina/URIs.hpp" +#include "machina/Updates.hpp" +#include "machina_config.h" + +#include "Edge.hpp" +#include "JackDriver.hpp" +#include "LearnRequest.hpp" +#include "MachineBuilder.hpp" +#include "MidiAction.hpp" + +using namespace machina; +using namespace std; + +namespace machina { + +JackDriver::JackDriver(Forge& forge, SPtr<Machine> machine) + : Driver(forge, machine) + , _client(NULL) + , _machine_changed(0) + , _input_port(NULL) + , _output_port(NULL) + , _context(forge, 48000, MACHINA_PPQN, 120.0) + , _frames_unit(TimeUnit::FRAMES, 48000) + , _beats_unit(TimeUnit::BEATS, 19200) + , _stop(0) + , _stop_flag(false) + , _record_dur(_frames_unit) // = 0 + , _is_activated(false) +{ + _context.set_sink(this); +} + +JackDriver::~JackDriver() +{ + detach(); +} + +void +JackDriver::attach(const std::string& client_name) +{ + // Already connected + if (_client) { + return; + } + + jack_set_error_function(jack_error_cb); + + _client = jack_client_open(client_name.c_str(), JackNullOption, NULL, NULL); + + if (_client == NULL) { + _is_activated = false; + } else { + jack_set_error_function(jack_error_cb); + jack_on_shutdown(_client, jack_shutdown_cb, this); + jack_set_process_callback(_client, jack_process_cb, this); + } + + if (jack_client()) { + + _context.time().set_tick_rate(sample_rate()); + + _input_port = jack_port_register(jack_client(), + "in", + JACK_DEFAULT_MIDI_TYPE, + JackPortIsInput, + 0); + + if (!_input_port) { + std::cerr << "WARNING: Failed to create MIDI input port." + << std::endl; + } + + _output_port = jack_port_register(jack_client(), + "out", + JACK_DEFAULT_MIDI_TYPE, + JackPortIsOutput, + 0); + + if (!_output_port) { + std::cerr << "WARNING: Failed to create MIDI output port." + << std::endl; + } + + if (!_machine) { + _machine = SPtr<Machine>( + new Machine(TimeUnit::frames( + jack_get_sample_rate(jack_client())))); + } + } +} + +void +JackDriver::detach() +{ + if (_is_activated) { + _is_activated = false; + _stop.timed_wait(std::chrono::seconds(1)); + } + + if (_input_port) { + jack_port_unregister(jack_client(), _input_port); + _input_port = NULL; + } + + if (_output_port) { + jack_port_unregister(jack_client(), _output_port); + _output_port = NULL; + } + + if (_client) { + deactivate(); + jack_client_close(_client); + _client = NULL; + _is_activated = false; + } +} + +void +JackDriver::activate() +{ + if (!jack_activate(_client)) { + _is_activated = true; + } else { + _is_activated = false; + } +} + +void +JackDriver::deactivate() +{ + if (_client) { + jack_deactivate(_client); + } + + _is_activated = false; +} + +void +JackDriver::set_machine(SPtr<Machine> machine) +{ + if (machine == _machine) { + return; + } + + SPtr<Machine> last_machine = _last_machine; // Keep a reference + _machine_changed.reset(0); + assert(!last_machine.unique()); + _machine = machine; + if (is_activated()) { + _machine_changed.wait(); + } + assert(_machine == machine); + last_machine.reset(); +} + +void +JackDriver::read_input_recording(SPtr<Machine> machine, + const Raul::TimeSlice& time) +{ + const jack_nframes_t nframes = time.length_ticks().ticks(); + void* buf = jack_port_get_buffer(_input_port, nframes); + const jack_nframes_t n_events = jack_midi_get_event_count(buf); + + for (jack_nframes_t i = 0; i < n_events; ++i) { + jack_midi_event_t ev; + jack_midi_event_get(&ev, buf, i); + + const TimeStamp rel_time_frames = TimeStamp(_frames_unit, ev.time); + const TimeStamp time_frames = _record_dur + rel_time_frames; + _recorder->write(time.ticks_to_beats(time_frames), ev.size, ev.buffer); + } + + if (n_events > 0) { + _recorder->whip(); + } + + _record_dur += time.length_ticks(); +} + +void +JackDriver::read_input_playing(SPtr<Machine> machine, + const Raul::TimeSlice& time) +{ + const jack_nframes_t nframes = time.length_ticks().ticks(); + void* buf = jack_port_get_buffer(_input_port, nframes); + const jack_nframes_t n_events = jack_midi_get_event_count(buf); + + for (jack_nframes_t i = 0; i < n_events; ++i) { + jack_midi_event_t ev; + jack_midi_event_get(&ev, buf, i); + + if (ev.buffer[0] == 0x90) { + const SPtr<LearnRequest> learn = machine->pending_learn(); + if (learn) { + learn->enter_action()->set_event(ev.size, ev.buffer); + learn->start(_quantization, + TimeStamp(TimeUnit::frames(sample_rate()), + jack_last_frame_time(_client) + + ev.time, 0)); + } + + } else if (ev.buffer[0] == 0x80) { + const SPtr<LearnRequest> learn = machine->pending_learn(); + if (learn && learn->started()) { + learn->exit_action()->set_event(ev.size, ev.buffer); + learn->finish(TimeStamp(TimeUnit::frames(sample_rate()), + jack_last_frame_time(_client) + ev.time, + 0)); + + const uint64_t id = Stateful::next_id(); + write_set(_updates, id, + URIs::instance().rdf_type, + _forge.make_urid(URIs::instance().machina_MidiAction)); + write_set(_updates, learn->node()->id(), + URIs::instance().machina_enter_action, + _forge.make((int32_t)id)); + write_set(_updates, id, + URIs::instance().machina_note_number, + _forge.make((int32_t)ev.buffer[1])); + + machine->clear_pending_learn(); + } + } + } +} + +void +JackDriver::write_event(Raul::TimeStamp time, + size_t size, + const byte* event) +{ + if (!_output_port) { + return; + } + + const Raul::TimeSlice& slice = _context.time(); + + if (slice.beats_to_ticks(time) + slice.offset_ticks() < + slice.start_ticks()) { + std::cerr << "ERROR: Missed event by " + << (slice.start_ticks() + - slice.beats_to_ticks(time) + + slice.offset_ticks()) + << " ticks" + << "\n\tbpm: " << slice.bpm() + << "\n\tev time: " << slice.beats_to_ticks(time) + << "\n\tcycle_start: " << slice.start_ticks() + << "\n\tcycle_end: " << (slice.start_ticks() + + slice.length_ticks()) + << "\n\tcycle_length: " << slice.length_ticks() + << std::endl << std::endl; + return; + } + + const TimeDuration nframes = slice.length_ticks(); + const TimeStamp offset = slice.beats_to_ticks(time) + + slice.offset_ticks() - slice.start_ticks(); + + if (!(offset < slice.offset_ticks() + nframes)) { + std::cerr << "ERROR: Event offset " << offset << " outside cycle " + << "\n\tbpm: " << slice.bpm() + << "\n\tev time: " << slice.beats_to_ticks(time) + << "\n\tcycle_start: " << slice.start_ticks() + << "\n\tcycle_end: " << slice.start_ticks() + + slice.length_ticks() + << "\n\tcycle_length: " << slice.length_ticks() << std::endl; + } else { +#ifdef JACK_MIDI_NEEDS_NFRAMES + jack_midi_event_write( + jack_port_get_buffer(_output_port, nframes), offset, + event, size, nframes); +#else + jack_midi_event_write( + jack_port_get_buffer(_output_port, nframes.ticks()), offset.ticks(), + event, size); +#endif + } +} + +void +JackDriver::on_process(jack_nframes_t nframes) +{ + if (!_is_activated) { + _stop.post(); + return; + } + + _context.time().set_bpm(_bpm); + + assert(_output_port); + jack_midi_clear_buffer(jack_port_get_buffer(_output_port, nframes)); + + TimeStamp length_ticks(TimeStamp(_context.time().ticks_unit(), nframes)); + + _context.time().set_length(length_ticks); + _context.time().set_offset(TimeStamp(_context.time().ticks_unit(), 0, 0)); + + /* Take a reference to machine here and use only it during the process + * cycle so _machine can be switched with set_machine during a cycle. */ + SPtr<Machine> machine = _machine; + + // Machine was switched since last cycle, finalize old machine. + if (machine != _last_machine) { + if (_last_machine) { + assert(!_last_machine.unique()); // Realtime, can't delete + _last_machine->reset(_context.sink(), _last_machine->time()); // Exit all active states + _last_machine.reset(); // Cut our reference + } + _machine_changed.post(); // Signal we're done with it + } + + if (!machine) { + _last_machine = machine; + goto end; + } + + if (_stop_flag) { + machine->reset(_context.sink(), _context.time().start_beats()); + } + + switch (_play_state) { + case PlayState::STOPPED: + break; + case PlayState::PLAYING: + read_input_playing(machine, _context.time()); + break; + case PlayState::RECORDING: + case PlayState::STEP_RECORDING: + read_input_recording(machine, _context.time()); + break; + } + + if (machine->is_empty()) { + goto end; + } + + while (true) { + const uint32_t run_dur_frames = machine->run(_context, _updates); + + if (run_dur_frames == 0) { + // Machine didn't run at all (machine has no initial states) + machine->reset(_context.sink(), machine->time()); // Try again next cycle + _context.time().set_slice(TimeStamp(_frames_unit, 0, 0), + TimeStamp(_frames_unit, 0, 0)); + break; + + } else if (machine->is_finished()) { + // Machine ran for portion of cycle and is finished + machine->reset(_context.sink(), machine->time()); + + _context.time().set_slice(TimeStamp(_frames_unit, 0, 0), + TimeStamp(_frames_unit, nframes + - run_dur_frames, 0)); + _context.time().set_offset(TimeStamp(_frames_unit, run_dur_frames, + 0)); + + } else { + // Machine ran for entire cycle + _context.time().set_slice( + _context.time().start_ticks() + _context.time().length_ticks(), + TimeStamp(_frames_unit, 0, 0)); + break; + } + } + +end: + /* Remember the last machine run, in case a switch happens and + * we need to finalize it next cycle. */ + _last_machine = machine; + + if (_stop_flag) { + _context.time().set_slice(TimeStamp(_frames_unit, 0, 0), + TimeStamp(_frames_unit, 0, 0)); + _stop_flag = false; + _stop.post(); + } +} + +void +JackDriver::set_play_state(PlayState state) +{ + switch (state) { + case PlayState::STOPPED: + switch (_play_state) { + case PlayState::STOPPED: + break; + case PlayState::RECORDING: + case PlayState::STEP_RECORDING: + finish_record(); + // fallthru + case PlayState::PLAYING: + _stop_flag = true; + _stop.wait(); + } + break; + case PlayState::RECORDING: + start_record(false); + break; + case PlayState::STEP_RECORDING: + start_record(true); + break; + case PlayState::PLAYING: + if (_play_state == PlayState::RECORDING || + _play_state == PlayState::STEP_RECORDING) { + finish_record(); + } + } + Driver::set_play_state(state); +} + +void +JackDriver::start_record(bool step) +{ + const double q = (step || _quantize_record) ? _quantization : 0.0; + switch (_play_state) { + case PlayState::STOPPED: + case PlayState::PLAYING: + _recorder = SPtr<Recorder>( + new Recorder(_forge, 1024, _beats_unit, q, step)); + _record_dur = 0; + break; + case PlayState::RECORDING: + case PlayState::STEP_RECORDING: + _recorder->builder()->set_step(true); + break; + } + _play_state = step ? PlayState::STEP_RECORDING : PlayState::RECORDING; +} + +void +JackDriver::finish_record() +{ + _play_state = PlayState::PLAYING; + SPtr<Machine> machine = _recorder->finish(); + _recorder.reset(); + _machine->merge(*machine.get()); +} + +int +JackDriver::jack_process_cb(jack_nframes_t nframes, void* jack_driver) +{ + JackDriver* me = reinterpret_cast<JackDriver*>(jack_driver); + me->on_process(nframes); + return 0; +} + +void +JackDriver::jack_shutdown_cb(void* jack_driver) +{ + JackDriver* me = reinterpret_cast<JackDriver*>(jack_driver); + me->_client = NULL; +} + +void +JackDriver::jack_error_cb(const char* msg) +{ + cerr << "[JACK] Error: " << msg << endl; +} + +} // namespace machina diff --git a/src/engine/JackDriver.hpp b/src/engine/JackDriver.hpp new file mode 100644 index 0000000..4ebfaa1 --- /dev/null +++ b/src/engine/JackDriver.hpp @@ -0,0 +1,119 @@ +/* + 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_JACKDRIVER_HPP +#define MACHINA_JACKDRIVER_HPP + +#include <jack/jack.h> +#include <jack/midiport.h> + +#include "raul/Semaphore.hpp" + +#include "machina/Context.hpp" +#include "machina/Driver.hpp" +#include "machina/Machine.hpp" +#include "machina/types.hpp" + +#include "Recorder.hpp" + +namespace machina { + +class MidiAction; +class Node; + +/** Realtime JACK Driver. + * + * "Ticks" are individual frames when running under this driver, and all code + * in the processing context must be realtime safe (non-blocking). + */ +class JackDriver : public machina::Driver +{ +public: + JackDriver(Forge& forge, + SPtr<Machine> machine = SPtr<Machine>()); + ~JackDriver(); + + void attach(const std::string& client_name); + void detach(); + + void activate(); + void deactivate(); + + void set_machine(SPtr<Machine> machine); + + void write_event(Raul::TimeStamp time, + size_t size, + const unsigned char* event); + + void set_play_state(PlayState state); + + void start_transport() { jack_transport_start(_client); } + void stop_transport() { jack_transport_stop(_client); } + + void rewind_transport() { + jack_position_t zero; + zero.frame = 0; + zero.valid = (jack_position_bits_t)0; + jack_transport_reposition(_client, &zero); + } + + bool is_activated() const { return _is_activated; } + bool is_attached() const { return _client != NULL; } + bool is_realtime() const { return _client && jack_is_realtime(_client); } + + jack_nframes_t sample_rate() const { return jack_get_sample_rate(_client); } + jack_client_t* jack_client() const { return _client; } + +private: + void read_input_recording(SPtr<Machine> machine, + const Raul::TimeSlice& time); + + void read_input_playing(SPtr<Machine> machine, + const Raul::TimeSlice& time); + + static void jack_error_cb(const char* msg); + static int jack_process_cb(jack_nframes_t nframes, void* me); + static void jack_shutdown_cb(void* me); + + void start_record(bool step); + void finish_record(); + + void on_process(jack_nframes_t nframes); + + jack_client_t* _client; + + Raul::Semaphore _machine_changed; + SPtr<Machine> _last_machine; + + jack_port_t* _input_port; + jack_port_t* _output_port; + + Context _context; + + Raul::TimeUnit _frames_unit; + Raul::TimeUnit _beats_unit; + + Raul::Semaphore _stop; + bool _stop_flag; + + Raul::TimeDuration _record_dur; + SPtr<Recorder> _recorder; + bool _is_activated; +}; + +} // namespace machina + +#endif // MACHINA_JACKDRIVER_HPP diff --git a/src/engine/LearnRequest.cpp b/src/engine/LearnRequest.cpp new file mode 100644 index 0000000..5756dd1 --- /dev/null +++ b/src/engine/LearnRequest.cpp @@ -0,0 +1,56 @@ +/* + 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 "LearnRequest.hpp" +#include "quantize.hpp" + +namespace machina { + +LearnRequest::LearnRequest(SPtr<Raul::Maid> maid, SPtr<Node> node) + : _started(false) + , _start_time(TimeUnit(TimeUnit::BEATS, 19200), 0, 0) // irrelevant + , _quantization(0) // irrelevant + , _node(node) + , _enter_action(new MidiAction(4, NULL)) + , _exit_action(new MidiAction(4, NULL)) +{ +} + +SPtr<LearnRequest> +LearnRequest::create(SPtr<Raul::Maid> maid, SPtr<Node> node) +{ + return SPtr<LearnRequest>(new LearnRequest(maid, node)); +} + +void +LearnRequest::start(double q, Raul::TimeStamp time) +{ + _started = true; + _start_time = time; + _quantization = q; +} + +/** Add the learned actions to the node */ +void +LearnRequest::finish(TimeStamp time) +{ + _node->set_enter_action(_enter_action); + _node->set_exit_action(_exit_action); + + _node->set_duration(time - _start_time); +} + +} diff --git a/src/engine/LearnRequest.hpp b/src/engine/LearnRequest.hpp new file mode 100644 index 0000000..9cdfc3c --- /dev/null +++ b/src/engine/LearnRequest.hpp @@ -0,0 +1,61 @@ +/* + 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_LEARNREQUEST_HPP +#define MACHINA_LEARNREQUEST_HPP + +#include "raul/Maid.hpp" + +#include "machina/types.hpp" + +#include "MidiAction.hpp" +#include "Node.hpp" + +namespace machina { + +class Node; +class MidiAction; + +/** A request to MIDI learn a certain node. + */ +class LearnRequest : public Raul::Maid::Disposable +{ +public: + static SPtr<LearnRequest> create(SPtr<Raul::Maid> maid, SPtr<Node> node); + + void start(double q, Raul::TimeStamp time); + void finish(TimeStamp time); + + inline bool started() const { return _started; } + + const SPtr<Node>& node() { return _node; } + const SPtr<MidiAction>& enter_action() { return _enter_action; } + const SPtr<MidiAction>& exit_action() { return _exit_action; } + +private: + LearnRequest(SPtr<Raul::Maid> maid, SPtr<Node> node); + + bool _started; + TimeStamp _start_time; + double _quantization; + SPtr<Node> _node; + SPtr<MidiAction> _enter_action; + SPtr<MidiAction> _exit_action; +}; + +} // namespace machina + +#endif // MACHINA_LEARNREQUEST_HPP diff --git a/src/engine/Loader.cpp b/src/engine/Loader.cpp new file mode 100644 index 0000000..1c662d2 --- /dev/null +++ b/src/engine/Loader.cpp @@ -0,0 +1,193 @@ +/* + 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 <cmath> +#include <iostream> +#include <map> + +#include "lv2/lv2plug.in/ns/ext/midi/midi.h" + +#include "machina/Loader.hpp" +#include "machina/URIs.hpp" +#include "machina/Machine.hpp" + +#include "Edge.hpp" +#include "MidiAction.hpp" +#include "Node.hpp" +#include "SMFDriver.hpp" +#include "machina_config.h" + +using namespace Raul; +using namespace std; + +namespace machina { + +Loader::Loader(Forge& forge, Sord::World& rdf_world) + : _forge(forge) + , _rdf_world(rdf_world) +{} + +static SPtr<Action> +load_action(Sord::Model& model, Sord::Node node) +{ + if (!node.is_valid()) { + return SPtr<Action>(); + } + + Sord::URI rdf_type(model.world(), MACHINA_URI_RDF "type"); + Sord::URI midi_NoteOn(model.world(), LV2_MIDI__NoteOn); + Sord::URI midi_NoteOff(model.world(), LV2_MIDI__NoteOff); + Sord::URI midi_noteNumber(model.world(), LV2_MIDI__noteNumber); + Sord::URI midi_velocity(model.world(), LV2_MIDI__velocity); + + Sord::Node type = model.get(node, rdf_type, Sord::Node()); + uint8_t status = 0; + if (type == midi_NoteOn) { + status = LV2_MIDI_MSG_NOTE_ON; + } else if (type == midi_NoteOff) { + status = LV2_MIDI_MSG_NOTE_OFF; + } else { + return SPtr<Action>(); + } + + Sord::Node num_node = model.get(node, midi_noteNumber, Sord::Node()); + Sord::Node vel_node = model.get(node, midi_velocity, Sord::Node()); + + const uint8_t num = num_node.is_int() ? num_node.to_int() : 64; + const uint8_t vel = vel_node.is_int() ? vel_node.to_int() : 64; + const uint8_t event[3] = { status, num, vel }; + + return SPtr<Action>(new MidiAction(sizeof(event), event)); +} + +/** Load (create) all objects from RDF into the engine. + * + * @param uri URI of machine (resolvable URI to an RDF document). + * @return Loaded Machine. + */ +SPtr<Machine> +Loader::load(const std::string& uri) +{ + std::string document_uri = uri; + + // If "URI" doesn't contain a colon, try to resolve as a filename + if (uri.find(":") == string::npos) { + document_uri = "file://" + document_uri; + } + + cout << "Loading " << document_uri << endl; + + TimeUnit beats(TimeUnit::BEATS, MACHINA_PPQN); + + SPtr<Machine> machine(new Machine(beats)); + + typedef std::map<Sord::Node, SPtr<Node> > Created; + Created created; + + Sord::URI base_uri(_rdf_world, document_uri); + Sord::Model model(_rdf_world, document_uri); + + SerdEnv* env = serd_env_new(base_uri.to_serd_node()); + model.load_file(env, SERD_TURTLE, document_uri); + serd_env_free(env); + + Sord::Node nil; + + Sord::URI machina_SelectorNode(_rdf_world, MACHINA_NS_SelectorNode); + Sord::URI machina_duration(_rdf_world, MACHINA_NS_duration); + Sord::URI machina_edge(_rdf_world, MACHINA_NS_arc); + Sord::URI machina_head(_rdf_world, MACHINA_NS_head); + Sord::URI machina_node(_rdf_world, MACHINA_NS_node); + Sord::URI machina_onEnter(_rdf_world, MACHINA_NS_onEnter); + Sord::URI machina_onExit(_rdf_world, MACHINA_NS_onExit); + Sord::URI machina_probability(_rdf_world, MACHINA_NS_probability); + Sord::URI machina_start(_rdf_world, MACHINA_NS_start); + Sord::URI machina_tail(_rdf_world, MACHINA_NS_tail); + Sord::URI rdf_type(_rdf_world, MACHINA_URI_RDF "type"); + + Sord::Node subject = base_uri; + + // Get start node ID (but re-use existing start node) + Sord::Iter i = model.find(subject, machina_start, nil); + if (i.end()) { + cerr << "error: Machine has no start node" << std::endl; + } + created[i.get_object()] = machine->initial_node(); + + // Get remaining nodes + for (Sord::Iter i = model.find(subject, machina_node, nil); !i.end(); ++i) { + const Sord::Node& id = i.get_object(); + if (created.find(id) != created.end()) { + cerr << "warning: Machine lists the same node twice" << std::endl; + continue; + } + + // Create node + Sord::Iter d = model.find(id, machina_duration, nil); + SPtr<Node> node(new Node(TimeStamp(beats, d.get_object().to_float()))); + machine->add_node(node); + created[id] = node; + + node->set_enter_action( + load_action(model, model.get(id, machina_onEnter, nil))); + node->set_exit_action( + load_action(model, model.get(id, machina_onExit, nil))); + } + + // Get arcs + for (Sord::Iter i = model.find(subject, machina_edge, nil); !i.end(); ++i) { + Sord::Node edge = i.get_object(); + Sord::Iter t = model.find(edge, machina_tail, nil); + Sord::Iter h = model.find(edge, machina_head, nil); + Sord::Iter p = model.find(edge, machina_probability, nil); + + Sord::Node tail = t.get_object(); + Sord::Node head = h.get_object(); + Sord::Node probability = p.get_object(); + + float prob = probability.to_float(); + + Created::iterator tail_i = created.find(tail); + Created::iterator head_i = created.find(head); + + if (tail_i != created.end() && head_i != created.end()) { + const SPtr<Node> tail = tail_i->second; + const SPtr<Node> head = head_i->second; + tail->add_edge(SPtr<Edge>(new Edge(tail, head, prob))); + } else { + cerr << "warning: Ignored edge between unknown nodes " + << tail << " -> " << head << endl; + } + } + + if (machine && !machine->nodes().empty()) { + machine->reset(NULL, machine->time()); + return machine; + } else { + return SPtr<Machine>(); + } +} + +SPtr<Machine> +Loader::load_midi(const std::string& uri, + double q, + Raul::TimeDuration dur) +{ + SPtr<SMFDriver> file_driver(new SMFDriver(_forge, dur.unit())); + return file_driver->learn(uri, q, dur); +} + +} // namespace machina diff --git a/src/engine/MIDISink.hpp b/src/engine/MIDISink.hpp new file mode 100644 index 0000000..983dbba --- /dev/null +++ b/src/engine/MIDISink.hpp @@ -0,0 +1,37 @@ +/* + 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_MIDI_SINK_HPP +#define MACHINA_MIDI_SINK_HPP + +#include "raul/Deletable.hpp" +#include "raul/TimeStamp.hpp" + +namespace machina { + +/** Pure virtual base for anything you can write MIDI to. */ +class MIDISink + : public Raul::Deletable +{ +public: + virtual void write_event(Raul::TimeStamp time, + size_t ev_size, + const uint8_t* ev) = 0; +}; + +} // namespace machina + +#endif // MACHINA_MIDI_SINK_HPP diff --git a/src/engine/Machine.cpp b/src/engine/Machine.cpp new file mode 100644 index 0000000..b144b6f --- /dev/null +++ b/src/engine/Machine.cpp @@ -0,0 +1,389 @@ +/* + 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 <cstdlib> +#include <map> + +#include "sord/sordmm.hpp" + +#include "machina/Atom.hpp" +#include "machina/Context.hpp" +#include "machina/Machine.hpp" +#include "machina/URIs.hpp" +#include "machina/Updates.hpp" +#include "machina/types.hpp" + +#include "Edge.hpp" +#include "Node.hpp" +#include "LearnRequest.hpp" +#include "MidiAction.hpp" + +using namespace std; +using namespace Raul; + +namespace machina { + +Machine::Machine(TimeUnit unit) + : fitness(0.0) + , _initial_node(new Node(TimeStamp(unit, 0, 0), true)) + , _active_nodes(MAX_ACTIVE_NODES, SPtr<Node>()) + , _time(unit, 0, 0) + , _is_finished(false) +{ + _nodes.insert(_initial_node); +} + +void +Machine::assign(const Machine& copy) +{ + std::map< SPtr<Node>, SPtr<Node> > replacements; + + replacements[copy.initial_node()] = _initial_node; + + for (const auto& n : copy._nodes) { + if (!n->is_initial()) { + SPtr<machina::Node> node(new machina::Node(*n.get())); + _nodes.insert(node); + replacements[n] = node; + } + } + + for (const auto& n : _nodes) { + for (const auto& e : n->edges()) { + e->set_tail(n); + e->set_head(replacements[e->head()]); + } + } +} + +Machine::Machine(const Machine& copy) + : Stateful() // don't copy RDF ID + , fitness(0.0) + , _initial_node(new Node(TimeStamp(copy.time().unit(), 0, 0), true)) + , _active_nodes(MAX_ACTIVE_NODES, SPtr<Node>()) + , _time(copy.time()) + , _is_finished(false) +{ + _nodes.insert(_initial_node); + assign(copy); +} + +Machine& +Machine::operator=(const Machine& copy) +{ + if (© == this) { + return *this; + } + + fitness = copy.fitness; + + _active_nodes = std::vector< SPtr<Node> >(MAX_ACTIVE_NODES, SPtr<Node>()); + _is_finished = false; + _time = copy._time; + _pending_learn = SPtr<LearnRequest>(); + + _nodes.clear(); + _nodes.insert(_initial_node); + assign(copy); + + return *this; +} + +bool +Machine::operator==(const Machine& rhs) const +{ + return false; +} + +void +Machine::merge(const Machine& machine) +{ + for (const auto& m : machine.nodes()) { + if (m->is_initial()) { + for (const auto& e : m->edges()) { + e->set_tail(_initial_node); + _initial_node->edges().insert(e); + } + } else { + _nodes.insert(m); + } + } +} + +/** Always returns a node, unless there are none */ +SPtr<Node> +Machine::random_node() +{ + if (_nodes.empty()) { + return SPtr<Node>(); + } + + size_t i = rand() % _nodes.size(); + + // FIXME: O(n) worst case :( + for (Nodes::const_iterator n = _nodes.begin(); n != _nodes.end(); ++n, + --i) { + if (i == 0) { + return *n; + } + } + + return SPtr<Node>(); +} + +/** May return NULL even if edges exist (with low probability) */ +SPtr<Edge> +Machine::random_edge() +{ + SPtr<Node> tail = random_node(); + + for (size_t i = 0; i < _nodes.size() && tail->edges().empty(); ++i) { + tail = random_node(); + } + + return tail ? tail->random_edge() : SPtr<Edge>(); +} + +void +Machine::add_node(SPtr<Node> node) +{ + _nodes.insert(node); +} + +void +Machine::remove_node(SPtr<Node> node) +{ + _nodes.erase(node); + + for (Nodes::const_iterator n = _nodes.begin(); n != _nodes.end(); ++n) { + (*n)->remove_edge_to(node); + } +} + +void +Machine::reset(MIDISink* sink, Raul::TimeStamp time) +{ + if (!_is_finished) { + for (auto& n : _active_nodes) { + if (n) { + n->exit(sink, time); + n.reset(); + } + } + } + + _time = TimeStamp(_time.unit(), 0, 0); + _is_finished = false; +} + +SPtr<Node> +Machine::earliest_node() const +{ + SPtr<Node> earliest; + + for (const auto& n : _active_nodes) { + if (n && (!earliest || n->exit_time() < earliest->exit_time())) { + earliest = n; + } + } + + return earliest; +} + +bool +Machine::enter_node(Context& context, + SPtr<Node> node, + SPtr<Raul::RingBuffer> updates) +{ + assert(!node->is_active()); + assert(_active_nodes.size() == MAX_ACTIVE_NODES); + + /* FIXME: Would be best to use the MIDI note here as a hash key, at least + * while all actions are still MIDI notes... */ + size_t index = (rand() % MAX_ACTIVE_NODES); + for (size_t i = 0; i < MAX_ACTIVE_NODES; ++i) { + if (!_active_nodes[index]) { + node->enter(context.sink(), _time); + _active_nodes[index] = node; + + write_set(updates, + node->id(), + URIs::instance().machina_active, + context.forge().make(true)); + return true; + } + index = (index + 1) % MAX_ACTIVE_NODES; + } + + // If we get here, ran out of active node spots. Don't enter node + return false; +} + +void +Machine::exit_node(Context& context, + SPtr<Node> node, + SPtr<Raul::RingBuffer> updates) +{ + // Exit node + node->exit(context.sink(), _time); + + // Notify UI + write_set(updates, + node->id(), + URIs::instance().machina_active, + context.forge().make(false)); + + // Remove node from _active_nodes + for (auto& n : _active_nodes) { + if (n == node) { + n.reset(); + } + } + + // Activate successors + if (node->is_selector()) { + const double rand_normal = rand() / (double)RAND_MAX; // [0, 1] + double range_min = 0; + + for (const auto& e : node->edges()) { + if (!e->head()->is_active() + && rand_normal > range_min + && rand_normal < range_min + e->probability()) { + + enter_node(context, e->head(), updates); + break; + + } else { + range_min += e->probability(); + } + } + } else { + for (const auto& e : node->edges()) { + const double rand_normal = rand() / (double)RAND_MAX; // [0, 1] + if (rand_normal <= e->probability()) { + SPtr<Node> head = e->head(); + + if (!head->is_active()) { + enter_node(context, head, updates); + } + } + } + } +} + +uint32_t +Machine::run(Context& context, SPtr<Raul::RingBuffer> updates) +{ + if (_is_finished) { + return 0; + } + + const Raul::TimeSlice& time = context.time(); + + const TimeStamp end_frames = (time.start_ticks() + time.length_ticks()); + const TimeStamp end_beats = time.ticks_to_beats(end_frames); + + if (_time.is_zero()) { // Initial run + // Exit any active nodes + for (auto& n : _active_nodes) { + if (n && n->is_active()) { + n->exit(context.sink(), _time); + write_set(updates, + n->id(), + URIs::instance().machina_active, + context.forge().make(false)); + } + n.reset(); + } + + // Enter initial node + enter_node(context, _initial_node, updates); + + if (_initial_node->edges().empty()) { // Nowhere to go, exit + _is_finished = true; + return 0; + } + } + + while (true) { + SPtr<Node> earliest = earliest_node(); + if (!earliest) { + // No more active states, machine is finished + _is_finished = true; + break; + } + + const TimeStamp exit_time = earliest->exit_time(); + if (time.beats_to_ticks(exit_time) < end_frames) { + // Earliest active state ends this cycle, exit it + _time = earliest->exit_time(); + exit_node(context, earliest, updates); + + } else { + // Earliest active state ends in the future, done this cycle + _time = end_beats; + break; + } + + } + + return time.beats_to_ticks(_time).ticks() - time.start_ticks().ticks(); +} + +void +Machine::learn(SPtr<Raul::Maid> maid, SPtr<Node> node) +{ + _pending_learn = LearnRequest::create(maid, node); +} + +void +Machine::write_state(Sord::Model& model) +{ + using namespace Raul; + + model.add_statement(model.base_uri(), + Sord::URI(model.world(), MACHINA_URI_RDF "type"), + Sord::URI(model.world(), MACHINA_NS_Machine)); + + for (Nodes::const_iterator n = _nodes.begin(); n != _nodes.end(); ++n) { + (*n)->write_state(model); + + if ((*n)->is_initial()) { + model.add_statement(model.base_uri(), + Sord::URI(model.world(), MACHINA_NS_start), + (*n)->rdf_id(model.world())); + } else { + model.add_statement(model.base_uri(), + Sord::URI(model.world(), MACHINA_NS_node), + (*n)->rdf_id(model.world())); + } + } + + for (Nodes::const_iterator n = _nodes.begin(); n != _nodes.end(); ++n) { + for (Node::Edges::const_iterator e = (*n)->edges().begin(); + e != (*n)->edges().end(); ++e) { + + (*e)->write_state(model); + + model.add_statement(model.base_uri(), + Sord::URI(model.world(), MACHINA_NS_arc), + (*e)->rdf_id(model.world())); + } + + } +} + +} // namespace machina diff --git a/src/engine/MachineBuilder.cpp b/src/engine/MachineBuilder.cpp new file mode 100644 index 0000000..d812b06 --- /dev/null +++ b/src/engine/MachineBuilder.cpp @@ -0,0 +1,325 @@ +/* + 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 "lv2/lv2plug.in/ns/ext/midi/midi.h" + +#include "machina/Machine.hpp" +#include "machina/types.hpp" + +#include "Edge.hpp" +#include "MachineBuilder.hpp" +#include "MidiAction.hpp" +#include "Node.hpp" +#include "quantize.hpp" + +using namespace std; +using namespace Raul; + +namespace machina { + +MachineBuilder::MachineBuilder(SPtr<Machine> machine, double q, bool step) + : _quantization(q) + , _time(machine->time().unit()) // = 0 + , _machine(machine) + , _initial_node(machine->initial_node()) // duration 0 + , _connect_node(_initial_node) + , _connect_node_end_time(_time) // = 0 + , _step_duration(_time.unit(), q) + , _step(step) +{} + +void +MachineBuilder::reset() +{ + _time = TimeStamp(_machine->time().unit()); // = 0 + _connect_node = _initial_node; + _connect_node_end_time = _time; // = 0 +} + +bool +MachineBuilder::is_delay_node(SPtr<Node> node) const +{ + return node != _initial_node && + !node->enter_action() && + !node->exit_action(); +} + +/** Set the duration of a node, with quantization. + */ +void +MachineBuilder::set_node_duration(SPtr<Node> node, + Raul::TimeDuration d) const +{ + if (_step) { + node->set_duration(_step_duration); + return; + } + + Raul::TimeStamp q_dur = quantize(TimeStamp(d.unit(), _quantization), d); + + // Never quantize a note to duration 0 + if (q_dur.is_zero() && (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. + */ +SPtr<Node> +MachineBuilder::connect_nodes(SPtr<Machine> m, + SPtr<Node> tail, + Raul::TimeStamp tail_end_time, + SPtr<Node> head, + Raul::TimeStamp head_start_time) +{ + SPtr<Node> delay_node; + if (tail == head) { + return delay_node; + } + + if (is_delay_node(tail) && tail->edges().empty()) { + // Tail is a delay node, just accumulate the time difference into it + set_node_duration(tail, + tail->duration() + head_start_time - tail_end_time); + tail->add_edge(SPtr<Edge>(new Edge(tail, head))); + } else if (_step || (head_start_time == tail_end_time)) { + // Connect directly + tail->add_edge(SPtr<Edge>(new Edge(tail, head))); + } else { + // Need to actually create a delay node + delay_node = SPtr<Node>(new Node(head_start_time - tail_end_time)); + tail->add_edge(SPtr<Edge>(new Edge(tail, delay_node))); + delay_node->add_edge(SPtr<Edge>(new Edge(delay_node, head))); + m->add_node(delay_node); + } + + return delay_node; +} + +void +MachineBuilder::note_on(Raul::TimeStamp t, size_t ev_size, uint8_t* buf) +{ + SPtr<Node> node; + if (_step && _poly_nodes.empty() && is_delay_node(_connect_node)) { + /* Stepping and the connect node is the merge node after a polyphonic + group. Re-use it to avoid creating delay nodes in step mode. */ + node = _connect_node; + node->set_duration(_step_duration); + } else { + node = SPtr<Node>(new Node(default_duration())); + } + + node->set_enter_action(SPtr<Action>(new MidiAction(ev_size, buf))); + + if (_step && _poly_nodes.empty()) { + t = _time = _time + _step_duration; // Advance time one step + } + + SPtr<Node> this_connect_node; + Raul::TimeStamp this_connect_node_end_time(t.unit()); + + /* If currently polyphonic, use a poly node with no successors as connect + node, for more sensible patterns like what a human would build. */ + if (!_poly_nodes.empty()) { + for (PolyList::iterator j = _poly_nodes.begin(); + j != _poly_nodes.end(); ++j) { + if (j->second->edges().empty()) { + this_connect_node = j->second; + this_connect_node_end_time = j->first + j->second->duration(); + break; + } + } + } + + /* Currently monophonic, or didn't find a poly node, so use _connect_node + which is maintained below on note off events. */ + if (!this_connect_node) { + this_connect_node = _connect_node; + this_connect_node_end_time = _connect_node_end_time; + } + + SPtr<Node> delay_node = connect_nodes( + _machine, this_connect_node, this_connect_node_end_time, node, t); + + if (delay_node) { + _connect_node = delay_node; + _connect_node_end_time = t; + } + + node->enter(NULL, t); + _active_nodes.push_back(node); +} + +void +MachineBuilder::resolve_note(Raul::TimeStamp time, + size_t ev_size, + uint8_t* buf, + SPtr<Node> resolved) +{ + resolved->set_exit_action(SPtr<Action>(new MidiAction(ev_size, buf))); + + if (_active_nodes.size() == 1) { + if (_step) { + time = _time = _time + _step_duration; + } + + // Last active note + _connect_node_end_time = time; + + if (!_poly_nodes.empty()) { + // Finish a polyphonic section + _connect_node = SPtr<Node>(new Node(TimeStamp(_time.unit(), 0, 0))); + _machine->add_node(_connect_node); + + connect_nodes(_machine, resolved, time, _connect_node, time); + + for (PolyList::iterator j = _poly_nodes.begin(); + j != _poly_nodes.end(); ++j) { + _machine->add_node(j->second); + if (j->second->edges().empty()) { + connect_nodes(_machine, j->second, + j->first + j->second->duration(), + _connect_node, time); + } + } + _poly_nodes.clear(); + + _machine->add_node(resolved); + + } else { + // Just monophonic + if (is_delay_node(_connect_node) + && _connect_node->duration().is_zero() + && (_connect_node->edges().size() == 1) + && ((*_connect_node->edges().begin())->head() == resolved)) { + // Trim useless delay node if possible (after poly sections) + + _connect_node->edges().clear(); + _connect_node->set_enter_action(resolved->enter_action()); + _connect_node->set_exit_action(resolved->exit_action()); + resolved->set_enter_action(SPtr<Action>()); + resolved->set_exit_action(SPtr<Action>()); + set_node_duration(_connect_node, resolved->duration()); + resolved = _connect_node; + _machine->add_node(_connect_node); + + } else { + _connect_node = resolved; + _machine->add_node(resolved); + } + } + + } else { + // Polyphonic, add this node to poly list + _poly_nodes.push_back(make_pair(resolved->enter_time(), resolved)); + _connect_node = resolved; + _connect_node_end_time = _time; + } + + if (resolved->is_active()) { + resolved->exit(NULL, _time); + } +} + +void +MachineBuilder::event(Raul::TimeStamp time, + size_t ev_size, + uint8_t* buf) +{ + if (ev_size == 0) { + return; + } + + if (!_step) { + _time = time; + } + + if ((buf[0] & 0xF0) == LV2_MIDI_MSG_NOTE_ON) { + note_on(time, ev_size, buf); + } else if ((buf[0] & 0xF0) == LV2_MIDI_MSG_NOTE_OFF) { + for (ActiveList::iterator i = _active_nodes.begin(); + i != _active_nodes.end(); ++i) { + SPtr<MidiAction> action = dynamic_ptr_cast<MidiAction>( + (*i)->enter_action()); + if (!action) { + continue; + } + + const size_t ev_size = action->event_size(); + const uint8_t* ev = action->event(); + + if ((ev[0] & 0xF0) == LV2_MIDI_MSG_NOTE_ON && + (ev[0] & 0x0F) == (buf[0] & 0x0F) && + ev[1] == buf[1]) { + // Same channel and note as on event + resolve_note(time, ev_size, buf, *i); + _active_nodes.erase(i); + break; + } + } + } +} + +/** Finish the constructed machine and prepare it for use. + * Resolve any stuck notes, quantize, etc. + */ +void +MachineBuilder::resolve() +{ + // Resolve stuck notes + if (!_active_nodes.empty()) { + for (list<SPtr<Node> >::iterator i = _active_nodes.begin(); + i != _active_nodes.end(); ++i) { + cerr << "WARNING: Resolving stuck note." << endl; + SPtr<MidiAction> action = dynamic_ptr_cast<MidiAction>( + (*i)->enter_action()); + if (!action) { + continue; + } + + const size_t ev_size = action->event_size(); + const uint8_t* ev = action->event(); + if (ev_size == 3 && (ev[0] & 0xF0) == LV2_MIDI_MSG_NOTE_ON) { + uint8_t st((LV2_MIDI_MSG_NOTE_OFF & 0xF0) | (ev[0] & 0x0F)); + const uint8_t note_off[3] = { st, ev[1], 0x40 }; + (*i)->set_exit_action( + SPtr<Action>(new MidiAction(3, note_off))); + set_node_duration((*i), _time - (*i)->enter_time()); + (*i)->exit(NULL, _time); + _machine->add_node((*i)); + } + } + _active_nodes.clear(); + } + + // Add initial node if necessary + if (!_machine->nodes().empty()) { + _machine->add_node(_initial_node); + } +} + +SPtr<Machine> +MachineBuilder::finish() +{ + resolve(); + + return _machine; +} + +} // namespace machina diff --git a/src/engine/MachineBuilder.hpp b/src/engine/MachineBuilder.hpp new file mode 100644 index 0000000..83baf78 --- /dev/null +++ b/src/engine/MachineBuilder.hpp @@ -0,0 +1,87 @@ +/* + 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_MACHINEBUILDER_HPP +#define MACHINA_MACHINEBUILDER_HPP + +#include <stdint.h> + +#include <list> + +#include "machina/types.hpp" +#include "raul/TimeStamp.hpp" + +namespace machina { + +class Machine; +class Node; + +class MachineBuilder +{ +public: + MachineBuilder(SPtr<Machine> machine, + double quantization, + bool step); + + void event(Raul::TimeStamp time, size_t size, unsigned char* buf); + + void set_step(bool step) { _step = step; } + + void reset(); + void resolve(); + + SPtr<Machine> finish(); + +private: + bool is_delay_node(SPtr<Node> node) const; + void set_node_duration(SPtr<Node> node, Raul::TimeDuration d) const; + + void note_on(Raul::TimeStamp t, size_t ev_size, uint8_t* buf); + + void resolve_note(Raul::TimeStamp t, + size_t ev_size, + uint8_t* buf, + SPtr<Node> resolved); + + SPtr<Node>connect_nodes(SPtr<Machine> m, + SPtr<Node> tail, + Raul::TimeStamp tail_end_time, + SPtr<Node> head, + Raul::TimeStamp head_start_time); + + Raul::TimeStamp default_duration() { + return _step ? _step_duration : Raul::TimeStamp(_time.unit(), 0, 0); + } + + typedef std::list<SPtr<Node> > ActiveList; + ActiveList _active_nodes; + + typedef std::list<std::pair<Raul::TimeStamp, SPtr<Node> > > PolyList; + PolyList _poly_nodes; + + double _quantization; + Raul::TimeStamp _time; + SPtr<Machine> _machine; + SPtr<Node> _initial_node; + SPtr<Node> _connect_node; + Raul::TimeStamp _connect_node_end_time; + Raul::TimeStamp _step_duration; + bool _step; +}; + +} // namespace machina + +#endif // MACHINA_MACHINEBUILDER_HPP diff --git a/src/engine/MidiAction.cpp b/src/engine/MidiAction.cpp new file mode 100644 index 0000000..b5df155 --- /dev/null +++ b/src/engine/MidiAction.cpp @@ -0,0 +1,103 @@ +/* + 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 "lv2/lv2plug.in/ns/ext/midi/midi.h" +#include "machina/Atom.hpp" +#include "machina/URIs.hpp" +#include "machina/types.hpp" + +#include "MIDISink.hpp" +#include "MidiAction.hpp" + +namespace machina { + +/** Create a MIDI action. + * + * Creating a NULL MidiAction is okay, pass event=NULL and the action will + * simply do nothing until a set_event (for MIDI learning). + */ +MidiAction::MidiAction(size_t size, + const byte* event) + : _size(0) + , _max_size(size) +{ + _event = new byte[_max_size]; + set_event(size, event); +} + +MidiAction::~MidiAction() +{ + delete[] _event.load(); +} + +bool +MidiAction::set_event(size_t size, const byte* new_event) +{ + byte* const event = _event.load(); + if (size <= _max_size) { + _event = NULL; + if (size > 0 && new_event) { + memcpy(event, new_event, size); + } + _size = size; + _event = event; + return true; + } else { + return false; + } +} + +void +MidiAction::execute(MIDISink* sink, Raul::TimeStamp time) +{ + const byte* const ev = _event.load(); + if (ev && sink) { + sink->write_event(time, _size, ev); + } +} + +void +MidiAction::write_state(Sord::Model& model) +{ + const uint8_t* ev = event(); + const uint8_t type = (ev[0] & 0xF0); + if (type == LV2_MIDI_MSG_NOTE_ON) { + model.add_statement( + rdf_id(model.world()), + Sord::URI(model.world(), MACHINA_URI_RDF "type"), + Sord::URI(model.world(), LV2_MIDI__NoteOn)); + } else if (type == LV2_MIDI_MSG_NOTE_OFF) { + model.add_statement( + rdf_id(model.world()), + Sord::URI(model.world(), MACHINA_URI_RDF "type"), + Sord::URI(model.world(), LV2_MIDI__NoteOff)); + } else { + std::cerr << "warning: Unable to serialise MIDI event" << std::endl; + } + + model.add_statement( + rdf_id(model.world()), + Sord::URI(model.world(), LV2_MIDI__noteNumber), + Sord::Literal::integer(model.world(), (int)ev[1])); + if (ev[2] != 64) { + model.add_statement( + rdf_id(model.world()), + Sord::URI(model.world(), LV2_MIDI__velocity), + Sord::Literal::integer(model.world(), (int)ev[2])); + } +} + +} // namespace machina diff --git a/src/engine/MidiAction.hpp b/src/engine/MidiAction.hpp new file mode 100644 index 0000000..64a2c65 --- /dev/null +++ b/src/engine/MidiAction.hpp @@ -0,0 +1,57 @@ +/* + 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_MIDIACTION_HPP +#define MACHINA_MIDIACTION_HPP + +#include <atomic> + +#include "raul/TimeSlice.hpp" + +#include "machina/types.hpp" + +#include "Action.hpp" + +namespace machina { + +class MIDISink; + +class MidiAction : public Action +{ +public: + ~MidiAction(); + + MidiAction(size_t size, + const unsigned char* event); + + size_t event_size() { return _size; } + byte* event() { return _event.load(); } + + bool set_event(size_t size, const byte* event); + + void execute(MIDISink* sink, Raul::TimeStamp time); + + virtual void write_state(Sord::Model& model); + +private: + size_t _size; + const size_t _max_size; + std::atomic<byte*> _event; +}; + +} // namespace machina + +#endif // MACHINA_MIDIACTION_HPP diff --git a/src/engine/Mutation.cpp b/src/engine/Mutation.cpp new file mode 100644 index 0000000..fed2ad4 --- /dev/null +++ b/src/engine/Mutation.cpp @@ -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/>. +*/ + +#include <iostream> +#include <cstdlib> + +#include "machina/Machine.hpp" +#include "machina/Mutation.hpp" + +#include "ActionFactory.hpp" +#include "Edge.hpp" +#include "MidiAction.hpp" + +using namespace std; + +namespace machina { +namespace Mutation { + +void +Compress::mutate(Random& rng, Machine& machine) +{ + //cout << "COMPRESS" << endl; + + // Trim disconnected nodes + for (Machine::Nodes::iterator i = machine.nodes().begin(); + i != machine.nodes().end(); ) { + Machine::Nodes::iterator next = i; + ++next; + + if ((*i)->edges().empty()) { + machine.remove_node(*i); + } + + i = next; + } +} + +void +AddNode::mutate(Random& rng, Machine& machine) +{ + //cout << "ADD NODE" << endl; + + // Create random node + SPtr<Node> node(new Node(Raul::TimeDuration{machine.time().unit()})); + node->set_selector(true); + + SPtr<Node> note_node = machine.random_node(); + if (!note_node) { + return; + } + + uint8_t note = rand() % 128; + + SPtr<MidiAction> enter_action = dynamic_ptr_cast<MidiAction>( + note_node->enter_action()); + if (enter_action) { + note = enter_action->event()[1]; + } + + node->set_enter_action(ActionFactory::note_on(note)); + node->set_exit_action(ActionFactory::note_off(note)); + machine.add_node(node); + + // Insert after some node + SPtr<Node> tail = machine.random_node(); + if (tail && (tail != node) /* && !node->connected_to(tail)*/) { + tail->add_edge(SPtr<Edge>(new Edge(tail, node))); + } + + // Insert before some other node + SPtr<Node> head = machine.random_node(); + if (head && (head != node) /* && !head->connected_to(node)*/) { + node->add_edge(SPtr<Edge>(new Edge(node, head))); + } +} + +void +RemoveNode::mutate(Random& rng, Machine& machine) +{ + //cout << "REMOVE NODE" << endl; + + SPtr<Node> node = machine.random_node(); + if (node && !node->is_initial()) { + machine.remove_node(node); + } +} + +void +AdjustNode::mutate(Random& rng, Machine& machine) +{ + //cout << "ADJUST NODE" << endl; + + SPtr<Node> node = machine.random_node(); + if (node) { + SPtr<MidiAction> enter_action = dynamic_ptr_cast<MidiAction>( + node->enter_action()); + SPtr<MidiAction> exit_action = dynamic_ptr_cast<MidiAction>( + node->exit_action()); + if (enter_action && exit_action) { + const uint8_t note = rand() % 128; + enter_action->event()[1] = note; + exit_action->event()[1] = note; + } + node->set_changed(); + } +} + +void +SwapNodes::mutate(Random& rng, Machine& machine) +{ + //cout << "SWAP NODE" << endl; + + if (machine.nodes().size() <= 1) { + return; + } + + SPtr<Node> a = machine.random_node(); + SPtr<Node> b = machine.random_node(); + while (b == a) { + b = machine.random_node(); + } + + SPtr<MidiAction> a_enter = dynamic_ptr_cast<MidiAction>(a->enter_action()); + SPtr<MidiAction> a_exit = dynamic_ptr_cast<MidiAction>(a->exit_action()); + SPtr<MidiAction> b_enter = dynamic_ptr_cast<MidiAction>(b->enter_action()); + SPtr<MidiAction> b_exit = dynamic_ptr_cast<MidiAction>(b->exit_action()); + + uint8_t note_a = a_enter->event()[1]; + uint8_t note_b = b_enter->event()[1]; + + a_enter->event()[1] = note_b; + a_exit->event()[1] = note_b; + b_enter->event()[1] = note_a; + b_exit->event()[1] = note_a; +} + +void +AddEdge::mutate(Random& rng, Machine& machine) +{ + //cout << "ADJUST EDGE" << endl; + + SPtr<Node> tail = machine.random_node(); + SPtr<Node> head = machine.random_node(); + + if (tail && head && tail != head) { + // && !tail->connected_to(head) && !head->connected_to(tail) + SPtr<Edge> edge(new Edge(tail, head)); + edge->set_probability(rand() / (float)RAND_MAX); + tail->add_edge(SPtr<Edge>(new Edge(tail, head))); + } +} + +void +RemoveEdge::mutate(Random& rng, Machine& machine) +{ + //cout << "REMOVE EDGE" << endl; + + SPtr<Node> tail = machine.random_node(); + if (tail && !(tail->is_initial() && tail->edges().size() == 1)) { + tail->remove_edge(tail->random_edge()); + } +} + +void +AdjustEdge::mutate(Random& rng, Machine& machine) +{ + //cout << "ADJUST EDGE" << endl; + + SPtr<Edge> edge = machine.random_edge(); + if (edge) { + edge->set_probability(rand() / (float)RAND_MAX); + edge->tail().lock()->edges_changed(); + } +} + +} // namespace Mutation +} // namespace machina diff --git a/src/engine/Node.cpp b/src/engine/Node.cpp new file mode 100644 index 0000000..7d8a870 --- /dev/null +++ b/src/engine/Node.cpp @@ -0,0 +1,271 @@ +/* + 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 <cassert> +#include <iostream> + +#include "sord/sordmm.hpp" + +#include "machina/Atom.hpp" +#include "machina/URIs.hpp" + +#include "ActionFactory.hpp" +#include "Edge.hpp" +#include "Node.hpp" + +using namespace Raul; +using namespace std; + +namespace machina { + +Node::Node(TimeDuration duration, bool initial) + : _enter_time(duration.unit()) + , _duration(duration) + , _is_initial(initial) + , _is_selector(false) + , _is_active(false) +{} + +Node::Node(const Node& copy) + : Stateful() // don't copy RDF ID + , _enter_time(copy._enter_time) + , _duration(copy._duration) + , _enter_action(ActionFactory::copy(copy._enter_action)) + , _exit_action(ActionFactory::copy(copy._exit_action)) + , _is_initial(copy._is_initial) + , _is_selector(copy._is_selector) + , _is_active(false) +{ + for (Edges::const_iterator i = copy._edges.begin(); i != copy._edges.end(); + ++i) { + SPtr<Edge> edge(new Edge(*i->get())); + _edges.insert(edge); + } +} + +static inline bool +action_equals(SPtr<const Action> a, SPtr<const Action> b) +{ + return (a == b) || (a && b && *a.get() == *b.get()); +} + +bool +Node::operator==(const Node& rhs) const +{ + return _duration == rhs._duration && + _is_initial == rhs._is_initial && + _is_selector == rhs._is_selector && + _is_active == rhs._is_active && + action_equals(_enter_action, rhs.enter_action()) && + action_equals(_exit_action, rhs.exit_action()); + // TODO: compare edges +} + +/** Always returns an edge, unless there are none */ +SPtr<Edge> +Node::random_edge() +{ + SPtr<Edge> ret; + if (_edges.empty()) { + return ret; + } + + size_t i = rand() % _edges.size(); + + // FIXME: O(n) worst case :( + for (Edges::const_iterator e = _edges.begin(); e != _edges.end(); ++e, + --i) { + if (i == 0) { + ret = *e; + break; + } + } + + return ret; +} + +void +Node::edges_changed() +{ + if (!_is_selector) { + return; + } + + // Normalize edge probabilities if we're a selector + double prob_sum = 0; + + for (Edges::iterator i = _edges.begin(); i != _edges.end(); ++i) { + prob_sum += (*i)->probability(); + } + + for (Edges::iterator i = _edges.begin(); i != _edges.end(); ++i) { + (*i)->set_probability((*i)->probability() / prob_sum); + } + + _changed = true; +} + +void +Node::set_selector(bool yn) +{ + _is_selector = yn; + + if (yn) { + edges_changed(); + } + + _changed = true; +} + +void +Node::set_enter_action(SPtr<Action> action) +{ + _enter_action = action; + _changed = true; +} + +void +Node::set_exit_action(SPtr<Action> action) +{ + _exit_action = action; + _changed = true; +} + +void +Node::enter(MIDISink* sink, TimeStamp time) +{ + if (!_is_active) { + _changed = true; + _is_active = true; + _enter_time = time; + + if (sink && _enter_action) { + _enter_action->execute(sink, time); + } + } +} + +void +Node::exit(MIDISink* sink, TimeStamp time) +{ + if (_is_active) { + if (sink && _exit_action) { + _exit_action->execute(sink, time); + } + + _changed = true; + _is_active = false; + _enter_time = 0; + } +} + +SPtr<Edge> +Node::edge_to(SPtr<Node> head) const +{ + // TODO: Make logarithmic + for (Edges::const_iterator i = _edges.begin(); i != _edges.end(); ++i) { + if ((*i)->head() == head) { + return *i; + } + } + return SPtr<Edge>(); +} + +void +Node::add_edge(SPtr<Edge> edge) +{ + assert(edge->tail().lock().get() == this); + if (edge_to(edge->head())) { + return; + } + + _edges.insert(edge); + edges_changed(); +} + +void +Node::remove_edge(SPtr<Edge> edge) +{ + _edges.erase(edge); + edges_changed(); +} + +bool +Node::connected_to(SPtr<Node> node) +{ + return bool(edge_to(node)); +} + +SPtr<Edge> +Node::remove_edge_to(SPtr<Node> node) +{ + SPtr<Edge> edge = edge_to(node); + if (edge) { + _edges.erase(edge); // TODO: avoid double search + edges_changed(); + } + return edge; +} + +void +Node::set(URIInt key, const Atom& value) +{ + if (key == URIs::instance().machina_initial) { + std::cerr << "error: Attempt to change node initial state" << std::endl; + } else if (key == URIs::instance().machina_selector) { + set_selector(value.get<int32_t>()); + } +} + +void +Node::write_state(Sord::Model& model) +{ + using namespace Raul; + + const Sord::Node& rdf_id = this->rdf_id(model.world()); + + if (_is_selector) + model.add_statement( + rdf_id, + Sord::URI(model.world(), MACHINA_URI_RDF "type"), + Sord::URI(model.world(), MACHINA_NS_SelectorNode)); + else + model.add_statement( + rdf_id, + Sord::URI(model.world(), MACHINA_URI_RDF "type"), + Sord::URI(model.world(), MACHINA_NS_Node)); + + model.add_statement( + rdf_id, + Sord::URI(model.world(), MACHINA_NS_duration), + Sord::Literal::decimal(model.world(), _duration.to_double(), 7)); + + if (_enter_action) { + _enter_action->write_state(model); + model.add_statement(rdf_id, + Sord::URI(model.world(), MACHINA_NS_onEnter), + _enter_action->rdf_id(model.world())); + } + + if (_exit_action) { + _exit_action->write_state(model); + model.add_statement(rdf_id, + Sord::URI(model.world(), MACHINA_NS_onExit), + _exit_action->rdf_id(model.world())); + } +} + +} // namespace machina diff --git a/src/engine/Node.hpp b/src/engine/Node.hpp new file mode 100644 index 0000000..cae75f5 --- /dev/null +++ b/src/engine/Node.hpp @@ -0,0 +1,116 @@ +/* + 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_NODE_HPP +#define MACHINA_NODE_HPP + +#include <set> + +#include "machina/types.hpp" + +#include "Action.hpp" +#include "MIDISink.hpp" +#include "Schrodinbit.hpp" +#include "Stateful.hpp" + +namespace machina { + +class Edge; +using Raul::TimeDuration; +using Raul::TimeStamp; +using Raul::TimeUnit; + +/** A node is a state (as in a FSM diagram), or "note". + * + * It contains a action, as well as a duration and pointers to its + * successors (states/nodes that (may) follow it). + * + * Initial nodes do not have enter actions (since they are entered at + * an undefined point in time <= 0). + */ +class Node : public Stateful +{ +public: + Node(TimeDuration duration, bool initial=false); + Node(const Node& copy); + + bool operator==(const Node& rhs) const; + + void set_enter_action(SPtr<Action> action); + void set_exit_action(SPtr<Action> action); + + SPtr<const Action> enter_action() const { return _enter_action; } + SPtr<Action> enter_action() { return _enter_action; } + SPtr<const Action> exit_action() const { return _exit_action; } + SPtr<Action> exit_action() { return _exit_action; } + + void enter(MIDISink* sink, TimeStamp time); + void exit(MIDISink* sink, TimeStamp time); + + void edges_changed(); + + void add_edge(SPtr<Edge> edge); + void remove_edge(SPtr<Edge> edge); + SPtr<Edge> remove_edge_to(SPtr<Node> node); + bool connected_to(SPtr<Node> node); + + void set(URIInt key, const Atom& value); + void write_state(Sord::Model& model); + + bool is_initial() const { return _is_initial; } + bool is_active() const { return _is_active; } + TimeStamp enter_time() const { return _enter_time; } + TimeStamp exit_time() const { return _enter_time + _duration; } + TimeDuration duration() const { return _duration; } + void set_duration(TimeDuration d) { _duration = d; } + bool is_selector() const { return _is_selector; } + void set_selector(bool i); + + inline bool changed() { return _changed; } + inline void set_changed() { _changed = true; } + + struct EdgeHeadOrder { + inline bool operator()(const SPtr<const Edge>& a, + const SPtr<const Edge>& b) { + return a.get() < b.get(); + } + }; + + SPtr<Edge> edge_to(SPtr<Node> head) const; + + typedef std::set<SPtr<Edge>, EdgeHeadOrder> Edges; + + Edges& edges() { return _edges; } + + SPtr<Edge> random_edge(); + +private: + Node& operator=(const Node& other); // undefined + + TimeStamp _enter_time; ///< valid iff _is_active + TimeDuration _duration; + SPtr<Action> _enter_action; + SPtr<Action> _exit_action; + Edges _edges; + Schrodinbit _changed; + bool _is_initial; + bool _is_selector; + bool _is_active; +}; + +} // namespace machina + +#endif // MACHINA_NODE_HPP diff --git a/src/engine/Problem.cpp b/src/engine/Problem.cpp new file mode 100644 index 0000000..3b05fed --- /dev/null +++ b/src/engine/Problem.cpp @@ -0,0 +1,383 @@ +/* + 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/>. +*/ + +#define __STDC_LIMIT_MACROS 1 + +#include <stdint.h> + +#include <set> +#include <vector> +#include <iostream> + +#include "lv2/lv2plug.in/ns/ext/midi/midi.h" + +#include "eugene/Problem.hpp" + +#include "machina/Machine.hpp" + +#include "ActionFactory.hpp" +#include "Edge.hpp" +#include "MidiAction.hpp" +#include "Problem.hpp" +#include "SMFReader.hpp" +#include "machina_config.h" + +using namespace std; + +namespace machina { + +Problem::Problem(TimeUnit unit, + const std::string& target_midi, + SPtr<Machine> seed) + : _unit(unit) + , _target(*this) + , _seed(new Machine(*seed.get())) +{ + SMFReader smf; + const bool opened = smf.open(target_midi); + assert(opened); + + smf.seek_to_track(2); // FIXME: ? + + uint8_t buf[4]; + uint32_t ev_size; + uint32_t delta_time; + while (smf.read_event(4, buf, &ev_size, &delta_time) >= 0) { + // time ignored + _target.write_event(TimeStamp(_unit, 0.0), ev_size, buf); +#if 0 + //_target._length += delta_time / (double)smf.ppqn(); + if ((buf[0] & 0xF0) == MIDI_CMD_NOTE_ON) { + const uint8_t note = buf[1]; + /*++_target._note_frequency[note]; + ++_target.n_notes();*/ + ++_target._counts[note]; + _target._notes.push_back(note); + } +#endif + } + + cout << "Target notes: " << _target.n_notes() << endl; + + _target.compute(); +} + +float +Problem::evaluate(const Machine& const_machine) const +{ + #if 0 + //cout << "("; + + // kluuudge + Machine& machine = const_cast<Machine&>(const_machine); + + map<Machine*, float>::const_iterator cached = _fitness.find(&machine); + if (cached != _fitness.end()) { + return cached->second; + } + + SPtr<Evaluator> eval(new Evaluator(*this)); + + //machine.reset(); + machine.set_sink(eval); + + // FIXME: timing stuff here isn't right at all... + + static const unsigned ppqn = MACHINA_PPQN; + Raul::TimeSlice time(ppqn, ppqn, 120.0); + time.set_slice(TimeStamp(_unit, 0, 0), TimeDuration(_unit, 2 * ppqn)); + + machine.run(time); + if (eval->n_notes() == 0) { + return 0.0f; // bad dog + } + TimeStamp end(_unit, time.start_ticks().ticks() + 2 * ppqn); + time.set_slice(end, TimeStamp(_unit, 0, 0)); + + while (eval->n_notes() < _target.n_notes()) { + machine.run(time); + if (machine.is_finished()) { + machine.reset(time.start_ticks()); + } + time.set_slice(end, TimeStamp(end.unit(), 0, 0)); + } + + eval->compute(); + + // count +#if 0 + float f = 0; + + for (uint8_t i = 0; i < 128; ++i) { + /*if (eval->_note_frequency[i] <= _target._note_frequency[i]) + f += eval->_note_frequency[i]; + else + f -= _target._note_frequency[i] - eval->_note_frequency[i];*/ + //f -= fabs(eval->_note_frequency[i] - _target._note_frequency[i]); + } +#endif + //cout << ")"; + + // distance + //float f = distance(eval->_notes, _target._notes); + + float f = 0.0; + + for (Evaluator::Patterns::const_iterator i = eval->_patterns.begin(); + i != eval->_patterns.end(); ++i) { + // Reward for matching patterns + if (_target._patterns.find(i->first) != _target._patterns.end()) { + Evaluator::Patterns::const_iterator c = _target._patterns.find( + i->first); + const uint32_t cnt + = (c == _target._patterns.end()) ? 1 : c->second; + f += min(i->second, cnt) * (i->first.length()); + + } else { + // Punish for bad patterns + const uint32_t invlen = (eval->_order - i->first.length() + 1); + f -= (i->second / (float)eval->_patterns.size() + * (float)(invlen * invlen * invlen)) * 4; + } + } + + // Punish for missing patterns + for (Evaluator::Patterns::const_iterator i = _target._patterns.begin(); + i != _target._patterns.end(); ++i) { + if (eval->_patterns.find(i->first) == _target._patterns.end()) { + f -= i->second / (float)_target.n_notes() + * (float)(eval->_order - i->first.length() + 1); + } + } + + //cout << "f = " << f << endl; + + _fitness[&machine] = f; + + return f; + #endif + return 0.0f; +} + +void +Problem::Evaluator::write_event(Raul::TimeStamp time, + size_t ev_size, + const uint8_t* ev) throw (std::logic_error) +{ + if ((ev[0] & 0xF0) == LV2_MIDI_MSG_NOTE_ON) { + + const uint8_t note = ev[1]; + + if (_first_note == 0) { + _first_note = note; + } + + /*++_note_frequency[note]; + ++n_notes();*/ + //_notes.push_back(note); + if (_read.length() == 0) { + _read = note; + return; + } + if (_read.length() == _order) { + _read = _read.substr(1); + } + + _read = _read + (char)note; + + for (size_t i = 0; i < _read.length(); ++i) { + const string pattern = _read.substr(i); + Patterns::iterator p = _patterns.find(pattern); + if (p != _patterns.end()) { + ++(p->second); + } else { + _patterns[pattern] = 1; + } + } + + ++_counts[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; + } + }*/ +} + +void +Problem::initial_population(eugene::Random& rng, + Population& pop, + size_t gene_size, + size_t pop_size) const +{ + // FIXME: ignores _seed and builds based on MIDI + // evolution of the visible machine would be nice.. + SPtr<Machine> base = SPtr<Machine>(new Machine(_unit)); + for (uint8_t i = 0; i < 128; ++i) { + if (_target._counts[i] > 0) { + //cout << "Initial note: " << (int)i << endl; + SPtr<Node> node(new Node(TimeDuration(_unit, 1 / 2.0))); + node->set_enter_action(ActionFactory::note_on(i)); + node->set_exit_action(ActionFactory::note_off(i)); + node->set_selector(true); + base->add_node(node); + } + } + + for (size_t i = 0; i < pop_size; ++i) { + // FIXME: double copy + Machine m(*base.get()); + + set< SPtr<Node> > unreachable; + + for (Machine::Nodes::iterator i = m.nodes().begin(); i != m.nodes().end(); + ++i) { + if (dynamic_ptr_cast<MidiAction>((*i)->enter_action())->event()[1] == + _target.first_note()) { + m.initial_node()->add_edge( + SPtr<Edge>(new Edge(m.initial_node(), *i))); + } else { + unreachable.insert(*i); + } + } + + SPtr<Node> cur = m.initial_node(); + unreachable.erase(cur); + SPtr<Node> head; + + while (!unreachable.empty()) { + if (rand() % 2) { + head = m.random_node(); + } else { + head = *unreachable.begin(); + } + + if (!head->connected_to(head) ) { + cur->add_edge(SPtr<Edge>(new Edge(cur, head))); + unreachable.erase(head); + cur = head; + } + } + + pop.push_back(m); + + /*cout << "initial # nodes: " << m.nodes().size(); + cout << "initial fitness: " << fitness(m) << endl;*/ + } +} + +/** levenshtein distance (edit distance) */ +size_t +Problem::distance(const std::vector<uint8_t>& source, + const std::vector<uint8_t>& target) const +{ + // Derived from http://www.merriampark.com/ldcpp.htm + + assert(source.size() < UINT16_MAX); + assert(target.size() < UINT16_MAX); + + // Step 1 + + const uint16_t n = source.size(); + const uint16_t m = target.size(); + if (n == 0) { + return m; + } + if (m == 0) { + return n; + } + + _matrix.resize(n + 1); + for (uint16_t i = 0; i <= n; i++) { + _matrix[i].resize(m + 1); + } + + // Step 2 + + for (uint16_t i = 0; i <= n; i++) { + _matrix[i][0] = i; + } + + for (uint16_t j = 0; j <= m; j++) { + _matrix[0][j] = j; + } + + // Step 3 + + for (uint16_t i = 1; i <= n; i++) { + + const uint8_t s_i = source[i - 1]; + + // Step 4 + + for (uint16_t j = 1; j <= m; j++) { + + const uint8_t t_j = target[j - 1]; + + // Step 5 + + uint16_t cost; + if (s_i == t_j) { + cost = 0; + } else { + cost = 1; + } + + // Step 6 + + const uint16_t above = _matrix[i - 1][j]; + const uint16_t left = _matrix[i][j - 1]; + const uint16_t diag = _matrix[i - 1][j - 1]; + uint16_t cell = min(above + 1, min(left + 1, diag + cost)); + + // Step 6A: Cover transposition, in addition to deletion, + // insertion and substitution. This step is taken from: + // Berghel, Hal ; Roach, David : "An Extension of Ukkonen's + // Enhanced Dynamic Programming ASM Algorithm" + // (http://www.acm.org/~hlb/publications/asm/asm.html) + + if (i > 2 && j > 2) { + uint16_t trans = _matrix[i - 2][j - 2] + 1; + if (source[i - 2] != t_j) { + trans++; + } + if (s_i != target[j - 2]) { + trans++; + } + if (cell > trans) { + cell = trans; + } + } + + _matrix[i][j] = cell; + } + } + + // Step 7 + + return _matrix[n][m]; +} + +} // namespace machina diff --git a/src/engine/Problem.hpp b/src/engine/Problem.hpp new file mode 100644 index 0000000..81e67d7 --- /dev/null +++ b/src/engine/Problem.hpp @@ -0,0 +1,135 @@ +/* + 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_PROBLEM_HPP +#define MACHINA_PROBLEM_HPP + +#include <stdint.h> + +#include <map> +#include <stdexcept> + +#include "machina/Machine.hpp" +#include "eugene/Problem.hpp" + +#include "MIDISink.hpp" + +namespace machina { + +class Problem : public eugene::Problem<Machine> { +public: + Problem(TimeUnit unit, + const std::string& target_midi, + SPtr<Machine> seed = SPtr<Machine>()); + virtual ~Problem() {} + + void seed(SPtr<Machine> parent) { _seed = parent; } + + float evaluate(const Machine& machine) const; + + bool fitness_less_than(float a, float b) const { return a < b; } + + void clear_fitness_cache() { _fitness.clear(); } + + void initial_population(eugene::Random& rng, + Population& pop, + size_t gene_size, + size_t pop_size) const; + +private: + size_t distance(const std::vector<uint8_t>& source, + const std::vector<uint8_t>& target) const; + + // count + /*struct FreqEvaluator : public Raul::MIDISink { + Evaluator(const Problem& problem) + : _problem(problem), _n_notes(0), _length(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(); + const Problem& _problem; + + size_t n_notes() const { return _n_notes; } + + float _note_frequency[128]; + size_t _n_notes; + double _length; + };*/ + + // distance + /*struct Evaluator : public Raul::MIDISink { + Evaluator(const Problem& problem) : _problem(problem) {} + void write_event(Raul::BeatTime time, + size_t ev_size, + const uint8_t* ev) throw (std::logic_error); + void compute(); + const Problem& _problem; + + size_t n_notes() const { return _notes.size(); } + + std::vector<uint8_t> _notes; + float _counts[128]; + };*/ + + struct Evaluator : public MIDISink { + explicit Evaluator(const Problem& problem) + : _problem(problem) + , _order(4) + , _n_notes(0) + , _first_note(0) + { + for (uint8_t i=0; i < 128; ++i) + _counts[i] = 0; + } + void write_event(TimeStamp time, + size_t ev_size, + const uint8_t* ev) throw (std::logic_error); + void compute(); + const Problem& _problem; + + size_t n_notes() const { return _n_notes; } + uint8_t first_note() const { return _first_note; } + + const uint32_t _order; + + std::string _read; + + typedef std::map<std::string, uint32_t> Patterns; + Patterns _patterns; + uint32_t _counts[128]; + size_t _n_notes; + uint8_t _first_note; + }; + + TimeUnit _unit; + + Evaluator _target; + SPtr<Machine> _seed; + + /// for levenshtein distance + mutable std::vector< std::vector<uint16_t> > _matrix; + + /// memoization + mutable std::map<Machine*, float> _fitness; +}; + +} // namespace machina + +#endif // MACHINA_PROBLEM_HPP diff --git a/src/engine/Recorder.cpp b/src/engine/Recorder.cpp new file mode 100644 index 0000000..21dfd4b --- /dev/null +++ b/src/engine/Recorder.cpp @@ -0,0 +1,69 @@ +/* + 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 <ios> +#include <iostream> + +#include "MachineBuilder.hpp" +#include "Recorder.hpp" + +using namespace std; +using namespace Raul; + +namespace machina { + +Recorder::Recorder(Forge& forge, + size_t buffer_size, + TimeUnit unit, + double q, + bool step) + : _unit(unit) + , _record_buffer(buffer_size) + , _builder(new MachineBuilder(SPtr<Machine>(new Machine(unit)), q, step)) +{} + +void +Recorder::_whipped() +{ + TimeStamp t(_unit); + size_t size; + unsigned char buf[4]; + + while (true) { + bool success = _record_buffer.read(sizeof(TimeStamp), (uint8_t*)&t); + if (success) { + success = _record_buffer.read(sizeof(size_t), (uint8_t*)&size); + } + if (success) { + success = _record_buffer.read(size, buf); + } + if (success) { + _builder->event(t, size, buf); + } else { + break; + } + } +} + +SPtr<Machine> +Recorder::finish() +{ + SPtr<Machine> machine = _builder->finish(); + _builder.reset(); + return machine; +} + +} diff --git a/src/engine/Recorder.hpp b/src/engine/Recorder.hpp new file mode 100644 index 0000000..c98c42b --- /dev/null +++ b/src/engine/Recorder.hpp @@ -0,0 +1,68 @@ +/* + 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_RECORDER_HPP +#define MACHINA_RECORDER_HPP + +#include "raul/RingBuffer.hpp" + +#include "machina/Machine.hpp" +#include "machina/types.hpp" + +#include "Slave.hpp" + +namespace machina { + +class MachineBuilder; + +class Recorder + : public Slave +{ +public: + Recorder(Forge& forge, + size_t buffer_size, + TimeUnit unit, + double q, + bool step); + + inline void write(Raul::TimeStamp time, size_t size, + const unsigned char* buf) { + if (_record_buffer.write_space() < + (sizeof(TimeStamp) + sizeof(size_t) + size)) { + std::cerr << "Record buffer overflow" << std::endl; + return; + } else { + _record_buffer.write(sizeof(TimeStamp), (uint8_t*)&time); + _record_buffer.write(sizeof(size_t), (uint8_t*)&size); + _record_buffer.write(size, buf); + } + } + + SPtr<MachineBuilder> builder() { return _builder; } + + SPtr<Machine> finish(); + +private: + virtual void _whipped(); + + TimeUnit _unit; + Raul::RingBuffer _record_buffer; + SPtr<MachineBuilder> _builder; +}; + +} // namespace machina + +#endif // MACHINA_RECORDER_HPP diff --git a/src/engine/SMFDriver.cpp b/src/engine/SMFDriver.cpp new file mode 100644 index 0000000..3dc51e2 --- /dev/null +++ b/src/engine/SMFDriver.cpp @@ -0,0 +1,162 @@ +/* + 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 <iostream> +#include <list> + +#include "machina/Context.hpp" +#include "machina/Machine.hpp" +#include "machina/types.hpp" + +#include "Edge.hpp" +#include "SMFDriver.hpp" +#include "SMFReader.hpp" +#include "SMFWriter.hpp" +#include "quantize.hpp" + +using namespace std; + +namespace machina { + +SMFDriver::SMFDriver(Forge& forge, Raul::TimeUnit unit) + : Driver(forge, SPtr<Machine>()) +{ + _writer = SPtr<SMFWriter>(new SMFWriter(unit)); +} + +/** Learn a single track from the MIDI file at `uri` + * + * @param track The track of the MIDI file to import, starting from 1. + * + * Currently only file:// URIs are supported. + * @return the resulting machine. + */ +SPtr<Machine> +SMFDriver::learn(const string& filename, + unsigned track, + double q, + Raul::TimeDuration max_duration) +{ + //assert(q.unit() == max_duration.unit()); + SPtr<Machine> m(new Machine(max_duration.unit())); + SPtr<MachineBuilder> builder = SPtr<MachineBuilder>( + new MachineBuilder(m, q, false)); + SMFReader reader; + + if (!reader.open(filename)) { + cerr << "Unable to open MIDI file " << filename << endl; + return SPtr<Machine>(); + } + + if (track > reader.num_tracks()) { + return SPtr<Machine>(); + } else { + learn_track(builder, reader, track, q, max_duration); + } + + m->reset(NULL, m->time()); + + if (m->nodes().size() > 1) { + return m; + } else { + return SPtr<Machine>(); + } +} + +/** Learn all tracks from a MIDI file into a single machine. + * + * This will result in one disjoint subgraph in the machine for each track. + */ +SPtr<Machine> +SMFDriver::learn(const string& filename, double q, Raul::TimeStamp max_duration) +{ + SPtr<Machine> m(new Machine(max_duration.unit())); + SPtr<MachineBuilder> builder = SPtr<MachineBuilder>( + new MachineBuilder(m, q, false)); + SMFReader reader; + + if (!reader.open(filename)) { + cerr << "Unable to open MIDI file " << filename << endl; + return SPtr<Machine>(); + } + + for (unsigned t = 1; t <= reader.num_tracks(); ++t) { + builder->reset(); + learn_track(builder, reader, t, q, max_duration); + } + + m->reset(NULL, m->time()); + + if (m->nodes().size() > 1) { + return m; + } else { + return SPtr<Machine>(); + } +} + +void +SMFDriver::learn_track(SPtr<MachineBuilder> builder, + SMFReader& reader, + unsigned track, + double q, + Raul::TimeDuration max_duration) +{ + const bool found_track = reader.seek_to_track(track); + if (!found_track) { + return; + } + + uint8_t buf[4]; + uint32_t ev_size; + uint32_t ev_delta_time; + + Raul::TimeUnit unit = Raul::TimeUnit(TimeUnit::BEATS, MACHINA_PPQN); + + uint64_t t = 0; + while (reader.read_event(4, buf, &ev_size, &ev_delta_time) >= 0) { + t += ev_delta_time; + + const uint32_t beats = t / (uint32_t)reader.ppqn(); + const uint32_t smf_ticks = t % (uint32_t)reader.ppqn(); + const double frac = smf_ticks / (double)reader.ppqn(); + const uint32_t ticks = frac * MACHINA_PPQN; + + if (!max_duration.is_zero() && t > max_duration.to_double()) { + break; + } + + if (ev_size > 0) { + // TODO: quantize + builder->event(TimeStamp(unit, beats, ticks), ev_size, buf); + } + } + + builder->resolve(); +} + +void +SMFDriver::run(SPtr<Machine> machine, Raul::TimeStamp max_time) +{ + // FIXME: unit kludge (tempo only) + Context context(_forge, machine->time().unit().ppt(), + _writer->unit().ppt(), 120.0); + context.set_sink(this); + context.time().set_slice(TimeStamp(max_time.unit(), 0, 0), + context.time().beats_to_ticks(max_time)); + machine->run(context, SPtr<Raul::RingBuffer>()); +} + +} // namespace machina diff --git a/src/engine/SMFDriver.hpp b/src/engine/SMFDriver.hpp new file mode 100644 index 0000000..e8b9a4b --- /dev/null +++ b/src/engine/SMFDriver.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 MACHINA_SMFDRIVER_HPP +#define MACHINA_SMFDRIVER_HPP + +#include "machina/Driver.hpp" +#include "machina/types.hpp" + +#include "MachineBuilder.hpp" +#include "SMFReader.hpp" +#include "SMFWriter.hpp" + +namespace machina { + +class Node; +class Machine; + +class SMFDriver : public Driver +{ +public: + SMFDriver(Forge& forge, Raul::TimeUnit unit); + + SPtr<Machine> learn(const std::string& filename, + double q, + Raul::TimeDuration max_duration); + + SPtr<Machine> learn(const std::string& filename, + unsigned track, + double q, + Raul::TimeDuration max_duration); + + void run(SPtr<Machine> machine, Raul::TimeStamp max_time); + + void write_event(Raul::TimeStamp time, + size_t ev_size, + const unsigned char* ev) + { _writer->write_event(time, ev_size, ev); } + + SPtr<SMFWriter> writer() { return _writer; } + +private: + SPtr<SMFWriter> _writer; + + void learn_track(SPtr<MachineBuilder> builder, + SMFReader& reader, + unsigned track, + double q, + Raul::TimeDuration max_duration); +}; + +} // namespace machina + +#endif // MACHINA_SMFDRIVER_HPP diff --git a/src/engine/SMFReader.cpp b/src/engine/SMFReader.cpp new file mode 100644 index 0000000..5976f75 --- /dev/null +++ b/src/engine/SMFReader.cpp @@ -0,0 +1,323 @@ +/* + This file is part of Machina. + Copyright 2007-2013 David Robillard <http://drobilla.net> + + Raul 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. + + Raul 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 Raul. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include <arpa/inet.h> +#include <cassert> +#include <cstdio> +#include <cstring> +#include <string> + +#include "lv2/lv2plug.in/ns/ext/midi/midi.h" + +#include "SMFReader.hpp" + +using std::endl; + +namespace machina { + +/** Return the size of the given event NOT including the status byte, + * or -1 if unknown (eg sysex) + */ +static int +midi_event_size(unsigned char status) +{ + if (status >= 0x80 && status <= 0xE0) { + status &= 0xF0; // mask off the channel + } + + switch (status) { + case LV2_MIDI_MSG_NOTE_OFF: + case LV2_MIDI_MSG_NOTE_ON: + case LV2_MIDI_MSG_NOTE_PRESSURE: + case LV2_MIDI_MSG_CONTROLLER: + case LV2_MIDI_MSG_BENDER: + case LV2_MIDI_MSG_SONG_POS: + return 2; + + case LV2_MIDI_MSG_PGM_CHANGE: + case LV2_MIDI_MSG_CHANNEL_PRESSURE: + case LV2_MIDI_MSG_MTC_QUARTER: + case LV2_MIDI_MSG_SONG_SELECT: + return 1; + + case LV2_MIDI_MSG_TUNE_REQUEST: + case LV2_MIDI_MSG_CLOCK: + case LV2_MIDI_MSG_START: + case LV2_MIDI_MSG_CONTINUE: + case LV2_MIDI_MSG_STOP: + case LV2_MIDI_MSG_ACTIVE_SENSE: + case LV2_MIDI_MSG_RESET: + return 0; + + case LV2_MIDI_MSG_SYSTEM_EXCLUSIVE: + return -1; + } + + return -1; +} + +SMFReader::SMFReader(const std::string filename) + : _fd(NULL) + , _ppqn(0) + , _track(0) + , _track_size(0) +{ + if (filename.length() > 0) { + open(filename); + } +} + +SMFReader::~SMFReader() +{ + if (_fd) { + close(); + } +} + +bool +SMFReader::open(const std::string& filename) +{ + if (_fd) { + throw std::logic_error( + "Attempt to start new read while write in progress."); + } + + std::cout << "Opening SMF file " << filename << " for reading." << endl; + + _fd = fopen(filename.c_str(), "r+"); + + if (_fd) { + // Read type (bytes 8..9) + fseek(_fd, 0, SEEK_SET); + char mthd[5]; + mthd[4] = '\0'; + fread(mthd, 1, 4, _fd); + if (strcmp(mthd, "MThd")) { + std::cerr << filename << " is not an SMF file, aborting." << endl; + fclose(_fd); + _fd = NULL; + return false; + } + + // Read type (bytes 8..9) + fseek(_fd, 8, SEEK_SET); + uint16_t type_be = 0; + fread(&type_be, 2, 1, _fd); + _type = ntohs(type_be); + + // Read number of tracks (bytes 10..11) + uint16_t num_tracks_be = 0; + fread(&num_tracks_be, 2, 1, _fd); + _num_tracks = ntohs(num_tracks_be); + + // Read PPQN (bytes 12..13) + uint16_t ppqn_be = 0; + fread(&ppqn_be, 2, 1, _fd); + _ppqn = ntohs(ppqn_be); + + // TODO: Absolute (SMPTE seconds) time support + if ((_ppqn & 0x8000) != 0) { + throw UnsupportedTime(); + } + + seek_to_track(1); + + return true; + } else { + return false; + } +} + +/** Seek to the start of a given track, starting from 1. + * Returns true if specified track was found. + */ +bool +SMFReader::seek_to_track(unsigned track) +{ + if (track == 0) { + throw std::logic_error("Seek to track 0 out of range (must be >= 1)"); + } + + if (!_fd) { + throw std::logic_error("Attempt to seek to track on unopened SMF file."); + } + + unsigned track_pos = 0; + + fseek(_fd, 14, SEEK_SET); + char id[5]; + id[4] = '\0'; + uint32_t chunk_size = 0; + + while (!feof(_fd)) { + fread(id, 1, 4, _fd); + + if (!strcmp(id, "MTrk")) { + ++track_pos; + } else { + std::cerr << "Unknown chunk ID " << id << endl; + } + + uint32_t chunk_size_be; + fread(&chunk_size_be, 4, 1, _fd); + chunk_size = ntohl(chunk_size_be); + + if (track_pos == track) { + break; + } + + fseek(_fd, chunk_size, SEEK_CUR); + } + + if (!feof(_fd) && track_pos == track) { + _track = track; + _track_size = chunk_size; + return true; + } else { + return false; + } +} + +/** Read an event from the current position in file. + * + * File position MUST be at the beginning of a delta time, or this will die very messily. + * ev.buffer must be of size ev.size, and large enough for the event. The returned event + * will have it's time field set to it's delta time (so it's the caller's responsibility + * to keep track of delta time, even for ignored events). + * + * Returns event length (including status byte) on success, 0 if event was + * skipped (eg a meta event), or -1 on EOF (or end of track). + * + * If `buf` is not large enough to hold the event, 0 will be returned, but ev_size + * set to the actual size of the event. + */ +int +SMFReader::read_event(size_t buf_len, + uint8_t* buf, + uint32_t* ev_size, + uint32_t* delta_time) +{ + if (_track == 0) { + throw std::logic_error("Attempt to read from unopened SMF file"); + } + + if (!_fd || feof(_fd)) { + return -1; + } + + assert(buf_len > 0); + assert(buf); + assert(ev_size); + assert(delta_time); + + // Running status state + static uint8_t last_status = 0; + static uint32_t last_size = 0; + + *delta_time = read_var_len(_fd); + int status = fgetc(_fd); + if (status == EOF) { + throw PrematureEOF(); + } else if (status > 0xFF) { + throw CorruptFile(); + } + + if (status < 0x80) { + if (last_status == 0) { + throw CorruptFile(); + } + status = last_status; + *ev_size = last_size; + fseek(_fd, -1, SEEK_CUR); + } else { + last_status = status; + *ev_size = midi_event_size(status) + 1; + last_size = *ev_size; + } + + buf[0] = static_cast<uint8_t>(status); + + if (status == 0xFF) { + *ev_size = 0; + if (feof(_fd)) { + throw PrematureEOF(); + } + uint8_t type = fgetc(_fd); + const uint32_t size = read_var_len(_fd); + + if (type == 0x2F) { + return -1; // we hit the logical EOF anyway... + } else { + fseek(_fd, size, SEEK_CUR); + return 0; + } + } + + if ((*ev_size > buf_len) || (*ev_size == 0) || feof(_fd)) { + // Skip event, return 0 + fseek(_fd, *ev_size - 1, SEEK_CUR); + return 0; + } else { + // Read event, return size + if (ferror(_fd)) { + throw CorruptFile(); + } + + fread(buf + 1, 1, *ev_size - 1, _fd); + + if (((buf[0] & 0xF0) == 0x90) && (buf[2] == 0) ) { + buf[0] = (0x80 | (buf[0] & 0x0F)); + buf[2] = 0x40; + } + + return *ev_size; + } +} + +void +SMFReader::close() +{ + if (_fd) { + fclose(_fd); + } + + _fd = NULL; +} + +uint32_t +SMFReader::read_var_len(FILE* fd) +{ + if (feof(fd)) { + throw PrematureEOF(); + } + + uint32_t value; + uint8_t c; + + if ((value = getc(fd)) & 0x80) { + value &= 0x7F; + do { + if (feof(fd)) { + throw PrematureEOF(); + } + value = (value << 7) + ((c = getc(fd)) & 0x7F); + } while (c & 0x80); + } + + return value; +} + +} // namespace machina diff --git a/src/engine/SMFReader.hpp b/src/engine/SMFReader.hpp new file mode 100644 index 0000000..7147f38 --- /dev/null +++ b/src/engine/SMFReader.hpp @@ -0,0 +1,87 @@ +/* + 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_SMF_READER_HPP +#define MACHINA_SMF_READER_HPP + +#include <stdexcept> +#include <string> +#include <inttypes.h> +#include "raul/TimeStamp.hpp" + +namespace machina { + +/** Standard Midi File (Type 0) Reader + * + * Currently this only reads SMF files with tempo-based timing. + * \ingroup raul + */ +class SMFReader +{ +public: + class PrematureEOF + : public std::exception + { + const char* what() const throw() { return "Unexpected end of file"; } + }; + class CorruptFile + : public std::exception + { + const char* what() const throw() { return "Corrupted file"; } + }; + class UnsupportedTime + : public std::exception + { + const char* what() const throw() { return + "Unsupported time stamp type (SMPTE)"; } + }; + + explicit SMFReader(const std::string filename = ""); + ~SMFReader(); + + bool open(const std::string& filename); + + bool seek_to_track(unsigned track); + + uint16_t type() const { return _type; } + uint16_t ppqn() const { return _ppqn; } + size_t num_tracks() { return _num_tracks; } + + int read_event(size_t buf_len, + uint8_t* buf, + uint32_t* ev_size, + uint32_t* ev_delta_time); + + void close(); + + static uint32_t read_var_len(FILE* fd); + +protected: + /** size of SMF header, including MTrk chunk header */ + static const uint32_t HEADER_SIZE = 22; + + std::string _filename; + FILE* _fd; + uint16_t _type; + uint16_t _ppqn; + uint16_t _num_tracks; + uint32_t _track; + uint32_t _track_size; +}; + +} // namespace machina + +#endif // MACHINA_SMF_READER_HPP diff --git a/src/engine/SMFWriter.cpp b/src/engine/SMFWriter.cpp new file mode 100644 index 0000000..25d25c9 --- /dev/null +++ b/src/engine/SMFWriter.cpp @@ -0,0 +1,241 @@ +/* + 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 <arpa/inet.h> +#include <stdint.h> +#include <stdio.h> + +#include <cassert> +#include <cstdio> +#include <cstring> +#include <limits> +#include <string> + +#include "SMFWriter.hpp" + +using std::endl; + +namespace machina { + +/** Create a new SMF writer. + * + * @param unit Must match the time stamp of ALL events passed to write, or + * terrible things will happen. + * + * *** NOTE: Only beat time is implemented currently. + */ +SMFWriter::SMFWriter(Raul::TimeUnit unit) + : _fd(NULL) + , _unit(unit) + , _start_time(unit, 0, 0) + , _last_ev_time(unit, 0, 0) + , _track_size(0) + , _header_size(0) +{ + if (unit.type() == Raul::TimeUnit::BEATS) { + assert(unit.ppt() < std::numeric_limits<uint16_t>::max()); + } +} + +SMFWriter::~SMFWriter() +{ + if (_fd) { + finish(); + } +} + +/** Start a write to an SMF file. + * + * @param filename Filename to write to. + * @param start_time Beat time corresponding to t=0 in the file (timestamps + * passed to write_event will have this value subtracted before writing). + */ +bool +SMFWriter::start(const std::string& filename, + Raul::TimeStamp start_time) +{ + if (_fd) { + throw std::logic_error( + "Attempt to start new write while write in progress."); + } + + std::cout << "Opening SMF file " << filename << " for writing." << endl; + + _fd = fopen(filename.c_str(), "w+"); + + if (_fd) { + _track_size = 0; + _filename = filename; + _start_time = start_time; + _last_ev_time = 0; + // write a tentative header to pad file out so writing starts at the right offset + write_header(); + } + + return (_fd == 0) ? false : true; +} + +/** Write an event at the end of the file. + * + * @param time The absolute time of the event, relative to the start of the + * file (the start_time parameter to start). Must be monotonically increasing + * on successive calls to this method. + */ +void +SMFWriter::write_event(Raul::TimeStamp time, + size_t ev_size, + const unsigned char* ev) +{ + if (time < _start_time) { + throw std::logic_error("Event time is before file start time"); + } else if (time < _last_ev_time) { + throw std::logic_error("Event time not monotonically increasing"); + } else if (time.unit() != _unit) { + throw std::logic_error("Event has unexpected time unit"); + } + + Raul::TimeStamp delta_time = time; + delta_time -= _start_time; + + fseek(_fd, 0, SEEK_END); + + uint64_t delta_ticks = delta_time.ticks() * _unit.ppt() + + delta_time.subticks(); + size_t stamp_size = 0; + + /* If delta time is too long (i.e. overflows), write out empty + * "proprietary" events to reach the desired time. + * Any SMF reading application should interpret this correctly + * (by accumulating the delta time and ignoring the event) */ + while (delta_ticks > VAR_LEN_MAX) { + static unsigned char null_event[] = { 0xFF, 0x7F, 0x0 }; + stamp_size = write_var_len(VAR_LEN_MAX); + fwrite(null_event, 1, 3, _fd); + _track_size += stamp_size + 3; + delta_ticks -= VAR_LEN_MAX; + } + + assert(delta_ticks <= VAR_LEN_MAX); + stamp_size = write_var_len(static_cast<uint32_t>(delta_ticks)); + fwrite(ev, 1, ev_size, _fd); + + _last_ev_time = time; + _track_size += stamp_size + ev_size; +} + +void +SMFWriter::flush() +{ + if (_fd) { + fflush(_fd); + } +} + +void +SMFWriter::finish() +{ + if (!_fd) { + throw std::logic_error( + "Attempt to finish write with no write in progress."); + } + + write_footer(); + fclose(_fd); + _fd = NULL; +} + +void +SMFWriter::write_header() +{ + std::cout << "SMF Flushing header\n"; + + const uint16_t type = htons(0); // SMF Type 0 (single track) + const uint16_t ntracks = htons(1); // Number of tracks (always 1 for Type 0) + const uint16_t division = htons(_unit.ppt()); // PPQN + + char data[6]; + memcpy(data, &type, 2); + memcpy(data + 2, &ntracks, 2); + memcpy(data + 4, &division, 2); + //data[4] = _ppqn & 0xF0; + //data[5] = _ppqn & 0x0F; + + _fd = freopen(_filename.c_str(), "r+", _fd); + assert(_fd); + fseek(_fd, 0, 0); + write_chunk("MThd", 6, data); + write_chunk_header("MTrk", _track_size); +} + +void +SMFWriter::write_footer() +{ + std::cout << "Writing EOT\n"; + + fseek(_fd, 0, SEEK_END); + write_var_len(1); // whatever... + static const unsigned char eot[4] = { 0xFF, 0x2F, 0x00 }; // end-of-track meta-event + fwrite(eot, 1, 4, _fd); +} + +void +SMFWriter::write_chunk_header(const char id[4], uint32_t length) +{ + const uint32_t length_be = htonl(length); + + fwrite(id, 1, 4, _fd); + fwrite(&length_be, 4, 1, _fd); +} + +void +SMFWriter::write_chunk(const char id[4], uint32_t length, void* data) +{ + write_chunk_header(id, length); + + fwrite(data, 1, length, _fd); +} + +/** Write an SMF variable length value. + * + * @return size (in bytes) of the value written. + */ +size_t +SMFWriter::write_var_len(uint32_t value) +{ + size_t ret = 0; + + uint32_t buffer = value & 0x7F; + + while ((value >>= 7)) { + buffer <<= 8; + buffer |= ((value & 0x7F) | 0x80); + } + + while (true) { + //printf("Writing var len byte %X\n", (unsigned char)buffer); + ++ret; + fputc(buffer, _fd); + if (buffer & 0x80) { + buffer >>= 8; + } else { + break; + } + } + + return ret; +} + +} // namespace machina diff --git a/src/engine/SMFWriter.hpp b/src/engine/SMFWriter.hpp new file mode 100644 index 0000000..18bf3ec --- /dev/null +++ b/src/engine/SMFWriter.hpp @@ -0,0 +1,73 @@ +/* + 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_SMF_WRITER_HPP +#define MACHINA_SMF_WRITER_HPP + +#include <stdexcept> +#include <string> + +#include "raul/TimeStamp.hpp" + +#include "MIDISink.hpp" + +namespace machina { + +/** Standard Midi File (Type 0) Writer + * \ingroup raul + */ +class SMFWriter + : public MIDISink +{ +public: + explicit SMFWriter(Raul::TimeUnit unit); + ~SMFWriter(); + + bool start(const std::string& filename, + Raul::TimeStamp start_time); + + Raul::TimeUnit unit() const { return _unit; } + + void write_event(Raul::TimeStamp time, + size_t ev_size, + const unsigned char* ev); + + void flush(); + + void finish(); + +protected: + static const uint32_t VAR_LEN_MAX = 0x0FFFFFFF; + + void write_header(); + void write_footer(); + + void write_chunk_header(const char id[4], uint32_t length); + void write_chunk(const char id[4], uint32_t length, void* data); + size_t write_var_len(uint32_t val); + + std::string _filename; + FILE* _fd; + Raul::TimeUnit _unit; + Raul::TimeStamp _start_time; + Raul::TimeStamp _last_ev_time; ///< Time last event was written relative to _start_time + uint32_t _track_size; + uint32_t _header_size; ///< size of SMF header, including MTrk chunk header +}; + +} // namespace machina + +#endif // MACHINA_SMF_WRITER_HPP diff --git a/src/engine/Schrodinbit.hpp b/src/engine/Schrodinbit.hpp new file mode 100644 index 0000000..0721eb9 --- /dev/null +++ b/src/engine/Schrodinbit.hpp @@ -0,0 +1,42 @@ +/* + 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 SCHRODINBIT_HPP +#define SCHRODINBIT_HPP + +/** A flag which becomes false when its value is observed + */ +class Schrodinbit +{ +public: + Schrodinbit() : _flag(false) {} + + inline operator bool() { + const bool ret = _flag; + _flag = false; + return ret; + } + + inline bool operator=(bool flag) { + _flag = flag; + return flag; + } + +private: + bool _flag; +}; + +#endif // SCHRODINBIT_HPP diff --git a/src/engine/Slave.hpp b/src/engine/Slave.hpp new file mode 100644 index 0000000..74ec06b --- /dev/null +++ b/src/engine/Slave.hpp @@ -0,0 +1,73 @@ +/* + 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_SLAVE_HPP +#define MACHINA_SLAVE_HPP + +#include <thread> + +#include "raul/Semaphore.hpp" + +namespace machina { + +/** Thread driven by (realtime safe) signals. + * + * Use this to perform some task in a separate thread you want to 'drive' + * from a realtime (or otherwise) thread. + */ +class Slave +{ +public: + Slave() + : _whip(0) + , _exit_flag(false) + , _thread(&Slave::_run, this) + {} + + virtual ~Slave() { + _exit_flag = true; + _whip.post(); + _thread.join(); + } + + /** Tell the slave to do whatever work it does. Realtime safe. */ + inline void whip() { _whip.post(); } + +protected: + /** Worker method. + * + * This is called once from this thread every time whip() is called. + * Implementations likely want to put a single (non loop) chunk of code + * here, e.g. to process an event. + */ + virtual void _whipped() = 0; + + Raul::Semaphore _whip; + +private: + inline void _run() { + while (_whip.wait() && !_exit_flag) { + _whipped(); + } + } + + bool _exit_flag; + std::thread _thread; +}; + +} // namespace machina + +#endif // MACHINA_SLAVE_HPP diff --git a/src/engine/Stateful.cpp b/src/engine/Stateful.cpp new file mode 100644 index 0000000..7608a06 --- /dev/null +++ b/src/engine/Stateful.cpp @@ -0,0 +1,39 @@ +/* + 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 "Stateful.hpp" + +namespace machina { + +uint64_t Stateful::_next_id = 1; + +Stateful::Stateful() + : _id(next_id()) +{} + +const Sord::Node& +Stateful::rdf_id(Sord::World& world) const +{ + if (!_rdf_id.is_valid()) { + std::ostringstream ss; + ss << "b" << _id; + _rdf_id = Sord::Node(world, Sord::Node::BLANK, ss.str()); + } + + return _rdf_id; +} + +} // namespace machina diff --git a/src/engine/Stateful.hpp b/src/engine/Stateful.hpp new file mode 100644 index 0000000..b15d863 --- /dev/null +++ b/src/engine/Stateful.hpp @@ -0,0 +1,70 @@ +/* + 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_STATEFUL_HPP +#define MACHINA_STATEFUL_HPP + +#include <stdint.h> + +#include "sord/sordmm.hpp" + +#include "machina/Atom.hpp" +#include "machina/types.hpp" + +namespace Raul { +class Atom; +} + +namespace machina { + +class URIs; + +class Stateful +{ +public: + Stateful(); + + virtual ~Stateful() {} + + virtual void set(URIInt key, const Atom& value) {} + virtual void write_state(Sord::Model& model) = 0; + + uint64_t id() const { return _id; } + const Sord::Node& rdf_id(Sord::World& world) const; + + static uint64_t next_id() { return _next_id++; } + +protected: + explicit Stateful(uint64_t id) : _id(id) {} + +private: + static uint64_t _next_id; + + uint64_t _id; + mutable Sord::Node _rdf_id; +}; + +class StatefulKey : public Stateful +{ +public: + explicit StatefulKey(uint64_t id) : Stateful(id) {} + + void write_state(Sord::Model& model) {} +}; + +} // namespace machina + +#endif // MACHINA_STATEFUL_HPP diff --git a/src/engine/URIs.cpp b/src/engine/URIs.cpp new file mode 100644 index 0000000..2b43b81 --- /dev/null +++ b/src/engine/URIs.cpp @@ -0,0 +1,23 @@ +/* + 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/URIs.hpp" + +namespace machina { + +URIs* URIs::_instance = NULL; + +} // namespace machina diff --git a/src/engine/Updates.cpp b/src/engine/Updates.cpp new file mode 100644 index 0000000..ce655e4 --- /dev/null +++ b/src/engine/Updates.cpp @@ -0,0 +1,66 @@ +/* + 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 "lv2/lv2plug.in/ns/ext/atom/atom.h" + +#include "machina/Atom.hpp" +#include "machina/Updates.hpp" +#include "machina/types.hpp" +#include "raul/RingBuffer.hpp" + +namespace machina { + +void +write_set(SPtr<Raul::RingBuffer> buf, + uint64_t subject, + URIInt key, + const Atom& value) +{ + const uint32_t update_type = UPDATE_SET; + buf->write(sizeof(update_type), &update_type); + buf->write(sizeof(subject), &subject); + buf->write(sizeof(key), &key); + + const LV2_Atom atom = { value.size(), value.type() }; + buf->write(sizeof(LV2_Atom), &atom); + buf->write(value.size(), value.get_body()); +} + +uint32_t +read_set(SPtr<Raul::RingBuffer> buf, + uint64_t* subject, + URIInt* key, + Atom* value) +{ + uint32_t update_type = 0; + buf->read(sizeof(update_type), &update_type); + if (update_type != UPDATE_SET) { + return 0; + } + + buf->read(sizeof(*subject), subject); + buf->read(sizeof(*key), key); + + LV2_Atom atom = { 0, 0 }; + buf->read(sizeof(LV2_Atom), &atom); + *value = Atom(atom.size, atom.type, NULL); + buf->read(atom.size, value->get_body()); + + return sizeof(update_type) + sizeof(*subject) + sizeof(*key) + + sizeof(LV2_Atom) + atom.size; +} + +} diff --git a/src/engine/machina/Atom.hpp b/src/engine/machina/Atom.hpp new file mode 100644 index 0000000..710cc26 --- /dev/null +++ b/src/engine/machina/Atom.hpp @@ -0,0 +1,217 @@ +/* + This file is part of Machina. + Copyright 2007-2013 David Robillard <http://drobilla.net> + + Raul 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. + + Raul 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 Raul. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef MACHINA_ATOM_HPP +#define MACHINA_ATOM_HPP + +#include <stdint.h> + +#include <cassert> +#include <cstdlib> +#include <cstring> +#include <string> + +namespace machina { + +class Forge; + +/** + A generic typed data container. + + An Atom holds a value with some type and size, both specified by a uint32_t. + Values with size less than sizeof(void*) are stored inline: no dynamic + allocation occurs so Atoms may be created in hard real-time threads. + Otherwise, if the size is larger than sizeof(void*), the value will be + dynamically allocated in a separate chunk of memory. +*/ +class Atom { +public: + Atom() : _size(0), _type(0) { _val._blob = NULL; } + ~Atom() { dealloc(); } + + typedef uint32_t TypeID; + + /** Contruct a raw atom. + * + * Typically this is not used directly, use Forge methods to make atoms. + */ + Atom(uint32_t size, TypeID type, const void* body) + : _size(size) + , _type(type) + { + if (is_reference()) { + _val._blob = malloc(size); + } + if (body) { + memcpy(get_body(), body, size); + } + } + + Atom(const Atom& copy) + : _size(copy._size) + , _type(copy._type) + { + if (is_reference()) { + _val._blob = malloc(_size); + memcpy(_val._blob, copy._val._blob, _size); + } else { + memcpy(&_val, ©._val, _size); + } + } + + Atom& operator=(const Atom& other) { + if (&other == this) { + return *this; + } + dealloc(); + _size = other._size; + _type = other._type; + if (is_reference()) { + _val._blob = malloc(_size); + memcpy(_val._blob, other._val._blob, _size); + } else { + memcpy(&_val, &other._val, _size); + } + return *this; + } + + inline bool operator==(const Atom& other) const { + if (_type == other.type() && _size == other.size()) { + if (is_reference()) { + return !memcmp(_val._blob, other._val._blob, _size); + } else { + return !memcmp(&_val, &other._val, _size); + } + } + return false; + } + + inline bool operator!=(const Atom& other) const { + return !operator==(other); + } + + inline bool operator<(const Atom& other) const { + if (_type == other.type()) { + if (is_reference()) { + return memcmp(_val._blob, other._val._blob, _size) < 0; + } else { + return memcmp(&_val, &other._val, _size) < 0; + } + } + return _type < other.type(); + } + + inline uint32_t size() const { return _size; } + inline bool is_valid() const { return _type; } + inline TypeID type() const { return _type; } + + inline const void* get_body() const { + return is_reference() ? _val._blob : &_val; + } + + inline void* get_body() { + return is_reference() ? _val._blob : &_val; + } + + template <typename T> const T& get() const { + assert(size() == sizeof(T)); + return *static_cast<const T*>(get_body()); + } + + template <typename T> const T* ptr() const { + return static_cast<const T*>(get_body()); + } + +private: + friend class Forge; + + /** Free dynamically allocated value, if applicable. */ + inline void dealloc() { + if (is_reference()) { + free(_val._blob); + } + } + + /** Return true iff this value is dynamically allocated. */ + inline bool is_reference() const { + return _size > sizeof(_val); + } + + uint32_t _size; + TypeID _type; + + union { + intptr_t _val; + void* _blob; + } _val; +}; + +class Forge { +public: + Forge() + : Int(1) + , Float(2) + , Bool(3) + , URI(4) + , URID(5) + , String(6) + {} + + virtual ~Forge() {} + + Atom make() { return Atom(); } + Atom make(int32_t v) { return Atom(sizeof(v), Int, &v); } + Atom make(float v) { return Atom(sizeof(v), Float, &v); } + Atom make(bool v) { + const int32_t iv = v ? 1 : 0; + return Atom(sizeof(int32_t), Bool, &iv); + } + + Atom make_urid(int32_t v) { return Atom(sizeof(int32_t), URID, &v); } + + Atom alloc(uint32_t size, uint32_t type, const void* val) { + return Atom(size, type, val); + } + + Atom alloc(const char* v) { + const size_t len = strlen(v); + return Atom(len + 1, String, v); + } + + Atom alloc(const std::string& v) { + return Atom(v.length() + 1, String, v.c_str()); + } + + Atom alloc_uri(const char* v) { + const size_t len = strlen(v); + return Atom(len + 1, URI, v); + } + + Atom alloc_uri(const std::string& v) { + return Atom(v.length() + 1, URI, v.c_str()); + } + + Atom::TypeID Int; + Atom::TypeID Float; + Atom::TypeID Bool; + Atom::TypeID URI; + Atom::TypeID URID; + Atom::TypeID String; +}; + +} // namespace machina + +#endif // MACHINA_ATOM_HPP diff --git a/src/engine/machina/Context.hpp b/src/engine/machina/Context.hpp new file mode 100644 index 0000000..766975b --- /dev/null +++ b/src/engine/machina/Context.hpp @@ -0,0 +1,51 @@ +/* + 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_CONTEXT_HPP +#define MACHINA_CONTEXT_HPP + +#include "machina/Atom.hpp" +#include "machina/types.hpp" +#include "raul/TimeSlice.hpp" + +namespace machina { + +class MIDISink; + +class Context +{ +public: + Context(Forge& forge, uint32_t rate, uint32_t ppqn, double bpm) + : _forge(forge) + , _time(rate, ppqn, bpm) + {} + + void set_sink(MIDISink* sink) { _sink = sink; } + + Forge& forge() { return _forge; } + const Raul::TimeSlice& time() const { return _time; } + Raul::TimeSlice& time() { return _time; } + MIDISink* sink() { return _sink; } + +private: + Forge& _forge; + Raul::TimeSlice _time; + MIDISink* _sink; +}; + +} // namespace machina + +#endif // MACHINA_CONTEXT_HPP diff --git a/src/engine/machina/Controller.hpp b/src/engine/machina/Controller.hpp new file mode 100644 index 0000000..833b5b2 --- /dev/null +++ b/src/engine/machina/Controller.hpp @@ -0,0 +1,80 @@ +/* + 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/>. +*/ + +#ifndef MACHINA_CONTROLLER_HPP +#define MACHINA_CONTROLLER_HPP + +#include <stdint.h> + +#include <set> + +#include "raul/RingBuffer.hpp" +#include "raul/Maid.hpp" + +#include "machina/Model.hpp" +#include "machina/URIs.hpp" +#include "machina/types.hpp" + +#include "Stateful.hpp" + +namespace Raul { +class Atom; +} + +namespace machina { + +class Engine; +class Machine; + +class Controller +{ +public: + Controller(SPtr<Engine> engine, Model& model); + + uint64_t create(const Properties& properties); + uint64_t connect(uint64_t tail_id, uint64_t head_id); + + void set_property(uint64_t object_id, URIInt key, const Atom& value); + + void learn(SPtr<Raul::Maid> maid, uint64_t node_id); + void disconnect(uint64_t tail_id, uint64_t head_id); + void erase(uint64_t id); + + void announce(SPtr<Machine> machine); + + void process_updates(); + +private: + SPtr<Stateful> find(uint64_t id); + + struct StatefulComparator { + inline bool operator()(SPtr<Stateful> a, SPtr<Stateful> b) const { + return a->id() < b->id(); + } + }; + + typedef std::set<SPtr<Stateful>, StatefulComparator> Objects; + Objects _objects; + + SPtr<Engine> _engine; + Model& _model; + + SPtr<Raul::RingBuffer> _updates; +}; + +} + +#endif // MACHINA_CONTROLLER_HPP diff --git a/src/engine/machina/Driver.hpp b/src/engine/machina/Driver.hpp new file mode 100644 index 0000000..546c4d6 --- /dev/null +++ b/src/engine/machina/Driver.hpp @@ -0,0 +1,86 @@ +/* + 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_DRIVER_HPP +#define MACHINA_DRIVER_HPP + +#include "raul/RingBuffer.hpp" + +#include "machina/types.hpp" + +#include "MIDISink.hpp" + +namespace machina { + +class Machine; + +class Driver : public MIDISink +{ +public: + Driver(Forge& forge, SPtr<Machine> machine) + : _forge(forge) + , _machine(machine) + , _play_state(PlayState::STOPPED) + , _bpm(120.0) + , _quantization(0.125) + , _quantize_record(0) + {} + + enum class PlayState { + STOPPED, + PLAYING, + RECORDING, + STEP_RECORDING + }; + + virtual ~Driver() {} + + SPtr<Machine> machine() { return _machine; } + + virtual void set_machine(SPtr<Machine> machine) { + _machine = machine; + } + + SPtr<Raul::RingBuffer> update_sink() { return _updates; } + + void set_update_sink(SPtr<Raul::RingBuffer> b) { + _updates = b; + } + + virtual void set_bpm(double bpm) { _bpm = bpm; } + virtual void set_quantization(double q) { _quantization = q; } + virtual void set_quantize_record(bool q) { _quantize_record = q; } + virtual void set_play_state(PlayState state) { _play_state = state; } + + virtual bool is_activated() const { return false; } + virtual void activate() {} + virtual void deactivate() {} + + PlayState play_state() const { return _play_state; } + +protected: + Forge& _forge; + SPtr<Machine> _machine; + SPtr<Raul::RingBuffer> _updates; + PlayState _play_state; + double _bpm; + double _quantization; + bool _quantize_record; +}; + +} // namespace machina + +#endif // MACHINA_JACKDRIVER_HPP diff --git a/src/engine/machina/Engine.hpp b/src/engine/machina/Engine.hpp new file mode 100644 index 0000000..dd40f42 --- /dev/null +++ b/src/engine/machina/Engine.hpp @@ -0,0 +1,68 @@ +/* + 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_ENGINE_HPP +#define MACHINA_ENGINE_HPP + +#include <string> + +#include "machina/Atom.hpp" +#include "machina/Driver.hpp" +#include "machina/Loader.hpp" +#include "machina/types.hpp" + +namespace machina { + +class Machine; + +class Engine +{ +public: + Engine(Forge& forge, + SPtr<Driver> driver, + Sord::World& rdf_world); + + Sord::World& rdf_world() { return _rdf_world; } + + static SPtr<Driver> new_driver(Forge& forge, + const std::string& name, + SPtr<Machine> machine); + + SPtr<Driver> driver() { return _driver; } + SPtr<Machine> machine() { return _driver->machine(); } + Forge& forge() { return _forge; } + + SPtr<Machine> load_machine(const std::string& uri); + SPtr<Machine> load_machine_midi(const std::string& uri, + double q, + Raul::TimeDuration dur); + + void export_midi(const std::string& filename, + Raul::TimeDuration dur); + + void set_bpm(double bpm); + void set_quantization(double beat_fraction); + +private: + SPtr<Driver> _driver; + Sord::World& _rdf_world; + Loader _loader; + Forge _forge; +}; + +} // namespace machina + +#endif // MACHINA_ENGINE_HPP diff --git a/src/engine/machina/Evolver.hpp b/src/engine/machina/Evolver.hpp new file mode 100644 index 0000000..400c177 --- /dev/null +++ b/src/engine/machina/Evolver.hpp @@ -0,0 +1,72 @@ +/* + This file is part of Machina. + Copyright 2007-2017 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_EVOLVER_HPP +#define MACHINA_EVOLVER_HPP + +#include <atomic> +#include <memory> +#include <thread> + +#include "eugene/GA.hpp" +#include "eugene/Random.hpp" +#include "machina/types.hpp" +#include "raul/TimeStamp.hpp" + +#include "Machine.hpp" +#include "Schrodinbit.hpp" + +namespace eugene { +template<typename G> class HybridMutation; +} + +namespace machina { + +class Problem; + +class Evolver +{ +public: + Evolver(Raul::TimeUnit unit, + const std::string& target_midi, + SPtr<Machine> seed); + + void seed(SPtr<Machine> parent); + bool improvement() { return _improvement; } + + void start(); + void join(); + + const Machine& best() { return _ga->best(); } + + typedef eugene::GA<Machine> MachinaGA; + +private: + void run(); + + eugene::Random _rng; + SPtr<MachinaGA> _ga; + SPtr<Problem> _problem; + float _seed_fitness; + Schrodinbit _improvement; + std::atomic<bool> _exit_flag; + + std::unique_ptr<std::thread> _thread; +}; + +} // namespace machina + +#endif // MACHINA_EVOLVER_HPP diff --git a/src/engine/machina/Loader.hpp b/src/engine/machina/Loader.hpp new file mode 100644 index 0000000..3fa66ff --- /dev/null +++ b/src/engine/machina/Loader.hpp @@ -0,0 +1,49 @@ +/* + 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_LOADER_HPP +#define MACHINA_LOADER_HPP + +#include "machina/Atom.hpp" +#include "machina/types.hpp" +#include "raul/TimeStamp.hpp" +#include "sord/sordmm.hpp" + +using Sord::Namespaces; + +namespace machina { + +class Machine; + +class Loader +{ +public: + Loader(Forge& forge, Sord::World& rdf_world); + + SPtr<Machine> load(const std::string& filename); + + SPtr<Machine> load_midi(const std::string& filename, + double q, + Raul::TimeDuration dur); + +private: + Forge& _forge; + Sord::World& _rdf_world; +}; + +} // namespace machina + +#endif // MACHINA_LOADER_HPP diff --git a/src/engine/machina/Machine.hpp b/src/engine/machina/Machine.hpp new file mode 100644 index 0000000..7c9855d --- /dev/null +++ b/src/engine/machina/Machine.hpp @@ -0,0 +1,132 @@ +/* + 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_MACHINE_HPP +#define MACHINA_MACHINE_HPP + +#include <vector> +#include <set> + +#include "machina/types.hpp" +#include "machina/Atom.hpp" +#include "raul/RingBuffer.hpp" +#include "raul/TimeSlice.hpp" +#include "sord/sordmm.hpp" + +#include "types.hpp" +#include "Node.hpp" + +namespace machina { + +class Context; +class LearnRequest; + +/** A (Finite State) Machine. + */ +class Machine : public Stateful +{ +public: + explicit Machine(TimeUnit unit); + + /** Copy a Machine. + * + * Creates a deep copy which is the 'same' machine, but with fresh state, + * i.e. all nodes are inactive and time is at zero. + */ + Machine(const Machine& copy); + + /** Completely replace this machine's contents with a deep copy. */ + Machine& operator=(const Machine& copy); + + bool operator==(const Machine& rhs) const; + + /** Merge another machine into this machine. */ + void merge(const Machine& machine); + + bool is_empty() { return _nodes.empty(); } + bool is_finished() { return _is_finished; } + + void add_node(SPtr<Node> node); + void remove_node(SPtr<Node> node); + void learn(SPtr<Raul::Maid> maid, SPtr<Node> node); + + void write_state(Sord::Model& model); + + /** Exit all active nodes and reset time to 0. */ + void reset(MIDISink* sink, Raul::TimeStamp time); + + /** Run the machine for a (real) time slice. + * + * Returns the duration of time the machine actually ran in frames. + * + * Caller can check is_finished() to determine if the machine still has any + * active nodes. If not, time() will return the exact time stamp the + * machine actually finished on (so it can be restarted immediately + * with sample accuracy if necessary). + */ + uint32_t run(Context& context, SPtr<Raul::RingBuffer> updates); + + // Any context + inline Raul::TimeStamp time() const { return _time; } + + SPtr<LearnRequest> pending_learn() { return _pending_learn; } + void clear_pending_learn() { _pending_learn.reset(); } + + typedef std::set< SPtr<Node> > Nodes; + Nodes& nodes() { return _nodes; } + const Nodes& nodes() const { return _nodes; } + + SPtr<Node> initial_node() const { return _initial_node; } + + SPtr<Node> random_node(); + SPtr<Edge> random_edge(); + + float fitness; // For GA + +private: + /** Return the active Node with the earliest exit time. */ + SPtr<Node> earliest_node() const; + + void assign(const Machine& other); + + /** Enter a node at the current time (called by run()). + * + * @return true if node was entered, otherwise voics are exhausted. + */ + bool enter_node(Context& context, + SPtr<Node> node, + SPtr<Raul::RingBuffer> updates); + + /** Exit a node at the current time (called by run()). */ + void exit_node(Context& context, + SPtr<Node> node, + SPtr<Raul::RingBuffer> updates); + + static const size_t MAX_ACTIVE_NODES = 128; + + SPtr<Node> _initial_node; + std::vector< SPtr<Node> > _active_nodes; + + SPtr<LearnRequest> _pending_learn; + Nodes _nodes; + Raul::TimeStamp _time; + + bool _is_finished; +}; + +} // namespace machina + +#endif // MACHINA_MACHINE_HPP diff --git a/src/engine/machina/Model.hpp b/src/engine/machina/Model.hpp new file mode 100644 index 0000000..32a332e --- /dev/null +++ b/src/engine/machina/Model.hpp @@ -0,0 +1,44 @@ +/* + This file is part of Machina. + Copyright 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/>. +*/ + +#ifndef MACHINA_MODEL_HPP +#define MACHINA_MODEL_HPP + +#include <stdint.h> + +#include <map> + +#include "machina/Atom.hpp" +#include "machina/types.hpp" + +namespace machina { + +class Model +{ +public: + virtual ~Model() {} + + virtual void new_object(uint64_t id, const Properties& properties) = 0; + + virtual void erase_object(uint64_t id) = 0; + + virtual void set(uint64_t id, URIInt key, const Atom& value) = 0; + virtual const Atom& get(uint64_t id, URIInt key) const = 0; +}; + +} // namespace machina + +#endif // MACHINA_MODEL_HPP diff --git a/src/engine/machina/Mutation.hpp b/src/engine/machina/Mutation.hpp new file mode 100644 index 0000000..69f5ee4 --- /dev/null +++ b/src/engine/machina/Mutation.hpp @@ -0,0 +1,60 @@ +/* + 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_MACHINE_MUTATION_HPP +#define MACHINA_MACHINE_MUTATION_HPP + +#include "machina_config.h" + +#ifdef HAVE_EUGENE +# include "eugene/Mutation.hpp" +# define SUPER : public eugene::Mutation<Machine> +#else +# define SUPER : public Mutation +#endif + +namespace machina { + +#ifdef HAVE_EUGENE +typedef eugene::Random Random; +#else +struct Random {}; +#endif + +class Machine; + +namespace Mutation { + +struct Mutation { + virtual ~Mutation() {} + + virtual void mutate(Random& rng, Machine& machine) = 0; +}; + +struct Compress SUPER { void mutate(Random& rng, Machine& machine); }; +struct AddNode SUPER { void mutate(Random& rng, Machine& machine); }; +struct RemoveNode SUPER { void mutate(Random& rng, Machine& machine); }; +struct AdjustNode SUPER { void mutate(Random& rng, Machine& machine); }; +struct SwapNodes SUPER { void mutate(Random& rng, Machine& machine); }; +struct AddEdge SUPER { void mutate(Random& rng, Machine& machine); }; +struct RemoveEdge SUPER { void mutate(Random& rng, Machine& machine); }; +struct AdjustEdge SUPER { void mutate(Random& rng, Machine& machine); }; + +} // namespace Mutation + +} // namespace machina + +#endif // MACHINA_MACHINE_MUTATION_HPP diff --git a/src/engine/machina/URIs.hpp b/src/engine/machina/URIs.hpp new file mode 100644 index 0000000..f770723 --- /dev/null +++ b/src/engine/machina/URIs.hpp @@ -0,0 +1,93 @@ +/* + 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_URIS_HPP +#define MACHINA_URIS_HPP + +#include <stdint.h> + +#include "machina/Atom.hpp" +#include "machina/types.hpp" + +#define MACHINA_URI_RDF "http://www.w3.org/1999/02/22-rdf-syntax-ns#" + +#define MACHINA_NS "http://drobilla.net/ns/machina#" + +#define MACHINA_NS_Machine MACHINA_NS "Machine" +#define MACHINA_NS_Node MACHINA_NS "Node" +#define MACHINA_NS_SelectorNode MACHINA_NS "SelectorNode" +#define MACHINA_NS_arc MACHINA_NS "arc" +#define MACHINA_NS_duration MACHINA_NS "duration" +#define MACHINA_NS_head MACHINA_NS "head" +#define MACHINA_NS_node MACHINA_NS "node" +#define MACHINA_NS_onEnter MACHINA_NS "onEnter" +#define MACHINA_NS_onExit MACHINA_NS "onExit" +#define MACHINA_NS_probability MACHINA_NS "probability" +#define MACHINA_NS_start MACHINA_NS "start" +#define MACHINA_NS_tail MACHINA_NS "tail" + +namespace machina { + +class URIs +{ +public: + static void init() { _instance = new URIs(); } + + static inline const URIs& instance() { assert(_instance); return *_instance; } + + URIInt machina_Edge; + URIInt machina_MidiAction; + URIInt machina_Node; + URIInt machina_active; + URIInt machina_canvas_x; + URIInt machina_canvas_y; + URIInt machina_duration; + URIInt machina_enter_action; + URIInt machina_exit_action; + URIInt machina_head_id; + URIInt machina_initial; + URIInt machina_note_number; + URIInt machina_probability; + URIInt machina_selector; + URIInt machina_tail_id; + URIInt rdf_type; + +private: + URIs() + : machina_Edge(100) + , machina_MidiAction(101) + , machina_Node(102) + , machina_active(1) + , machina_canvas_x(2) + , machina_canvas_y(3) + , machina_duration(4) + , machina_enter_action(11) + , machina_exit_action(12) + , machina_head_id(5) + , machina_initial(6) + , machina_note_number(13) + , machina_probability(7) + , machina_selector(8) + , machina_tail_id(9) + , rdf_type(10) + {} + + static URIs* _instance; +}; + +} // namespace machina + +#endif // MACHINA_URIS_HPP diff --git a/src/engine/machina/Updates.hpp b/src/engine/machina/Updates.hpp new file mode 100644 index 0000000..ff09af9 --- /dev/null +++ b/src/engine/machina/Updates.hpp @@ -0,0 +1,46 @@ +/* + 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_UPDATES_HPP +#define MACHINA_UPDATES_HPP + +#include <stdint.h> + +#include "machina/Atom.hpp" +#include "machina/types.hpp" +#include "raul/RingBuffer.hpp" + +namespace machina { + +enum UpdateType { + UPDATE_SET = 1 +}; + +void +write_set(SPtr<Raul::RingBuffer> buf, + uint64_t subject, + URIInt key, + const Atom& value); + +uint32_t +read_set(SPtr<Raul::RingBuffer> buf, + uint64_t* subject, + URIInt* key, + Atom* value); + +} // namespace machina + +#endif // MACHINA_UPDATES_HPP diff --git a/src/engine/machina/types.hpp b/src/engine/machina/types.hpp new file mode 100644 index 0000000..61137b7 --- /dev/null +++ b/src/engine/machina/types.hpp @@ -0,0 +1,69 @@ +/* + 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/>. +*/ + +#ifndef MACHINA_TYPES_HPP +#define MACHINA_TYPES_HPP + +#include <map> +#include <memory> + +#include "raul/Maid.hpp" + +namespace machina { + +typedef unsigned char byte; + +typedef uint32_t URIInt; + +class Atom; +typedef std::map<URIInt, Atom> Properties; + +#if __cplusplus >= 201103L +template <class T> +using SPtr = std::shared_ptr<T>; + +template <class T> +using WPtr = std::weak_ptr<T>; + +template <class T> +using MPtr = Raul::managed_ptr<T>; +#else +#define SPtr std::shared_ptr +#define WPtr std::weak_ptr +#define MPtr Raul::managed_ptr +#endif + +template <class T> +void NullDeleter(T* ptr) {} + +template<class T, class U> +SPtr<T> static_ptr_cast(const SPtr<U>& r) { + return std::static_pointer_cast<T>(r); +} + +template<class T, class U> +SPtr<T> dynamic_ptr_cast(const SPtr<U>& r) { + return std::dynamic_pointer_cast<T>(r); +} + +template<class T, class U> +SPtr<T> const_ptr_cast(const SPtr<U>& r) { + return std::const_pointer_cast<T>(r); +} + +} // namespace machina + +#endif // MACHINA_TYPES_HPP diff --git a/src/engine/quantize.hpp b/src/engine/quantize.hpp new file mode 100644 index 0000000..9cf939a --- /dev/null +++ b/src/engine/quantize.hpp @@ -0,0 +1,46 @@ +/* + 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_QUANTIZE_HPP +#define MACHINA_QUANTIZE_HPP + +#include <cmath> + +#include "raul/TimeStamp.hpp" + +namespace machina { + +inline TimeStamp +quantize(TimeStamp q, TimeStamp t) +{ + assert(q.unit() == t.unit()); + // FIXME: Precision problem? Should probably stay in discrete domain + const double qd = q.to_double(); + const double td = t.to_double(); + return TimeStamp(t.unit(), (qd > 0) ? lrint(td / qd) * qd : td); +} + +inline double +quantize(double q, double t) +{ + return (q > 0) + ? lrint(t / q) * q + : t; +} + +} // namespace machina + +#endif // MACHINA_QUANTIZE_HPP diff --git a/src/engine/quantize_test.cpp b/src/engine/quantize_test.cpp new file mode 100644 index 0000000..5410489 --- /dev/null +++ b/src/engine/quantize_test.cpp @@ -0,0 +1,41 @@ +/* + 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 <iostream> + +#include "quantize.hpp" + +using namespace std; +using namespace machina; + +int +main() +{ + TimeStamp q(TimeUnit(TimeUnit::BEATS, 19200), 0.25); + + for (double in = 0.0; in < 32; in += 0.23) { + TimeStamp beats(TimeUnit(TimeUnit::BEATS, 19200), in); + + /*cout << "Q(" << in << ", 1/4) = " + << quantize(q, beats) << endl;*/ + + if (quantize(q, beats).subticks() % (19200 / 4) != 0) { + return 1; + } + } + + return 0; +} diff --git a/src/engine/smf_test.cpp b/src/engine/smf_test.cpp new file mode 100644 index 0000000..d98a696 --- /dev/null +++ b/src/engine/smf_test.cpp @@ -0,0 +1,84 @@ +/* + 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 <iostream> +#include <string> +#include "raul/log.hpp" +#include "raul/SMFReader.hpp" +#include "raul/SMFWriter.hpp" + +using namespace std; +using namespace machina; + +int +main(int argc, char** argv) +{ +#define CHECK(cond) \ + do { if (!(cond)) { \ + error << "Test at " << __FILE__ << ":" << __LINE__ \ + << " failed: " << __STRING(cond) << endl; \ + return 1; \ + } \ + } \ + while (0) + + static const uint16_t ppqn = 19200; + + const char* filename = NULL; + + if (argc < 2) { + filename = "./test.mid"; + SMFWriter writer(TimeUnit(TimeUnit::BEATS, ppqn)); + writer.start(string(filename), TimeStamp(writer.unit(), 0, 0)); + writer.finish(); + } else { + filename = argv[1]; + } + + SMFReader reader; + bool opened = reader.open(filename); + + if (!opened) { + cerr << "Unable to open SMF file " << filename << endl; + return -1; + } + + CHECK(reader.type() == 0); + CHECK(reader.num_tracks() == 1); + CHECK(reader.ppqn() == ppqn); + + for (unsigned t = 1; t <= reader.num_tracks(); ++t) { + reader.seek_to_track(t); + + unsigned char buf[4]; + uint32_t ev_size; + uint32_t ev_delta_time; + while (reader.read_event(4, buf, &ev_size, &ev_delta_time) >= 0) { + + cout << t << ": Event, size = " << ev_size << ", time = " + << ev_delta_time; + cout << ":\t"; + cout.flags(ios::hex); + for (uint32_t i = 0; i < ev_size; ++i) { + cout << "0x" << static_cast<int>(buf[i]) << " "; + } + cout.flags(ios::dec); + cout << endl; + } + } + + return 0; +} diff --git a/src/engine/wscript b/src/engine/wscript new file mode 100644 index 0000000..1ec63a2 --- /dev/null +++ b/src/engine/wscript @@ -0,0 +1,45 @@ +#!/usr/bin/env python +from waflib.extras import autowaf as autowaf + +def build(bld): + core_source = ''' + ActionFactory.cpp + Controller.cpp + Edge.cpp + Engine.cpp + JackDriver.cpp + LearnRequest.cpp + Loader.cpp + Machine.cpp + MachineBuilder.cpp + MidiAction.cpp + Mutation.cpp + Node.cpp + Recorder.cpp + SMFDriver.cpp + SMFReader.cpp + SMFWriter.cpp + Stateful.cpp + Updates.cpp + URIs.cpp + ''' + if bld.env.HAVE_EUGENE: + core_source += ''' + Evolver.cpp + Problem.cpp + ''' + obj = bld(features = 'cxx cxxshlib') + obj.source = core_source + obj.export_includes = ['.'] + obj.includes = ['.', '..', '../..'] + obj.name = 'libmachina_engine' + obj.target = 'machina_engine' + obj.cxxflags = '-pthread' + if bld.env.CXX_NAME != 'clang': + obj.linkflags = '-pthread' + core_libs = 'RAUL SERD SORD JACK LV2' + if bld.env.HAVE_EUGENE: + core_libs += ' EUGENE ' + autowaf.use_lib(bld, obj, core_libs) + + bld.add_post_fun(autowaf.run_ldconfig) |