mirror of
https://github.com/Ardour/ardour.git
synced 2025-12-11 09:06:33 +01:00
CoreMIDI: fix capture/playback alignment
MIDI playback used samples instead of usec. MIDI capture used time-stamp from previous cycle. buffer-size changes were not applied to MIDI port latency
This commit is contained in:
parent
bef4596dd1
commit
e6bed9330f
3 changed files with 58 additions and 41 deletions
|
|
@ -1067,7 +1067,7 @@ CoreAudioBackend::coremidi_rediscover()
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
LatencyRange lr;
|
LatencyRange lr;
|
||||||
lr.min = lr.max = _samples_per_period; // TODO add per-port midi-systemic latency
|
lr.min = lr.max = 0; // TODO add per-port midi-systemic latency
|
||||||
set_latency_range (p, false, lr);
|
set_latency_range (p, false, lr);
|
||||||
BackendPortPtr pp = std::dynamic_pointer_cast<BackendPort>(p);
|
BackendPortPtr pp = std::dynamic_pointer_cast<BackendPort>(p);
|
||||||
pp->set_hw_port_name(_midiio->port_name(i, true));
|
pp->set_hw_port_name(_midiio->port_name(i, true));
|
||||||
|
|
@ -1090,7 +1090,7 @@ CoreAudioBackend::coremidi_rediscover()
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
LatencyRange lr;
|
LatencyRange lr;
|
||||||
lr.min = lr.max = _samples_per_period; // TODO add per-port midi-systemic latency
|
lr.min = lr.max = 0; // TODO add per-port midi-systemic latency
|
||||||
set_latency_range (p, false, lr);
|
set_latency_range (p, false, lr);
|
||||||
BackendPortPtr pp = std::dynamic_pointer_cast<BackendPort>(p);
|
BackendPortPtr pp = std::dynamic_pointer_cast<BackendPort>(p);
|
||||||
pp->set_hw_port_name(_midiio->port_name(i, false));
|
pp->set_hw_port_name(_midiio->port_name(i, false));
|
||||||
|
|
@ -1098,7 +1098,6 @@ CoreAudioBackend::coremidi_rediscover()
|
||||||
_port_change_flag.store (1);
|
_port_change_flag.store (1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
assert(_system_midi_out.size() == _midiio->n_midi_outputs());
|
assert(_system_midi_out.size() == _midiio->n_midi_outputs());
|
||||||
assert(_system_midi_in.size() == _midiio->n_midi_inputs());
|
assert(_system_midi_in.size() == _midiio->n_midi_inputs());
|
||||||
|
|
||||||
|
|
@ -1236,14 +1235,25 @@ CoreAudioBackend::get_latency_range (PortEngine::PortHandle port_handle, bool fo
|
||||||
}
|
}
|
||||||
|
|
||||||
r = port->latency_range (for_playback);
|
r = port->latency_range (for_playback);
|
||||||
if (port->is_physical() && port->is_terminal() && port->type() == DataType::AUDIO) {
|
if (port->is_physical() && port->is_terminal()) {
|
||||||
if (port->is_input() && for_playback) {
|
if (port->type() == DataType::AUDIO) {
|
||||||
r.min += _samples_per_period + _hw_audio_input_latency;
|
if (port->is_input() && for_playback) {
|
||||||
r.max += _samples_per_period + _hw_audio_input_latency;
|
r.min += _samples_per_period + _hw_audio_input_latency;
|
||||||
}
|
r.max += _samples_per_period + _hw_audio_input_latency;
|
||||||
if (port->is_output() && !for_playback) {
|
}
|
||||||
r.min += _samples_per_period + _hw_audio_output_latency;
|
if (port->is_output() && !for_playback) {
|
||||||
r.max += _samples_per_period + _hw_audio_output_latency;
|
r.min += _samples_per_period + _hw_audio_output_latency;
|
||||||
|
r.max += _samples_per_period + _hw_audio_output_latency;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (port->is_input() && for_playback) {
|
||||||
|
//r.min += _samples_per_period;
|
||||||
|
//r.max += _samples_per_period;
|
||||||
|
}
|
||||||
|
if (port->is_output() && !for_playback) {
|
||||||
|
r.min += _samples_per_period;
|
||||||
|
r.max += _samples_per_period;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return r;
|
return r;
|
||||||
|
|
@ -1440,11 +1450,12 @@ CoreAudioBackend::process_callback (const uint32_t n_samples, const uint64_t hos
|
||||||
/* port-connection change */
|
/* port-connection change */
|
||||||
pre_process();
|
pre_process();
|
||||||
|
|
||||||
// cycle-length in usec
|
|
||||||
const double nominal_time = 1e6 * n_samples / _samplerate;
|
|
||||||
|
|
||||||
clock1 = g_get_monotonic_time();
|
clock1 = g_get_monotonic_time();
|
||||||
|
|
||||||
|
//_midiio->start_cycle (AudioGetCurrentHostTime (), 1e9 * n_samples / _samplerate);
|
||||||
|
_midiio->start_cycle (host_time, 1e9 * n_samples / _samplerate);
|
||||||
|
_last_process_start = host_time;
|
||||||
|
|
||||||
/* get midi */
|
/* get midi */
|
||||||
i=0;
|
i=0;
|
||||||
for (std::vector<BackendPortPtr>::const_iterator it = _system_midi_in.begin (); it != _system_midi_in.end (); ++it, ++i) {
|
for (std::vector<BackendPortPtr>::const_iterator it = _system_midi_in.begin (); it != _system_midi_in.end (); ++it, ++i) {
|
||||||
|
|
@ -1458,7 +1469,7 @@ CoreAudioBackend::process_callback (const uint32_t n_samples, const uint64_t hos
|
||||||
|
|
||||||
port->clear_events ();
|
port->clear_events ();
|
||||||
|
|
||||||
while (_midiio->recv_event (i, nominal_time, time_ns, data, size)) {
|
while (_midiio->recv_event (i, time_ns, data, size)) {
|
||||||
pframes_t time = floor((float) time_ns * _samplerate * 1e-9);
|
pframes_t time = floor((float) time_ns * _samplerate * 1e-9);
|
||||||
assert (time < n_samples);
|
assert (time < n_samples);
|
||||||
port->parse_events (time, data, size);
|
port->parse_events (time, data, size);
|
||||||
|
|
@ -1477,9 +1488,6 @@ CoreAudioBackend::process_callback (const uint32_t n_samples, const uint64_t hos
|
||||||
memset ((*it)->get_buffer (n_samples), 0, n_samples * sizeof (Sample));
|
memset ((*it)->get_buffer (n_samples), 0, n_samples * sizeof (Sample));
|
||||||
}
|
}
|
||||||
|
|
||||||
_midiio->start_cycle();
|
|
||||||
_last_process_start = host_time;
|
|
||||||
|
|
||||||
if (engine.process_callback (n_samples)) {
|
if (engine.process_callback (n_samples)) {
|
||||||
fprintf(stderr, "ENGINE PROCESS ERROR\n");
|
fprintf(stderr, "ENGINE PROCESS ERROR\n");
|
||||||
//_pcmio->pcm_stop ();
|
//_pcmio->pcm_stop ();
|
||||||
|
|
@ -1498,7 +1506,7 @@ CoreAudioBackend::process_callback (const uint32_t n_samples, const uint64_t hos
|
||||||
for (std::vector<BackendPortPtr>::const_iterator it = _system_midi_out.begin (); it != _system_midi_out.end (); ++it, ++i) {
|
for (std::vector<BackendPortPtr>::const_iterator it = _system_midi_out.begin (); it != _system_midi_out.end (); ++it, ++i) {
|
||||||
const CoreMidiBuffer *src = std::dynamic_pointer_cast<CoreMidiPort>(*it)->const_buffer();
|
const CoreMidiBuffer *src = std::dynamic_pointer_cast<CoreMidiPort>(*it)->const_buffer();
|
||||||
for (CoreMidiBuffer::const_iterator mit = src->begin (); mit != src->end (); ++mit) {
|
for (CoreMidiBuffer::const_iterator mit = src->begin (); mit != src->end (); ++mit) {
|
||||||
_midiio->send_event (i, mit->timestamp (), mit->data (), mit->size ());
|
_midiio->send_event (i, mit->timestamp () * 1e9 / _samplerate , mit->data (), mit->size ());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -103,7 +103,7 @@ static void midiInputCallback(const MIDIPacketList *list, void *procRef, void *s
|
||||||
#endif
|
#endif
|
||||||
if (rb->write_space() > sizeof(uint32_t) + len) {
|
if (rb->write_space() > sizeof(uint32_t) + len) {
|
||||||
rb->write ((uint8_t*)&len, sizeof(uint32_t));
|
rb->write ((uint8_t*)&len, sizeof(uint32_t));
|
||||||
rb->write ((uint8_t*)p, len);
|
rb->write ((uint8_t const*)p, len);
|
||||||
}
|
}
|
||||||
#ifndef NDEBUG
|
#ifndef NDEBUG
|
||||||
else {
|
else {
|
||||||
|
|
@ -144,7 +144,9 @@ CoreMidiIo::CoreMidiIo()
|
||||||
, _rb (0)
|
, _rb (0)
|
||||||
, _n_midi_in (0)
|
, _n_midi_in (0)
|
||||||
, _n_midi_out (0)
|
, _n_midi_out (0)
|
||||||
, _time_at_cycle_start (0)
|
, _send_start (0)
|
||||||
|
, _recv_start (0)
|
||||||
|
, _recv_end (0)
|
||||||
, _active (false)
|
, _active (false)
|
||||||
, _enabled (true)
|
, _enabled (true)
|
||||||
, _run (false)
|
, _run (false)
|
||||||
|
|
@ -196,9 +198,17 @@ CoreMidiIo::cleanup()
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
CoreMidiIo::start_cycle()
|
CoreMidiIo::start_cycle (MIDITimeStamp time_at_cycle_start, double cycle_time_us)
|
||||||
{
|
{
|
||||||
_time_at_cycle_start = AudioGetCurrentHostTime();
|
_send_start = time_at_cycle_start;
|
||||||
|
_recv_end = time_at_cycle_start;
|
||||||
|
|
||||||
|
MIDITimeStamp cycle_time = AudioConvertNanosToHostTime (cycle_time_us);
|
||||||
|
if (cycle_time <= time_at_cycle_start) {
|
||||||
|
_recv_start = time_at_cycle_start - cycle_time;
|
||||||
|
} else {
|
||||||
|
_recv_start = 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
|
|
@ -242,9 +252,9 @@ CoreMidiIo::notify_proc(const MIDINotification *message)
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t
|
size_t
|
||||||
CoreMidiIo::recv_event (uint32_t port, double cycle_time_us, uint64_t &time, uint8_t *d, size_t &s)
|
CoreMidiIo::recv_event (uint32_t port, uint64_t &time, uint8_t *d, size_t &s)
|
||||||
{
|
{
|
||||||
if (!_active || _time_at_cycle_start == 0) {
|
if (!_active || _recv_start == 0) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
assert(port < _n_midi_in);
|
assert(port < _n_midi_in);
|
||||||
|
|
@ -266,18 +276,15 @@ CoreMidiIo::recv_event (uint32_t port, double cycle_time_us, uint64_t &time, uin
|
||||||
_input_queue[port].push_back(std::shared_ptr<CoreMIDIPacket>(new _CoreMIDIPacket ((MIDIPacket*)&packet)));
|
_input_queue[port].push_back(std::shared_ptr<CoreMIDIPacket>(new _CoreMIDIPacket ((MIDIPacket*)&packet)));
|
||||||
}
|
}
|
||||||
|
|
||||||
UInt64 start = _time_at_cycle_start;
|
|
||||||
UInt64 end = AudioConvertNanosToHostTime(AudioConvertHostTimeToNanos(_time_at_cycle_start) + cycle_time_us * 1e3);
|
|
||||||
|
|
||||||
for (CoreMIDIQueue::iterator it = _input_queue[port].begin (); it != _input_queue[port].end (); ) {
|
for (CoreMIDIQueue::iterator it = _input_queue[port].begin (); it != _input_queue[port].end (); ) {
|
||||||
if ((*it)->timeStamp < end) {
|
if ((*it)->timeStamp < _recv_end) {
|
||||||
if ((*it)->timeStamp < start) {
|
if ((*it)->timeStamp < _recv_start) {
|
||||||
uint64_t dt = AudioConvertHostTimeToNanos(start - (*it)->timeStamp);
|
uint64_t dt = AudioConvertHostTimeToNanos(_recv_start - (*it)->timeStamp);
|
||||||
/* note: it used to be 10ms (insert handwavy explanation about percievable latency)
|
/* note: it used to be 10ms (insert handwavy explanation about percievable latency)
|
||||||
* turns out some midi-keyboads connected via bluetooth can be late by as much as 50ms
|
* turns out some midi-keyboads connected via bluetooth can be late by as much as 50ms
|
||||||
* https://discourse.ardour.org/t/ardour-not-getting-all-messages-from-midi-keyboard/107618/13?
|
* https://discourse.ardour.org/t/ardour-not-getting-all-messages-from-midi-keyboard/107618/13?
|
||||||
*/
|
*/
|
||||||
if (dt > 6e7 && (*it)->timeStamp != 0) { // 60ms slack and a timestamp is given
|
if (dt > 8e7 && (*it)->timeStamp != 0) { // 60ms slack and a timestamp is given
|
||||||
#ifndef NDEBUG
|
#ifndef NDEBUG
|
||||||
printf("Dropped Stale Midi Event. dt:%.2fms\n", dt * 1e-6);
|
printf("Dropped Stale Midi Event. dt:%.2fms\n", dt * 1e-6);
|
||||||
#endif
|
#endif
|
||||||
|
|
@ -298,7 +305,7 @@ CoreMidiIo::recv_event (uint32_t port, double cycle_time_us, uint64_t &time, uin
|
||||||
}
|
}
|
||||||
time = 0;
|
time = 0;
|
||||||
} else {
|
} else {
|
||||||
time = AudioConvertHostTimeToNanos((*it)->timeStamp - start);
|
time = AudioConvertHostTimeToNanos((*it)->timeStamp - _recv_start);
|
||||||
}
|
}
|
||||||
s = std::min(s, (size_t) (*it)->length);
|
s = std::min(s, (size_t) (*it)->length);
|
||||||
if (s > 0) {
|
if (s > 0) {
|
||||||
|
|
@ -308,7 +315,7 @@ CoreMidiIo::recv_event (uint32_t port, double cycle_time_us, uint64_t &time, uin
|
||||||
return s;
|
return s;
|
||||||
} else {
|
} else {
|
||||||
#ifndef NDEBUG
|
#ifndef NDEBUG
|
||||||
uint64_t dt = AudioConvertHostTimeToNanos((*it)->timeStamp - end);
|
uint64_t dt = AudioConvertHostTimeToNanos((*it)->timeStamp - _recv_end);
|
||||||
printf("Postponed future Midi Event. dt:%.2fms\n", dt * 1e-6);
|
printf("Postponed future Midi Event. dt:%.2fms\n", dt * 1e-6);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
@ -319,15 +326,15 @@ CoreMidiIo::recv_event (uint32_t port, double cycle_time_us, uint64_t &time, uin
|
||||||
}
|
}
|
||||||
|
|
||||||
int
|
int
|
||||||
CoreMidiIo::send_event (uint32_t port, double reltime_us, const uint8_t *d, const size_t s)
|
CoreMidiIo::send_event (uint32_t port, double reltime_ns, const uint8_t *d, const size_t s)
|
||||||
{
|
{
|
||||||
if (!_active || _time_at_cycle_start == 0) {
|
if (!_active || _send_start == 0) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
assert(port < _n_midi_out);
|
assert(port < _n_midi_out);
|
||||||
UInt64 ts = AudioConvertHostTimeToNanos(_time_at_cycle_start);
|
UInt64 ts = AudioConvertHostTimeToNanos(_send_start);
|
||||||
ts += reltime_us * 1e3;
|
ts += reltime_ns;
|
||||||
|
|
||||||
// TODO use a single packet list.. queue all events first..
|
// TODO use a single packet list.. queue all events first..
|
||||||
MIDIPacketList pl;
|
MIDIPacketList pl;
|
||||||
|
|
|
||||||
|
|
@ -72,10 +72,10 @@ public:
|
||||||
void start ();
|
void start ();
|
||||||
void stop ();
|
void stop ();
|
||||||
|
|
||||||
void start_cycle ();
|
void start_cycle (MIDITimeStamp, double cycle_ns);
|
||||||
|
|
||||||
int send_event (uint32_t, double, const uint8_t *, const size_t);
|
int send_event (uint32_t, double, const uint8_t *, const size_t);
|
||||||
size_t recv_event (uint32_t, double, uint64_t &, uint8_t *, size_t &);
|
size_t recv_event (uint32_t, uint64_t &, uint8_t *, size_t &);
|
||||||
|
|
||||||
uint32_t n_midi_inputs (void) const { return _n_midi_in; }
|
uint32_t n_midi_inputs (void) const { return _n_midi_in; }
|
||||||
uint32_t n_midi_outputs (void) const { return _n_midi_out; }
|
uint32_t n_midi_outputs (void) const { return _n_midi_out; }
|
||||||
|
|
@ -108,7 +108,9 @@ private:
|
||||||
uint32_t _n_midi_in;
|
uint32_t _n_midi_in;
|
||||||
uint32_t _n_midi_out;
|
uint32_t _n_midi_out;
|
||||||
|
|
||||||
MIDITimeStamp _time_at_cycle_start;
|
MIDITimeStamp _send_start;
|
||||||
|
MIDITimeStamp _recv_start;
|
||||||
|
MIDITimeStamp _recv_end;
|
||||||
bool _active; // internal deactivate during discovery etc
|
bool _active; // internal deactivate during discovery etc
|
||||||
bool _enabled; // temporary disable, e.g. during freewheeli
|
bool _enabled; // temporary disable, e.g. during freewheeli
|
||||||
bool _run; // general status
|
bool _run; // general status
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue