Mute-Group Explorations
Chris Ahlstrom
2015-12-23 to 2017-06-22

In this document we explore mute-groups, how they might work, and
the fixes of Tim to them.  The primary module under discussion is the
perform module.

   m_play_screen_offset:

      This is a variable I introduced to save some multiplications.
      It is set to m_playing_screen * m_seqs_in_set in the
      perform::set_playing_screenset() function.

      That function is called in handle_midi_control() to implement the
      c_midi_control_play_ss command.  Also called in
      perform::mainwnd_key_event() to handle the keypress in mainwnd.

      perform::set_playing_screenset() also sets m_playing_screen to the
      selected screen-set.

      m_play_screen_offset is also used in select_group_mute().  That function
      acts if m_mode_group_learn is true.  It gets the playing status of all
      the sequences in the "playing" screenset and sets each mute-group member
      in that screenset to the that playing status.

   m_screenset [replaces m_playscreen_offset in the select_mute_group()
   function, but the usage makes it essentially replace m_playing_screen.]

	https://linuxmusicians.com/viewtopic.php?t=2443&start=15

        For anyone else who is interested in working with Mute Groups -
        I will point out here the main problem that I had getting them
        working (and this applies more to using it from the laptop rather
        than the Midi side of things)...

        To set up the Mute Groups you press the 'L' button on the top of
        the SEQ24 window, and then press a key on your keyboard to 'learn'
        or 'save' the preset. Looking at the list of keys assigned for
        these mute groups (in File, Options, Keyboard), the first bank of
        keys are ! ' ? etc and th second bank are Q W E etc...  When you
        ask the program to 'learn' the key, you can't use the SHIFT key,
        so (on Windows at least) you are not able to use the ! or other
        symbol keys. Similarly, you need to make sure CAPS LOCK is on
        before you start the 'learn' process (as it won't recognise q only
        Q)

            Sequencer64 fixes this issue by automatically converting these
            keys to their shifted versions when group-learn mode is
            active.
        
        (I'm sure you could change all the key settings in the Keyboard
        screen to help overcome some of these issues)

        Once that is working OK, you can configure the MIDI settings in
        similar ways to assigning midi commands to toggle loops - just
        note that you have to use the 'ON' option in the .seq24rc file
        (which is the second set of numbers) - you cannot use the first set
        (used to 'toggle').

Active vs Playing screen-set:

    Remember: groups work with playing screen set only.  So change the
    screenset and give the command to make it the playing one (I set the
    HOME key for this).

    Groups work with the 32 sequences in the playing screen set (see
    after).

    m_playscreen:  When the Home key (the playset key) is struck,
    perform::set_playing_screenset() function goes to m_playscreen_offset
    for each sequence in the play-screen, gets the playing state of that
    sequence, and copies it into m_track_mute_state[].  This saves the
    statuses of the current playscreen.

    Then the play-screen and offset is set to the active screenset.
    The mute_group_tracks() function turns on each track in the new
    play-screen (or screen-set as per tdeagan below) according to the
    previous play-screen settings.

    m_screenset: tdeagan fix uses this value instead of m_playscreen to
    select which set to apply all of the mute states.

    In summary, the playscreen-set functionality takes the current
    play screen, gets its status, and applies them to the current
    screen-set, making it the new playscreen.

