aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorDavid Robillard <d@drobilla.net>2007-03-31 05:28:01 +0000
committerDavid Robillard <d@drobilla.net>2007-03-31 05:28:01 +0000
commitf8883a56e44a42e97ca84392fdbff61e11540fcd (patch)
tree745eef6b5685b9f668c48b36ae6c761bee49b271 /src
parentdd6be02a7478225f19f02432919b64b96b733172 (diff)
downloadmachina-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
Diffstat (limited to 'src')
-rw-r--r--src/engine/JackDriver.cpp56
-rw-r--r--src/engine/MachineBuilder.cpp246
-rw-r--r--src/engine/Makefile.am4
-rw-r--r--src/engine/Recorder.cpp66
-rw-r--r--src/engine/SMFDriver.cpp208
-rw-r--r--src/engine/machina/Driver.hpp4
-rw-r--r--src/engine/machina/JackDriver.hpp11
-rw-r--r--src/engine/machina/MachineBuilder.hpp69
-rw-r--r--src/engine/machina/Makefile.am4
-rw-r--r--src/engine/machina/Recorder.hpp53
-rw-r--r--src/engine/machina/SMFDriver.hpp28
-rw-r--r--src/gui/MachinaCanvas.cpp2
-rw-r--r--src/gui/MachinaGUI.cpp39
-rw-r--r--src/gui/MachinaGUI.hpp63
-rw-r--r--src/gui/machina.glade57
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>