X-Git-Url: https://main.carlh.net/gitweb/?a=blobdiff_plain;f=libs%2Fardour%2Fmidi_port.cc;h=7a87c4c273f8e91dd16de8ddbbef1a53119f9feb;hb=6bc95465d6137da74907bb528861681deec7b2df;hp=5f18810122e713b84a490116fc3aa033324f0eaa;hpb=73192bc1a7ea55fa1864dc3826845b15c00dd2ec;p=ardour.git diff --git a/libs/ardour/midi_port.cc b/libs/ardour/midi_port.cc index 5f18810122..7a87c4c273 100644 --- a/libs/ardour/midi_port.cc +++ b/libs/ardour/midi_port.cc @@ -19,74 +19,148 @@ #include #include -#include "ardour/midi_port.h" +#include "pbd/compose.h" +#include "pbd/debug.h" + +#include "ardour/audioengine.h" #include "ardour/data_type.h" +#include "ardour/debug.h" +#include "ardour/midi_buffer.h" +#include "ardour/midi_port.h" +#include "ardour/session.h" -using namespace ARDOUR; using namespace std; +using namespace ARDOUR; +using namespace PBD; + +#define port_engine AudioEngine::instance()->port_engine() -MidiPort::MidiPort (const std::string& name, Flags flags) +MidiPort::MidiPort (const std::string& name, PortFlags flags) : Port (name, DataType::MIDI, flags) , _has_been_mixed_down (false) - , _resolve_in_process (false) + , _resolve_required (false) + , _input_active (true) + , _always_parse (false) + , _trace_on (false) { - _buffer = new MidiBuffer (raw_buffer_size(0)); + _buffer = new MidiBuffer (AudioEngine::instance()->raw_buffer_size (DataType::MIDI)); } MidiPort::~MidiPort() { + if (_shadow_port) { + AudioEngine::instance()->unregister_port (_shadow_port); + _shadow_port.reset (); + } + delete _buffer; } void MidiPort::cycle_start (pframes_t nframes) { + framepos_t now = AudioEngine::instance()->sample_time_at_cycle_start(); + + Port::cycle_start (nframes); + _buffer->clear (); - assert (_buffer->size () == 0); - if (sends_output ()) { - jack_midi_clear_buffer (jack_port_get_buffer (_jack_port, nframes)); + if (sends_output () && _port_handle) { + port_engine.midi_clear (port_engine.get_buffer (_port_handle, nframes)); } -} -MidiBuffer & -MidiPort::get_midi_buffer (framecnt_t nframes, framecnt_t offset) -{ - if (_has_been_mixed_down) { - return *_buffer; - } + if (_always_parse || (receives_input() && _trace_on)) { + MidiBuffer& mb (get_midi_buffer (nframes)); - if (receives_input ()) { + /* dump incoming MIDI to parser */ - void* jack_buffer = jack_port_get_buffer (_jack_port, nframes); - const pframes_t event_count = jack_midi_get_event_count(jack_buffer); + for (MidiBuffer::iterator b = mb.begin(); b != mb.end(); ++b) { + uint8_t* buf = (*b).buffer(); - assert (event_count < _buffer->capacity()); + _self_parser.set_timestamp (now + (*b).time()); - /* suck all relevant MIDI events from the JACK MIDI port buffer - into our MidiBuffer - */ + uint32_t limit = (*b).size(); - for (pframes_t i = 0; i < event_count; ++i) { + for (size_t n = 0; n < limit; ++n) { + _self_parser.scanner (buf[n]); + } + } + } - jack_midi_event_t ev; + if (inbound_midi_filter) { + MidiBuffer& mb (get_midi_buffer (nframes)); + inbound_midi_filter (mb, mb); + } - jack_midi_event_get (&ev, jack_buffer, i); + if (_shadow_port) { + MidiBuffer& mb (get_midi_buffer (nframes)); + if (shadow_midi_filter (mb, _shadow_port->get_midi_buffer (nframes))) { + _shadow_port->flush_buffers (nframes); + } + } - if (ev.buffer[0] == 0xfe) { - /* throw away active sensing */ - continue; - } +} - if (ev.time >= offset && ev.time < (offset + nframes)) { - _buffer->push_back (ev); - } else { - cerr << "Dropping incoming MIDI at time " << ev.time << "; offset=" << offset << " limit=" << (offset + nframes) << "\n"; +Buffer& +MidiPort::get_buffer (pframes_t nframes) +{ + return get_midi_buffer (nframes); +} + +MidiBuffer & +MidiPort::get_midi_buffer (pframes_t nframes) +{ + if (_has_been_mixed_down) { + return *_buffer; + } + + if (receives_input ()) { + + if (_input_active) { + + void* buffer = port_engine.get_buffer (_port_handle, nframes); + const pframes_t event_count = port_engine.get_midi_event_count (buffer); + + /* suck all relevant MIDI events from the MIDI port buffer + into our MidiBuffer + */ + + for (pframes_t i = 0; i < event_count; ++i) { + + pframes_t timestamp; + size_t size; + uint8_t const* buf; + + port_engine.midi_event_get (timestamp, size, &buf, buffer, i); + + if (buf[0] == 0xfe) { + /* throw away active sensing */ + continue; + } + + /* check that the event is in the acceptable time range */ + if ((timestamp < (_global_port_buffer_offset + _port_buffer_offset)) || + (timestamp >= (_global_port_buffer_offset + _port_buffer_offset + nframes))) { + cerr << "Dropping incoming MIDI at time " << timestamp << "; offset=" + << _global_port_buffer_offset << " limit=" + << (_global_port_buffer_offset + _port_buffer_offset + nframes) << "\n"; + continue; + } + + if ((buf[0] & 0xF0) == 0x90 && buf[2] == 0) { + /* normalize note on with velocity 0 to proper note off */ + uint8_t ev[3]; + ev[0] = 0x80 | (buf[0] & 0x0F); /* note off */ + ev[1] = buf[1]; + ev[2] = 0x40; /* default velocity */ + _buffer->push_back (timestamp, size, ev); + } else { + _buffer->push_back (timestamp, size, buf); + } } - } - if (nframes) { - _has_been_mixed_down = true; + } else { + _buffer->silence (nframes); } } else { @@ -100,7 +174,6 @@ MidiPort::get_midi_buffer (framecnt_t nframes, framecnt_t offset) return *_buffer; } - void MidiPort::cycle_end (pframes_t /*nframes*/) { @@ -114,51 +187,185 @@ MidiPort::cycle_split () } void -MidiPort::flush_buffers (pframes_t nframes, framepos_t time, framecnt_t offset) +MidiPort::resolve_notes (void* port_buffer, MidiBuffer::TimeType when) +{ + for (uint8_t channel = 0; channel <= 0xF; channel++) { + + uint8_t ev[3] = { ((uint8_t) (MIDI_CMD_CONTROL | channel)), MIDI_CTL_SUSTAIN, 0 }; + + /* we need to send all notes off AND turn the + * sustain/damper pedal off to handle synths + * that prioritize sustain over AllNotesOff + */ + + if (port_engine.midi_event_put (port_buffer, when, ev, 3) != 0) { + cerr << "failed to deliver sustain-zero on channel " << (int)channel << " on port " << name() << endl; + } + + ev[1] = MIDI_CTL_ALL_NOTES_OFF; + + if (port_engine.midi_event_put (port_buffer, when, ev, 3) != 0) { + cerr << "failed to deliver ALL NOTES OFF on channel " << (int)channel << " on port " << name() << endl; + } + } +} + +void +MidiPort::flush_buffers (pframes_t nframes) { if (sends_output ()) { - void* jack_buffer = jack_port_get_buffer (_jack_port, nframes); + void* port_buffer = 0; - // Feed the data through the MidiStateTracker - bool did_loop; + if (_resolve_required) { + port_buffer = port_engine.get_buffer (_port_handle, nframes); + /* resolve all notes at the start of the buffer */ + resolve_notes (port_buffer, _global_port_buffer_offset); + _resolve_required = false; + } - _midi_state_tracker.track (_buffer->begin(), _buffer->end(), did_loop); + if (_buffer->empty()) { + return; + } - if (did_loop || _resolve_in_process) { - /* add necessary note offs */ - _midi_state_tracker.resolve_notes (*_buffer, time); + if (!port_buffer) { + port_buffer = port_engine.get_buffer (_port_handle, nframes); } - _resolve_in_process = false; for (MidiBuffer::iterator i = _buffer->begin(); i != _buffer->end(); ++i) { - const Evoral::Event& ev = *i; + + const Evoral::Event ev (*i, false); + + + if (sends_output() && _trace_on) { + uint8_t const * const buf = ev.buffer(); + const framepos_t now = AudioEngine::instance()->sample_time_at_cycle_start(); + + _self_parser.set_timestamp (now + ev.time()); + + uint32_t limit = ev.size(); + + for (size_t n = 0; n < limit; ++n) { + _self_parser.scanner (buf[n]); + } + } + // event times are in frames, relative to cycle start - assert (ev.time() < (nframes + offset)); +#ifndef NDEBUG + if (DEBUG_ENABLED (DEBUG::MidiIO)) { + const Session* s = AudioEngine::instance()->session(); + const framepos_t now = (s ? s->transport_frame() : 0); + DEBUG_STR_DECL(a); + DEBUG_STR_APPEND(a, string_compose ("MidiPort %8 %1 pop event @ %2 (global %4, within %5 gpbo %6 pbo %7 sz %3 ", _buffer, ev.time(), ev.size(), + now + ev.time(), nframes, _global_port_buffer_offset, _port_buffer_offset, name())); + for (size_t i=0; i < ev.size(); ++i) { + DEBUG_STR_APPEND(a,hex); + DEBUG_STR_APPEND(a,"0x"); + DEBUG_STR_APPEND(a,(int)(ev.buffer()[i])); + DEBUG_STR_APPEND(a,' '); + } + DEBUG_STR_APPEND(a,'\n'); + DEBUG_TRACE (DEBUG::MidiIO, DEBUG_STR(a).str()); + } +#endif + + assert (ev.time() < (nframes + _global_port_buffer_offset + _port_buffer_offset)); - if (ev.time() >= offset) { - if (jack_midi_event_write (jack_buffer, (jack_nframes_t) ev.time(), ev.buffer(), ev.size()) != 0) { - cerr << "write failed, drop flushed note off on the floor, time " << ev.time() << " > " << offset << endl; - } - } else { - cerr << "drop flushed event on the floor, time " << ev.time() << " < " << offset << endl; - } + if (ev.time() >= _global_port_buffer_offset + _port_buffer_offset) { + if (port_engine.midi_event_put (port_buffer, (pframes_t) ev.time(), ev.buffer(), ev.size()) != 0) { + cerr << "write failed, drop flushed note off on the floor, time " + << ev.time() << " > " << _global_port_buffer_offset + _port_buffer_offset << endl; + } + } else { + cerr << "drop flushed event on the floor, time " << ev.time() + << " too early for " << _global_port_buffer_offset + << " + " << _port_buffer_offset; + for (size_t xx = 0; xx < ev.size(); ++xx) { + cerr << ' ' << hex << (int) ev.buffer()[xx]; + } + cerr << dec << endl; + } } + + /* done.. the data has moved to the port buffer, mark it so + */ + + _buffer->clear (); } } +void +MidiPort::require_resolve () +{ + _resolve_required = true; +} + void MidiPort::transport_stopped () { - _resolve_in_process = true; + _resolve_required = true; +} + +void +MidiPort::realtime_locate () +{ + _resolve_required = true; +} + +void +MidiPort::reset () +{ + Port::reset (); + delete _buffer; + cerr << name() << " new MIDI buffer of size " << AudioEngine::instance()->raw_buffer_size (DataType::MIDI) << endl; + _buffer = new MidiBuffer (AudioEngine::instance()->raw_buffer_size (DataType::MIDI)); } -size_t -MidiPort::raw_buffer_size (pframes_t nframes) const +void +MidiPort::set_input_active (bool yn) +{ + _input_active = yn; +} + +void +MidiPort::set_always_parse (bool yn) +{ + _always_parse = yn; +} + +void +MidiPort::set_trace_on (bool yn) { - return jack_midi_max_event_size (jack_port_get_buffer (_jack_port, nframes)); + _trace_on = yn; } +int +MidiPort::add_shadow_port (string const & name, MidiFilter mf) +{ + if (!ARDOUR::Port::receives_input()) { + return -1; + } + + if (_shadow_port) { + return -2; + } + + shadow_midi_filter = mf; + + if (!(_shadow_port = boost::dynamic_pointer_cast (AudioEngine::instance()->register_output_port (DataType::MIDI, name, false, PortFlags (Shadow|IsTerminal))))) { + return -3; + } + + /* forward on our port latency to the shadow port. + + XXX: need to capture latency changes and forward them too. + */ + + LatencyRange latency = private_latency_range (false); + _shadow_port->set_private_latency_range (latency, false); + + return 0; +}