From another source:

    You can toggle the playing status of up to 32 previously defined
    mute/unmute patterns (groups) in the active screen set, similar to
    hardware sequencers.  This is done either by one of the 'group toggle'
    keys or by a MIDI controller, both assigned in the .seq24rc file.  A
    Mute/Unmute pattern (group) is stored by holding a 'group learn' key
    while pressing the corresponding 'group toggle' key.  There are also
    keys assigned to turn on/off the group functionality.
    
        * Replace

    Holding down 'Left Ctrl' while selecting a sequence will mute all
    other sequences and turn on the selected sequences.  This is the
    default with seq24 as can be seen by removing ~/.seq24rc and
    then opening the Options / Keyboard tab.

        * Restore

    Holding 'Alt' will save the state of playing sequences and restore
    them when 'Alt' is lifted.  This works with both Alt-Left and
    Alt-Right keys, and is called the "Snapshot 1" and "Snapshot 2"
    functionality.

    Holding 'Left Ctrl' and 'Alt' at the same time will enable you to flip
    over to new sequences briefly and then flip right back upon lifting
    'Alt'.

    This is not a configurable item, as far as we can tell, and we may
    need to make it so in Sequencer64.

        * Queue

    Holding 'Right Ctrl' will queue a on/off toggle for a sequence when
    the loop ends. Queue also works for mute/unmute patterns (groups), in
    this case every sequence will toggle its status after its individual
    loop end. 

        * Keep queue

    Pressing the 'keep queue' key assigned in the .seq24rc file activates
    permanent queue mode until you use the temporary Queue function again
    pressing 'Right Ctrl'. 

    Sequencer64 likes the backslash for this functionality.

---------

    Late to reply to this I know, but SEQ24 will do what you want. I am not
    at my music computer right now, but I think you do ALT click or CTRL
    click, and it puts your command in a buffer and will activate it at
    the end of the clip.  Hope that helps.

---------

Source-code investigations:

    mainwid:

        Muting status is tested by sequence::get_playing().  This value may
        also be stored in mainwid::m_last_playing[].  However, that member
        seems to be unused.  We are removing it.

        Queued status is tested by sequence::get_queued().

    mainwnd:

        Clicking the L button (a toggle) and pressing a mute-group key
        stores the current mute state (of the set OR the whole thing???) in
        that key.  This is passed to perform::learn_toggle().

    perform:

        If m_mode_group_learn, the L button calls unset_mode_group_learn(),
        otherwise, it calls set_mode_group_learn().

        int index = mute_group_offset(gtrack);

            Gets offset re m_mute_group_selected * c_seqs_in_set.

        set_group_mute_state (int gtrack, bool muted) (and get_....)

            Sets m_mute_group[index] = muted;
                   
        select_group_mute (int mutegroup)

            If in group-learn mode, sets the mute-group status of all
            sequences in that screen-set to their current playing status.

        m_playing_screen = m_screenset;
        m_playscreen_offset = m_playing_screen * m_seqs_in_set;
        m_mute_group[1024] contains the mutings for all mute groups.
        bool m_mode_group flag for memorizing?

Life-cycle:

    midifile (parse and write):

        c_mutegroups:

            for groupmute in 0..c_seqs_in_set
                select_group_mute(groupmute);
                for gmutestate in 0..c_seqs_in_set
                    set_group_mute_state(gmutestate != 0);

            Somehow can we make this optional in the file?

    optionsfile (parse and write):

        [mute-group]:

            Similar to midifile, reading the mute-group number and giving
            it to select_group_mute(), and then the 4 x 8 = 32 state values
            and giving them to set_group_mute_state() or getting them from
            get_group_mute_state().

        [midi-control]:

            Writes the value for gmute.

    mainwnd:

        L button --> learn_toggle()

        Key presses:

            if (perf().get_key_groups().count(ev->keyval) != 0)
            {
                perf().select_and_mute_group    /* activate mute group key  */
                (
                    perf().lookup_keygroup_group(ev->keyval)
                );
            }

            bool mgl = perf().is_learn_mode() && ev->keyval != (group_learn);

                Cancels learn mode, calls unset_mode_group_learn().

    perform:

        mainwnd_key_event():

        press:

        key == keys().queue() || key == keys().keep_queue()
            set_sequence_control_status(c_status_queue);

        key == keys().snapshot_1() || key == keys().snapshot_2()
            set_sequence_control_status(c_status_snapshot);

        key == keys().set_playing_screenset()
            set_playing_screenset();

        key == keys().group_on()
            set_mode_group_mute();

        key == keys().group_off()
            unset_mode_group_mute();

        key == keys().group_learn()
            set_mode_group_learn();

        release:

        key == keys().replace()
            unset_sequence_control_status(c_status_replace);

        key == keys().queue()
            unset_sequence_control_status(c_status_queue);

        key == keys().snapshot_1() || key == keys().snapshot_2()
            unset_sequence_control_status(c_status_snapshot);

        key == keys().group_learn()
            unset_mode_group_learn();
            
            bool m_mode_group
            bool m_mode_group_learn
            int m_mute_group_selected
            int m_screenset;
            int m_playing_screen;
            return m_mode_group_learn;

            void mute_group_tracks()
            void select_and_mute_group(int group)
            void set_mode_group_mute() and void unset_mode_group_mute()
            void select_group_mute(int mute)
            void set_mode_group_learn()
            void unset_mode_group_learn()
            bool is_group_learning() return m_mode_group_learn
            void set_and_copy_mute_group(int group)
            int mute_group_offset (int track)
    {
        return clamp_track(track) + m_mute_group_selected * c_seqs_in_set;
    }
        
         ------------------------------
        |   bool m_mute_group[1024]    |
         ------------------------------


         ------------------------------
        | bool m_tracks_mute_state[32] |
         ------------------------------

