diff options
author | David Robillard <d@drobilla.net> | 2007-03-31 05:28:01 +0000 |
---|---|---|
committer | David Robillard <d@drobilla.net> | 2007-03-31 05:28:01 +0000 |
commit | f8883a56e44a42e97ca84392fdbff61e11540fcd (patch) | |
tree | 745eef6b5685b9f668c48b36ae6c761bee49b271 | |
parent | dd6be02a7478225f19f02432919b64b96b733172 (diff) | |
download | machina-f8883a56e44a42e97ca84392fdbff61e11540fcd.tar.gz machina-f8883a56e44a42e97ca84392fdbff61e11540fcd.tar.bz2 machina-f8883a56e44a42e97ca84392fdbff61e11540fcd.zip |
Realtime MIDI recording.
git-svn-id: http://svn.drobilla.net/lad/machina@383 a436a847-0d15-0410-975c-d299462d15a1
-rw-r--r-- | src/engine/JackDriver.cpp | 56 | ||||
-rw-r--r-- | src/engine/MachineBuilder.cpp | 246 | ||||
-rw-r--r-- | src/engine/Makefile.am | 4 | ||||
-rw-r--r-- | src/engine/Recorder.cpp | 66 | ||||
-rw-r--r-- | src/engine/SMFDriver.cpp | 208 | ||||
-rw-r--r-- | src/engine/machina/Driver.hpp | 4 | ||||
-rw-r--r-- | src/engine/machina/JackDriver.hpp | 11 | ||||
-rw-r--r-- | src/engine/machina/MachineBuilder.hpp | 69 | ||||
-rw-r--r-- | src/engine/machina/Makefile.am | 4 | ||||
-rw-r--r-- | src/engine/machina/Recorder.hpp | 53 | ||||
-rw-r--r-- | src/engine/machina/SMFDriver.hpp | 28 | ||||
-rw-r--r-- | src/gui/MachinaCanvas.cpp | 2 | ||||
-rw-r--r-- | src/gui/MachinaGUI.cpp | 39 | ||||
-rw-r--r-- | src/gui/MachinaGUI.hpp | 63 | ||||
-rw-r--r-- | src/gui/machina.glade | 57 |
15 files changed, 668 insertions, 242 deletions
diff --git a/src/engine/JackDriver.cpp b/src/engine/JackDriver.cpp index 606a8ff..ed40e2a 100644 --- a/src/engine/JackDriver.cpp +++ b/src/engine/JackDriver.cpp @@ -33,6 +33,7 @@ JackDriver::JackDriver(SharedPtr<Machine> machine) , _cycle_time(1/48000.0, 120.0) , _bpm(120.0) , _quantization(120.0) + , _recording(0) { if (!_machine) _machine = SharedPtr<Machine>(new Machine()); @@ -95,11 +96,15 @@ JackDriver::detach() void JackDriver::set_machine(SharedPtr<Machine> machine) -{ +{ + SharedPtr<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(); } @@ -108,10 +113,26 @@ JackDriver::process_input(SharedPtr<Machine> machine, const TimeSlice& time) { // We only actually read Jack input at the beginning of a cycle assert(time.offset_ticks() == 0); + assert(_input_port); using namespace std; - //if (_learn_node) { + if (_recording.get()) { + const jack_nframes_t nframes = time.length_ticks(); + void* jack_buffer = jack_port_get_buffer(_input_port, nframes); + const jack_nframes_t event_count = jack_midi_get_event_count(jack_buffer, nframes); + + for (jack_nframes_t i=0; i < event_count; ++i) { + jack_midi_event_t ev; + jack_midi_event_get(&ev, jack_buffer, i, nframes); + + _recorder->write(_record_time + ev.time, ev.size, ev.buffer); + } + + if (event_count > 0) + _recorder->whip(); + + } else { const jack_nframes_t nframes = time.length_ticks(); void* jack_buffer = jack_port_get_buffer(_input_port, nframes); const jack_nframes_t event_count = jack_midi_get_event_count(jack_buffer, nframes); @@ -149,7 +170,7 @@ JackDriver::process_input(SharedPtr<Machine> machine, const TimeSlice& time) //std::cerr << "EVENT: " << std::hex << (int)ev.buffer[0] << "\n"; } - //} + } } @@ -199,13 +220,16 @@ JackDriver::on_process(jack_nframes_t nframes) // 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(); // Exit all active states - assert(_last_machine.use_count() > 1); // Realtime, can't delete _last_machine.reset(); // Cut our reference } _machine_changed.post(); // Signal we're done with it } + if (_recording.get()) + _record_time += nframes; + if (!machine) return; @@ -256,4 +280,28 @@ JackDriver::on_process(jack_nframes_t nframes) } +void +JackDriver::start_record() +{ + std::cerr << "START RECORD" << std::endl; + // FIXME: hardcoded size + _recorder = SharedPtr<Recorder>(new Recorder(1024, 1.0/(double)sample_rate())); + _recorder->start(); + _record_time = 0; + _recording = 1; +} + + +void +JackDriver::finish_record() +{ + _recording = 0; + SharedPtr<Machine> machine = _recorder->finish(); + std::cout << "Learned machine! " << machine->nodes().size() << " nodes." << std::endl; + _recorder.reset(); + machine->activate(); + set_machine(machine); +} + + } // namespace Machina diff --git a/src/engine/MachineBuilder.cpp b/src/engine/MachineBuilder.cpp new file mode 100644 index 0000000..a1a6ed8 --- /dev/null +++ b/src/engine/MachineBuilder.cpp @@ -0,0 +1,246 @@ +/* This file is part of Machina. + * Copyright (C) 2007 Dave Robillard <http://drobilla.net> + * + * Machina is free software; you can redistribute it and/or modify it under the + * terms of the GNU General Public License as published by the Free Software + * Foundation; either version 2 of the License, or (at your option) any later + * version. + * + * Machina is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <algorithm> +#include <raul/midi_events.h> +#include "machina/MachineBuilder.hpp" +#include "machina/Machine.hpp" +#include "machina/Node.hpp" +#include "machina/Edge.hpp" + +using namespace std; + +namespace Machina { + + +MachineBuilder::MachineBuilder(SharedPtr<Machine> machine) + : _time(0) + , _machine(machine) + , _initial_node(new Node()) + , _connect_node(_initial_node) + , _connect_node_end_time(0) +{ + _initial_node->set_initial(true); +} + + +void +MachineBuilder::reset() +{ + _initial_node = SharedPtr<Node>(new Node()); + _initial_node->set_initial(true); + _connect_node = _initial_node; + _connect_node_end_time = 0; + _time = 0; +} + + +bool +MachineBuilder::is_delay_node(SharedPtr<Node> node) const +{ + if (node->enter_action() || node->exit_action()) + return false; + else + return true; +} + + +/** Connect two nodes, inserting a delay node between them if necessary. + * + * If a delay node is added to the machine, it is returned. + */ +SharedPtr<Node> +MachineBuilder::connect_nodes(SharedPtr<Machine> m, + SharedPtr<Node> tail, Raul::BeatTime tail_end_time, + SharedPtr<Node> head, Raul::BeatTime head_start_time) +{ + assert(tail != head); + assert(head_start_time >= tail_end_time); + + SharedPtr<Node> delay_node; + + //cerr << "Connect nodes durations: " << tail->duration() << " .. " << head->duration() << endl; + //cerr << "Connect nodes times: " << tail_end_time << " .. " << head_start_time << endl; + + if (head_start_time == tail_end_time) { + // Connect directly + tail->add_outgoing_edge(SharedPtr<Edge>(new Edge(tail, head))); + } else if (is_delay_node(tail)) { + // Tail is a delay node, just accumulate the time difference into it + tail->set_duration(tail->duration() + head_start_time - tail_end_time); + tail->add_outgoing_edge(SharedPtr<Edge>(new Edge(tail, head))); + } else { + // Need to actually create a delay node + //cerr << "Adding delay node for " << tail_end_time << " .. " << head_start_time << endl; + delay_node = SharedPtr<Node>(new Node()); + delay_node->set_duration(head_start_time - tail_end_time); + tail->add_outgoing_edge(SharedPtr<Edge>(new Edge(tail, delay_node))); + delay_node->add_outgoing_edge(SharedPtr<Edge>(new Edge(delay_node, head))); + m->add_node(delay_node); + } + + return delay_node; +} + + +void +MachineBuilder::event(Raul::BeatTime time_offset, + size_t ev_size, + unsigned char* buf) +{ + Raul::BeatTime t = _time + time_offset; + + 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> node(new Node()); + + node->set_enter_action(SharedPtr<Action>(new MidiAction(ev_size, buf))); + + SharedPtr<Node> 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<Raul::MIDISink>(), t); + _active_nodes.push_back(node); + + } else if ((buf[0] & 0xF0) == MIDI_CMD_NOTE_OFF) { + //cerr << "NOTE OFF: " << (int)buf[1] << endl; + for (ActiveList::iterator i = _active_nodes.begin(); i != _active_nodes.end(); ++i) { + SharedPtr<MidiAction> action = PtrCast<MidiAction>((*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<Node> resolved = *i; + + resolved->set_exit_action(SharedPtr<Action>(new MidiAction(ev_size, buf))); + resolved->set_duration(t - resolved->enter_time()); + + _connect_node_end_time = t; + + if (_active_nodes.size() == 1) { + if (_poly_nodes.size() > 0) { + + _connect_node = SharedPtr<Node>(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); + connect_nodes(_machine, j->second, j->first + j->second->duration(), + _connect_node, t); + } + _poly_nodes.clear(); + + _machine->add_node(resolved); + + } else { + // Trim useless delay node, if possible + // (these happen after polyphonic sections) + if (is_delay_node(_connect_node) && _connect_node->duration() == 0 + && _connect_node->outgoing_edges().size() == 1 + && (*_connect_node->outgoing_edges().begin())->head() == resolved) { + _connect_node->outgoing_edges().clear(); + assert(_connect_node->outgoing_edges().empty()); + _connect_node->set_enter_action(resolved->enter_action()); + _connect_node->set_exit_action(resolved->exit_action()); + resolved->remove_enter_action(); + resolved->remove_exit_action(); + _connect_node->set_duration(resolved->duration()); + resolved = _connect_node; + if (_machine->nodes().find(_connect_node) == _machine->nodes().end()) + _machine->add_node(_connect_node); + } else { + _connect_node = resolved; + _machine->add_node(resolved); + } + } + + } else { + _poly_nodes.push_back(make_pair(resolved->enter_time(), resolved)); + } + + if (resolved->is_active()) + resolved->exit(SharedPtr<Raul::MIDISink>(), t); + + _active_nodes.erase(i); + + break; + } + } + } + } +} + + +/** Resolve any stuck notes. + */ +void +MachineBuilder::resolve() +{ + if ( ! _active_nodes.empty()) { + for (list<SharedPtr<Node> >::iterator i = _active_nodes.begin(); i != _active_nodes.end(); ++i) { + cerr << "WARNING: Resolving stuck note from MIDI file." << endl; + SharedPtr<MidiAction> action = PtrCast<MidiAction>((*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) { + unsigned char note_off[3] = { ((MIDI_CMD_NOTE_OFF & 0xF0) | (ev[0] & 0x0F)), ev[1], 0x40 }; + (*i)->set_exit_action(SharedPtr<Action>(new MidiAction(3, note_off))); + (*i)->set_duration(_time - (*i)->enter_time()); + (*i)->exit(SharedPtr<Raul::MIDISink>(), _time); + _machine->add_node((*i)); + } + } + _active_nodes.clear(); + } + + if (_machine->nodes().size() > 0 + && _machine->nodes().find(_initial_node) == _machine->nodes().end()) + _machine->add_node(_initial_node); +} + + +SharedPtr<Machine> +MachineBuilder::finish() +{ + resolve(); + + return _machine; +} + + +} // namespace Machina diff --git a/src/engine/Makefile.am b/src/engine/Makefile.am index c526633..dc7e8a4 100644 --- a/src/engine/Makefile.am +++ b/src/engine/Makefile.am @@ -15,7 +15,9 @@ libmachina_la_SOURCES = \ ActionFactory.cpp \ SMFDriver.cpp \ Engine.cpp \ - LearnRequest.cpp + LearnRequest.cpp \ + Recorder.cpp \ + MachineBuilder.cpp if WITH_JACK libmachina_la_SOURCES += JackDriver.cpp diff --git a/src/engine/Recorder.cpp b/src/engine/Recorder.cpp new file mode 100644 index 0000000..78eafe6 --- /dev/null +++ b/src/engine/Recorder.cpp @@ -0,0 +1,66 @@ +/* This file is part of Machina. + * Copyright (C) 2007 Dave Robillard <http://drobilla.net> + * + * Machina is free software; you can redistribute it and/or modify it under the + * terms of the GNU General Public License as published by the Free Software + * Foundation; either version 2 of the License, or (at your option) any later + * version. + * + * Machina is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <iostream> +#include <ios> +#include "machina/Recorder.hpp" +#include "machina/MachineBuilder.hpp" + +using namespace std; +using namespace Raul; + +namespace Machina { + + +Recorder::Recorder(size_t buffer_size, double tick_rate) + : _tick_rate(tick_rate) + , _record_buffer(buffer_size) + , _builder(new MachineBuilder(SharedPtr<Machine>(new Machine()))) +{ +} + + +void +Recorder::_whipped() +{ + cerr << "Whipped" << endl; + + TickTime t; + size_t size; + unsigned char buf[4]; + + while (_record_buffer.read(&t, &size, buf)) { + //cout << "RECORD EVENT: t=" << t * _tick_rate << ", size=" << size << ", buf=0x" + // << std::hex << (int)((unsigned char)buf[0]) << std::dec << endl; + + _builder->set_time(t * _tick_rate); + _builder->event(0, size, buf); + } +} + + +SharedPtr<Machine> +Recorder::finish() +{ + SharedPtr<Machine> machine = _builder->finish(); + _builder.reset(); + return machine; +} + + +} + diff --git a/src/engine/SMFDriver.cpp b/src/engine/SMFDriver.cpp index 5b6d940..dc7ca59 100644 --- a/src/engine/SMFDriver.cpp +++ b/src/engine/SMFDriver.cpp @@ -20,7 +20,6 @@ #include <glibmm/convert.h> #include <raul/Quantizer.h> #include <raul/SharedPtr.h> -#include <raul/midi_events.h> #include <raul/SMFWriter.h> #include <raul/SMFReader.h> #include "machina/Machine.hpp" @@ -50,7 +49,7 @@ SharedPtr<Machine> SMFDriver::learn(const string& filename, unsigned track, double q, Raul::BeatTime max_duration) { SharedPtr<Machine> m(new Machine()); - + SharedPtr<MachineBuilder> builder = SharedPtr<MachineBuilder>(new MachineBuilder(m)); Raul::SMFReader reader; if (!reader.open(filename)) { @@ -61,7 +60,7 @@ SMFDriver::learn(const string& filename, unsigned track, double q, Raul::BeatTim if (track > reader.num_tracks()) return SharedPtr<Machine>(); else - learn_track(m, reader, track, q, max_duration); + learn_track(builder, reader, track, q, max_duration); m->reset(); @@ -80,15 +79,17 @@ SharedPtr<Machine> SMFDriver::learn(const string& filename, double q, Raul::BeatTime max_duration) { SharedPtr<Machine> m(new Machine()); - + SharedPtr<MachineBuilder> builder = SharedPtr<MachineBuilder>(new MachineBuilder(m)); Raul::SMFReader reader; + if (!reader.open(filename)) { cerr << "Unable to open MIDI file " << filename << endl; return SharedPtr<Machine>(); } for (unsigned t=1; t <= reader.num_tracks(); ++t) { - learn_track(m, reader, t, q, max_duration); + builder->reset(); + learn_track(builder, reader, t, q, max_duration); } m->reset(); @@ -100,80 +101,17 @@ SMFDriver::learn(const string& filename, double q, Raul::BeatTime max_duration) } -bool -SMFDriver::is_delay_node(SharedPtr<Node> node) const -{ - if (node->enter_action() || node->exit_action()) - return false; - else - return true; -} - - -/** Connect two nodes, inserting a delay node between them if necessary. - * - * If a delay node is added to the machine, it is returned. - */ -SharedPtr<Node> -SMFDriver::connect_nodes(SharedPtr<Machine> m, - SharedPtr<Node> tail, Raul::BeatTime tail_end_time, - SharedPtr<Node> head, Raul::BeatTime head_start_time) -{ - assert(tail != head); - assert(head_start_time >= tail_end_time); - - SharedPtr<Node> delay_node; - - //cerr << "Connect nodes durations: " << tail->duration() << " .. " << head->duration() << endl; - //cerr << "Connect nodes times: " << tail_end_time << " .. " << head_start_time << endl; - - if (head_start_time == tail_end_time) { - // Connect directly - tail->add_outgoing_edge(SharedPtr<Edge>(new Edge(tail, head))); - } else if (is_delay_node(tail)) { - // Tail is a delay node, just accumulate the time difference into it - tail->set_duration(tail->duration() + head_start_time - tail_end_time); - tail->add_outgoing_edge(SharedPtr<Edge>(new Edge(tail, head))); - } else { - // Need to actually create a delay node - //cerr << "Adding delay node for " << tail_end_time << " .. " << head_start_time << endl; - delay_node = SharedPtr<Node>(new Node()); - delay_node->set_duration(head_start_time - tail_end_time); - tail->add_outgoing_edge(SharedPtr<Edge>(new Edge(tail, delay_node))); - delay_node->add_outgoing_edge(SharedPtr<Edge>(new Edge(delay_node, head))); - m->add_node(delay_node); - } - - return delay_node; -} - - void -SMFDriver::learn_track(SharedPtr<Machine> m, - Raul::SMFReader& reader, - unsigned track, - double q, - Raul::BeatTime max_duration) +SMFDriver::learn_track(SharedPtr<MachineBuilder> builder, + Raul::SMFReader& reader, + unsigned track, + double q, + Raul::BeatTime max_duration) { const bool found_track = reader.seek_to_track(track); if (!found_track) return; - typedef list<SharedPtr<Node> > ActiveList; - ActiveList active_nodes; - - typedef list<pair<Raul::BeatTime, SharedPtr<Node> > > PolyList; - PolyList poly_nodes; - - SharedPtr<Node> initial_node(new Node()); - initial_node->set_initial(true); - //m->add_node(initial_node); - - SharedPtr<Node> connect_node = initial_node; - Raul::BeatTime connect_node_end_time = 0; - - unsigned added_nodes = 0; - Raul::BeatTime unquantized_t = 0; Raul::BeatTime t = 0; unsigned char buf[4]; @@ -183,130 +121,16 @@ SMFDriver::learn_track(SharedPtr<Machine> m, unquantized_t += ev_time / (double)reader.ppqn(); t = Raul::Quantizer::quantize(q, unquantized_t); + builder->set_time(t); + if (max_duration != 0 && t > max_duration) break; - //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> node(new Node()); - - node->set_enter_action(SharedPtr<Action>(new MidiAction(ev_size, buf))); - - SharedPtr<Node> delay_node = connect_nodes(m, connect_node, connect_node_end_time, node, t); - if (delay_node) { - connect_node = delay_node; - connect_node_end_time = t; - } - - node->enter(SharedPtr<Raul::MIDISink>(), t); - active_nodes.push_back(node); - - } else if ((buf[0] & 0xF0) == MIDI_CMD_NOTE_OFF) { - //cerr << "NOTE OFF: " << (int)buf[1] << endl; - for (ActiveList::iterator i = active_nodes.begin(); i != active_nodes.end(); ++i) { - SharedPtr<MidiAction> action = PtrCast<MidiAction>((*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<Node> resolved = *i; - - resolved->set_exit_action(SharedPtr<Action>(new MidiAction(ev_size, buf))); - resolved->set_duration(t - resolved->enter_time()); - - ++added_nodes; - - connect_node_end_time = t; - - if (active_nodes.size() == 1) { - if (poly_nodes.size() > 0) { - - connect_node = SharedPtr<Node>(new Node()); - m->add_node(connect_node); - - connect_nodes(m, resolved, t, connect_node, t); - - for (PolyList::iterator j = poly_nodes.begin(); - j != poly_nodes.end(); ++j) { - m->add_node(j->second); - connect_nodes(m, j->second, j->first + j->second->duration(), - connect_node, t); - } - poly_nodes.clear(); - - m->add_node(resolved); - - } else { - // Trim useless delay node, if possible - // (these happen after polyphonic sections) - if (is_delay_node(connect_node) && connect_node->duration() == 0 - && connect_node->outgoing_edges().size() == 1 - && (*connect_node->outgoing_edges().begin())->head() == resolved) { - connect_node->outgoing_edges().clear(); - assert(connect_node->outgoing_edges().empty()); - connect_node->set_enter_action(resolved->enter_action()); - connect_node->set_exit_action(resolved->exit_action()); - resolved->remove_enter_action(); - resolved->remove_exit_action(); - connect_node->set_duration(resolved->duration()); - resolved = connect_node; - if (m->nodes().find(connect_node) == m->nodes().end()) - m->add_node(connect_node); - } else { - connect_node = resolved; - m->add_node(resolved); - } - } - - } else { - poly_nodes.push_back(make_pair(resolved->enter_time(), resolved)); - } - - if (resolved->is_active()) - resolved->exit(SharedPtr<Raul::MIDISink>(), t); - - active_nodes.erase(i); - - break; - } - } - } - } + if (ev_size > 0) + builder->event(0, ev_size, buf); } - - // Resolve any stuck notes when the rest of the machine is finished - if ( ! active_nodes.empty()) { - for (list<SharedPtr<Node> >::iterator i = active_nodes.begin(); i != active_nodes.end(); ++i) { - cerr << "WARNING: Resolving stuck note from MIDI file." << endl; - SharedPtr<MidiAction> action = PtrCast<MidiAction>((*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) { - unsigned char note_off[3] = { ((MIDI_CMD_NOTE_OFF & 0xF0) | (ev[0] & 0x0F)), ev[1], 0x40 }; - (*i)->set_exit_action(SharedPtr<Action>(new MidiAction(3, note_off))); - (*i)->set_duration(t - (*i)->enter_time()); - (*i)->exit(SharedPtr<Raul::MIDISink>(), t); - m->add_node((*i)); - ++added_nodes; - } - } - active_nodes.clear(); - } - - if (m->nodes().find(initial_node) == m->nodes().end()) - m->add_node(initial_node); + builder->resolve(); } diff --git a/src/engine/machina/Driver.hpp b/src/engine/machina/Driver.hpp index a1a38a2..bf4d2be 100644 --- a/src/engine/machina/Driver.hpp +++ b/src/engine/machina/Driver.hpp @@ -39,6 +39,10 @@ public: virtual void activate() {} virtual void deactivate() {} + virtual bool recording() { return false; } + virtual void start_record() {} + virtual void finish_record() {} + protected: SharedPtr<Machine> _machine; }; diff --git a/src/engine/machina/JackDriver.hpp b/src/engine/machina/JackDriver.hpp index a048c0c..47b718d 100644 --- a/src/engine/machina/JackDriver.hpp +++ b/src/engine/machina/JackDriver.hpp @@ -22,11 +22,12 @@ #include <raul/JackDriver.h> #include <raul/SharedPtr.h> #include <raul/DoubleBuffer.h> +#include <raul/MIDIRingBuffer.h> #include <raul/Semaphore.h> #include <jack/midiport.h> #include "Machine.hpp" #include "Driver.hpp" - +#include "Recorder.hpp" namespace Machina { @@ -61,6 +62,10 @@ public: void set_bpm(double bpm) { _bpm.set(bpm); } void set_quantization(double quantization) { _quantization.set(quantization); } + bool recording() { return _recording.get(); } + void start_record(); + void finish_record(); + private: void process_input(SharedPtr<Machine> machine, const Raul::TimeSlice& time); @@ -76,6 +81,10 @@ private: Raul::DoubleBuffer<double> _bpm; Raul::DoubleBuffer<double> _quantization; + + Raul::TickTime _record_time; + Raul::AtomicInt _recording; + SharedPtr<Recorder> _recorder; }; diff --git a/src/engine/machina/MachineBuilder.hpp b/src/engine/machina/MachineBuilder.hpp new file mode 100644 index 0000000..a01ebd2 --- /dev/null +++ b/src/engine/machina/MachineBuilder.hpp @@ -0,0 +1,69 @@ +/* This file is part of Machina. + * Copyright (C) 2007 Dave Robillard <http://drobilla.net> + * + * Machina is free software; you can redistribute it and/or modify it under the + * terms of the GNU General Public License as published by the Free Software + * Foundation; either version 2 of the License, or (at your option) any later + * version. + * + * Machina is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef MACHINA_MACHINEBUILDER_HPP +#define MACHINA_MACHINEBUILDER_HPP + +#include <list> +#include <raul/types.h> +#include <raul/SharedPtr.h> + +namespace Machina { + +class Machine; +class Node; + + +class MachineBuilder { +public: + MachineBuilder(SharedPtr<Machine> machine); + + void set_time(Raul::BeatTime time) { _time = time; } + + void event(Raul::BeatTime time_offset, size_t size, unsigned char* buf); + + void reset(); + void resolve(); + + SharedPtr<Machine> finish(); + +private: + bool is_delay_node(SharedPtr<Node> node) const; + + SharedPtr<Node> + connect_nodes(SharedPtr<Machine> m, + SharedPtr<Node> tail, Raul::BeatTime tail_end_time, + SharedPtr<Node> head, Raul::BeatTime head_start_time); + + typedef std::list<SharedPtr<Node> > ActiveList; + ActiveList _active_nodes; + + typedef std::list<std::pair<Raul::BeatTime, SharedPtr<Node> > > PolyList; + PolyList _poly_nodes; + + Raul::BeatTime _time; + + SharedPtr<Machine> _machine; + SharedPtr<Node> _initial_node; + SharedPtr<Node> _connect_node; + Raul::BeatTime _connect_node_end_time; +}; + + +} // namespace Machina + +#endif // MACHINA_MACHINEBUILDER_HPP diff --git a/src/engine/machina/Makefile.am b/src/engine/machina/Makefile.am index ce19080..5907be5 100644 --- a/src/engine/machina/Makefile.am +++ b/src/engine/machina/Makefile.am @@ -11,7 +11,9 @@ libmachinainclude_HEADERS = \ ActionFactory.hpp \ Driver.hpp \ LearnRequest.hpp \ - Engine.hpp + Engine.hpp \ + Recorder.hpp \ + MachineBuilder.hpp if WITH_JACK JackDriver.hpp diff --git a/src/engine/machina/Recorder.hpp b/src/engine/machina/Recorder.hpp new file mode 100644 index 0000000..b969df0 --- /dev/null +++ b/src/engine/machina/Recorder.hpp @@ -0,0 +1,53 @@ +/* This file is part of Machina. + * Copyright (C) 2007 Dave Robillard <http://drobilla.net> + * + * Machina is free software; you can redistribute it and/or modify it under the + * terms of the GNU General Public License as published by the Free Software + * Foundation; either version 2 of the License, or (at your option) any later + * version. + * + * Machina is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef MACHINA_RECORDER_HPP +#define MACHINA_RECORDER_HPP + +#include <raul/types.h> +#include <raul/Slave.h> +#include <raul/SharedPtr.h> +#include <raul/MIDIRingBuffer.h> +#include "Machine.hpp" + +namespace Machina { + +class MachineBuilder; + + +class Recorder : public Raul::Slave { +public: + Recorder(size_t buffer_size, double tick_rate); + + inline void write(Raul::TickTime time, size_t size, const unsigned char* buf) { + _record_buffer.write(time, size, buf); + } + + SharedPtr<Machine> finish(); + +private: + virtual void _whipped(); + + double _tick_rate; + Raul::MIDIRingBuffer _record_buffer; + SharedPtr<MachineBuilder> _builder; +}; + + +} // namespace Machina + +#endif // MACHINA_RECORDER_HPP diff --git a/src/engine/machina/SMFDriver.hpp b/src/engine/machina/SMFDriver.hpp index 7fec256..7b22a26 100644 --- a/src/engine/machina/SMFDriver.hpp +++ b/src/engine/machina/SMFDriver.hpp @@ -25,6 +25,7 @@ #include <raul/SMFReader.h> #include "machina/types.hpp" #include "machina/Driver.hpp" +#include "machina/MachineBuilder.hpp" namespace Machina { @@ -37,8 +38,14 @@ class SMFDriver : public Driver, public: SMFDriver(SharedPtr<Machine> machine = SharedPtr<Machine>()); - SharedPtr<Machine> learn(const std::string& filename, double q=0.0, Raul::BeatTime max_duration=0); - SharedPtr<Machine> learn(const std::string& filename, unsigned track, double q=0.0, Raul::BeatTime max_duration=0); + SharedPtr<Machine> learn(const std::string& filename, + double q=0.0, + Raul::BeatTime max_duration=0); + + SharedPtr<Machine> learn(const std::string& filename, + unsigned track, + double q=0.0, + Raul::BeatTime max_duration=0); void run(SharedPtr<Machine> machine, Raul::BeatTime max_time); @@ -54,19 +61,12 @@ public: private: SharedPtr<Raul::SMFWriter> _writer; - - bool is_delay_node(SharedPtr<Node> node) const; - - SharedPtr<Node> - connect_nodes(SharedPtr<Machine> m, - SharedPtr<Node> tail, Raul::BeatTime tail_end_time, - SharedPtr<Node> head, Raul::BeatTime head_start_time); - void learn_track(SharedPtr<Machine> machine, - Raul::SMFReader& reader, - unsigned track, - double q, - Raul::BeatTime max_duration=0); + void learn_track(SharedPtr<MachineBuilder> builder, + Raul::SMFReader& reader, + unsigned track, + double q, + Raul::BeatTime max_duration=0); }; diff --git a/src/gui/MachinaCanvas.cpp b/src/gui/MachinaCanvas.cpp index 134b885..f2a8ebf 100644 --- a/src/gui/MachinaCanvas.cpp +++ b/src/gui/MachinaCanvas.cpp @@ -230,7 +230,7 @@ MachinaCanvas::build(SharedPtr<Machina::Machine> machine) Gtk::Main::iteration(false); } - arrange(); + //arrange(); /* while (Gtk::Main::events_pending()) Gtk::Main::iteration(false); diff --git a/src/gui/MachinaGUI.cpp b/src/gui/MachinaGUI.cpp index de432aa..cf0b07f 100644 --- a/src/gui/MachinaGUI.cpp +++ b/src/gui/MachinaGUI.cpp @@ -71,6 +71,9 @@ MachinaGUI::MachinaGUI(SharedPtr<Machina::Engine> engine) xml->get_widget("bpm_spinbutton", _bpm_spinbutton); xml->get_widget("quantize_checkbutton", _quantize_checkbutton); xml->get_widget("quantize_spinbutton", _quantize_spinbutton); + xml->get_widget("record_but", _record_button); + xml->get_widget("stop_but", _stop_button); + xml->get_widget("play_but", _play_button); xml->get_widget("zoom_normal_but", _zoom_normal_button); xml->get_widget("zoom_full_but", _zoom_full_button); xml->get_widget("arrange_but", _arrange_button); @@ -83,11 +86,16 @@ MachinaGUI::MachinaGUI(SharedPtr<Machina::Engine> engine) //_zoom_slider->signal_value_changed().connect(sigc::mem_fun(this, &MachinaGUI::zoom_changed)); + _record_button->signal_toggled().connect(sigc::mem_fun(this, &MachinaGUI::record_toggled)); + _stop_button->signal_clicked().connect(sigc::mem_fun(this, &MachinaGUI::stop_clicked)); + _play_button->signal_toggled().connect(sigc::mem_fun(this, &MachinaGUI::play_toggled)); + _zoom_normal_button->signal_clicked().connect(sigc::bind( sigc::mem_fun(this, &MachinaGUI::zoom), 1.0)); _zoom_full_button->signal_clicked().connect(sigc::mem_fun(_canvas.get(), &MachinaCanvas::zoom_full)); _arrange_button->signal_clicked().connect(sigc::mem_fun(_canvas.get(), &MachinaCanvas::arrange)); + _menu_file_open->signal_activate().connect( sigc::mem_fun(this, &MachinaGUI::menu_file_open)); @@ -226,6 +234,8 @@ MachinaGUI::scrolled_window_event(GdkEvent* event) void MachinaGUI::update_toolbar() { + _record_button->set_active(_engine->driver()->recording()); + _play_button->set_active(true); _bpm_spinbutton->set_sensitive(_bpm_radiobutton->get_active()); _quantize_spinbutton->set_sensitive(_quantize_checkbutton->get_active()); } @@ -612,3 +622,32 @@ MachinaGUI::menu_help_help() _help_dialog->hide(); } + +void +MachinaGUI::record_toggled() +{ + if (_record_button->get_active() && ! _engine->driver()->recording()) { + _engine->driver()->start_record(); + } else if (_engine->driver()->recording()) { + _engine->driver()->finish_record(); + _canvas->build(_engine->machine()); + } +} + + +void +MachinaGUI::stop_clicked() +{ + if (_engine->driver()->recording()) + _engine->driver()->finish_record(); + + update_toolbar(); +} + + +void +MachinaGUI::play_toggled() +{ +} + + diff --git a/src/gui/MachinaGUI.hpp b/src/gui/MachinaGUI.hpp index 5dd943f..96fb772 100644 --- a/src/gui/MachinaGUI.hpp +++ b/src/gui/MachinaGUI.hpp @@ -73,6 +73,10 @@ protected: void update_toolbar(); bool scrolled_window_event(GdkEvent* ev); + void record_toggled(); + void stop_clicked(); + void play_toggled(); + void on_pane_position_changed(); void on_messages_expander_changed(); @@ -94,34 +98,37 @@ protected: Gtk::Main* _gtk_main; - Gtk::Window* _main_window; - Gtk::Dialog* _help_dialog; - Gtk::AboutDialog* _about_window; - Gtk::Toolbar* _toolbar; - Gtk::MenuItem* _menu_file_open; - Gtk::MenuItem* _menu_file_save; - Gtk::MenuItem* _menu_file_save_as; - Gtk::MenuItem* _menu_file_quit; - Gtk::MenuItem* _menu_import_midi; - Gtk::MenuItem* _menu_export_midi; - Gtk::MenuItem* _menu_export_graphviz; - Gtk::MenuItem* _menu_help_about; - Gtk::CheckMenuItem* _menu_view_toolbar; - //Gtk::CheckMenuItem* _menu_view_messages; - Gtk::MenuItem* _menu_view_refresh; - Gtk::MenuItem* _menu_help_help; - Gtk::ScrolledWindow* _canvas_scrolledwindow; - Gtk::TextView* _status_text; - Gtk::Paned* _main_paned; - Gtk::Expander* _messages_expander; - Gtk::RadioButton* _slave_radiobutton; - Gtk::RadioButton* _bpm_radiobutton; - Gtk::SpinButton* _bpm_spinbutton; - Gtk::CheckButton* _quantize_checkbutton; - Gtk::SpinButton* _quantize_spinbutton; - Gtk::ToolButton* _zoom_normal_button; - Gtk::ToolButton* _zoom_full_button; - Gtk::ToolButton* _arrange_button; + Gtk::Window* _main_window; + Gtk::Dialog* _help_dialog; + Gtk::AboutDialog* _about_window; + Gtk::Toolbar* _toolbar; + Gtk::MenuItem* _menu_file_open; + Gtk::MenuItem* _menu_file_save; + Gtk::MenuItem* _menu_file_save_as; + Gtk::MenuItem* _menu_file_quit; + Gtk::MenuItem* _menu_import_midi; + Gtk::MenuItem* _menu_export_midi; + Gtk::MenuItem* _menu_export_graphviz; + Gtk::MenuItem* _menu_help_about; + Gtk::CheckMenuItem* _menu_view_toolbar; + //Gtk::CheckMenuItem* _menu_view_messages; + Gtk::MenuItem* _menu_view_refresh; + Gtk::MenuItem* _menu_help_help; + Gtk::ScrolledWindow* _canvas_scrolledwindow; + Gtk::TextView* _status_text; + Gtk::Paned* _main_paned; + Gtk::Expander* _messages_expander; + Gtk::RadioButton* _slave_radiobutton; + Gtk::RadioButton* _bpm_radiobutton; + Gtk::SpinButton* _bpm_spinbutton; + Gtk::CheckButton* _quantize_checkbutton; + Gtk::SpinButton* _quantize_spinbutton; + Gtk::ToggleToolButton* _record_button; + Gtk::ToolButton* _stop_button; + Gtk::ToggleToolButton* _play_button; + Gtk::ToolButton* _zoom_normal_button; + Gtk::ToolButton* _zoom_full_button; + Gtk::ToolButton* _arrange_button; }; #endif // MACHINA_GUI_H diff --git a/src/gui/machina.glade b/src/gui/machina.glade index 5b12fa1..01f8ec1 100644 --- a/src/gui/machina.glade +++ b/src/gui/machina.glade @@ -238,6 +238,63 @@ <property name="show_arrow">True</property> <child> + <widget class="GtkToggleToolButton" id="record_but"> + <property name="visible">True</property> + <property name="stock_id">gtk-media-record</property> + <property name="visible_horizontal">True</property> + <property name="visible_vertical">True</property> + <property name="is_important">False</property> + <property name="active">False</property> + </widget> + <packing> + <property name="expand">False</property> + <property name="homogeneous">True</property> + </packing> + </child> + + <child> + <widget class="GtkToolButton" id="stop_but"> + <property name="visible">True</property> + <property name="stock_id">gtk-media-stop</property> + <property name="visible_horizontal">True</property> + <property name="visible_vertical">True</property> + <property name="is_important">False</property> + </widget> + <packing> + <property name="expand">False</property> + <property name="homogeneous">True</property> + </packing> + </child> + + <child> + <widget class="GtkToggleToolButton" id="play_but"> + <property name="visible">True</property> + <property name="stock_id">gtk-media-play</property> + <property name="visible_horizontal">True</property> + <property name="visible_vertical">True</property> + <property name="is_important">False</property> + <property name="active">False</property> + </widget> + <packing> + <property name="expand">False</property> + <property name="homogeneous">True</property> + </packing> + </child> + + <child> + <widget class="GtkSeparatorToolItem" id="separatortoolitem4"> + <property name="visible">True</property> + <property name="draw">True</property> + <property name="visible_horizontal">True</property> + <property name="visible_vertical">True</property> + </widget> + <packing> + <property name="expand">False</property> + <property name="homogeneous">False</property> + </packing> + </child> + + <child> <widget class="GtkToolItem" id="toolitem1"> <property name="visible">True</property> <property name="visible_horizontal">True</property> |