diff --git a/README.md b/README.md index 6b34e171..09258c39 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # README for Seq66 0.99.1 Chris Ahlstrom -2015-09-10 to 2022-09-14 +2015-09-10 to 2022-09-27 __Seq66__ is a MIDI sequencer and live-looper with a hardware-sampler-like grid-pattern interface, sets and playlists for song management, a scale and @@ -95,12 +95,16 @@ Windows, and using a conventional source tarball. * Version 0.99.1: * Issue #93. Revisited to fix related open pattern-editor issues. - * Issue #100. In progress; works but still can overflow buffer under - heavy playback. + * Issue #100. In progress. Added custom ringbuffer for MIDI message + objects to replace JACK's ringbuffer. Issue still somewhat + intractable :-( * Various fixes: * Fixed partial breakage of pattern-merge function. * Fixed odd breakage of ALSA playback in release mode. * Fixed Stop button when another Master has started playback. + * Shift-click on Stop button rewinds JACK transport when running + as JACK Slave. + * Display of some JACK server settings in Edit / Preferences. * Version 0.99.0: * Issue #44. Record live sequence changes functionality beefed up to handle recording without snapping. diff --git a/TODO b/TODO index d8dcc663..a019a338 100644 --- a/TODO +++ b/TODO @@ -1,6 +1,6 @@ TO DO for Seq66 0.99.1 (Sequencer64 refactored for C++14 and Qt) Chris Ahlstrom -2019-04-13 to 2022-09-12 +2019-04-13 to 2022-09-27 Ongoing efforts: @@ -500,6 +500,8 @@ Version 2 features: - Make main menu hide-able. - Allow multiple instances of seq66. - More consistent support for undo / redo / unmodify. + - Add console output for every user action. + - Add scripting language? - Clean up kruft in setmapper/screenset. - Support more than one tune? - A way to lay out a pattern from one track into another tracks diff --git a/contrib/scripts/jackctl b/contrib/scripts/jackctl new file mode 100755 index 00000000..eb073294 --- /dev/null +++ b/contrib/scripts/jackctl @@ -0,0 +1,102 @@ +#!/bin/bash +# +#****************************************************************************** +# jackctl +#------------------------------------------------------------------------------ +## +# \file jackctl +# \library Any project +# \author Chris Ahlstrom +# \date 2022-09-25 +# \update 2022-09-26 +# \version $Revision$ +# \license $XPC_SUITE_GPL_LICENSE$ +# +# The above is modified by the following to remove even the mild GPL +# restrictions: +# +# Use this script in any manner whatsoever. You don't even need to give +# me any credit. However, keep in mind the value of the GPL in keeping +# software and its descendant modifications available to the community +# for all time. +# +# See "jack_control --help" for a list of options. +# +# ALSA parameters partial list: +# +# device: ALSA device name (type:isset:default:value). +# capture: Optionally set port (str:notset:none:none). +# playback: Optionally set port (str:notset:none:none). +# rate: Set the sample rate (48000). +# period: Frames per period (the "cycle", time between process +# callback calls (1024). +# nperiod: Number of periods (cycles) of latency (2). +# midi-driver: ALSA MIDI driver. +# +# /proc/asound/cards (on our system): +# +# 0: CODEC USB audio box. +# 1: nanoKEY2 Korg keyboard. +# 2: HDMI Onboard HDMI. +# 3: PCH Intel on-board sound. +# 4: NVidia Onboard NVidia card. +# 5: Midi A generic USB MIDI cable. +# 6: Mini LaunchPad Mini. +# +#------------------------------------------------------------------------------ + +JCTL_OPERATION="start" +JCTL_DRIVER="alsa" +JCTL_DEVICE="hw:CODEC" # see "cat /proc/asound/cards" +JCTL_RATE="48000" +JCTL_LATENCY="2" +JCTL_PERIOD="2048" # for TESTING + +if [ "$1" == "--list" ] ; then + echo "Available drivers:" + jack_control dl + echo "Selecting ALSA (seq). Available parameters:" + jack_control ds $JCTL_DRIVER + jack_control dp +elif [ "$1" == "--help" ] ; then + cat << E_O_F +Usage: + jackctl Start with the usual parameters (TBD). + jackctl --list List the drivers and the ALSA parameters. + jackctl --start Start the JACK server, that's it. + jackctl --stop Stop the JACK server. + jackctl --kill Stop the JACK server and exit jackdbus. + jackctl --period F Change the period of the JACK server. + jackctl --nperiod P Change ALSA period (playback latency, 2 or 3). + jackctl --help Show this message. + +Getting tired of qjackctl and jackdbus wrestling with each other on +a newer Ubuntu system that runs jackdbus. +E_O_F +elif [ "$1" == "--start" ] ; then + jack_control start +elif [ "$1" == "--stop" ] ; then + jack_control stop +elif [ "$1" == "--kill" ] ; then + jack_control stop + jack_control exit +elif [ "$1" == "--period" ] ; then + JCTL_PERIOD="$2" + jack_control dps period $JCTL_PERIOD +elif [ "$1" == "--nperiod" ] ; then + JCTL_LATENCY="$2" + jack_control dps nperiod $JCTL_LATENCY +else + jack_control start + jack_control ds $JCTL_DRIVER + jack_control dps device $JCTL_DEVICE + jack_control dps rate $JCTL_RATE + jack_control dps nperiods $JCTL_LATENCY + jack_control dps period $JCTL_PERIOD +fi + +#****************************************************************************** +# jackctl +#------------------------------------------------------------------------------ +# vim: ts=3 sw=3 et ft=sh +#------------------------------------------------------------------------------ diff --git a/contrib/scripts/recordpa b/contrib/scripts/recordpa index 1f5f0be5..7988c583 100755 --- a/contrib/scripts/recordpa +++ b/contrib/scripts/recordpa @@ -1,4 +1,4 @@ -#!/bin/sh +#!/bin/bash # #****************************************************************************** # recordpa @@ -8,7 +8,7 @@ # \library Any project # \author Chris Ahlstrom # \date 2022-09-14 -# \update 2022-09-14 +# \update 2022-09-24 # \version $Revision$ # \license $XPC_SUITE_GPL_LICENSE$ # @@ -23,9 +23,33 @@ # Simply records the output of our USB audio device as received through # PulseAudio. # +# parecord is equivalent to "pacat -r --file-format". Refer to the pacat(1) +# man page for pacat's flags. To get the sources for audio record. +# +# $ pactl list sources short +# +# The following two lines produce the same result. +# +# $ parecord -d alsa_input.pci-0000_00_1f.3.analog-stereo speech.flac +# $ parecord -d 1 speech.flac +# #------------------------------------------------------------------------------ -parecord --channels=1 --record --device=alsa_output.usb-Burr-Brown_from_TI_USB_Audio_CODEC-00.analog-stereo.monitor $1 +if [ "$1" == "--list" ] ; then + pactl list sources short +elif [ "$1" == "--help" ] ; then + cat << E_O_F +Usage: + recordpa --help + recordpa --list + recordpa x.wav + record x.wav +E_O_F +elif [ "$2" == "" ] ; then + parecord --channels=1 --record --device=alsa_output.usb-Burr-Brown_from_TI_USB_Audio_CODEC-00.analog-stereo.monitor $1 +else + parecord --channels=1 --record --device=$1 $2 +fi #****************************************************************************** # recordpa diff --git a/contrib/scripts/ystart b/contrib/scripts/ystart index 31e7a93b..2a028a17 100755 --- a/contrib/scripts/ystart +++ b/contrib/scripts/ystart @@ -1,7 +1,7 @@ #!/bin/bash # # Date 2020-11-25 -# Updated 2021-03-14 (Pi day!) +# Updated 2022-09-26 (Pi day! No more.) # # YOSHPATH="/usr/bin" # @@ -13,20 +13,29 @@ YOSHIMI="yoshimi" OPTIONS="" REPOPATH="Home/ca/mls/git" CFGPATH="$HOME/$REPOPATH/yoshimi-cookbook/sequencer64/b4uacuse" -STATEFILE="yoshimi-b4uacuse-gm.state" +DOHELP="no" if [ "$1" == "latest" ] ; then YOSHPATH="/usr/local/bin" - YOSHIMI="yoshimi" + YOSHIMI="yoshimi-1.7.2rc1" shift fi if [ "$1" == "jack" ] ; then - OPTIONS="--jack-midi --jack-audio" + OPTIONS="-j -J" elif [ "$1" == "alsa" ] ; then - OPTIONS="--alsa-midi --alsa-audio" + OPTIONS="-a -A" +elif [ "$1" == "A" ] ; then + OPTIONS="--alsa-midi --alsa-audio=CODEC" +elif [ "$1" == "jA" ] ; then + OPTIONS="-j -A" +elif [ "$1" == "help" ] ; then + echo "Options: jack alsa A jA" + DOHELP="yes" fi -echo "Running $YOSHPATH/$YOSHIMI $OPTIONS --state=$CFGPATH/$STATEFILE" -$YOSHPATH/$YOSHIMI $OPTIONS --state=$CFGPATH/$STATEFILE +if [ "$DOHELP" == "no" ] ; then +echo "Running $YOSHPATH/$YOSHIMI $OPTIONS --state=$CFGPATH/yoshimi-b4uacuse-gm.state" +$YOSHPATH/$YOSHIMI $OPTIONS --state=$CFGPATH/yoshimi-b4uacuse-gm.state +fi diff --git a/libseq66/include/midi/jack_assistant.hpp b/libseq66/include/midi/jack_assistant.hpp index eb744a3f..d6fc0ba3 100644 --- a/libseq66/include/midi/jack_assistant.hpp +++ b/libseq66/include/midi/jack_assistant.hpp @@ -28,7 +28,7 @@ * \library seq66 application * \author Chris Ahlstrom * \date 2015-07-23 - * \updates 2022-09-17 + * \updates 2022-09-26 * \license GNU GPLv2 or above * * This class contains a number of functions that used to reside in the @@ -190,6 +190,20 @@ class jack_assistant friend void jack_session_callback (jack_session_event_t * ev, void * arg); #endif +public: + + /** + * p_position_structure holds frame_rate, ticks_per_beat, and + * beats/minute. + */ + + using parameters = struct + { + jack_position_t position; + int period_size; /* frames per cycle */ + int alsa_nperiod; /* usually 2 or 3 */ + }; + private: /** @@ -199,6 +213,14 @@ class jack_assistant static jack_status_pair_t sm_status_pairs []; + /** + * For issue #100, storage for the true JACK transport position, etc. + * Store the current JACK parameters, currently for display only. + * Tired of being fooled about the actual parameters. + */ + + static parameters sm_jack_parameters; + /** * Provides the performer object that needs this JACK assistant/scratchpad * class. @@ -352,6 +374,13 @@ class jack_assistant ~jack_assistant (); static void show_position (const jack_position_t & pos); + static bool save_jack_parameters + ( + const jack_position_t & p, + int periodsize = 0, + int alsanperiod = 0 + ); + static const parameters & get_jack_parameters (); performer & parent () /* getter needed for external callbacks. */ { @@ -456,7 +485,7 @@ class jack_assistant bool activate (); void start (); - void stop (); + void stop (bool rewind = false); void position (bool state, midipulse tick = 0); bool output (jack_scratchpad & pad); diff --git a/libseq66/include/midi/midibytes.hpp b/libseq66/include/midi/midibytes.hpp index d5088e4f..5ae09bf5 100644 --- a/libseq66/include/midi/midibytes.hpp +++ b/libseq66/include/midi/midibytes.hpp @@ -28,7 +28,7 @@ * \library seq66 application * \author Chris Ahlstrom * \date 2018-11-09 - * \updates 2022-09-16 + * \updates 2022-09-22 * \license GNU GPLv2 or above * * These alias specifications are intended to remove the ambiguity we have @@ -132,16 +132,9 @@ 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. - * - * By default we use only 4 bytes to encode the timestamp (e.g. for the - * JACK ringbuffer). See this macro in seq66_features.h. */ -#if defined SEQ66_8_BYTE_TIMESTAMPS -using midipulse = int64_t; -#else -using midipulse = int32_t; -#endif +using midipulse = long; /** * JACK encodes jack_time_t as a uint64_t (8-byte) value. We will use our own @@ -187,12 +180,7 @@ using midibooleans = std::vector; */ const midipulse c_null_midipulse = -1; /* ULONG_MAX later? */ - -#if defined SEQ66_8_BYTE_TIMESTAMPS -const midipulse c_midipulse_max = INT64_MAX; /* for sanity checks */ -#else -const midipulse c_midipulse_max = INT32_MAX; /* for sanity checks */ -#endif +const midipulse c_midipulse_max = LONG_MAX; /* for sanity checks */ /** * Defines the maximum number of MIDI values, and one more than the diff --git a/libseq66/include/play/performer.hpp b/libseq66/include/play/performer.hpp index cdf7d2e7..75825ea3 100644 --- a/libseq66/include/play/performer.hpp +++ b/libseq66/include/play/performer.hpp @@ -28,7 +28,7 @@ * \library seq66 application * \author Chris Ahlstrom * \date 2018-11-12 - * \updates 2022-09-03 + * \updates 2022-09-25 * \license GNU GPLv2 or above * * The main player! Coordinates sets, patterns, mutes, playlists, you name @@ -1741,12 +1741,17 @@ class performer #endif } - void stop_jack () - { #if defined SEQ66_JACK_SUPPORT - m_jack_asst.stop(); -#endif + void stop_jack (bool rewind = false) + { + m_jack_asst.stop(rewind); } +#else + void stop_jack (bool /* rewind */) + { + // No JACK code available + } +#endif /** * Initializes JACK support, if defined. The launch() function and @@ -2088,7 +2093,7 @@ class performer } void delay_stop (); - void auto_stop (); + void auto_stop (bool rewind = false); void auto_pause (); void auto_play (); void play_all_sets (midipulse tick); @@ -2412,7 +2417,7 @@ class performer void start_playing (); void play_count_in (); void pause_playing (); - void stop_playing (); + void stop_playing (bool rewind = false); void group_learn (bool flag); void group_learn_complete (const keystroke & k, bool good = true); bool needs_update (seq::number seqno = seq::all()) const; diff --git a/libseq66/include/seq66_features.h b/libseq66/include/seq66_features.h index c8e17348..edda7936 100644 --- a/libseq66/include/seq66_features.h +++ b/libseq66/include/seq66_features.h @@ -28,7 +28,7 @@ * \library seq66 application * \author Chris Ahlstrom * \date 2018-11-24 - * \updates 2022-09-22 + * \updates 2022-09-23 * \license GNU GPLv2 or above * * Some options (the "USE_xxx" options) specify experimental and @@ -52,40 +52,13 @@ /** * For issue #100, this macro enables using our new ring_buffer instead of - * jack_ringbuffer_t. + * jack_ringbuffer_t. We no longer attempt to add the timestamp to the + * JACK ringbuffer, even if this macro is disabled. Too many side issues, + * too much code, so disabling this macro preserves the old behavior. */ #define SEQ66_USE_MIDI_MESSAGE_RINGBUFFER -#if defined SEQ66_USE_MIDI_MESSAGE_RINGBUFFER -#undef SEQ66_ENCODE_TIMESTAMP_FOR_JACK -#else - -/** - * For issue #100, this macro enables: - * - * - Adding the current tick to the time-stamp of all event emitted. - * This is done in sequence::put_event_on_bus(). - * - Adding a 4-byte encoding of the time-stamp to the midi_message - * object. - * - Extracting this time-stamp in the JACK process output callback. - * - Using it to calculate the frame-number offset (sample offset) to - * be used in reserving the event. - */ - -#define SEQ66_ENCODE_TIMESTAMP_FOR_JACK -#endif - -/** - * Related to issue #100 changes, we want the option to use 8-byte MIDI - * timestamps (ticks, pulses). Enable this macro to support hours-long tunes - * at the highest supported PPQN, 19200. Most likely, will never needs this - * macro to be defined. Applies wherever the "midipulse" type is used. - * See the midipulse alias defined in libseq66/include/midi/midibytes.h. - */ - -#undef SEQ66_8_BYTE_TIMESTAMPS - /** * Choose between C++ or bare pthreads. Affects only the performer class. * For condition_variables and mutexes, we are forced to stick with the diff --git a/libseq66/include/util/ring_buffer.hpp b/libseq66/include/util/ring_buffer.hpp index 77b33dd5..7f6ba515 100644 --- a/libseq66/include/util/ring_buffer.hpp +++ b/libseq66/include/util/ring_buffer.hpp @@ -20,7 +20,7 @@ */ /** - * \file ring_buffer.cpp + * \file ring_buffer.hpp * * This module defines our own ringbuffer that support objects, not just * characters. @@ -28,7 +28,7 @@ * \library seq66 application * \author Chris Ahlstrom * \date 2022-09-19 - * \updates 2022-09-21 + * \updates 2022-09-27 * \license GNU GPLv2 or above */ @@ -68,6 +68,7 @@ class ring_buffer volatile size_type m_head; /**< Index where next item is read. */ size_type m_size_mask; /**< Restricts index to < buffer size. */ bool m_locked; /**< Is memory locked? NOT YET SUPPORTED. */ + size_type m_contents_max; /**< Useful in trouble-shooting. */ int m_dropped; /**< Number of items overwritten in run. */ public: @@ -94,11 +95,21 @@ class ring_buffer initialize(); } + int buffer_size () const + { + return int(m_buffer_size); + } + int count () const { return int(m_contents_size); } + int count_max () const + { + return int(m_contents_max); + } + bool empty () const { return count() == 0; @@ -185,6 +196,9 @@ class ring_buffer { ++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 */ } @@ -210,6 +224,7 @@ ring_buffer::ring_buffer (size_type sz) : m_head (0), /* supports empty buffer case */ m_size_mask (0), m_locked (false), + m_contents_max (0), m_dropped (0) { int power_of_two; @@ -226,8 +241,9 @@ ring_buffer::ring_buffer (size_type sz) : /** * Free all data associated with the ringbuffer `m_rb'. * - * Note that we will have some work to do (like writing an allocator that uses - * an unswappable block of memory for the vector data) if we define this macro. + * Note that we will have some work to do (like writing an allocator that + * uses an unswappable block of memory for the vector data) if we define this + * macro. */ template @@ -409,7 +425,7 @@ ring_buffer::push_back (const value_type & item) } /* - * Free functions (for testing). + * Free functions (for testing the ring_buffer). */ #if defined SEQ66_PLATFORM_DEBUG @@ -423,7 +439,7 @@ extern bool run_ring_test (); #endif // SEQ66_RING_BUFFER_HPP /* - * ring_buffer.cpp + * ring_buffer.hpp * * vim: sw=4 ts=4 wm=4 et ft=cpp */ diff --git a/libseq66/src/midi/jack_assistant.cpp b/libseq66/src/midi/jack_assistant.cpp index ddefc781..c0c99410 100644 --- a/libseq66/src/midi/jack_assistant.cpp +++ b/libseq66/src/midi/jack_assistant.cpp @@ -25,7 +25,7 @@ * \library seq66 application * \author Chris Ahlstrom * \date 2015-09-14 - * \updates 2022-09-17 + * \updates 2022-09-26 * \license GNU GPLv2 or above * * This module was created from code that existed in the performer object. @@ -145,6 +145,46 @@ namespace seq66 * ------------------------------------------------------------------------- */ +/** + * For issue #100, storage for the true JACK transport position + * Also might was well save them all, if only for display (later). + * We are concerned only with the settings germane to JACK offset + * calculations. We leave out beats_per_bar and beat_type for now. + */ + +jack_assistant::parameters jack_assistant::sm_jack_parameters; + +bool +jack_assistant::save_jack_parameters +( + const jack_position_t & p, + int periodsize, + int alsanperiod +) +{ + jack_position_t & currpos = sm_jack_parameters.position; + bool result = + ( + (p.ticks_per_beat != currpos.ticks_per_beat) || + (p.beats_per_minute != currpos.beats_per_minute) || + (p.frame_rate != currpos.frame_rate) + ); + if (result) + { + currpos = p; + sm_jack_parameters.period_size = periodsize; + sm_jack_parameters.alsa_nperiod = alsanperiod; + async_safe_errprint("JACK transport changed"); + } + return result; +} + +const jack_assistant::parameters & +jack_assistant::get_jack_parameters () +{ + return sm_jack_parameters; +} + /** * Apparently, MIDI pulses are 10 times the size of JACK ticks. So we need, * in some places, to convert pulses (ticks) to JACK ticks by multiplying by @@ -285,8 +325,24 @@ jack_transport_callback (jack_nframes_t /*nframes*/, void * arg) jack_position_t pos; jack_transport_state_t s = ::jack_transport_query(j->client(), &pos); performer & p = j->parent(); + int psize = ::jack_get_buffer_size(j->client()); + + /* + * Save changes for potential display. + */ + + (void) jack_assistant::save_jack_parameters(pos, psize); + #if defined SEQ66_PLATFORM_DEBUG_TMI - printf("Transport frame %u\n", unsigned(pos.frame)); + 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()) { @@ -750,7 +806,7 @@ show_jack_statuses (unsigned bits) /* * ------------------------------------------------------------------------- - * JACK helper functions + * More JACK helper functions * ------------------------------------------------------------------------- */ @@ -1149,13 +1205,20 @@ jack_assistant::start () /** * If JACK is supported, stops the JACK transport. This function assumes * that m_jack_client is not null, if m_jack_running is true. + * + * \param rewind + * If true (the default is false), then set the JACK position to 0. */ void -jack_assistant::stop () +jack_assistant::stop (bool rewind) { if (m_jack_running) + { ::jack_transport_stop(m_jack_client); + if (rewind) + set_position(0); + } else if (rc().with_jack()) (void) error_message("Sync stop: JACK not running"); } @@ -1655,6 +1718,7 @@ jack_assistant::output (jack_scratchpad & pad) midipulse midi_ticks; m_frame_current = ::jack_get_current_transport_frame(m_jack_client); m_frame_last = m_frame_current; + jack_assistant::set_position(m_frame_current); pad.js_dumping = true; /* "[Start JACK Playback]" */ /* diff --git a/libseq66/src/play/performer.cpp b/libseq66/src/play/performer.cpp index e3f525a5..3b433309 100644 --- a/libseq66/src/play/performer.cpp +++ b/libseq66/src/play/performer.cpp @@ -24,7 +24,7 @@ * \library seq66 application * \author Chris Ahlstrom and others * \date 2018-11-12 - * \updates 2022-09-12 + * \updates 2022-09-25 * \license GNU GPLv2 or above * * Also read the comments in the Seq64 version of this module, perform. @@ -4436,10 +4436,14 @@ performer::pause_playing () * to false. With stop, reset the start-tick to either the left-tick or the * 0th tick (to be determined, currently resets to 0). If looping, act like * pause_playing(), but allow reset to the left tick (as opposed to 0). + * + * \param rewind + * If true (the default is false), then set the performer and JACK positions + * to 0. */ void -performer::stop_playing () +performer::stop_playing (bool rewind) { m_max_extent = 0; if (looping()) @@ -4449,9 +4453,12 @@ performer::stop_playing () } else { - stop_jack(); + stop_jack(rewind); stop(); m_dont_reset_ticks = false; + if (rewind) + set_tick(0); /* ca 2022-09-25 */ + for (auto notify : m_notify) (void) notify->on_automation_change(automation::slot::stop); } @@ -4520,14 +4527,18 @@ performer::auto_pause () * Added an is_running() check for when JACK transport is running at startup, * which sets that flag, but not is_pattern_playing(); the result was that * we could not stop playback with Seq66's Stop button. + * + * \param rewind + * If true (the default is false), then reset the performer and JACK + * positions to 0. */ void -performer::auto_stop () +performer::auto_stop (bool rewind) { if (is_pattern_playing() || is_running()) /* normal & JACK, hmmmm */ { - stop_playing(); + stop_playing(rewind); is_pattern_playing(false); /* diff --git a/seq_qt5/forms/qseditoptions.ui b/seq_qt5/forms/qseditoptions.ui index 90dc2a4f..6a043fbb 100644 --- a/seq_qt5/forms/qseditoptions.ui +++ b/seq_qt5/forms/qseditoptions.ui @@ -29,7 +29,7 @@ - 4 + 3 @@ -1185,6 +1185,130 @@ Range: 0.5 to 3.0 JACK Transport/MIDI + + + + 364 + 8 + 201 + 17 + + + + + 75 + true + + + + JACK Server Settings + + + + + + 360 + 32 + 226 + 161 + + + + + + + N periods + + + + + + + false + + + Indicates the number of JACK ticks per +beat, normally PPQN/10. + + + + + + + false + + + The number of JACK frames per second. +48000 Hz by default. + + + + + + + Period size + + + + + + + JACK ticks/beat + + + + + + + false + + + The number of frames in a period. A period +is a "cycle", and is the number of frames in +a period and the number frames between +JACK process callbacks. + + + + + + + false + + + The number of ALSA periods, 2 by default, +3 recommended for USB. Also called "latency". +Does not effect the time between process +callbacks. + + + + + + + Frame rate + + + + + + + Beats/minute + + + + + + + false + + + JACK's current beats/minute setting. + + + + + diff --git a/seq_qt5/forms/qsmainwnd.ui b/seq_qt5/forms/qsmainwnd.ui index bcf07775..be9b5c28 100644 --- a/seq_qt5/forms/qsmainwnd.ui +++ b/seq_qt5/forms/qsmainwnd.ui @@ -1382,13 +1382,13 @@ recording. - 30 + 32 28 - 30 + 36 28 diff --git a/seq_qt5/src/qseditoptions.cpp b/seq_qt5/src/qseditoptions.cpp index d24768e4..046411a0 100644 --- a/seq_qt5/src/qseditoptions.cpp +++ b/seq_qt5/src/qseditoptions.cpp @@ -24,7 +24,7 @@ * \library seq66 application * \author Chris Ahlstrom * \date 2018-01-01 - * \updates 2022-09-01 + * \updates 2022-09-26 * \license GNU GPLv2 or above * * This version is located in Edit / Preferences. @@ -50,6 +50,7 @@ #include +#include "midi/jack_assistant.hpp" /* seq66::jack_assistant statics */ #include "os/daemonize.hpp" /* seq66::signal_for_restart() */ #include "play/performer.hpp" /* seq66::performer class */ #include "util/filefunctions.hpp" /* seq66::filename_base() */ @@ -608,6 +609,27 @@ qseditoptions::setup_tab_jack () this, SLOT(slot_jack_auto_connect()) ); + /* + * Display of current JACK Server settings. + */ + + jack_assistant::parameters pos = jack_assistant::get_jack_parameters(); + std::string temp = std::to_string(int(pos.position.frame_rate)); + ui->lineEditFrameRate->setText(qt(temp)); + + temp = std::to_string(pos.period_size); /* int */ + ui->lineEditPeriod->setText(qt(temp)); + + temp = std::to_string(pos.alsa_nperiod); /* int */ + ui->lineEditNperiod->setText(qt(temp)); + + char tmp[32]; + snprintf(tmp, sizeof tmp, "%.2f", pos.position.ticks_per_beat); + ui->lineEditTicksPerBeat->setText(tmp); + + snprintf(tmp, sizeof tmp, "%.2f", pos.position.beats_per_minute); + ui->lineEditBeatsPerMinute->setText(tmp); + /* * Create a button group to manage the mutual status of the JACK Live and * Song Mode buttons. @@ -1874,7 +1896,6 @@ qseditoptions::slot_ui_scaling () QString qheight = ui->lineEditUiScalingHeight->text(); /* h */ ui_scaling_helper(qs, qheight); modify_usr(); - // reload_needed(true); } /** @@ -2095,9 +2116,9 @@ void qseditoptions::sync_usr () { char tmp[32]; - snprintf(tmp, sizeof tmp, "%g", usr().window_scale()); + snprintf(tmp, sizeof tmp, "%.2f", usr().window_scale()); ui->lineEditUiScaling->setText(tmp); - snprintf(tmp, sizeof tmp, "%g", usr().window_scale_y()); + snprintf(tmp, sizeof tmp, "%.2f", usr().window_scale_y()); ui->lineEditUiScalingHeight->setText(tmp); ui->chkNoteResume->setChecked(usr().resume_note_ons()); diff --git a/seq_qt5/src/qseventslots.cpp b/seq_qt5/src/qseventslots.cpp index 6e7956dc..c6c63c6a 100644 --- a/seq_qt5/src/qseventslots.cpp +++ b/seq_qt5/src/qseventslots.cpp @@ -25,7 +25,7 @@ * \library seq66 application * \author Chris Ahlstrom * \date 2018-08-13 - * \updates 2022-07-05 + * \updates 2022-09-24 * \license GNU GPLv2 or above * * Also note that, currently, the editable_events container does not support @@ -164,7 +164,7 @@ qseventslots::events_to_string () const { int row = 0; result += - " No. Timestamp Event Status Ch. D0 D1 " + " No. Ticks Timestamp Event Status Ch. D0 D1 " "Link-time Rank\n" ; for (const auto & ei : m_event_container) @@ -349,10 +349,11 @@ qseventslots::event_to_string snprintf ( - line, sizeof line, "%4d %s %12s 0x%02x Ch %2s %5s %5s %12s 0x%04x\n", - index, ev.timestamp_string().c_str(), ev.status_string().c_str(), - rawstatus, ev.channel_string().c_str(), data_0.c_str(), - data_1.c_str(), linktime.c_str(), ev.get_rank() + line, sizeof line, + "%4d %6ld %s %12s 0x%02x Ch %2s %5s %5s %12s 0x%04x\n", + index, long(ev.timestamp()), ev.timestamp_string().c_str(), + ev.status_string().c_str(), rawstatus, ev.channel_string().c_str(), + data_0.c_str(), data_1.c_str(), linktime.c_str(), ev.get_rank() ); return std::string(line); } diff --git a/seq_qt5/src/qsmainwnd.cpp b/seq_qt5/src/qsmainwnd.cpp index c039210c..58d40db3 100644 --- a/seq_qt5/src/qsmainwnd.cpp +++ b/seq_qt5/src/qsmainwnd.cpp @@ -24,7 +24,7 @@ * \library seq66 application * \author Chris Ahlstrom * \date 2018-01-01 - * \updates 2022-09-12 + * \updates 2022-09-25 * \license GNU GPLv2 or above * * The main window is known as the "Patterns window" or "Patterns panel". It @@ -948,7 +948,12 @@ qsmainwnd::make_perf_frame_in_tab () void qsmainwnd::stop_playing () { - cb_perf().auto_stop(); + Qt::KeyboardModifiers qkm = QGuiApplication::keyboardModifiers(); + if (qkm & Qt::ShiftModifier) + cb_perf().auto_stop(true); /* rewind to 0 */ + else + cb_perf().auto_stop(); + ui->btnPause->setChecked(false); ui->btnPlay->setChecked(false); } diff --git a/seq_rtmidi/include/midi_jack_data.hpp b/seq_rtmidi/include/midi_jack_data.hpp index 2fa431c5..9e4d05e5 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-22 + * \updates 2022-09-26 * \license See above. * * GitHub issue #165: enabled a build and run with no JACK support. @@ -146,6 +146,8 @@ class midi_jack_data 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 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 () { diff --git a/seq_rtmidi/include/rtmidi_types.hpp b/seq_rtmidi/include/rtmidi_types.hpp index 3bf0a7eb..eafdc77c 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-21 + * \updates 2022-09-22 * \license See above. * * The lack of hiding of these types within a class is a little to be @@ -115,21 +115,6 @@ api_to_int (rtmidi_api api) return static_cast(api); } -/** - * The size of a Seq66 MIDI timestamp in bytes, or 0 if we're - * not encoding timestamps in the JACK ringbuffer. - */ - -#if defined SEQ66_ENCODE_TIMESTAMP_FOR_JACK -#if defined SEQ66_8_BYTE_TIMESTAMPS -const size_t c_timestamp_size = 8; -#else -const size_t c_timestamp_size = 4; -#endif -#else -const size_t c_timestamp_size = 0; -#endif - /** * Provides a handy capsule for a MIDI message, based on the * std::vector data type from the RtMidi project. @@ -168,6 +153,19 @@ class midi_message private: + /** + * Provide a static counter to keep track of events. Currently needed for + * trouble-shooting. We don't care about wraparound. + */ + + static unsigned sm_msg_number; + + /** + * Provides the message counter value when this event was created. + */ + + unsigned m_msg_number; + /** * Holds the event status and data bytes. */ @@ -184,23 +182,15 @@ class midi_message public: midi_message (midipulse ts = 0); - midi_message (const midibyte * mbs, size_t sz); + midi_message (const midibyte * mbs, std::size_t sz); -#if defined SEQ66_ENCODE_TIMESTAMP_FOR_JACK - static midipulse extract_timestamp (const midibyte * mbs, size_t sz); - static size_t size_of_timestamp () - { - return c_timestamp_size; - } -#endif - - midibyte & operator [] (size_t i) + midibyte & operator [] (std::size_t i) { static midibyte s_zero = 0; return (i < m_bytes.size()) ? m_bytes[i] : s_zero ; } - const midibyte & operator [] (size_t i) const + const midibyte & operator [] (std::size_t i) const { static midibyte s_zero = 0; return (i < m_bytes.size()) ? m_bytes[i] : s_zero ; @@ -211,23 +201,14 @@ class midi_message return reinterpret_cast(&m_bytes[0]); } - int buffer_count () const + const midibyte * event_bytes () const // bypasses timestamp { - return int(m_bytes.size()); + return m_bytes.data(); } - bool buffer_empty () const + unsigned msg_number () const { - return m_bytes.empty(); - } - - const midibyte * event_bytes () const // bypasses timestamp - { -#if defined SEQ66_ENCODE_TIMESTAMP_FOR_JACK - return m_bytes.data() + size_of_timestamp(); -#else - return m_bytes.data(); -#endif + return m_msg_number; } bool empty () const @@ -237,12 +218,7 @@ class midi_message int event_count () const // was "count" { -#if defined SEQ66_ENCODE_TIMESTAMP_FOR_JACK - return m_bytes.size() <= size_of_timestamp() ? - 0 : int(m_bytes.size() - size_of_timestamp()) ; -#else return int(m_bytes.size()); -#endif } void push (midibyte b) @@ -250,11 +226,6 @@ class midi_message m_bytes.push_back(b); } -#if defined SEQ66_ENCODE_TIMESTAMP_FOR_JACK - bool push_timestamp (midipulse b); - midipulse extract_timestamp () const; -#endif - midipulse timestamp () const { return m_timestamp; @@ -264,13 +235,7 @@ class midi_message bool is_sysex () const { -#if defined SEQ66_ENCODE_TIMESTAMP_FOR_JACK - int index = int(size_of_timestamp()); -#else - int index = 0; -#endif - return m_bytes.size() > 0 ? - event::is_sysex_msg(m_bytes[index]) : false ; + return m_bytes.size() > 0 ? event::is_sysex_msg(m_bytes[0]) : false ; } void show () const; diff --git a/seq_rtmidi/src/midi_jack.cpp b/seq_rtmidi/src/midi_jack.cpp index dd53b9f9..77ad4562 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-22 + * \updates 2022-09-27 * \license See above. * * Written primarily by Alexander Svetalkin, with updates for delta time by @@ -193,10 +193,16 @@ * we play the Sequencer64 MIDI tune "b4uacuse-stress.midi", we can fail to * write to the ringbuffer. So we've doubled the size. Without timestamps, * that allows about 10K events. With time-stamps, about 4.7K events. + * + * For the new midi_message ringbuffer, running the stress file from the + * 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! */ #if defined SEQ66_USE_MIDI_MESSAGE_RINGBUFFER -static const size_t c_jack_ringbuffer_size = 1024; /* tentative */ +static const size_t c_jack_ringbuffer_size = 4096; /* tentative */ #else static const size_t c_jack_ringbuffer_size = 32768; /* was 16384 */ #endif @@ -208,6 +214,12 @@ static const size_t c_jack_ringbuffer_size = 32768; /* was 16384 */ namespace seq66 { +/* + ---------------------------------------------------------------------------- + Static items and JACK callback functions. + ---------------------------------------------------------------------------- + */ + /** * The buffer size for basic MIDI messages. Probably excludes SysEx * messages. @@ -338,10 +350,9 @@ jack_process_rtmidi_input (jack_nframes_t framect, void * arg) return 0; } -#undef USE_FRAME_OFFSET_PEEKING - /** - * Handles peeking and reading data from the JACK ringbuffer. + * Handles peeking and reading data from our replacement for the JACK + * ringbuffer. * * \param buffmsg * The ringbuffer to check. It is expected to hold two bytes (a short) @@ -373,7 +384,7 @@ jack_process_rtmidi_input (jack_nframes_t framect, void * arg) #if defined SEQ66_USE_MIDI_MESSAGE_RINGBUFFER -jack_nframes_t +static jack_nframes_t jack_get_event_data ( ring_buffer * buffmsg, @@ -389,7 +400,7 @@ 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; + process = result >= lastoffset; /* next process cycle? */ if (process) { size_t datasz = size_t(msg.event_count()); @@ -398,58 +409,24 @@ 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 - result = UINT32_MAX; /* belay handling until next frame */ - } - return result; -} - -#else - -jack_nframes_t -jack_get_event_data -( - jack_ringbuffer_t * buffmsg, - jack_nframes_t framect, - jack_nframes_t lastoffset, - char * dest, size_t & destsz -) -{ - jack_nframes_t result = UINT32_MAX; - short space = 0; - const size_t sz = sizeof(space); - char * sp = reinterpret_cast(&space); - bool process = ::jack_ringbuffer_peek(buffmsg, sp, sz) >= sz; - char test[16]; - if (process) - { - char temp[s_message_buffer_size]; /* hardwired for now */ - size_t amount = space + sz; /* data + size of data */ - size_t ct = ::jack_ringbuffer_peek(buffmsg, temp, amount) == amount; - process = ct == amount; - if (process) { - size_t tssize = midi_message::size_of_timestamp(); - const char * next = &temp[0] + sz; - const midibyte * mbytes = reinterpret_cast(next); - midipulse ts = midi_message::extract_timestamp(mbytes, ct); - result = midi_jack_data::jack_frame_offset(framect, ts); - process = result >= lastoffset; - if (process) - { - size_t datasz = amount - sz - tssize; - if (datasz <= destsz) - { - next += tssize; - memcpy(dest, next, datasz); - destsz = datasz; - } - ::jack_ringbuffer_read_advance(buffmsg, amount); - } - else - result = UINT32_MAX; +#if defined SEQ66_PLATFORM_DEBUG + printf("[debug] ts %ld BELAYED #%u offset %d\n", + long(ts), msg.msg_number(), int(result)); +#endif + result = UINT32_MAX; /* belay to next cycle */ } } return result; @@ -458,7 +435,7 @@ jack_get_event_data #endif // defined SEQ66_USE_MIDI_MESSAGE_RINGBUFFER /** - * Defines the JACK process output callback. It is the JACK process callback + * 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 @@ -468,10 +445,12 @@ jack_get_event_data * -# Loop while the number of bytes available for reading [via * jack_ringbuffer_read_space()] is non-zero. Note that the second * parameter is where the data is copied. + * NOTE: This byte buffer has been replaced by a midi_message + * ring-buffer. * -# Get the size of each event, and allocate space for an event to be * written to an event port buffer (JACK reserve/write functions). * -# Read the data from the ringbuffer into this port buffer. JACK - * should then send it to the remote port. + * will then send it to the remote port. * * Since this is an output port, "buff" is the area to which we can write * data, to send it to the "remote" (i.e. outside our application) port. The @@ -481,21 +460,13 @@ jack_get_event_data * We were wondering if, like the JACK midiseq example program, we need to * wrap the out-process in a for-loop over the number of frames. In our * tests, we are getting 1024 frames, and the code seems to work without that - * loop. - * - * A for-loop over the number of frames? See discussion above. - * - * Note that we now access midi_message data through static functions, - * otherwise the overhead induces buffer overruns. + * loop. A for-loop over the number of frames? See discussion above. * - * Why are we reading here? That's where our app has dumped the next set - * of MIDI events to output. - * - * Could consider using JACK callbacks to detect changes. That would be more - * robust. + * Could consider using JACK callbacks to detect server-setting changes. + * That would be more robust. * * \param framect - * The number of frames to be processes + * The number of frames to be processed. * * \param arg * A pointer to the midi_jack_data object, which holds basic information @@ -512,15 +483,37 @@ jack_process_rtmidi_output (jack_nframes_t framect, void * arg) { char mbuffer[s_message_buffer_size]; char * mbuf = &mbuffer[0]; - midi_jack_data * jackdata = reinterpret_cast(arg); - jack_client_t * client = jackdata->jack_client(); /* for JACK state */ jack_port_t * jackport = jackdata->jack_port(); void * buf = ::jack_port_get_buffer(jackport, framect); - jack_position_t pos; /* holds JACK state */ - (void) ::jack_transport_query(client, &pos); /* transport state */ + jack_position_t pos = jack_assistant::get_jack_parameters().position; if (midi_jack_data::recalculate_frame_factor(pos)) - async_safe_errprint("JACK transport settings changed"); + 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); @@ -536,11 +529,11 @@ jack_process_rtmidi_output (jack_nframes_t framect, void * arg) const jack_midi_data_t * data = reinterpret_cast(mbuf); - int rc = ::jack_midi_event_write(buf, offset, data, destsz); + int rc = ::jack_midi_event_write(buf, offset /* 0 */, data, destsz); if (rc != 0) { async_safe_errprint("JACK MIDI buffer overflow"); - ::jack_midi_clear_buffer(buf); // or "break'? + break; /* ::jack_midi_clear_buffer(buf) */ } last_offset = offset; } @@ -558,7 +551,7 @@ jack_process_rtmidi_output (jack_nframes_t framect, void * arg) midi_jack_data * jackdata = reinterpret_cast(arg); jack_port_t * jackport = jackdata->jack_port(); jack_ringbuffer_t * buffmsg = jackdata->jack_buffmessage(); - int space = 0; + short space = 0; const size_t sz = sizeof(space); char * sp = reinterpret_cast(&space); void * buf = ::jack_port_get_buffer(jackport, framect); @@ -566,7 +559,12 @@ jack_process_rtmidi_output (jack_nframes_t framect, void * arg) for (;;) { size_t msgsz = ::jack_ringbuffer_read_space(buffmsg); - if (msgsz > 0) + if (msgsz > 32000) + { + async_safe_errprint("Impossible event size"); + break; + } + else if (msgsz > 0) { (void) ::jack_ringbuffer_read(buffmsg, sp, sz); jack_midi_data_t * md = ::jack_midi_event_reserve(buf, 0, space); @@ -835,7 +833,9 @@ jack_port_register_callback (jack_port_id_t portid, int regv, void * arg) } /* - * MIDI JACK base class + ---------------------------------------------------------------------------- + MIDI JACK base class + ---------------------------------------------------------------------------- */ /** @@ -869,7 +869,8 @@ midi_jack::midi_jack (midibus & parentbus, midi_info & masterinfo) : (void) jack_info().add(*this); /* - * New for issue #100. + * New for issue #100. These are only tentative values, and are replaced + * once transport is active. */ jack_data().jack_ticks_per_beat(ppqn() * 10.0); @@ -886,7 +887,20 @@ midi_jack::~midi_jack () { #if defined SEQ66_USE_MIDI_MESSAGE_RINGBUFFER if (not_nullptr(jack_data().jack_buffer())) - delete jack_data().jack_buffer(); + { + ring_buffer * rb = jack_data().jack_buffer(); + if (rb->dropped() > 0) + { + char tmp[64]; + snprintf + ( + tmp, sizeof tmp, "%d events dropped, %d max/%d\n", + rb->dropped(), rb->count_max(), rb->buffer_size() + ); + (void) warn_message("ring-buffer", tmp); + delete jack_data().jack_buffer(); + } + } #else if (not_nullptr(jack_data().jack_buffmessage())) ::jack_ringbuffer_free(jack_data().jack_buffmessage()); @@ -1054,7 +1068,7 @@ midi_jack::details () const std::string result = parent_bus().bus_name(); result += ":"; result += parent_bus().port_name(); - result += "--->"; + result += "-->"; result += m_remote_port_name; return result; } @@ -1197,11 +1211,8 @@ midi_jack::api_play (const event * e24, midibyte channel) 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"); - - jack_nframes_t offset = midi_jack_data::jack_frame_offset(p); #endif midi_message message(e24->timestamp()); /* issue #100 */ @@ -1245,7 +1256,7 @@ midi_jack::send_message (const midi_message & message) jack_data().jack_buffer()->push_back(message); // ring_buffer return true; #else - int nbytes = message.buffer_count(); + int nbytes = message.event_count(); bool result = nbytes > 0 && nbytes < int(s_message_buffer_size); if (result) { @@ -1721,7 +1732,6 @@ midi_jack::create_ringbuffer (size_t rbsize) if (result) jack_data().jack_buffmessage(rb); #endif - if (! result) { m_error_string = "JACK ringbuffer create error"; diff --git a/seq_rtmidi/src/midi_jack_data.cpp b/seq_rtmidi/src/midi_jack_data.cpp index f861be5e..507ebe3b 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-09-22 + * \updates 2022-09-27 * \license See above. * * GitHub issue #165: enabled a build and run with no JACK support. @@ -34,6 +34,10 @@ #if defined SEQ66_JACK_SUPPORT +#if defined SEQ66_PLATFORM_DEBUG_TMI +#include "midi/calculations.hpp" /* srq66::pulse_length_us() */ +#endif + /* * Do not document the namespace; it breaks Doxygen. */ @@ -59,7 +63,7 @@ midi_jack_data::midi_jack_data () : m_jack_client (nullptr), m_jack_port (nullptr), #if defined SEQ66_USE_MIDI_MESSAGE_RINGBUFFER - m_jack_buffer (nullptr), // ring_buffer + m_jack_buffer (nullptr), /* ring_buffer */ #else m_jack_buffmessage (nullptr), #endif @@ -126,8 +130,22 @@ midi_jack_data::recalculate_frame_factor (const jack_position_t & pos) } if (changed) { - sm_jack_frame_factor = (jack_frame_rate() * 600) / + /* + * 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()); +#if defined SEQ66_PLATFORM_DEBUG_TMI + double pl = pulse_length_us + ( + jack_beats_per_minute(), 0.1 * jack_ticks_per_beat() + ); + printf("[debug] frames/tick = %g; ticks/beat = %g; pulse = %g us\n", + sm_jack_frame_factor, jack_ticks_per_beat(), pl); +#endif } return changed; } @@ -153,24 +171,23 @@ midi_jack_data::recalculate_frame_factor (const jack_position_t & pos) * get the length of a tick in seconds: 60 / ppqn / bpm. Therefore the * tick time t(p) = p * 60 / ppqn / bpm. * -# What is the number of frames at at given time? The duration of each - * frame is D = F / R. So the time at the end of frame Fn is - * T(n) = n * F / R. + * frame is D = 1 / R. So the time at the end of frame Fn is + * T(n) = n / R. * * The latency L of a frame is given by the number of frames, F, the sampling * rate R (e.g. 48000), and the number of periods P: L = P * F / R. Here is a * brief table for 48000, 2 periods: * \verbatim - Frames Latency - 16 0.667 ms - 32 1.333 ms - 64 2.667 ms - 128 5.333 ms - 256 10.667 ms - 512 21.333 ms - 1024 42.667 ms - 2048 85.333 ms - 4096 170.667 ms + Frames Latency Qjackctl P=2 Qjackctl P-3 + 32 0.667 ms (doubles them) (triples them) + 64 1.333 ms + 128 2.667 ms + 256 5.333 ms + 512 10.667 ms + 1024 21.333 ms + 2048 42.667 ms + 4096 85.333 ms \endverbatim * * The jackd arguments from its man apge: @@ -181,6 +198,9 @@ midi_jack_data::recalculate_frame_factor (const jack_position_t & pos) * as low as possible to avoid xruns. * - --nperiods: The number of periods of playback latency, P. Defaults * to the minimum, 2. For USB audio, 3 is recommended. + * This an ALSA engine parameter, but it might contribute + * to the duration of a process callback cycle [proved + * using probe code to calculate microseconds.] * * So, given a tick position p, what is the frame offset needed? * @@ -213,10 +233,6 @@ midi_jack_data::recalculate_frame_factor (const jack_position_t & pos) * \param p * The running timestamp, in pulses, of the event being emitted. * - * \param pos - * Provides the current JACK position parameters. Some of the important - * parameters, with sample value, are: - * * \return * Returns a putative offset value to use in reserving the event. */ @@ -224,21 +240,54 @@ 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) { - double temp = p * jack_frame_factor(); - jack_nframes_t result = jack_nframes_t(temp); + jack_nframes_t result = jack_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 * %g --> frame %g; offset %u\n", + long(p), jack_frame_factor(), temp, unsigned(result)); +#endif + return result; } jack_nframes_t midi_jack_data::jack_frame_estimate (midipulse p) { - double temp = p * jack_frame_factor() + 0.5; + double temp = p * jack_frame_factor(); return jack_nframes_t(temp); } +/** + * Calculates the putative cycle number, without truncation, because we want + * the fractional part. + * + * \param f + * The current frame number. + * + * \param F + * The number of frames in a cycle. (A power of 2.) + * + * \return + * The cycle number with fractional portion. + */ + +double +midi_jack_data::cycle (jack_nframes_t f, jack_nframes_t F) +{ + return double(f) / double(F); +} + +double +midi_jack_data::pulse_cycle (midipulse p, jack_nframes_t F) +{ + return p * jack_frame_factor() / double(F); +} + } // namespace seq66 #endif // SEQ66_JACK_SUPPORT diff --git a/seq_rtmidi/src/rtmidi_types.cpp b/seq_rtmidi/src/rtmidi_types.cpp index 263e1e6c..11a6ed6a 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-22 + * \updates 2022-09-23 * \license See above. * * Provides some basic types for the (heavily-factored) rtmidi library, very @@ -47,6 +47,8 @@ namespace seq66 * class midi_message */ +unsigned midi_message::sm_msg_number = 0; + /** * Constructs an empty MIDI message. Well, empty except for the timestamp * bytes. The caller will then push the data bytes for the MIDI event. @@ -56,12 +58,11 @@ namespace seq66 */ midi_message::midi_message (midipulse ts) : - m_bytes (), - m_timestamp (ts) + m_msg_number (sm_msg_number++), + m_bytes (), + m_timestamp (ts) { -#if defined SEQ66_ENCODE_TIMESTAMP_FOR_JACK - (void) push_timestamp(ts); -#endif + // No code } /** @@ -76,145 +77,18 @@ midi_message::midi_message (midipulse ts) : * The putative size of the byte array. */ -midi_message::midi_message (const midibyte * mbs, size_t sz) : - m_bytes (), - m_timestamp (0) +midi_message::midi_message (const midibyte * mbs, std::size_t sz) : + m_msg_number (sm_msg_number++), + m_bytes (), + m_timestamp (0) { -#if defined SEQ66_ENCODE_TIMESTAMP_FOR_JACK - m_timestamp = extract_timestamp(mbs, sz); -#endif - for (size_t i = 0; i < sz; ++i) + for (std::size_t i = 0; i < sz; ++i) m_bytes.push_back(*mbs++); } -#if defined SEQ66_ENCODE_TIMESTAMP_FOR_JACK - -/** - * These function handle adding a time-stamp to the message as bytes. - * The format of the midi_message is now: - * - * - 4 bytes of a pulse (tick). - * - The rest of the message bytes. - * - * These implementations are based on midifile::write_long() and - * midifile::read_long(). - * - * However, using sizeof(midipulse) [i.e. sizeof(long)] is tricky, because, - * according to section 6.2.8 of Stroustrop's C++ 11 book, the size of a long - * is 4 at a minimum, but it is NOT guaranteed that it must be less than - * the size of a long long (8 bytes). And in gcc/g++ the size of a long is - * 8 bytes. Do we want to encode the 4 MSB's that will always be 0? - * - * The maximum 4-byte signed value is M = 2,147,483,648. At our highest PPQN, - * P = 19200, this M / (P * 4) = 27962 measures. At 320 beats/minutes, this - * would be almost 350 minutes of music. - * - * For now, we will make it configurable to support 8 byte timestamps in - * a midi_message. - * - * Remember that the bytes are pushed to the back of the vector. - * - * \return - * Returns true if the message was empty. Otherwise, we cannot push a - * timestamp. - */ - -bool -midi_message::push_timestamp (midipulse b) -{ - bool result = buffer_empty(); - if (result) - { -#if defined SEQ66_8_BYTE_TIMESTAMPS - push((b & 0xFF00000000000000) >> 56); - push((b & 0xFF00000000000000) >> 48); - push((b & 0xFF00000000000000) >> 40); - push((b & 0xFF00000000000000) >> 32); -#endif - push((b & 0x00000000FF000000) >> 24); - push((b & 0x0000000000FF0000) >> 16); - push((b & 0x000000000000FF00) >> 8); - push((b & 0x00000000000000FF)); - } - return result; -} - -midipulse -midi_message::extract_timestamp () const -{ - midipulse result = 0; - if (buffer_count() > int(size_of_timestamp())) - { -#if defined SEQ66_8_BYTE_TIMESTAMPS - result = (midipulse(m_bytes[0]) << 56); - result += (midipulse(m_bytes[1]) << 48); - result += (midipulse(m_bytes[2]) << 40); - result += (midipulse(m_bytes[3]) << 32); - result += (midipulse(m_bytes[4]) << 24); - result += (midipulse(m_bytes[5]) << 16); - result += (midipulse(m_bytes[6]) << 8); - result += midipulse(m_bytes[7]); -#else - result = (midipulse(m_bytes[0]) << 24); - result += (midipulse(m_bytes[1]) << 16); - result += (midipulse(m_bytes[2]) << 8); - result += midipulse(m_bytes[3]); -#endif - } - return result; -} - -/** - * Static function. - */ - -midipulse -midi_message::extract_timestamp (const midibyte * mbs, size_t sz) -{ - midipulse result = 0; - if (sz >= size_of_timestamp()) - { -#if defined SEQ66_8_BYTE_TIMESTAMPS - result = (midipulse(mbs[0]) << 56); - result += (midipulse(mbs[1]) << 48); - result += (midipulse(mbs[2]) << 40); - result += (midipulse(mbs[3]) << 32); - result += (midipulse(mbs[4]) << 24); - result += (midipulse(mbs[5]) << 16); - result += (midipulse(mbs[6]) << 8); - result += midipulse(mbs[7]); -#else - result = (midipulse(mbs[0]) << 24); - result += (midipulse(mbs[1]) << 16); - result += (midipulse(mbs[2]) << 8); - result += midipulse(mbs[3]); -#endif - } - return result; -} - -#endif //defined SEQ66_ENCODE_TIMESTAMP_FOR_JACK - void midi_message::timestamp (midipulse t) { -#if defined SEQ66_ENCODE_TIMESTAMP_FOR_JACK - container temp; - int c = buffer_count() - int(size_of_timestamp()); - bool havedata = c >= int(size_of_timestamp()); - if (havedata) - { - for (size_t i = size_of_timestamp(); i < size_t(c); ++i) - temp.push_back(m_bytes[i]); - } - m_bytes.clear(); - if (push_timestamp(t) && havedata) - { - for (size_t i = 0; i < temp.size(); ++i) - push(temp[i]); - } - m_bytes = temp; -#endif m_timestamp = t; }