Flow diagrams:

 o  "rc" file --->
        [mute-group] --->
        select_group_mute(g) --->
        m_mute_group_selected = g (we are not in learn mode) --->
        set_group_mute_state(track, gm[track]) for 32 sets --->
        m_mute_group[mute_group_offset(track)] = muted;
                       |
                        ---> track + m_mute_group_selected * c_seqs_in_set;

    In the other direction, we write c_gmute_tracks (1024, replaced now by
    c_max_sequence), select_group_mute(g) for each of 32 groups, and
    write 32 get_group_mute_state(track) values for each group.

 o  MIDI file --->
        c_mutegroups --->
        length, must match c_gmute_tracks (now c_max_sequence) --->
        m_mute_group_selected = g (we are not in learn mode) --->
        set_group_mute_state(track, mute != 0)

    Q:  What happens if we turn on learn mode, then read a MIDI file
        containing a c_mutegroups section?  Does not look like learn-mode
        makes a difference in this case.

 o  L button --->
        perform::learn_toggle()

 o  mainwnd key "group_learn" in learn-mode --->
        
My Brief Examples:

    These examples assume the latest Sequencer64, with support for
    Auto-Shift (acting as if the Shift key was pressed, a form of
    Shift-Lock) in Group-Learn mode.  They also assume the nicer
    group-keys layout obtained by copying contrib/configs/sequenceer64.rc.keys
    to ~/.config/sequencer64/sequencer64.rc.

Simple:

    Load contrib/midi/b4uacuse-GM-format.midi.  Arm patterns 4 ("Vocal")
    and 5 ("Rhythm (Chord...").  Press the "L" button or press Ctrl-L.
    Then click the "1" key.  You should see a message to the effect of
    "MIDI mute group learn success:  Key 'exclam' (code = 33) successfully
    mapped."

    Exit Sequencer64 to save the configuration.  Open the rewritten
    ~/.config/sequencer64/sequencer64.rc file.  Note the following values:

    [mute-group]

    1024    # group mute value count (0 or 1024)
    0 [0 0 0 0 1 1 0 0] [0 0 0 0 0 0 0 0] [0 0 0 0 0 0 0 0] [0 0 0 0 0 0 0 0]
               ^ ^
               | |
               |  ------- pattern 5
                --------- pattern 4

    Now, if one does not save contrib/midi/b4uacuse-GM-format.midi, when
    it is loaded again, these settings are lost.  To keep them for that
    song, save the song before exiting.

    Loading the song again, the pressing Shift-1 ("!") will turn on those
    sequences.  Pressing another one (e.g. Shift-Q) will turn them off.

