diff --git a/libseq66/include/midi/midibytes.hpp b/libseq66/include/midi/midibytes.hpp index 5ae09bf5..c3a1a620 100644 --- a/libseq66/include/midi/midibytes.hpp +++ b/libseq66/include/midi/midibytes.hpp @@ -132,14 +132,22 @@ using jacktick = long; * However, if you make this value unsigned, then perfroll won't show any * notes in the sequence bars!!! Also, a number of manipulations of this * type currently depend upon it being a signed value. + * + * JACK timestamps are in units of "frames": + * + * typedef uint32_t jack_nframes_t; + * #define JACK_MAX_FRAMES (4294967295U) // UINT32_MAX + * + * A long value is the same size in 32-bit code, and longer in 64-bit code, + * so we can use a midipulse to hold a frame number. */ using midipulse = long; /** - * JACK encodes jack_time_t as a uint64_t (8-byte) value. We will use our own - * alias, of course. The unsigned long long type is guaranteed to be at least - * 8 bytes long on all platforms, but could be longer. + * JACK encodes jack_time_t as a uint64_t (8-byte) value. We will use our + * own alias, of course. The unsigned long long type is guaranteed to be at + * least 8 bytes long on all platforms, but could be longer. */ using microsec = uint64_t; diff --git a/libseq66/src/midi/calculations.cpp b/libseq66/src/midi/calculations.cpp index 796a1697..5c6f1ff6 100644 --- a/libseq66/src/midi/calculations.cpp +++ b/libseq66/src/midi/calculations.cpp @@ -25,7 +25,7 @@ * \library seq66 application * \author Chris Ahlstrom * \date 2015-11-07 - * \updates 2022-10-10 + * \updates 2022-10-12 * \license GNU GPLv2 or above * * This code was moved from the globals module so that other modules @@ -1471,6 +1471,9 @@ midipulse closest_snap (int S, midipulse p) { midipulse result = p; + if (p < 0) + return 0; + if (S > 0) { midipulse Sn0 = p - (p % S); @@ -1486,6 +1489,9 @@ midipulse down_snap (int S, midipulse p) { midipulse result = p; + if (p < 0) + return 0; + if (S > 0) result = midipulse(p - (p % S)); @@ -1496,6 +1502,9 @@ midipulse up_snap (int S, midipulse p) { midipulse result = p; + if (p < 0) + return 0; + if (S > 0) { midipulse Sn0 = p - (p % S); diff --git a/libseq66/src/play/sequence.cpp b/libseq66/src/play/sequence.cpp index a8386250..a50f493a 100644 --- a/libseq66/src/play/sequence.cpp +++ b/libseq66/src/play/sequence.cpp @@ -25,7 +25,7 @@ * \library seq66 application * \author Chris Ahlstrom * \date 2015-07-24 - * \updates 2022-10-10 + * \updates 2022-10-12 * \license GNU GPLv2 or above * * The functionality of this class also includes handling some of the @@ -3994,8 +3994,6 @@ sequence::set_trigger_offset (midipulse trigger_offset) } else m_trigger_offset = trigger_offset; - -// modify(false); /* issue #90 flag change w/o notify */ } /** @@ -5812,7 +5810,6 @@ sequence::change_ppqn (int p) { m_length = rescale_tick(m_length, p, m_ppqn); m_ppqn = p; -// result = apply_length(0, p, 0); result = apply_length(0, 0, 0); /* use new PPQN */ m_triggers.change_ppqn(p); } diff --git a/seq_qt5/include/qseqeditframe64.hpp b/seq_qt5/include/qseqeditframe64.hpp index 436a51d5..b28e02d3 100644 --- a/seq_qt5/include/qseqeditframe64.hpp +++ b/seq_qt5/include/qseqeditframe64.hpp @@ -58,11 +58,6 @@ * - qsmainwnd_height = 580, qsmainwnd.ui */ -// #define QSEQEDITFRAME64_WIDTH 680 -// #define QSEQEDITFRAME64_HEIGHT 920 -// #define QSEQEDITFRAME64_BASE_HEIGHT 572 -// #define QSEQEDITFRAME64_ROLL_HEIGHT 250 - /* * A few forward declarations. The Qt header files are in the cpp file. */ @@ -133,9 +128,7 @@ class qseqeditframe64 final : public qseqframe, protected performer::callbacks qseqeditframe64 ( - performer & p, - sequence & s, // int seqid - QWidget * parent, + performer & p, sequence & s, QWidget * parent, bool shorter = false ); virtual ~qseqeditframe64 (); @@ -392,7 +385,7 @@ private slots: qpatternfix * m_patternfix_wnd; /** - * Menus for Tools and it Harmonic Transpose sub-menu. + * Menus for Tools and its Harmonic Transpose sub-menu. * Menu for the Background Sequences button. * Menu for the Event Data button. * Menu for the "mini" Event Data button. diff --git a/seq_rtmidi/include/midi_jack_data.hpp b/seq_rtmidi/include/midi_jack_data.hpp index 8269c472..06e4eb24 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-29 + * \updates 2022-10-14 * \license See above. * * GitHub issue #165: enabled a build and run with no JACK support. @@ -70,13 +70,13 @@ class midi_jack_data static jack_nframes_t sm_jack_frame_rate; /* e.g. 48000 or 96000 Hz */ static jack_nframes_t sm_jack_start_frame; /* 0 or large number? */ - static double sm_jack_ticks_per_beat; /* seems to be 10 * PPQN */ - 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_nframes_t sm_size_compensation; /* from ttymidi.c */ static jack_time_t sm_cycle_time_us; /* time between callbacks */ static jack_time_t sm_pulse_time_us; /* duration of a MIDI pulse */ + static double sm_jack_ticks_per_beat; /* seems to be 10 * PPQN */ + static double sm_jack_beats_per_minute; /* the BPM for the song */ + static double sm_jack_frame_factor; /* frames per PPQN tick */ /** * Holds the JACK sequencer client pointer so that it can be used by the @@ -153,15 +153,26 @@ class midi_jack_data jack_nframes_t F ); static jack_nframes_t frame_offset (jack_nframes_t F, midipulse p); + static jack_nframes_t frame_offset + ( + jack_nframes_t fbase, jack_nframes_t F, midipulse p + ); +#if defined USE_JACK_TIME_OFFSET_FUNCTION static jack_nframes_t time_offset ( jack_nframes_t F, midipulse p, jack_time_t Tpop, jack_time_t Tpush ); +#endif 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 double frame (midipulse p) + { + return double(p) * frame_factor(); + } + static jack_nframes_t frame_rate () { return sm_jack_frame_rate; @@ -192,6 +203,11 @@ class midi_jack_data return sm_cycle_frame_count; } + static jack_nframes_t size_compensation () + { + return sm_size_compensation; + } + static jack_time_t cycle_time_us () { return sm_cycle_time_us; @@ -232,6 +248,11 @@ class midi_jack_data sm_cycle_frame_count = cfc; } + static void size_compensation (jack_nframes_t szc) + { + sm_size_compensation = szc; + } + static void cycle_time_us (jack_time_t jt) { sm_cycle_time_us = jt; diff --git a/seq_rtmidi/include/rtmidi_types.hpp b/seq_rtmidi/include/rtmidi_types.hpp index 9181f677..6a46fd56 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-30 + * \updates 2022-10-14 * \license See above. * * The lack of hiding of these types within a class is a little to be @@ -174,14 +174,15 @@ class midi_message /** * Holds the time at which the message was sent to the ring-buffer, in - * microseconds. + * microseconds. This is an unsigned 64-bit integer. */ microsec m_push_time_us; /** * Holds the (optional) timestamp of the MIDI message. Non-zero only - * in the JACK implementation at present. + * in the JACK implementation at present. It can also hold a JACK + * frame number. The caller can know this only by context at present. */ midipulse m_timestamp; @@ -241,6 +242,11 @@ class midi_message return m_push_time_us; } + long push_time_ms () const + { + return long(m_push_time_us / 1000); + } + void push_time_us (microsec ptus) { m_push_time_us = ptus; diff --git a/seq_rtmidi/src/midi_jack.cpp b/seq_rtmidi/src/midi_jack.cpp index 3bccfad9..9124a6b4 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-10-01 + * \updates 2022-10-14 * \license See above. * * Written primarily by Alexander Svetalkin, with updates for delta time by @@ -149,7 +149,7 @@ * respect to the current process cycle. This is the only JACK time function * that returns exact time: when used during the process callback it always * returns the same value (until the next process callback, where it will - * return that value + `blocksize`, etc). The return value is guaranteed to + * return that value + 'nframes', etc). The return value is guaranteed to * be monotonic and linear in this fashion unless an XRUN occurs. If an XRUN * occurs, clients must check this value again, as time may have advanced in * a non-linear way (e.g. cycles may have been skipped). @@ -353,6 +353,8 @@ jack_process_rtmidi_input (jack_nframes_t framect, void * arg) return 0; } +#if defined SEQ66_USE_MIDI_MESSAGE_RINGBUFFER + /** * Handles peeking and reading data from our replacement for the JACK * ringbuffer. @@ -385,7 +387,7 @@ jack_process_rtmidi_input (jack_nframes_t framect, void * arg) * callback cycle. */ -#if defined SEQ66_USE_MIDI_MESSAGE_RINGBUFFER +#if defined USE_THIS_CODE static jack_nframes_t jack_get_event_data @@ -401,12 +403,33 @@ jack_get_event_data bool process = buffmsg->read_space() > 0; if (process) { + static unsigned s_last_msg_number = 0; const midi_message & msg = buffmsg->front(); midipulse ts = msg.timestamp(); + unsigned msgno = msg.msg_number(); + if (msgno < s_last_msg_number) + async_safe_errprint("ring_buffer wraparound?"); + + s_last_msg_number = msgno; if (s_use_offset) { result = midi_jack_data::frame_offset(framect, ts); - process = result >= lastoffset; /* next cycle? */ + + /* + * Sort of helps to just not belay. + * + * process = result >= lastoffset; // next cycle? + */ + + if (result < lastoffset) + result = framect - 1; + +#if defined SEQ66_PLATFORM_DEBUG_NOT_READY + char value[c_async_safe_utoa_size]; + char text[c_async_safe_utoa_size + 32]; + + async_safe_strprint("~"); +#endif } else result = 0; @@ -423,10 +446,68 @@ jack_get_event_data } else { - async_safe_errprint("MIDI event belayed"); + char value[c_async_safe_utoa_size]; + char text[c_async_safe_utoa_size + 32]; + std::strcpy(text, "Event #"); + async_safe_utoa(value, msg.msg_number()); + std::strcat(text, value); + std::strcat(text, " belayed offset "); + async_safe_utoa(value, unsigned(result)); + std::strcat(text, value); + async_safe_errprint(text); result = lastoffset; /* UINT32_MAX */ - destsz = 0; + destsz = 0; /* also a flag */ + } + } + return result; +} + +#endif // defined USE_THIS_CODE + +/** + * \return + * Returns + */ + +static jack_nframes_t +jack_get_event_data +( + midi_jack_data * jackdata, + jack_nframes_t framect, + jack_nframes_t cycle_start, + jack_nframes_t & last_frame, + char * dest, size_t & destsz +) +{ + jack_nframes_t result = UINT32_MAX; + ring_buffer * buffmsg = jackdata->jack_buffer(); + bool process = buffmsg->read_space() > 0; + if (process) + { + const midi_message & msg = buffmsg->front(); + jack_nframes_t frame = jack_nframes_t(msg.timestamp()); + frame += framect - midi_jack_data::size_compensation(); + if (last_frame > frame) + frame = last_frame; + else + last_frame = frame; + + if (frame > cycle_start) + { + result = frame - cycle_start; + if (result >= framect) + result = framect - 1; + } + else + result = 0; + + size_t datasz = size_t(msg.event_count()); + if (datasz <= destsz) + { + memcpy(dest, msg.event_bytes(), datasz); + destsz = datasz; } + buffmsg->pop_front(); } return result; } @@ -484,32 +565,51 @@ jack_process_rtmidi_output (jack_nframes_t framect, void * arg) char * mbuf = &mbuffer[0]; midi_jack_data * jackdata = reinterpret_cast(arg); jack_port_t * jackport = jackdata->jack_port(); +#if ! defined USE_PULSE_TIMESTAMP + const jack_nframes_t cycle_start = + ::jack_last_frame_time(jackdata->jack_client()); +#endif 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, framect)) async_safe_errprint("JACK settings changed"); +#if defined USE_PULSE_TIMESTAMP jack_nframes_t lastoffset = 0; +#else + jack_nframes_t last_frame = 0; +#endif + ::jack_midi_clear_buffer(buf); for (;;) { size_t destsz = s_message_buffer_size; +#if defined USE_PULSE_TIMESTAMP jack_nframes_t offset = jack_get_event_data ( jackdata->jack_buffer(), framect, lastoffset, mbuf, destsz ); +#else + jack_nframes_t offset = jack_get_event_data + ( + jackdata, framect, cycle_start, last_frame, mbuf, destsz + ); +#endif if (destsz > 0 && valid_frame_offset(offset)) { const jack_midi_data_t * data = reinterpret_cast(mbuf); - int rc = ::jack_midi_event_write(buf, offset /* 0 */, data, destsz); + int rc = ::jack_midi_event_write(buf, offset, data, destsz); if (rc != 0) { async_safe_errprint("JACK MIDI write error"); break; /* ::jack_midi_clear_buffer(buf) */ } +#if defined USE_PULSE_TIMESTAMP lastoffset = offset; +#endif } else break; @@ -1216,12 +1316,34 @@ bool midi_jack::send_message (const midi_message & message) { #if defined SEQ66_USE_MIDI_MESSAGE_RINGBUFFER + ring_buffer * rb = jack_data().jack_buffer(); + +#if defined SEQ66_PLATFORM_DEBUG_TMI midi_message & ncmessage = const_cast(message); ncmessage.push_time_us(::jack_get_time()); /* Tpush */ + printf + ( + "#%u: Tpush = %lu ms\n", + message.msg_number(), message.push_time_ms() + ); +#endif + + /* + * Let's try the frame count instead of the timestamp. See the ttymidi.c + * module. + */ + +#if ! defined USE_PULSE_TIMESTAMP + midi_message & ncmessage = const_cast(message); + ncmessage.timestamp(midipulse(::jack_frame_time(jack_data().jack_client()))); +#endif + 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); if (result) @@ -1240,6 +1362,7 @@ midi_jack::send_message (const midi_message & message) result = (count1 > 0) && (count2 > 0); } return result; + #endif } @@ -1426,12 +1549,24 @@ midi_jack::api_set_ppqn (int /*ppqn*/) * Empty body for setting BPM. */ +#if defined SEQ66_PLATFORM_DEBUG_TMI + +void +midi_jack::api_set_beats_per_minute (midibpm bp) +{ + printf("midi_jack set bpm %g\n", bp); +} + +#else + void -midi_jack::api_set_beats_per_minute (midibpm /*bpm*/) +midi_jack::api_set_beats_per_minute (midibpm /* bp */) { // No code needed yet } +#endif + /** * Gets the name of the current port via jack_port_name(). This is different * from get_port_name(index), which simply looks up the port-name in the diff --git a/seq_rtmidi/src/midi_jack_data.cpp b/seq_rtmidi/src/midi_jack_data.cpp index 398f46fb..6ca9dd02 100644 --- a/seq_rtmidi/src/midi_jack_data.cpp +++ b/seq_rtmidi/src/midi_jack_data.cpp @@ -24,7 +24,7 @@ * \library seq66 application * \author Chris Ahlstrom * \date 2022-09-13 - * \updates 2022-10-10 + * \updates 2022-10-14 * \license See above. * * GitHub issue #165: enabled a build and run with no JACK support. @@ -53,13 +53,15 @@ namespace seq66 */ jack_nframes_t midi_jack_data::sm_jack_frame_rate = 0; +jack_nframes_t midi_jack_data::sm_jack_start_frame = 0; +jack_nframes_t midi_jack_data::sm_cycle_frame_count = 0; +jack_nframes_t midi_jack_data::sm_size_compensation = 0; +jack_time_t midi_jack_data::sm_cycle_time_us = 0; +jack_time_t midi_jack_data::sm_pulse_time_us = 0; 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 @@ -83,8 +85,7 @@ midi_jack_data::midi_jack_data () : } /** - * This destructor currently does nothing. We rely on the enclosing class - * to close out the things that it created. + * This destructor currently does nothing, as it owns nothing. */ midi_jack_data::~midi_jack_data () @@ -148,29 +149,26 @@ midi_jack_data::recalculate_frame_factor if (changed) { /* - * A value of 2 or 3 would represent the period. + * A value of 2 or 3 would represent the ALSA nperiod, which does + * not affect (purportedly) the calculations. */ -//// const double adjustment = 20.0; /* 10.0 also works. Hmmmm? */ -//// const double adjustment = 2.0; /* nperiod??? */ - const double adjustment = 1.0; /* nperiod??? */ + const double adjustment = 1.0; /* nperiod? */ + double factor = 600.0 / /* seconds/pulse */ + ( + adjustment * ticks_per_beat() * beats_per_minute() + ); cycle_frame_count(F); cycle_time_us(1000000.0 * double(F) / frame_rate()); - pulse_time_us(1000000.0 * 600.0 / - (ticks_per_beat() * beats_per_minute())); - -#if defined USE_ORIGINAL_CALCULATION - frame_factor((frame_rate() * 600.0) / - (ticks_per_beat() * beats_per_minute())); /* P in denom? */ -#else - frame_factor((frame_rate() * 60.0) / - (adjustment * ticks_per_beat() * beats_per_minute())); -#endif + pulse_time_us(1000000.0 * factor); /* microsec/pulse */ + frame_factor(frame_rate() * factor); /* frames/pulse */ + size_compensation(jack_nframes_t(double(F)* 0.10 + 0.5)); #if defined SEQ66_PLATFORM_DEBUG_TMI printf ( -"[debug] frames/cycle %u, %u us; frames/tick %g; ticks/beat %g; pulse %u us\n", + "[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()) ); @@ -195,7 +193,8 @@ midi_jack_data::recalculate_frame_factor * * -# We assume that the tick position p is in the same relative location * relative to the current frame when placed into the ringbuffer and when - * retrieved from the ring buffer. + * retrieved from the ring buffer. Actually, this is unlikely, but + * all events spacing should still be preserved. * -# We can use the calculations modules pulse_length_us() function to * get the length of a tick in seconds: 60 / ppqn / bpm. Therefore the * tick time t(p) = p * 60 / ppqn / bpm. @@ -269,17 +268,9 @@ midi_jack_data::recalculate_frame_factor jack_nframes_t midi_jack_data::frame_offset (jack_nframes_t F, midipulse 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 defined SEQ66_PLATFORM_DEBUG_TMI - printf("[debug] ts %ld --> frame %g; offset %u\n", - long(p), temp, unsigned(result)); -#endif + jack_nframes_t result = frame_estimate(p) + start_frame(); + if (F > 1) + result = result % F; return result; } @@ -287,10 +278,12 @@ midi_jack_data::frame_offset (jack_nframes_t F, midipulse p) jack_nframes_t midi_jack_data::frame_estimate (midipulse p) { - double temp = p * frame_factor(); + double temp = double(p) * frame_factor(); return jack_nframes_t(temp); } +#if defined USE_JACK_TIME_OFFSET_FUNCTION + /** * Function to adjust a MIDI event timestamp for lag when calculating what * frame offset it needs. @@ -313,9 +306,11 @@ midi_jack_data::frame_estimate (midipulse p) * -# 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). + * 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 @@ -333,9 +328,6 @@ midi_jack_data::time_offset 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? @@ -343,6 +335,8 @@ midi_jack_data::time_offset return result; } +#endif // defined USE_JACK_TIME_OFFSET_FUNCTION + /** * Calculates the putative cycle number, without truncation, because we want * the fractional part. diff --git a/seq_rtmidi/src/midi_jack_info.cpp b/seq_rtmidi/src/midi_jack_info.cpp index 07f486df..1b5dbaf9 100644 --- a/seq_rtmidi/src/midi_jack_info.cpp +++ b/seq_rtmidi/src/midi_jack_info.cpp @@ -24,7 +24,7 @@ * \library seq66 application * \author Chris Ahlstrom * \date 2017-01-01 - * \updates 2022-10-02 + * \updates 2022-10-13 * \license See above. * * This class is meant to collect a whole bunch of JACK information about @@ -113,23 +113,12 @@ jack_process_io (jack_nframes_t nframes, void * arg) { if (mj->enabled()) { -#if defined SEQ66_PLATFORM_DEBUG_TMI /* printf() asynch unsafe */ - if (mj->is_input_port()) - printf("Enabled: %s\n", mj->port_name().c_str()); -#endif midi_jack_data * mjp = &mj->jack_data(); if (mj->parent_bus().is_input_port()) (void) jack_process_rtmidi_input(nframes, mjp); else (void) jack_process_rtmidi_output(nframes, mjp); } -#if defined SEQ66_PLATFORM_DEBUG_TMI /* printf() asynch unsafe */ - else - { - if (mj->is_input_port()) - printf("Disabled: %s\n", mj->port_name().c_str()); - } -#endif } } return 0;