From 74688702fa060fb6aaa413b06deceec6c78d74a6 Mon Sep 17 00:00:00 2001 From: David Robillard Date: Wed, 4 Apr 2007 18:01:08 +0000 Subject: Quantization while recording. git-svn-id: http://svn.drobilla.net/lad/machina@393 a436a847-0d15-0410-975c-d299462d15a1 --- src/engine/JackDriver.cpp | 4 +- src/engine/Loader.cpp | 8 +- src/engine/MachineBuilder.cpp | 243 +++++++++++++++++++++------------- src/engine/Recorder.cpp | 5 +- src/engine/SMFDriver.cpp | 4 +- src/engine/machina/MachineBuilder.hpp | 6 +- src/engine/machina/Recorder.hpp | 2 +- src/gui/MachinaGUI.cpp | 2 + 8 files changed, 170 insertions(+), 104 deletions(-) (limited to 'src') diff --git a/src/engine/JackDriver.cpp b/src/engine/JackDriver.cpp index 3493105..25ff496 100644 --- a/src/engine/JackDriver.cpp +++ b/src/engine/JackDriver.cpp @@ -32,7 +32,7 @@ JackDriver::JackDriver(SharedPtr machine) , _output_port(NULL) , _cycle_time(1/48000.0, 120.0) , _bpm(120.0) - , _quantization(120.0) + , _quantization(0.0) , _recording(0) { if (!_machine) @@ -309,7 +309,7 @@ JackDriver::start_record() { std::cerr << "START RECORD" << std::endl; // FIXME: hardcoded size - _recorder = SharedPtr(new Recorder(1024, (1.0/(double)sample_rate()) * (_bpm.get() / 60.0))); + _recorder = SharedPtr(new Recorder(1024, (1.0/(double)sample_rate()) * (_bpm.get() / 60.0), _quantization.get())); _recorder->start(); _record_time = 0; _recording = 1; diff --git a/src/engine/Loader.cpp b/src/engine/Loader.cpp index b3e4c81..57905ba 100644 --- a/src/engine/Loader.cpp +++ b/src/engine/Loader.cpp @@ -125,7 +125,7 @@ Loader::load(const Glib::ustring& uri) const Glib::ustring& node_id = (*i)["node"]; const Glib::ustring& duration = (*i)["duration"]; - cout << "Node: " << node_id << " - " << duration << endl; + //cout << "Node: " << node_id << " - " << duration << endl; if (created.find(node_id.collate_key()) == created.end()) { SharedPtr node(new Node(strtod(duration.c_str(), NULL), false)); @@ -137,9 +137,9 @@ Loader::load(const Glib::ustring& uri) } - for (Created::iterator n = created.begin(); n != created.end(); ++n) { + /*for (Created::iterator n = created.begin(); n != created.end(); ++n) { cout << "NODE: " << n->first << endl; - } + }*/ /* Get note actions */ @@ -157,7 +157,7 @@ Loader::load(const Glib::ustring& uri) const Glib::ustring& node_id = (*i)["node"]; const Glib::ustring& note = (*i)["note"]; - cerr << "NOTE: " << node_id << " = " << note << endl; + //cerr << "NOTE: " << node_id << " = " << note << endl; Created::iterator node_i = created.find(node_id); if (node_i != created.end()) { diff --git a/src/engine/MachineBuilder.cpp b/src/engine/MachineBuilder.cpp index c8ad273..03d20b9 100644 --- a/src/engine/MachineBuilder.cpp +++ b/src/engine/MachineBuilder.cpp @@ -17,18 +17,21 @@ #include #include +#include #include "machina/MachineBuilder.hpp" #include "machina/Machine.hpp" #include "machina/Node.hpp" #include "machina/Edge.hpp" using namespace std; +using namespace Raul; namespace Machina { -MachineBuilder::MachineBuilder(SharedPtr machine) - : _time(0) +MachineBuilder::MachineBuilder(SharedPtr machine, Raul::BeatTime q) + : _quantization(q) + , _time(0) , _machine(machine) , _initial_node(new Node()) , _connect_node(_initial_node) @@ -109,118 +112,163 @@ MachineBuilder::event(Raul::BeatTime time_offset, { Raul::BeatTime t = _time + time_offset; + if (ev_size == 0) + return; + //cerr << "t = " << t << endl; - - if (ev_size > 0) { - if ((buf[0] & 0xF0) == MIDI_CMD_NOTE_ON) { - //cerr << "NOTE ON: " << (int)buf[1] << ", channel = " << (int)(buf[0] & 0x0F) << endl; - SharedPtr node(new Node()); - - node->set_enter_action(SharedPtr(new MidiAction(ev_size, buf))); - - SharedPtr delay_node = connect_nodes(_machine, - _connect_node, _connect_node_end_time, node, t); - - /*if (delay_node) { - _connect_node = delay_node; - _connect_node_end_time = t; - }*/ - - node->enter(SharedPtr(), t); - _active_nodes.push_back(node); - - } else if ((buf[0] & 0xF0) == MIDI_CMD_NOTE_OFF) { - //cerr << "NOTE OFF: " << (int)buf[1] << endl; - for (ActiveList::iterator i = _active_nodes.begin(); i != _active_nodes.end(); ++i) { - SharedPtr action = PtrCast((*i)->enter_action()); - if (!action) - continue; - - const size_t ev_size = action->event_size(); - const unsigned char* ev = action->event(); - if (ev_size == 3 && (ev[0] & 0xF0) == MIDI_CMD_NOTE_ON - && (ev[0] & 0x0F) == (buf[0] & 0x0F) // same channel - && ev[1] == buf[1]) // same note - { - //cerr << "FOUND MATCHING NOTE OFF!\n"; - - SharedPtr resolved = *i; - - resolved->set_exit_action(SharedPtr(new MidiAction(ev_size, buf))); - resolved->set_duration(t - resolved->enter_time()); - - if (_active_nodes.size() == 1) { - - //cerr << "{ RESOLVING, t= " << t << "\n"; - - _connect_node_end_time = t; - - if (_poly_nodes.size() > 0) { - _connect_node = SharedPtr(new Node()); - _machine->add_node(_connect_node); + if ((buf[0] & 0xF0) == MIDI_CMD_NOTE_ON) { + //cerr << "NOTE ON: " << (int)buf[1] << ", channel = " << (int)(buf[0] & 0x0F) << endl; + + SharedPtr node(new Node()); + node->set_enter_action(SharedPtr(new MidiAction(ev_size, buf))); + + SharedPtr this_connect_node; + Raul::BeatTime this_connect_node_end_time; + + // If currently polyphonic, use a poly node with no successors as connect node + // Results in patterns closes to what a human would choose + if ( ! _poly_nodes.empty()) { + for (PolyList::iterator j = _poly_nodes.begin(); j != _poly_nodes.end(); ++j) { + if (j->second->outgoing_edges().empty()) { + this_connect_node = j->second; + this_connect_node_end_time = j->first + j->second->duration(); + cerr << "POLY CONNECT!\n"; + 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) { + cerr << "NO POLY CONNECT\n"; + this_connect_node = _connect_node; + this_connect_node_end_time = _connect_node_end_time; + } + + + SharedPtr delay_node = connect_nodes(_machine, + this_connect_node, this_connect_node_end_time, node, t); + + if (delay_node) + cerr << "XXXXXXXXXXXXXX NOTE ON DELAY NODE\n"; + + if (delay_node) { + _connect_node = delay_node; + _connect_node_end_time = t; + } + + node->enter(SharedPtr(), t); + _active_nodes.push_back(node); + + } else if ((buf[0] & 0xF0) == MIDI_CMD_NOTE_OFF) { + //cerr << "NOTE OFF: " << (int)buf[1] << endl; + for (ActiveList::iterator i = _active_nodes.begin(); i != _active_nodes.end(); ++i) { + SharedPtr action = PtrCast((*i)->enter_action()); + if (!action) + continue; + + const size_t ev_size = action->event_size(); + const unsigned char* ev = action->event(); + + if (ev_size == 3 && (ev[0] & 0xF0) == MIDI_CMD_NOTE_ON + && (ev[0] & 0x0F) == (buf[0] & 0x0F) // same channel + && ev[1] == buf[1]) // same note + { + //cerr << "FOUND MATCHING NOTE OFF!\n"; - connect_nodes(_machine, resolved, t, _connect_node, t); + SharedPtr resolved = *i; - for (PolyList::iterator j = _poly_nodes.begin(); - j != _poly_nodes.end(); ++j) { - _machine->add_node(j->second); + resolved->set_exit_action(SharedPtr(new MidiAction(ev_size, buf))); + resolved->set_duration(t - resolved->enter_time()); + + // Last active note + if (_active_nodes.size() == 1) { + + cerr << "{ RESOLVING, t= " << t << "\n"; + + _connect_node_end_time = t; + + // Finish a polyphonic section + if (_poly_nodes.size() > 0) { + + cerr << "POLY\n"; + + _connect_node = SharedPtr(new Node()); + _machine->add_node(_connect_node); + + connect_nodes(_machine, resolved, t, _connect_node, t); + + for (PolyList::iterator j = _poly_nodes.begin(); j != _poly_nodes.end(); ++j) { + _machine->add_node(j->second); + if (j->second->outgoing_edges().size() == 0) connect_nodes(_machine, j->second, j->first + j->second->duration(), _connect_node, t); - } - _poly_nodes.clear(); - - _machine->add_node(resolved); + } + _poly_nodes.clear(); - } else { - // Trim useless delay node, if possible - // (these happen after polyphonic sections) - if (is_delay_node(_connect_node) && _connect_node->duration() == 0 - && _connect_node->outgoing_edges().size() == 1 - && (*_connect_node->outgoing_edges().begin())->head() == resolved) { - //cerr << "TRIMMING\n"; - _connect_node->outgoing_edges().clear(); - assert(_connect_node->outgoing_edges().empty()); - _connect_node->set_enter_action(resolved->enter_action()); - _connect_node->set_exit_action(resolved->exit_action()); - resolved->remove_enter_action(); - resolved->remove_exit_action(); - _connect_node->set_duration(resolved->duration()); - resolved = _connect_node; - if (_machine->nodes().find(_connect_node) == _machine->nodes().end()) - _machine->add_node(_connect_node); - } else { - //cerr << "RESOLVED\n"; - _connect_node = resolved; - _machine->add_node(resolved); - } - } - - //cerr << "}"; + _machine->add_node(resolved); + // Just monophonic } else { - //cerr << "ADDING POLY\n"; - _poly_nodes.push_back(make_pair(resolved->enter_time(), resolved)); - } + + cerr << "NO POLY\n"; + + // Trim useless delay node if possible (these appear after poly sections) + if (is_delay_node(_connect_node) && _connect_node->duration() == 0 + && _connect_node->outgoing_edges().size() == 1 + && (*_connect_node->outgoing_edges().begin())->head() == resolved) { + + cerr << "TRIMMING\n"; + _connect_node->outgoing_edges().clear(); + assert(_connect_node->outgoing_edges().empty()); + _connect_node->set_enter_action(resolved->enter_action()); + _connect_node->set_exit_action(resolved->exit_action()); + resolved->remove_enter_action(); + resolved->remove_exit_action(); + _connect_node->set_duration(resolved->duration()); + resolved = _connect_node; + if (_machine->nodes().find(_connect_node) == _machine->nodes().end()) + _machine->add_node(_connect_node); + + } else { - if (resolved->is_active()) - resolved->exit(SharedPtr(), t); + cerr << "RESOLVED\n"; + _connect_node = resolved; + _machine->add_node(resolved); + } + } - _active_nodes.erase(i); + cerr << "}"; - break; + // Polyphonic, add this state to poly list + } else { + cerr << "ADDING POLY\n"; + _poly_nodes.push_back(make_pair(resolved->enter_time(), resolved)); + _connect_node = resolved; + _connect_node_end_time = t; } + + if (resolved->is_active()) + resolved->exit(SharedPtr(), t); + + _active_nodes.erase(i); + + break; } } } } -/** Resolve any stuck notes. +/** 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 >::iterator i = _active_nodes.begin(); i != _active_nodes.end(); ++i) { cerr << "WARNING: Resolving stuck note." << endl; @@ -241,9 +289,22 @@ MachineBuilder::resolve() _active_nodes.clear(); } + // Add initial note if necessary if (_machine->nodes().size() > 0 && _machine->nodes().find(_initial_node) == _machine->nodes().end()) _machine->add_node(_initial_node); + + // Quantize + if (_quantization > 0) { + for (Machine::Nodes::iterator i = _machine->nodes().begin(); + i != _machine->nodes().end(); ++i) { + Raul::BeatTime q_dur = Quantizer::quantize(_quantization, (*i)->duration()); + + // Never quantize a note to duration 0 + if (q_dur > 0 || ( !(*i)->enter_action() && !(*i)->exit_action() )) + (*i)->set_duration(q_dur); + } + } } diff --git a/src/engine/Recorder.cpp b/src/engine/Recorder.cpp index 78eafe6..c6130ab 100644 --- a/src/engine/Recorder.cpp +++ b/src/engine/Recorder.cpp @@ -26,11 +26,12 @@ using namespace Raul; namespace Machina { -Recorder::Recorder(size_t buffer_size, double tick_rate) +Recorder::Recorder(size_t buffer_size, double tick_rate, double q) : _tick_rate(tick_rate) , _record_buffer(buffer_size) - , _builder(new MachineBuilder(SharedPtr(new Machine()))) + , _builder(new MachineBuilder(SharedPtr(new Machine()), q)) { + cerr << "XXXXXXXXXXXX new recorder, q=" << q << endl; } diff --git a/src/engine/SMFDriver.cpp b/src/engine/SMFDriver.cpp index dc7ca59..0283288 100644 --- a/src/engine/SMFDriver.cpp +++ b/src/engine/SMFDriver.cpp @@ -49,7 +49,7 @@ SharedPtr SMFDriver::learn(const string& filename, unsigned track, double q, Raul::BeatTime max_duration) { SharedPtr m(new Machine()); - SharedPtr builder = SharedPtr(new MachineBuilder(m)); + SharedPtr builder = SharedPtr(new MachineBuilder(m, q)); Raul::SMFReader reader; if (!reader.open(filename)) { @@ -79,7 +79,7 @@ SharedPtr SMFDriver::learn(const string& filename, double q, Raul::BeatTime max_duration) { SharedPtr m(new Machine()); - SharedPtr builder = SharedPtr(new MachineBuilder(m)); + SharedPtr builder = SharedPtr(new MachineBuilder(m, q)); Raul::SMFReader reader; if (!reader.open(filename)) { diff --git a/src/engine/machina/MachineBuilder.hpp b/src/engine/machina/MachineBuilder.hpp index a01ebd2..ac446c9 100644 --- a/src/engine/machina/MachineBuilder.hpp +++ b/src/engine/machina/MachineBuilder.hpp @@ -30,7 +30,8 @@ class Node; class MachineBuilder { public: - MachineBuilder(SharedPtr machine); + MachineBuilder(SharedPtr machine, + Raul::BeatTime quantization); void set_time(Raul::BeatTime time) { _time = time; } @@ -54,7 +55,8 @@ private: typedef std::list > > PolyList; PolyList _poly_nodes; - + + Raul::BeatTime _quantization; Raul::BeatTime _time; SharedPtr _machine; diff --git a/src/engine/machina/Recorder.hpp b/src/engine/machina/Recorder.hpp index b969df0..1bc07c3 100644 --- a/src/engine/machina/Recorder.hpp +++ b/src/engine/machina/Recorder.hpp @@ -31,7 +31,7 @@ class MachineBuilder; class Recorder : public Raul::Slave { public: - Recorder(size_t buffer_size, double tick_rate); + Recorder(size_t buffer_size, double tick_rate, double q); inline void write(Raul::TickTime time, size_t size, const unsigned char* buf) { _record_buffer.write(time, size, buf); diff --git a/src/gui/MachinaGUI.cpp b/src/gui/MachinaGUI.cpp index 4098ff8..3cf38bd 100644 --- a/src/gui/MachinaGUI.cpp +++ b/src/gui/MachinaGUI.cpp @@ -133,6 +133,8 @@ MachinaGUI::MachinaGUI(SharedPtr engine) sigc::mem_fun(this, &MachinaGUI::tempo_changed)); _quantize_checkbutton->signal_toggled().connect( sigc::mem_fun(this, &MachinaGUI::quantize_changed)); + _quantize_spinbutton->signal_changed().connect( + sigc::mem_fun(this, &MachinaGUI::quantize_changed)); connect_widgets(); -- cgit v1.2.1