Horse:

    Load horse.midi (the short version, stripped of mute groups).
    Unmute all patterns in set 0, press "L", and press "1".
    Unmute all patterns in set 1, press "L", and press "q".
    Now Shift-1 will turn on the same current *relative* set of patterns,
    affecting set 0 if selected or set 1 if selected.
    Same for Shift-q.
    Shift-w will turn off the current set no matter which is selected.

    Therefore, the mute-groups operate on whatever is the current set in
    view.  And turns off the other set that was "active" due to the mute
    group setting.

    So, if mute-group "!" is applied on set 0, it turns on the patterns in
    set 0.  Then if applied to set 1, it turns off the patterns in set 0
    and turns on the similar group of patterns in set 1.

    So a mute-group is not associate with a particular set, though it can
    turn off other sets.

perform::lookup_keyevent_key()
perform::mainwnd_key_event()

[keyboard-group]

Start                     space
Stop                      escape
Pause                   . period
Snapshot 1                Alt_L
Snapshot 2                Alt_R
BPM Up                  ' apostrophe
BPM Down                ; semicolon
Replace/Solo              F12
Queue                     F11
Keep Queue              \ backslash
Pattern Edit            = equal
Screenset Up            [ bracketright
Screenset Down          ] bracketleft
Set Playing Screenset     home
Event Edit              - minus
Pattern Shift           / slash (new)

Still available:

    Shifted:

        { curlyleft (?)
        } curlyright (?)
        | pipe (?)

[keyboard-control]

[ 1 ] [ 2 ] [ 3 ] [ 4 ] [ 5 ] [ 6 ] [ 7 ] [ 8 ]     9   0
[ q ] [ w ] [ e ] [ r ] [ t ] [ y ] [ u ] [ i ]     o   p
[ a ] [ s ] [ d ] [ f ] [ g ] [ h ] [ j ] [ k ]     l   ;   (see above)
[ z ] [ x ] [ c ] [ v ] [ b ] [ n ] [ m ] [ , ]     .   /   (see above)

[mute-group] (rational version)

[ ! ] [ " ] [ # ] [ $ ] [ % ] [ ^ ] [ & ] [ * ]     (   )
[ Q ] [ W ] [ E ] [ R ] [ T ] [ Y ] [ U ] [ I ]     O   P
[ A ] [ S ] [ D ] [ F ] [ G ] [ H ] [ J ] [ K ]     L   :
[ Z ] [ X ] [ C ] [ V ] [ B ] [ N ] [ M ] [ , ]     >   ?

Mute-group array:  32 x 32 (Armed status is same size)

    -   If any are active, the whole set is saved to the MIDI file.
    -   They are grouped by sets, and then patterns in each set.
        This is an issue when sets or patterns differ from a count of 32.
    -   select_group_mute(): saves the statuses of the current play-set
        (m_playscreen_offset), and copies them to the mute-group selected by
        the pressed key.  Must be in group-learn mode.  m_seqs_in_set pattern
        statuses are copied.  CAN BE AN ISSUE with varisets!
    -   select_and_copy_mute_group(): similar but different!
        Sets m_tracks_mute_state[].

Test:

    -#  Load b4uacuse-GM-format.midi
    -#  Don't start play.
        lookup_key_event() gets hit on two occasions for each sequence, on load.
        mainwnd_key_event() gets hit a number of times after that during load.
    -#  Unmute seq #2 ('a') by clicking it with the mouse.
        lookup_key_event() gets hit on two occasions for each sequence, on load.
        Then two more times for #2!
    -#  Same for unmuting #3, #4, #5.  (These events happen when muting as
        well; and a number of similar events when bringing up the Options
        dialog!).
    -#  Now press Shift-E (slot #10 in [mute-group]).
        -   Shift hits mainwnd_key_event(), no action, it returns false.
        -   e (ascii 69)  hits mainwnd_key_event(), no action, it returns false.
        -   lookup_keyevent_key(seqnum == 2) is hit; a 0 screen offset;
            get_key_events_rev().count(2) > 0, so 97 is the result.
            [0] = 49, [1] = 113, [2] = 97 *, [3] = 122, [4] = 50, ...
            This is used to show the 'a' as the hot-key for seq #2.
        -   Previous step for all other seqs.
        -   e (ascii 69)  hits mainwnd_key_event() again, this time as release.
            No action.
        -   Ascii 65505 (???) hits, no action.
        On another trial, Shift-E calls set_and_copy_mute_group(10).
        This sets m_mute_group_selected to 10.  Then, for m_seqs_in_set (!!)
        it sets:
        -   Adds the screen offset to the seq number.
        -   If in m_mode_group_learn mode, takes the active sequences and sets
            the destination sequence of the given mutegroup (10) to the playing
            state of the source sequence.
        -   Irregardless of learn mode, it sets m_tracks_mute_state[source]
            to m_mute_group[ m_mute_group_selected * m_seqs_in_set]. 

            Still can't find where a couple patters get set in the following!

    -#  Exit the app.  In optionsfile.cpp#991, the following calls occur for 32
        groups:
        -   perform::select_group_mute(group).  m_mode_group_learn is FALSE,
            so getting the playing seqs is skipped.  m_mute_group_selected is
            10 (E!), but it gets changed to 0.
        -   Then we loop through 32 patterns.
            NOTE:  The loop counter goes to c_max_groups, but this is wrong.
            It should either be 32 (c_seqs_in_set) OR the current
            m_seqs_in_set!!!
            Calls perform::get_group_state(seq = 0 to 31) which gets
            m_mute_group [ mute_group_offset(seq) ].
            This value is 0 for all except (group 0, seq 4) and
            (group 0, seq 5).  Why those two?
            Also, shouldn't perform::m_mute_group_selected be -1 instead of 0
            if not yet active?
    -#  In sequencer64.rc, section [mute-group], we now have 1 for
        row 0, block 1, items 4 and 5.

