/* This file is part of Machina. * Copyright (C) 2007 Dave Robillard * * Machina is free software; you can redistribute it and/or modify it under the * terms of the GNU General Public License as published by the Free Software * Foundation; either version 2 of the License, or (at your option) any later * version. * * Machina is distributed in the hope that it will be useful, but WITHOUT ANY * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. See the GNU General Public License for details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include "machina/JackDriver.hpp" #include "machina/MidiAction.hpp" #include "../config.h" #include "jack_compat.h" using namespace Raul; namespace Machina { JackDriver::JackDriver(SharedPtr machine) : Driver(machine) , _machine_changed(0) , _input_port(NULL) , _output_port(NULL) , _cycle_time(1/48000.0, 120.0) , _bpm(120.0) , _quantization(0.0) , _recording(0) { if (!_machine) _machine = SharedPtr(new Machine()); } JackDriver::~JackDriver() { detach(); } void JackDriver::attach(const std::string& client_name) { Raul::JackDriver::attach(client_name); if (jack_client()) { _cycle_time.set_tick_rate(1/(double)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; _machine->activate(); } } void JackDriver::detach() { _machine->deactivate(); 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; } Raul::JackDriver::detach(); } void JackDriver::set_machine(SharedPtr machine) { SharedPtr 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::process_input(SharedPtr 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 (_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); for (jack_nframes_t i=0; i < event_count; ++i) { jack_midi_event_t ev; jack_midi_event_get(&ev, jack_buffer, i); _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); for (jack_nframes_t i=0; i < event_count; ++i) { jack_midi_event_t ev; jack_midi_event_get(&ev, jack_buffer, i); if (ev.buffer[0] == 0x90) { const SharedPtr learn = machine->pending_learn(); if (learn) { learn->enter_action()->set_event(ev.size, ev.buffer); learn->start(_quantization.get(), time.ticks_to_beats(jack_last_frame_time(_client) + ev.time)); } } else if (ev.buffer[0] == 0x80) { const SharedPtr learn = machine->pending_learn(); if (learn) { if (learn->started()) { learn->exit_action()->set_event(ev.size, ev.buffer); learn->finish( time.ticks_to_beats(jack_last_frame_time(_client) + ev.time)); machine->clear_pending_learn(); } } } } } } void JackDriver::write_event(Raul::BeatTime time, size_t size, const byte* event) throw (std::logic_error) { if (!_output_port) return; if (_cycle_time.beats_to_ticks(time) + _cycle_time.offset_ticks() < _cycle_time.start_ticks()) { std::cerr << "ERROR: Missed event by " << _cycle_time.start_ticks() - (_cycle_time.beats_to_ticks(time) + _cycle_time.offset_ticks()) << " ticks" << "\n\tbpm: " << _cycle_time.bpm() << "\n\tev time: " << _cycle_time.beats_to_ticks(time) << "\n\tcycle_start: " << _cycle_time.start_ticks() << "\n\tcycle_end: " << _cycle_time.start_ticks() + _cycle_time.length_ticks() << "\n\tcycle_length: " << _cycle_time.length_ticks() << std::endl << std::endl; return; } const TickCount nframes = _cycle_time.length_ticks(); const TickCount offset = _cycle_time.beats_to_ticks(time) + _cycle_time.offset_ticks() - _cycle_time.start_ticks(); if ( ! (offset < _cycle_time.offset_ticks() + nframes)) { std::cerr << "ERROR: Event offset " << offset << " outside cycle " << "\n\tbpm: " << _cycle_time.bpm() << "\n\tev time: " << _cycle_time.beats_to_ticks(time) << "\n\tcycle_start: " << _cycle_time.start_ticks() << "\n\tcycle_end: " << _cycle_time.start_ticks() + _cycle_time.length_ticks() << "\n\tcycle_length: " << _cycle_time.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), offset, event, size); #endif } } void JackDriver::on_process(jack_nframes_t nframes) { _cycle_time.set_bpm(_bpm.get()); // (N.B. start time set at end of previous cycle) _cycle_time.set_offset(0); _cycle_time.set_length(nframes); assert(_output_port); #ifdef JACK_MIDI_NEEDS_NFRAMES jack_midi_clear_buffer(jack_port_get_buffer(_output_port, nframes), nframes); #else jack_midi_clear_buffer(jack_port_get_buffer(_output_port, nframes)); #endif /* 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. */ SharedPtr machine = _machine; machine->set_sink(shared_from_this()); // 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->set_sink(shared_from_this()); _last_machine->reset(); // Exit all active states _last_machine.reset(); // Cut our reference } _cycle_time.set_start(0); _machine_changed.post(); // Signal we're done with it } if (_recording.get()) _record_time += nframes; if (!machine) return; process_input(machine, _cycle_time); if (machine->is_empty() || !machine->is_activated()) return; while (true) { const BeatCount run_dur_beats = machine->run(_cycle_time); const TickCount run_dur_ticks = _cycle_time.beats_to_ticks(run_dur_beats); // Machine didn't run at all (empty, or no initial states) if (run_dur_beats == 0) { machine->reset(); // Try again next cycle _cycle_time.set_start(0); return; // Machine ran for portion of cycle (finished) } else if (run_dur_ticks < _cycle_time.length_ticks()) { const TickCount finish_offset = _cycle_time.offset_ticks() + run_dur_ticks; assert(finish_offset < nframes); machine->reset(); _cycle_time.set_start(0); _cycle_time.set_length(nframes - finish_offset); _cycle_time.set_offset(finish_offset); // Machine ran for entire cycle } else { if (machine->is_finished()) { machine->reset(); _cycle_time.set_start(0); } else { _cycle_time.set_start( _cycle_time.start_ticks() + _cycle_time.length_ticks()); } break; } } /* Remember the last machine run, in case a switch happens and * we need to finalize it next cycle. */ _last_machine = machine; } void JackDriver::reset() { /* FIXME: This should signal the audio thread and wait for it * to exit all active states to resolve stuck notes, then reset. */ _machine->deactivate(); _machine->reset(); _cycle_time.set_start(0); } void JackDriver::start_record() { // FIXME: Choose an appropriate maximum ringbuffer size _recorder = SharedPtr(new Recorder( 1024, (1.0/(double)sample_rate()) * (_bpm.get() / 60.0), _quantization.get())); _recorder->start(); _record_time = 0; _recording = 1; } void JackDriver::finish_record() { _recording = 0; SharedPtr machine = _recorder->finish(); _recorder.reset(); machine->activate(); _machine->nodes().append(machine->nodes()); } } // namespace Machina