diff --git a/libseq66/include/util/ring_buffer.hpp b/libseq66/include/util/ring_buffer.hpp index 7f6ba515..fe07f036 100644 --- a/libseq66/include/util/ring_buffer.hpp +++ b/libseq66/include/util/ring_buffer.hpp @@ -28,7 +28,7 @@ * \library seq66 application * \author Chris Ahlstrom * \date 2022-09-19 - * \updates 2022-09-27 + * \updates 2022-09-30 * \license GNU GPLv2 or above */ @@ -54,7 +54,7 @@ class ring_buffer public: using value_type = TYPE; - using reference = TYPE & ; + using reference = TYPE &; using const_reference = const TYPE &; using size_type = std::size_t; using container = std::vector; @@ -115,16 +115,6 @@ class ring_buffer return count() == 0; } - /* - * Useful? - */ - - bool default_slot (const_reference item) - { - static TYPE empty_value; - return item == empty_value; - } - int dropped () const { return m_dropped; @@ -136,8 +126,8 @@ class ring_buffer size_type read_space () const; size_type read (reference dest); size_type write (const_reference src); - void push_back (const value_type & value); + void pop_front () { if (m_contents_size > 0) @@ -169,39 +159,19 @@ class ring_buffer reference back () { - return m_buffer[previous_tail()]; // return m_buffer[m_tail]; + return m_buffer[previous_tail()]; } const_reference back () const { - return m_buffer[previous_tail()]; // return m_buffer[m_tail]; + return m_buffer[previous_tail()]; } private: // helper functions void initialize (); - - void increment_head () - { - if (m_contents_size > 0) - { - ++m_head; - --m_contents_size; - if (m_head == m_buffer_size) - m_head = 0; /* wrap around */ - } - } - - void increment_tail () - { - ++m_tail; - ++m_contents_size; - if (m_contents_size > m_contents_max) - m_contents_max = m_contents_size; - - if (m_tail == m_buffer_size) - m_tail = 0; /* wrap around */ - } + void increment_head (); + void increment_tail (); size_t previous_tail () { @@ -232,7 +202,6 @@ ring_buffer::ring_buffer (size_type sz) : ; size_type psize = size_t(1 << power_of_two); - m_buffer.reserve(psize); m_buffer_size = psize; m_size_mask = psize - 1; /* 0xFF... for index safety */ initialize(); @@ -266,6 +235,32 @@ ring_buffer::initialize () m_buffer.push_back(empty_value); /* prepare buffer for usage */ } +template +void +ring_buffer::increment_head () +{ + if (m_contents_size > 0) + { + ++m_head; + --m_contents_size; + if (m_head == m_buffer_size) + m_head = 0; /* wrap around */ + } +} + +template +void +ring_buffer::increment_tail () +{ + ++m_tail; + ++m_contents_size; + if (m_contents_size > m_contents_max) /* for checking */ + m_contents_max = m_contents_size; + + if (m_tail == m_buffer_size) + m_tail = 0; /* wrap around */ +} + /** * Lock the data block of `rb' using the system call 'mlock'. * Not nearly ready for prime time! @@ -310,12 +305,7 @@ template void ring_buffer::write_advance () { -#if defined USE_OLD_CODE - ++m_tail; - m_tail &= m_size_mask; -#else increment_tail(); -#endif } /** @@ -420,7 +410,7 @@ ring_buffer::push_back (const value_type & item) increment_head(); m_buffer[m_tail] = item; increment_tail(); - ++m_dropped; + ++m_dropped; /* for future use in expansion */ } } diff --git a/libseq66/src/midi/jack_assistant.cpp b/libseq66/src/midi/jack_assistant.cpp index c0c99410..7d9fd059 100644 --- a/libseq66/src/midi/jack_assistant.cpp +++ b/libseq66/src/midi/jack_assistant.cpp @@ -334,15 +334,15 @@ jack_transport_callback (jack_nframes_t /*nframes*/, void * arg) (void) jack_assistant::save_jack_parameters(pos, psize); #if defined SEQ66_PLATFORM_DEBUG_TMI - static jack_time_t s_last = 0; - jack_time_t timeus = ::jack_get_time(); - unsigned delta = unsigned(timeus - s_last); - if (pos.frame > 0) - { - printf("[debug] us %u, delta %u, frame %u\n", - unsigned(timeus), delta, unsigned(pos.frame)); - } - s_last = timeus; + static jack_time_t s_last = 0; + jack_time_t timeus = ::jack_get_time(); + unsigned delta = unsigned(timeus - s_last); + if (pos.frame > 0) + { + printf("[debug] us %u, delta %u, frame %u\n", + unsigned(timeus), delta, unsigned(pos.frame)); + } + s_last = timeus; #endif if (p.is_running()) { diff --git a/seq_rtmidi/include/midi_jack_data.hpp b/seq_rtmidi/include/midi_jack_data.hpp index 9e4d05e5..8269c472 100644 --- a/seq_rtmidi/include/midi_jack_data.hpp +++ b/seq_rtmidi/include/midi_jack_data.hpp @@ -27,7 +27,7 @@ * \library seq66 application * \author Chris Ahlstrom * \date 2017-01-02 - * \updates 2022-09-26 + * \updates 2022-09-29 * \license See above. * * GitHub issue #165: enabled a build and run with no JACK support. @@ -74,6 +74,10 @@ class midi_jack_data static double sm_jack_beats_per_minute; /* the BPM for the song */ static double sm_jack_frame_factor; /* frames per PPQN tick */ + static jack_nframes_t sm_cycle_frame_count; /* progress callback param */ + static jack_time_t sm_cycle_time_us; /* time between callbacks */ + static jack_time_t sm_pulse_time_us; /* duration of a MIDI pulse */ + /** * Holds the JACK sequencer client pointer so that it can be used by the * midibus objects. This is actually an opaque pointer; there is no way @@ -143,57 +147,101 @@ class midi_jack_data * Frame offset-related functions. */ - static bool recalculate_frame_factor (const jack_position_t & pos); - static jack_nframes_t jack_frame_offset (jack_nframes_t F, midipulse p); - static jack_nframes_t jack_frame_estimate (midipulse p); + static bool recalculate_frame_factor + ( + const jack_position_t & pos, + jack_nframes_t F + ); + static jack_nframes_t frame_offset (jack_nframes_t F, midipulse p); + static jack_nframes_t time_offset + ( + jack_nframes_t F, midipulse p, + jack_time_t Tpop, jack_time_t Tpush + ); + static jack_nframes_t frame_estimate (midipulse p); static double cycle (jack_nframes_t f, jack_nframes_t F); static double pulse_cycle (midipulse p, jack_nframes_t F); - static jack_nframes_t jack_frame_rate () + static jack_nframes_t frame_rate () { return sm_jack_frame_rate; } - static jack_nframes_t jack_start_frame () + static jack_nframes_t start_frame () { return sm_jack_start_frame; } - static double jack_ticks_per_beat () + static double ticks_per_beat () { return sm_jack_ticks_per_beat; } - static double jack_beats_per_minute () + static double beats_per_minute () { return sm_jack_beats_per_minute; } - static double jack_frame_factor () + static double frame_factor () { return sm_jack_frame_factor; } - static void jack_frame_rate (jack_nframes_t nf) + static jack_nframes_t cycle_frame_count () + { + return sm_cycle_frame_count; + } + + static jack_time_t cycle_time_us () + { + return sm_cycle_time_us; + } + + static jack_time_t pulse_time_us () + { + return sm_pulse_time_us; + } + + static void frame_rate (jack_nframes_t nf) { sm_jack_frame_rate = nf; } - static void jack_start_frame (jack_nframes_t nf) + static void start_frame (jack_nframes_t nf) { sm_jack_start_frame = nf; } - static void jack_ticks_per_beat (double tpb) + static void ticks_per_beat (double tpb) { sm_jack_ticks_per_beat = tpb; } - static void jack_beats_per_minute (double bp) + static void beats_per_minute (double bp) { sm_jack_beats_per_minute = bp; } + static void frame_factor (double ff) + { + sm_jack_frame_factor = ff; + } + + static void cycle_frame_count (jack_nframes_t cfc) + { + sm_cycle_frame_count = cfc; + } + + static void cycle_time_us (jack_time_t jt) + { + sm_cycle_time_us = jt; + } + + static void pulse_time_us (jack_time_t jt) + { + sm_pulse_time_us = jt; + } + /* * Basic member access. Getters and setters. */ diff --git a/seq_rtmidi/include/rtmidi_types.hpp b/seq_rtmidi/include/rtmidi_types.hpp index eafdc77c..9181f677 100644 --- a/seq_rtmidi/include/rtmidi_types.hpp +++ b/seq_rtmidi/include/rtmidi_types.hpp @@ -27,7 +27,7 @@ * \library seq66 application * \author Gary P. Scavone; severe refactoring by Chris Ahlstrom * \date 2016-11-20 - * \updates 2022-09-22 + * \updates 2022-09-30 * \license See above. * * The lack of hiding of these types within a class is a little to be @@ -173,8 +173,15 @@ class midi_message container m_bytes; /** - * Holds the (optional) timestamp of the MIDI message. Non-zero only - * in the JACK implementation. + * Holds the time at which the message was sent to the ring-buffer, in + * microseconds. + */ + + microsec m_push_time_us; + + /** + * Holds the (optional) timestamp of the MIDI message. Non-zero only + * in the JACK implementation at present. */ midipulse m_timestamp; @@ -183,6 +190,9 @@ class midi_message midi_message (midipulse ts = 0); midi_message (const midibyte * mbs, std::size_t sz); + midi_message (const midi_message & rhs) = default; + midi_message & operator = (const midi_message & rhs) = default; + ~midi_message () = default; midibyte & operator [] (std::size_t i) { @@ -226,12 +236,25 @@ class midi_message m_bytes.push_back(b); } + microsec push_time_us () const + { + return m_push_time_us; + } + + void push_time_us (microsec ptus) + { + m_push_time_us = ptus; + } + midipulse timestamp () const { return m_timestamp; } - void timestamp (midipulse t); + void timestamp (midipulse t) + { + m_timestamp = t; + } bool is_sysex () const { diff --git a/seq_rtmidi/src/midi_jack.cpp b/seq_rtmidi/src/midi_jack.cpp index 77ad4562..8c2fb572 100644 --- a/seq_rtmidi/src/midi_jack.cpp +++ b/seq_rtmidi/src/midi_jack.cpp @@ -24,7 +24,7 @@ * \library seq66 application * \author Gary P. Scavone; severe refactoring by Chris Ahlstrom * \date 2016-11-14 - * \updates 2022-09-27 + * \updates 2022-09-30 * \license See above. * * Written primarily by Alexander Svetalkin, with updates for delta time by @@ -53,6 +53,8 @@ * automatically to whatever is already present. Currently, though, new * devices that appear in the system won't be accessible until a restart. * (Or perhaps a reconnection using a JACK manager like QJackCtl.) + * Also, there is now an option to avoid the automatic connection of real + * (non-virtual) ports. * * Random JACK notes: * @@ -181,6 +183,12 @@ #include #endif +/* + * Enables our attempt to adjust for lag. Doesn't work worth a carp. + */ + +#undef USE_TIME_OFFSET_FUNCTION + #include "cfg/settings.hpp" /* seq66::rc() accessor function */ #include "midi/event.hpp" /* seq66::event from main library */ #include "midi/jack_assistant.hpp" /* seq66::jack_status_pair_t */ @@ -198,11 +206,12 @@ * Sequencer64 project, the maximum number of events in the ring buffer * is about 200 at 192 PPQN. At 960 PPQN, thousands of events are dropped * and the buffer maxes out (1024 events). Let's try 4096 instead. - * Weird, now that tune yields the max of 196! + * Weird, now that tune yields the max of 196! Let's try 2048. Might be a + * useful configuration option. */ #if defined SEQ66_USE_MIDI_MESSAGE_RINGBUFFER -static const size_t c_jack_ringbuffer_size = 4096; /* tentative */ +static const size_t c_jack_ringbuffer_size = 2048; /* tentative */ #else static const size_t c_jack_ringbuffer_size = 32768; /* was 16384 */ #endif @@ -399,8 +408,23 @@ jack_get_event_data { const midi_message & msg = buffmsg->front(); midipulse ts = msg.timestamp(); - result = midi_jack_data::jack_frame_offset(framect, ts); - process = result >= lastoffset; /* next process cycle? */ + +#if defined USE_TIME_OFFSET_FUNCTION + jack_time_t Tpush = msg.push_time_us(); + if (Tpush > 0) /* tricky code */ + { + jack_time_t Tpop = ::jack_get_time(); + result = midi_jack_data::time_offset(framect, ts, Tpop, Tpush); + } + else + { + result = jack_nframes_t(ts); /* tricky code */ + } +#else + result = midi_jack_data::frame_offset(framect, ts); +#endif + + process = result >= lastoffset; /* next cycle? */ if (process) { size_t datasz = size_t(msg.event_count()); @@ -409,24 +433,18 @@ jack_get_event_data memcpy(dest, msg.event_bytes(), datasz); destsz = datasz; } -#if defined SEQ66_PLATFORM_DEBUG - double Tpms = 1000.0 * 60.0 / (double(192) * double(200)); - int fn = int(midi_jack_data::jack_frame_estimate(ts)); - printf - ( - "[debug] ts %ld (%g ms) frame %i played #%u offset %d\n", - long(ts), ts * Tpms, fn, msg.msg_number(), int(result) - ); -#endif buffmsg->pop_front(); } else { -#if defined SEQ66_PLATFORM_DEBUG - printf("[debug] ts %ld BELAYED #%u offset %d\n", - long(ts), msg.msg_number(), int(result)); + async_safe_errprint("MIDI event belayed"); + +#if defined USE_TIME_OFFSET_FUNCTION + midi_message & msg = buffmsg->front(); + msg.timestamp(result); /* tricky code */ + msg.push_time_us(0); /* tricky code */ #endif - result = UINT32_MAX; /* belay to next cycle */ + result = UINT32_MAX; /* next cycle */ } } return result; @@ -435,11 +453,11 @@ jack_get_event_data #endif // defined SEQ66_USE_MIDI_MESSAGE_RINGBUFFER /** - * Defines the JACK output process callback. It is the JACK process callback - * for a MIDI output port (a midi_out_jack object associated with, for - * example, "system:midi_playback_1", representing, for example, a Korg - * nanoKEY2 to which we can send information), also known as a "Writable - * Client" by qjackctl. Here's how it works: + * Defines the JACK output process callback for a MIDI output port (a + * midi_out_jack object associated with, for example, + * "system:midi_playback_1", representing, for example, a Korg nanoKEY2 to + * which we can send information), also known as a "Writable Client" by + * qjackctl. Here's how it works: * * -# Get the JACK port buffer, for our local jack port. Clear it. * -# Loop while the number of bytes available for reading [via @@ -487,34 +505,9 @@ jack_process_rtmidi_output (jack_nframes_t framect, void * arg) jack_port_t * jackport = jackdata->jack_port(); void * buf = ::jack_port_get_buffer(jackport, framect); jack_position_t pos = jack_assistant::get_jack_parameters().position; - if (midi_jack_data::recalculate_frame_factor(pos)) + if (midi_jack_data::recalculate_frame_factor(pos, framect)) async_safe_errprint("JACK settings changed"); -#if defined SEQ66_PLATFORM_DEBUG_TMI /* jack_assistant */ - jack_client_t * client = jackdata->jack_client(); /* for JACK state */ - jack_position_t realpos; /* holds JACK state */ - (void) ::jack_transport_query(client, &realpos); /* transport state */ - if (realpos.frame > 0) - { - static bool s_inited = false; - static jack_time_t s_last = 0; - jack_time_t timeus = ::jack_get_time(); - if (! s_inited) - { - s_last = timeus; - s_inited = true; - printf("[test] initial time %d\n", unsigned(s_last) / 1000); - } - unsigned time = unsigned(timeus - s_last) / 1000; /* us to ms */ - printf - ( - "[test] time %u dur %u ms, frame %u\n", - unsigned(timeus) / 1000, unsigned(time), unsigned(realpos.frame) - ); - s_last = timeus; - } -#endif - jack_nframes_t last_offset = 0; ::jack_midi_clear_buffer(buf); for (;;) @@ -532,7 +525,7 @@ jack_process_rtmidi_output (jack_nframes_t framect, void * arg) int rc = ::jack_midi_event_write(buf, offset /* 0 */, data, destsz); if (rc != 0) { - async_safe_errprint("JACK MIDI buffer overflow"); + async_safe_errprint("JACK MIDI write error"); break; /* ::jack_midi_clear_buffer(buf) */ } last_offset = offset; @@ -559,9 +552,9 @@ jack_process_rtmidi_output (jack_nframes_t framect, void * arg) for (;;) { size_t msgsz = ::jack_ringbuffer_read_space(buffmsg); - if (msgsz > 32000) + if (msgsz >= c_jack_ringbuffer_size) { - async_safe_errprint("Impossible event size"); + async_safe_errprint("impossible event size"); break; } else if (msgsz > 0) @@ -873,8 +866,8 @@ midi_jack::midi_jack (midibus & parentbus, midi_info & masterinfo) : * once transport is active. */ - jack_data().jack_ticks_per_beat(ppqn() * 10.0); - jack_data().jack_beats_per_minute(bpm()); + jack_data().ticks_per_beat(ppqn() * 10.0); + jack_data().beats_per_minute(bpm()); } /** @@ -889,17 +882,17 @@ midi_jack::~midi_jack () if (not_nullptr(jack_data().jack_buffer())) { ring_buffer * rb = jack_data().jack_buffer(); - if (rb->dropped() > 0) + if (rb->dropped() > 0 || rb->count_max() > (rb->buffer_size() / 2)) { char tmp[64]; snprintf ( - tmp, sizeof tmp, "%d events dropped, %d max/%d\n", + tmp, sizeof tmp, "%d events dropped, %d max/%d", rb->dropped(), rb->count_max(), rb->buffer_size() ); (void) warn_message("ring-buffer", tmp); - delete jack_data().jack_buffer(); } + delete jack_data().jack_buffer(); } #else if (not_nullptr(jack_data().jack_buffmessage())) @@ -1205,16 +1198,6 @@ midi_jack::api_deinit_in () void midi_jack::api_play (const event * e24, midibyte channel) { -#if defined SEQ66_PLATFORM_DEBUG_TMI - jack_client_t * client = jack_data().jack_client(); - jack_nframes_t frame = jack_get_current_transport_frame(client); - jack_nframes_t estimate = midi_jack_data::jack_frame_estimate(p); - jack_position_t pos; /* holds JACK state */ - (void) ::jack_transport_query(client, &pos); /* transport state */ - if (midi_jack_data::recalculate_frame_factor(pos)) - async_safe_strprint("JACK transport settings changed"); -#endif - midi_message message(e24->timestamp()); /* issue #100 */ midibyte status = e24->get_status(channel); midibyte d0, d1; @@ -1228,21 +1211,20 @@ midi_jack::api_play (const event * e24, midibyte channel) { if (! send_message(message)) { - errprint("JACK play failed"); + errprint("JACK send event failed"); } } } /** * Sends a JACK MIDI output message. It writes the full message size and - * the message itself to the JACK ring buffer. The message size consists - * of the pulse timestamp and the status+data bytes. The timestamp was not - * provided by the original "rtmidi" implementation. This new data is + * the message itself to the JACK ring buffer (actually our new ring_buffer + * template. The timestamp was not provided by the original "rtmidi" + * implementation. This new data and the custom ringbuffer are * added for issue #100. * * \param message * Provides the MIDI message object, which contains the bytes to send. - * Note that this function supports only up to 32565 bytes. * * \return * Returns true if the buffer message and buffer size seem to be written @@ -1253,8 +1235,11 @@ bool midi_jack::send_message (const midi_message & message) { #if defined SEQ66_USE_MIDI_MESSAGE_RINGBUFFER - jack_data().jack_buffer()->push_back(message); // ring_buffer - return true; + ring_buffer * rb = jack_data().jack_buffer(); + midi_message & ncmessage = const_cast(message); + ncmessage.push_time_us(::jack_get_time()); /* Tpush */ + rb->push_back(message); + return rb->read_space() > 0; #else int nbytes = message.event_count(); bool result = nbytes > 0 && nbytes < int(s_message_buffer_size); @@ -1308,7 +1293,7 @@ midi_jack::api_sysex (const event * e24) { if (! send_message(message)) { - errprint("JACK sysex failed"); + errprint("JACK send sysex failed"); } } } diff --git a/seq_rtmidi/src/midi_jack_data.cpp b/seq_rtmidi/src/midi_jack_data.cpp index 507ebe3b..338c506f 100644 --- a/seq_rtmidi/src/midi_jack_data.cpp +++ b/seq_rtmidi/src/midi_jack_data.cpp @@ -24,12 +24,14 @@ * \library seq66 application * \author Chris Ahlstrom * \date 2022-09-13 - * \updates 2022-09-27 + * \updates 2022-09-30 * \license See above. * * GitHub issue #165: enabled a build and run with no JACK support. */ +#include /* std::trunc(double) functions */ + #include "midi_jack_data.hpp" /* seq66::midi_jack_data class */ #if defined SEQ66_JACK_SUPPORT @@ -55,6 +57,10 @@ double midi_jack_data::sm_jack_ticks_per_beat = 1920.0; double midi_jack_data::sm_jack_beats_per_minute = 120.0; double midi_jack_data::sm_jack_frame_factor = 1.0; +jack_nframes_t midi_jack_data::sm_cycle_frame_count = 0; +jack_time_t midi_jack_data::sm_cycle_time_us = 0; +jack_time_t midi_jack_data::sm_pulse_time_us = 0; + /** * \ctor midi_jack_data */ @@ -88,7 +94,7 @@ midi_jack_data::~midi_jack_data () /** * Emobdies the calculation of the pulse factor reasoned out in the - * jack_frame_offset() function below. + * frame_offset() function below. * * TODO: * @@ -102,49 +108,59 @@ midi_jack_data::~midi_jack_data () */ bool -midi_jack_data::recalculate_frame_factor (const jack_position_t & pos) +midi_jack_data::recalculate_frame_factor +( + const jack_position_t & pos, + jack_nframes_t F +) { bool changed = false; if ( (pos.ticks_per_beat > 1.0) && /* sanity check */ - (jack_ticks_per_beat() != pos.ticks_per_beat) + (ticks_per_beat() != pos.ticks_per_beat) ) { - jack_ticks_per_beat(pos.ticks_per_beat); + ticks_per_beat(pos.ticks_per_beat); changed = true; } if ( (pos.beats_per_minute > 1.0) && /* sanity check */ - (jack_beats_per_minute() != pos.beats_per_minute) + (beats_per_minute() != pos.beats_per_minute) ) { - jack_beats_per_minute(pos.beats_per_minute); + beats_per_minute(pos.beats_per_minute); changed = true; } - if (jack_frame_rate() != pos.frame_rate) + if (frame_rate() != pos.frame_rate) { - jack_frame_rate(pos.frame_rate); + frame_rate(pos.frame_rate); changed = true; } if (changed) { - /* - * This is the ALSA period, said not to matter but it does in some - * enviroments. Weird. If we prove that, we will have deal with it. - */ - - const double bpmtbpfactor = 600.0; /* no dividing by "periods" */ - sm_jack_frame_factor = (jack_frame_rate() * bpmtbpfactor) / - (jack_ticks_per_beat() * jack_beats_per_minute()); + const double tpbfactor = 600.0; /* no dividing by "periods" */ + cycle_frame_count(F); + cycle_time_us(1000000.0 * double(F) / frame_rate()); + pulse_time_us(1000000.0 * tpbfactor / + (ticks_per_beat() * beats_per_minute())); + +#if defined USE_ORIGINAL_CALCULATION + frame_factor((frame_rate() * tpbfactor) / + (ticks_per_beat() * beats_per_minute())); +#else + frame_factor((frame_rate() * 60.0) / + (10.0 * ticks_per_beat() * beats_per_minute())); +#endif + #if defined SEQ66_PLATFORM_DEBUG_TMI - double pl = pulse_length_us + printf ( - jack_beats_per_minute(), 0.1 * jack_ticks_per_beat() +"[debug] frames/cycle %u, %u us; frames/tick %g; ticks/beat %g; pulse %u us\n", + cycle_frame_count(), unsigned(cycle_time_us()), frame_factor(), + ticks_per_beat(), unsigned(pulse_time_us()) ); - printf("[debug] frames/tick = %g; ticks/beat = %g; pulse = %g us\n", - sm_jack_frame_factor, jack_ticks_per_beat(), pl); #endif } return changed; @@ -238,30 +254,82 @@ midi_jack_data::recalculate_frame_factor (const jack_position_t & pos) */ jack_nframes_t -midi_jack_data::jack_frame_offset (jack_nframes_t F, midipulse p) +midi_jack_data::frame_offset (jack_nframes_t F, midipulse p) { - jack_nframes_t result = jack_frame_estimate(p); + jack_nframes_t result = frame_estimate(p); #if defined SEQ66_PLATFORM_DEBUG_TMI double temp = result; #endif - if (F > 1) - result = result % F; + if (F > 1) + result = result % F; #if defined SEQ66_PLATFORM_DEBUG_TMI printf("[debug] ts %ld * %g --> frame %g; offset %u\n", - long(p), jack_frame_factor(), temp, unsigned(result)); + long(p), frame_factor(), temp, unsigned(result)); #endif return result; } jack_nframes_t -midi_jack_data::jack_frame_estimate (midipulse p) +midi_jack_data::frame_estimate (midipulse p) { - double temp = p * jack_frame_factor(); + double temp = p * frame_factor(); return jack_nframes_t(temp); } +/** + * Function to adjust a MIDI event timestamp for lag when calculating what + * frame offset it needs. + * + * Do we need the base time in microseconds when playback starts? + * + * -# Get the microseconds at push time (the time just before pushing + * the message onto the ringbuffer. This is Tpush. + * -# Set this time in the MIDI message, then push the message. + * -# In the process callback, get the message, and get the microseconds + * at pop (front) time, Tpop. + * -# Compute the lag as Lpushpop = Tpop - Tpush. + * -# Convert the event timestamp p into microseconds, Tp. + * -# Add the lag to this time: Treal = Tp + Lpushpop. + * -# Get the duration of a single callback cycle, Tcycle. + * -# Convert the Treal time to a frame offset. Calculate a floating- + * point frame number f = Treal / Tcycle. + * -# Truncate it to get an integer number of cycles. + * -# Deduct that from f. + * -# Use the fractional remainder to calculate the offset: + * result = Foffset = F * fraction. + * + * However, this function yields very bad playback at 4096 frames/cycle. + * It acts like the events are piling up in the ringbuffer. Removing the + * lag calculation make it play normally (but badly). + */ + +jack_nframes_t +midi_jack_data::time_offset +( + jack_nframes_t F, midipulse p, + jack_time_t Tpop, jack_time_t Tpush +) +{ + double Lpushpop = double(Tpop - Tpush); + double Tcycle = 1000000.0 * double(F) / frame_rate(); + double Tp = p * 1000000.0 * 600.0 / (ticks_per_beat() * beats_per_minute()); + double Treal = Tp + Lpushpop; /* double Treal = Tp; (see banner) */ + double f = Treal / Tcycle; + double truncation = std::trunc(f); + double fraction = f - truncation; + jack_nframes_t result = jack_nframes_t(F * fraction); +#if defined SEQ66_PLATFORM_DEBUG_TMI + printf("frame %g, ts %ld --> offset %u\n", f, long(p), int(result)); +#endif + if (result >= F) + { + // todo? + } + return result; +} + /** * Calculates the putative cycle number, without truncation, because we want * the fractional part. @@ -285,7 +353,7 @@ midi_jack_data::cycle (jack_nframes_t f, jack_nframes_t F) double midi_jack_data::pulse_cycle (midipulse p, jack_nframes_t F) { - return p * jack_frame_factor() / double(F); + return p * frame_factor() / double(F); } } // namespace seq66 diff --git a/seq_rtmidi/src/midi_jack_info.cpp b/seq_rtmidi/src/midi_jack_info.cpp index 2459d8a4..e9f0d775 100644 --- a/seq_rtmidi/src/midi_jack_info.cpp +++ b/seq_rtmidi/src/midi_jack_info.cpp @@ -720,10 +720,10 @@ midi_jack_info::lookup_midi_jack const portlist & ports = jack_ports(); /* midi_jack pointers */ #if defined SEQ66_PLATFORM_DEBUG - char value[c_async_safe_utoa_size]; - char temp[512]; if (rc().investigate()) { + char value[c_async_safe_utoa_size]; + char temp[512]; async_safe_utoa(value, unsigned(count())); std::strcpy(temp, "!! Port lookup: "); std::strcat(temp, value); @@ -740,6 +740,7 @@ midi_jack_info::lookup_midi_jack #if defined SEQ66_PLATFORM_DEBUG if (rc().investigate()) { + char temp[512]; temp[0] = 0; std::strcpy(temp, "jack port: "); std::strcat(temp, mj->port_name().c_str()); diff --git a/seq_rtmidi/src/rtmidi_types.cpp b/seq_rtmidi/src/rtmidi_types.cpp index 11a6ed6a..b92eb51b 100644 --- a/seq_rtmidi/src/rtmidi_types.cpp +++ b/seq_rtmidi/src/rtmidi_types.cpp @@ -24,7 +24,7 @@ * \library seq66 application * \author Gary P. Scavone; severe refactoring by Chris Ahlstrom * \date 2016-12-01 - * \updates 2022-09-23 + * \updates 2022-09-28 * \license See above. * * Provides some basic types for the (heavily-factored) rtmidi library, very @@ -60,6 +60,7 @@ unsigned midi_message::sm_msg_number = 0; midi_message::midi_message (midipulse ts) : m_msg_number (sm_msg_number++), m_bytes (), + m_push_time_us (0), m_timestamp (ts) { // No code @@ -67,7 +68,6 @@ midi_message::midi_message (midipulse ts) : /** * Constructs a midi_message from an array of bytes. - * Also sets the timestamp member based on the first few (4 or 8) bytes. * * \param mbs * Provides the data, which should start with the timestamp bytes, and @@ -80,18 +80,13 @@ midi_message::midi_message (midipulse ts) : midi_message::midi_message (const midibyte * mbs, std::size_t sz) : m_msg_number (sm_msg_number++), m_bytes (), + m_push_time_us (0), m_timestamp (0) { for (std::size_t i = 0; i < sz; ++i) m_bytes.push_back(*mbs++); } -void -midi_message::timestamp (midipulse t) -{ - m_timestamp = t; -} - /** * Shows the bytes in a message, for trouble-shooting. */