Check out gmute_tracks!  What does it do in full?

In seq24, mute-groups seem to act only on set 0.  If another set is the
active set, nothing happens.  This seems limiting.

IMPORTANT DIFFERENCE!

In Sequencer64, mute-groups act on the active set.  Also, they may need to
be cleared from the loaded MIDI song, if it has mute groups.  And they
will (or can?) unmute the patterns on the inactive sets.

Idea:  mute-groups that turn on the pattern/mute group for the set that
       matches the hot-key, even if the set is not active.

Idea:  mute-groups that control ALL patterns in all sets.

Weirdness to fix if not in "legacy" mode:  While the hot-keys layout
matches the patterns layout, the mute-group-keys do not.  Is this a real
issue?

TEST:

    $ seq64 -o sets=8x8 contrib/midi/b4uacuse-stress.midi

    Change to set 1 (the second set of 64 patterns, and turn on the active
    patterns in the first two columns.  Hit the Learn button, and press Q.

    The originally loaded mute groups are kept, and then added to
    sequencer64.rc are:

	16 [1 0 1 1 0 1 1 1] [0 1 1 1 1 1 1 1] [0 0 0 0 0 0 0 0] [0 0 0 0 0 0 0 0]

	'Q' is the 8th (re 0) mute-group, so this looks correct.

    However, it does not change the patterns.  BUG!  And then exiting
    seq64 changes that line back to all zeros.  In fact all mute-groups
    end up zeroed.  If we just start the run and exit without doing
    anything, the mute-groups are zeroed then, too.  They seem to be read
    in, but are then cleared!

    Another BUG:  when we go from set 0 to set 1, the first 32 hot-keys
    are shown simply as '//'.


OLD STUFF:

[mute-group]

[ ! ] [ " ] [ # ] [ $ ] [ % ] [ & ] [ / ] [ ( ] 
[ Q ] [ W ] [ E ] [ R ] [ T ] [ Y ] [ U ] [ I ] 
[ A ] [ S ] [ D ] [ F ] [ G ] [ H ] [ J ] [ K ] 
[ Z ] [ X ] [ C ] [ V ] [ B ] [ N ] [ M ] [ ; ]     (not <)

" should be @
& should be ^
/ should be &
( should be *
; should be ,


# vim: sw=4 ts=4 wm=8 et ft=sh
