/* This file is part of Machina. * Copyright 2007-2011 David 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 3 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 more details. * * You should have received a copy of the GNU General Public License * along with Machina. If not, see . */ #include #include #include "machina/Context.hpp" #include "machina/URIs.hpp" #include "machina/Updates.hpp" #include "machina_config.h" #include "JackDriver.hpp" #include "LearnRequest.hpp" #include "MidiAction.hpp" using namespace machina; using namespace std; namespace machina { JackDriver::JackDriver(Raul::Forge& forge, SPtr machine) : Driver(forge, machine) , _client(NULL) , _machine_changed(0) , _input_port(NULL) , _output_port(NULL) , _context(forge, 48000, MACHINA_PPQN, 120.0) , _frames_unit(TimeUnit::FRAMES, 48000) , _beats_unit(TimeUnit::BEATS, 19200) , _bpm(120.0) , _quantization(0.0f) , _stop(0) , _stop_flag(false) , _record_dur(_frames_unit) , // = 0 _recording(false) , _is_activated(false) { _context.set_sink(this); } JackDriver::~JackDriver() { detach(); } void JackDriver::attach(const std::string& client_name) { // Already connected if (_client) { return; } jack_set_error_function(jack_error_cb); _client = jack_client_open(client_name.c_str(), JackNullOption, NULL, NULL); if (_client == NULL) { _is_activated = false; } else { jack_set_error_function(jack_error_cb); jack_on_shutdown(_client, jack_shutdown_cb, this); jack_set_process_callback(_client, jack_process_cb, this); } if (jack_client()) { _context.time().set_tick_rate(sample_rate()); _input_port = jack_port_register( jack_client(), "in", JACK_DEFAULT_MIDI_TYPE, JackPortIsInput, 0); if (!_input_port) { std::cerr << "WARNING: Failed to create MIDI input port." << std::endl; } _output_port = jack_port_register( jack_client(), "out", JACK_DEFAULT_MIDI_TYPE, JackPortIsOutput, 0); if (!_output_port) { std::cerr << "WARNING: Failed to create MIDI output port." << std::endl; } if (!_machine) { _machine = SPtr( new Machine(TimeUnit::frames( jack_get_sample_rate(jack_client())))); } _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; } if (_client) { deactivate(); jack_client_close(_client); _client = NULL; _is_activated = false; } } void JackDriver::activate() { if (!jack_activate(_client)) { _is_activated = true; } else { _is_activated = false; } } void JackDriver::deactivate() { if (_client) { jack_deactivate(_client); } _is_activated = false; } void JackDriver::set_machine(SPtr machine) { if (machine == _machine) { return; } SPtr last_machine = _last_machine; // Keep a reference _machine_changed.reset(0); assert(!last_machine.unique()); _machine = machine; if (is_activated()) { _machine_changed.wait(); } assert(_machine == machine); last_machine.reset(); } void JackDriver::read_input_recording(SPtr machine, const Raul::TimeSlice& time) { const jack_nframes_t nframes = time.length_ticks().ticks(); void* buf = jack_port_get_buffer(_input_port, nframes); const jack_nframes_t n_events = jack_midi_get_event_count(buf); for (jack_nframes_t i = 0; i < n_events; ++i) { jack_midi_event_t ev; jack_midi_event_get(&ev, buf, i); const TimeStamp rel_time_frames = TimeStamp(_frames_unit, ev.time); const TimeStamp time_frames = _record_dur + rel_time_frames; _recorder->write(time.ticks_to_beats(time_frames), ev.size, ev.buffer); } if (n_events > 0) { _recorder->whip(); } _record_dur += time.length_ticks(); } void JackDriver::read_input_playing(SPtr machine, const Raul::TimeSlice& time) { const jack_nframes_t nframes = time.length_ticks().ticks(); void* buf = jack_port_get_buffer(_input_port, nframes); const jack_nframes_t n_events = jack_midi_get_event_count(buf); for (jack_nframes_t i = 0; i < n_events; ++i) { jack_midi_event_t ev; jack_midi_event_get(&ev, buf, i); if (ev.buffer[0] == 0x90) { const SPtr learn = machine->pending_learn(); if (learn) { learn->enter_action()->set_event(ev.size, ev.buffer); learn->start(_quantization.get(), TimeStamp(TimeUnit::frames(sample_rate()), jack_last_frame_time(_client) + ev.time, 0)); } } else if (ev.buffer[0] == 0x80) { const SPtr learn = machine->pending_learn(); if (learn && learn->started()) { learn->exit_action()->set_event(ev.size, ev.buffer); learn->finish( TimeStamp(TimeUnit::frames(sample_rate()), jack_last_frame_time(_client) + ev.time, 0)); const uint64_t id = Stateful::next_id(); write_set(_updates, id, URIs::instance().rdf_type, _forge.make_urid(URIs::instance(). machina_MidiAction)); write_set(_updates, learn->node()->id(), URIs::instance().machina_enter_action, _forge.make((int32_t)id)); write_set(_updates, id, URIs::instance().machina_note_number, _forge.make((int32_t)ev.buffer[1])); machine->clear_pending_learn(); } } } } void JackDriver::write_event(Raul::TimeStamp time, size_t size, const byte* event) { if (!_output_port) { return; } const Raul::TimeSlice& slice = _context.time(); if (slice.beats_to_ticks(time) + slice.offset_ticks() < slice.start_ticks()) { std::cerr << "ERROR: Missed event by " << slice.start_ticks() - (slice.beats_to_ticks(time) + slice.offset_ticks()) << " ticks" << "\n\tbpm: " << slice.bpm() << "\n\tev time: " << slice.beats_to_ticks(time) << "\n\tcycle_start: " << slice.start_ticks() << "\n\tcycle_end: " << slice.start_ticks() + slice.length_ticks() << "\n\tcycle_length: " << slice.length_ticks() << std::endl << std::endl; return; } const TimeDuration nframes = slice.length_ticks(); const TimeStamp offset = slice.beats_to_ticks(time) + slice.offset_ticks() - slice.start_ticks(); if (!(offset < slice.offset_ticks() + nframes)) { std::cerr << "ERROR: Event offset " << offset << " outside cycle " << "\n\tbpm: " << slice.bpm() << "\n\tev time: " << slice.beats_to_ticks(time) << "\n\tcycle_start: " << slice.start_ticks() << "\n\tcycle_end: " << slice.start_ticks() + slice.length_ticks() << "\n\tcycle_length: " << slice.length_ticks() << std::endl; } else { #ifdef JACK_MIDI_NEEDS_NFRAMES jack_midi_event_write( jack_port_get_buffer(_output_port, nframes), offset, event, size, nframes); #else jack_midi_event_write( jack_port_get_buffer(_output_port, nframes.ticks()), offset.ticks(), event, size); #endif } } void JackDriver::on_process(jack_nframes_t nframes) { _context.time().set_bpm(_bpm.get()); 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 TimeStamp length_ticks(TimeStamp(_context.time().ticks_unit(), nframes)); TimeStamp length_beats(_context.time().ticks_to_beats(length_ticks)); _context.time().set_length(length_ticks); _context.time().set_offset(TimeStamp(_context.time().ticks_unit(), 0, 0)); /* Take a reference to machine here and use only it during the process * cycle so _machine can be switched with set_machine during a cycle. */ SPtr machine = _machine; // Machine was switched since last cycle, finalize old machine. if (machine != _last_machine) { if (_last_machine) { assert(!_last_machine.unique()); // Realtime, can't delete _last_machine->reset(_context.sink(), _last_machine->time()); // Exit all active states _last_machine.reset(); // Cut our reference } _machine_changed.post(); // Signal we're done with it } if (!machine) { _last_machine = machine; return; } if (_stop_flag) { machine->reset(_context.sink(), _context.time().start_beats()); } if (_recording) { read_input_recording(machine, _context.time()); } else { read_input_playing(machine, _context.time()); } if (machine->is_empty() || !machine->is_activated()) { goto end; } while (true) { const uint32_t run_dur_frames = machine->run(_context, _updates); if (run_dur_frames == 0) { // Machine didn't run at all (machine has no initial states) machine->reset(_context.sink(), machine->time()); // Try again next cycle _context.time().set_slice(TimeStamp(_frames_unit, 0, 0), TimeStamp(_frames_unit, 0, 0)); break; } else if (machine->is_finished()) { // Machine ran for portion of cycle and is finished machine->reset(_context.sink(), machine->time()); _context.time().set_slice(TimeStamp(_frames_unit, 0, 0), TimeStamp(_frames_unit, nframes - run_dur_frames, 0)); _context.time().set_offset(TimeStamp(_frames_unit, run_dur_frames, 0)); } else { // Machine ran for entire cycle _context.time().set_slice( _context.time().start_ticks() + _context.time().length_ticks(), TimeStamp(_frames_unit, 0, 0)); break; } } end: /* Remember the last machine run, in case a switch happens and * we need to finalize it next cycle. */ _last_machine = machine; if (_stop_flag) { _context.time().set_slice(TimeStamp(_frames_unit, 0, 0), TimeStamp(_frames_unit, 0, 0)); _stop_flag = false; _stop.post(); } } void JackDriver::stop() { if (recording()) { finish_record(); } _stop_flag = true; _stop.wait(); _machine->deactivate(); } void JackDriver::start_record(bool step) { // FIXME: Choose an appropriate maximum ringbuffer size _recorder = SPtr( new Recorder(_forge, 1024, _beats_unit, _quantization.get(), step)); _recorder->start(); _record_dur = 0; _recording = true; } void JackDriver::finish_record() { _recording = false; SPtr machine = _recorder->finish(); _recorder.reset(); machine->activate(); _machine->nodes().insert(machine->nodes().begin(), machine->nodes().end()); } int JackDriver::jack_process_cb(jack_nframes_t nframes, void* jack_driver) { JackDriver* me = reinterpret_cast(jack_driver); assert(me); me->on_process(nframes); return 0; } void JackDriver::jack_shutdown_cb(void* jack_driver) { JackDriver* me = reinterpret_cast(jack_driver); assert(me); me->_client = NULL; } void JackDriver::jack_error_cb(const char* msg) { cerr << "[JACK] Error: " << msg << endl; } } // namespace machina