jwdj / EasyABC

EasyABC
GNU General Public License v2.0
95 stars 38 forks source link

Chords do not play in selection #90

Open markblinkhorn opened 8 months ago

markblinkhorn commented 8 months ago

Chords included in a selection of a tune (mouse drag part of the tune to select notes that includes notes in chords) are not played. The example below show chords selected in Voice 2, bars 1 and 2. These do not sound when play is pressed. If the single (unchorded) notes in Voice 1, bars 1 and 2 are selected then these do play as expected.

Untitled

See #77 for further discussion and background on this topic.

I have added code to the GetAbcToPlay function in easy_abc.py (located at approx line 4288) which addresses this issue. The basic logic to GatAbcToPlay is that if more than 2 notes are selected in the ABC editor then the _UN_selected notes are excluded from a reconstructed temporary ABC file that is then passed to Abc2midi to be played. The function uses a set of offsets to the selected notes in the tune as a basis for the exclusion. However, the offset to each note in a chord points to the opening '[' character and the current code ignores the actual notes within the chord, leading to the chord notes being added to the exclusion list - hence they do not sound when the play button is pressed.

The additional code I have provided checks for the '[' character and then modifies the offset so that it points to the actual note of the chord and then iterates through list until the ']' is encountered. I have tested this code with chords of 1 or more notes (yes I know a chord of one note is a logical fallacy, but someone might encode something like '[D2]' which, while stupid, is syntactically correct in ABC).

My modified code is given below. I have commented my additions with [SMB] 2024-03. Tested on Windows, Python 3.12.2 and Raspberry Debian bookworm, Python 3.11.2. Feel free to flame me if it breaks on anything else.

My thanks to @revad for assistance on this.

Incidentally, this has uncovered a further issue with note synchronisation if the selection includes multiple voices under some circumstances (which may be related to my test ABC code). I am still looking at this, but I am reasonably convinced that the code given here is producing correct ABC code to pass the selections on to Abc2midi.

    def GetAbcToPlay(self):
        # Helper funtion to return end_offset in a note given start_offset [SMB] 2024-03
        def _get_end_offset(note_offset):
            for (start_offset, end_offset, _) in notes:
                if start_offset == note_offset:
                    break
            return(end_offset)

        tune = self.GetSelectedTune()
        if tune:
            position, end_position = tune.offset_start, tune.offset_end
            if end_position > position and len(self.selected_note_descs) > 2: ## and False:
                text = self.editor.GetTextRange(position, end_position)
                notes = get_notes_from_abc(text)
                num_header_lines, first_note_line_index = self.get_num_extra_header_lines(tune)

                # workaround for the fact the abcm2ps returns incorrect row numbers
                # check the row number of the first note and if it doesn't agree with the actual value
                # then pretend that we have more or less extra header lines
                if self.music_pane.current_page.notes: # 1.3.6.2 [JWdJ] 2015-02
                    actual_first_row = self.music_pane.current_page.notes[0][2]-1
                    correction = (actual_first_row - first_note_line_index)
                    num_header_lines += correction

                temp = text.replace('\r\n', ' \n').replace('\r', '\n')  # re.sub(r'\r\n|\r', '\n', text)
                line_start_offset = [m.start(0) for m in re.finditer(r'(?m)^', temp)]

                selected_note_offsets = []

                for (_, _, abc_row, abc_col, desc) in self.selected_note_descs:
                    abc_row -= num_header_lines
                    selected_note_offsets.append(line_start_offset[abc_row-1]+abc_col)

                # parse selected_notes list looking for chords [SMB] 2024-03
                selected_note_offsets.sort()                
                i = 0
                while i < len(selected_note_offsets):
                    note_offset = selected_note_offsets[i]
                    if text[note_offset] == '[':
                        chord = True
                        note_offset += 1
                        while chord == True:
                            selected_note_offsets[i] = note_offset
                            end_offset = _get_end_offset(note_offset)
                            note_offset = end_offset
                            chord = text[note_offset] != ']'
                            if chord == True:
                                i += 1
                    i += 1

                unselected_note_offsets = [(start_offset, end_offset) for (start_offset, end_offset, _) in notes if not any(p for p in selected_note_offsets if start_offset <= p < end_offset)]
                unselected_note_offsets.sort()

                pieces = []
                pos = 0
                for start_offset, end_offset in unselected_note_offsets:
                    if start_offset > pos:
                        pieces.append(text[pos:start_offset])
                    pieces.append(' ' * (end_offset - start_offset))
                    pos = end_offset
                pieces.append(text[pos:])
                text = ''.join(pieces)

                # for some strange reason the MIDI sequence seems to be cut-off in the end if the last note is short
                # adding a silent extra note seems to fix this
                text = text + os.linesep + '%%MIDI control 7 0' + os.linesep + 'A2'
                return (tune, text)
            return (tune, self.editor.GetTextRange(position, end_position))
        return (tune, '')
revad commented 8 months ago

The order of constructs (from the standard) is: <grace notes>, <chord symbols>, <annotations>/<decorations> <accidentals>, <note>, <octave>, <note length> from which I infer that various symbols could appear between the [ and the first note of the chord.

But I struggle to find any examples that look natural. I don't know where slurs fit in to that hierarchy but
|c [(da] e) f| is valid I think, though |c ([da] e) f| looks more logical

L is a decoration and they precede the note, so |g [Lae'] b c'| is valid (is only one note of the chord to be emphasised?) but |g L[ae'] b c'| seems more likely - and marks the emphasis on the score.

Anyway, those contrived examples give index out of range, which you may or may not wish to allow for ;)

markblinkhorn commented 8 months ago

Agreed - I can force an index out of range error. This ABC fragment works when selected |"Em"(3([GgG,]AB G2) E2| where |"Em"(3[G(gG,]AB G2) E2| does not. (Placement of the start of the slur, just in case you didn't spot it immdiately.) Both examples play if included within a complete tune. I don't think there is an issue with the placement of the other constructs - you can see the gchord included in the above fragment, and this does work as expected.

I have not included code to deal with the case where the start_offset does not point directly to a note. I'll have a think about it...

markblinkhorn commented 8 months ago

More thoughts: In the tune fragment at the top of the post, there is a triplet in bar 4. Any selections made after this triplet are played out of synchronisation - the effect is particularly pronounced if BarFly mode is on and gchords are playing. In the actual tune example, there is another triplet at the end of bar 8 - and that pushes the synchronisation even further out for selections in subsequent bars. None of this is an issue when playing the whole tune.

I have reached the realisation that the current logic for creating tune fragments for playing is flawed; simply omitting the notes from the tune leaves too much 'gubbins' in the unselected bars and, ideally, the only thing remaining in unselected sections of tune should be the barlines. This, obviously, would require an extensive rewrite of the whole GetAbcToPlay function and I'm not sure I have the enthusiasm for that pro tempore. I will add some code to deal with the index overrun but I think I will have to live with the other limitations

revad commented 8 months ago

The way you can select part of a tune, and the notes turn orange, is quick and neat, but it has limitations. The main one, for me, is the inability to select a passage that starts on one line and ends on the next. For that I'd prefer an 'A-B repeat' feature like some CD players have: you mark the start and send of the section and it plays between those.

(I wondered whether a control-drag-rectangle could add the selected notes to the existing selection. There is a precedent for that action: control-play which ignores repeats. )

As for playing the selection, however it's selected, rather than subtracting previous notes from the whole tune, I would have though that you could just remove all existing ABC code up to the first index in the selection, and all after the last index. Wouldn't that clear the gubbins you refer to? You would do this by staff/voice and make sure they lined up. (You'd still need your chord fix in case the first selected index was [ - and maybe some other things.)

(This would incidentally fulfil what I assumed was the intention of the code I first knocked out in #77.)

revad commented 8 months ago

I implemented adding to a selection by control-dragging the selection rectangle - #91

markblinkhorn commented 8 months ago

The index out ot range errors can be avoided by adding a test of the magnitude of i in the chord processing while loop - as in:

                i = 0
                while i < len(selected_note_offsets):
                    note_offset = selected_note_offsets[i]
                    if text[note_offset] == '[':
                        chord = True
                        note_offset += 1
                        while chord == True and i < len(selected_note_offsets):
                            selected_note_offsets[i] = note_offset
                            end_offset = _get_end_offset(note_offset)
                            note_offset = end_offset
                            chord = text[note_offset] != ']'
                            if chord == True:
                                i += 1
                    i += 1

Crude, but effective. This handles cases where note enhancements have been made within chords - eg |[E2(B,2] [B,4)E4]| which, strictly speaking, would be better written as |([E2B,2] [B,4E4])| so that the only objects that appear within the chord boundaries are actual notes. However, this does handle the case by simply dropping the offending ABC which may result in the expected sounds not being heard.

The abc:standard:v2.1 strongly implies that only notes should appear within the chord:

4.17 Chords and unisons
Chords (i.e. more than one note head on a single stem) can be coded with [] symbols around the notes, e.g.

[CEGc]
indicates the chord of C major. They can be grouped in beams, e.g.

[d2f2][ce][df]
but there should be no spaces within the notation for a chord. 
markblinkhorn commented 8 months ago

@revad I've included your ctrl drag selection code #91 into my running copies of EasyABC and can confirm that it does work with my chord modifications. I feel that we have greatly improved the functionality of the note selection facility with these changes.

It can be a bit awkward using ctrl drag with a trackpad, but my manual dextirity is improving ;)

Mark

revad commented 8 months ago

" eg |[E2(B,2] [B,4)E4]| which, strictly speaking, would be better written as |([E2B,2] [B,4E4])| so that the only objects that appear within the chord boundaries are actual notes. "

Yes, I couldn't think of an example where putting anything else in a chord was necessary, or even a likely variation. But in that case what does that statement I quoted about 'The order of constructs' actually mean if not the order of the symbols? ...<chord symbols>, <annotations>/<decorations> <accidentals>, <note>...

I downloaded a couple of mega tunebooks and did a grep/regex on them, but couldn't find a real example to quote in my earlier quibble. But they were all trad/folkie, so pretty limited. Maybe among the players of baroque music on the orthotonophonium...

I'm happy with my version of EasyABC now - it does all I want. But I know from reading odd forums that other people use different subsets of features - like tunebooks for example - so it could do with some regression testing. I bet there are more snags lurking in python 3.10+

Somewhere I saw a post that you used to be able to start playing the tune from a particular point - they didn't say how. I don't know, I've not used it for long. (That can be done now using control-drag but not easily.) And why can't you have a two, or even a one-note selection?

markblinkhorn commented 8 months ago

IMO, the order of constructs thing is probably irrelevant - all of those symbols (should) occur outside of the chord brackets and have no effect on the modified ABC. In fact you could consider a 'chord' to be a special case of a 'note'.

As part of my debugging I had some print statements at the end of the GetAbcToPlay function:

                print(temp) #print the input ABC
                print("[--------------------------------------------]")
                print(text) #print the output ABC

A fragment of the output ABC is given below:

V:1 clef=treble name="1"
%%voicecolor black
      |"Am"            |A2 c3/2d/ e2|"G"            |"Em"(3         |
"Am"       |"G"       |"Am"   "Em"     |"Am"         ||
"Am"            |            |"G"            |"Em"(3         |
"C"       |"F"   "G"    |"Am"  "Em"     |"Am"     ||
"Am"             |             |"F"      "G"  |"C"         |
"Dm"        |"C"        |"Dm"   "Am"   "Em"  |"Am"  ||
"F"       |"C"       |       |"G"        |
"Am"       |"G"       |"Am"   "Em"     |"Am"  |]
V:2 clef=treble-8 name="2"
%%voicecolor maroon
  |L[     ] [     ]|L[E2A,2] [A,4E4]|[     ] [     ]|[     ] [     ]|
[     ] [     ]|[     ] [     ]|         |   ||
[     ] [     ]|[     ] [     ]|[     ] [     ]|[     ] [     ]|
[    ] [    ]|[    ] [    ] [     ]|[     ] [     ] [     ]|[     ]   ||
        |        |        |        |
        |        |        |  ||
       |       |       |        |
[     ] [     ]|[     ] [     ]|         |   |]

Only bar 2 was selected in both voices. You can see gchords, decorations and chord brackets remain in the unselected bars but these produce no audible output. I suspect that, in an ideal world, the brackets should also be removed from empty chords but, as it seems to have no deleterious effect, I haven't considered writing code to do that.

I have seen a post on the forums (somewhere) where the comment was made that they had gone back to a previous version because the later version didn't play from a point - I took that to mean the selection function no longer worked, but I could be misinterpreting their comment.

I agree with you that further regression testing of both these modifications is needed before asking for them to be merged but, for now, I am happy with what we have come up with. The added functionality suits my purposes too.. I'm only a humble flute player (what need do I have of chords?), I do not have an orthotonophonium (nor yet have I come across anyone who has even heard of one up until now. Thank you, I shall probably spend the rest of my day Wiki-surfing!).

I am certain there are more Python deprecation goodies lurking in there - although all of the ones that cause EasyABC to fall off its perch seem to have been fixed.

Incidentally there is a line at the start of GetAbcToPlay:

            if end_position > position and len(self.selected_note_descs) > 2: ## and False:

I'm not sure what the intention of the original author was (Lars perhaps) but if you change the 2 to a 1 then you can have a two-note selection. It seems to work most of the time... Back in the day, the TI-99/4A had a Music Maker cartridge which allowed you to write music notation by using the joystick to place a note on a stave. It played the note every time it was either placed or moved, it even played a whole octave if you dragged it that far. Needless to say, this quickly became tedious and you could not turn it off. I would not be an advocate for a 1 note selection!

revad commented 8 months ago

"...if you change the 2 to a 1 then you can have a two-note selection"

which it plays twice!

(You may need to unmute sound.)

simplescreenrecorder-2024-03-28_11.11.33.webm

markblinkhorn commented 8 months ago

"It seems to work most of the time"

Maybe this was why the original author left it at >2 ?

Mark


From: Dave Royal @.***> Sent: Thursday, 28 March 2024 11:23 To: jwdj/EasyABC Cc: Mark Blinkhorn; Author Subject: Re: [jwdj/EasyABC] Chords do not play in selection (Issue #90)

"...if you change the 2 to a 1 then you can have a two-note selection"

which it plays twice!

(You may need to unmute sound.)

simplescreenrecorder-2024-03-28_11.11.33.webmhttps://github.com/jwdj/EasyABC/assets/3061165/ca595ef9-f666-4d80-a4a4-b6476c556703

— Reply to this email directly, view it on GitHubhttps://github.com/jwdj/EasyABC/issues/90#issuecomment-2024951097, or unsubscribehttps://github.com/notifications/unsubscribe-auth/ACYCZW3HBDN3VIV43ASRW2LY2PVQDAVCNFSM6AAAAABE4RW4N6VHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDAMRUHE2TCMBZG4. You are receiving this because you authored the thread.Message ID: @.***>

revad commented 7 months ago

If I select any part of this tune it will make no sound

X:2
T: Maggie in the Woods:
C: v1
% v1  As copied from OCD Band original
M:4/4
L:1/4
K:G
%%gchordfont bold 16
|: D | "G"GDGA | "Em"Bee f/e/ | "G"dBA G/A/ | "D"BAAD |
"G"GDGA | "Em"Bee f/e/ | "G"dB"D"A B/A/ | "G"G2 G :|
|: d | "G"gf"Em"ed | "C"efg>e | "G"dB"D"A G/A/ | "G"BA"D"Ad |
"G"gf"Em"ed | "C"efg>e | "G"dB"D"A B/A/ | "G"G2 G :|

The guy who reported this discovered that if he appended a newline to the tune it played OK. This works, I think, because the newline splits off the final %%MIDI control 7 0 A2 sequence added by this:

# for some strange reason the MIDI sequence seems to be cut-off in the end if the last note is short
# adding a silent extra note seems to fix this
text = text + os.linesep + '%%MIDI control 7 0' + os.linesep + 'A2'

I think we need to know more about why that kludge was introduced before recommending its removal. When does this 'cut-off after a short note' happen - and does it still happen? What is it about this tune that causes the selection play problem? Any ideas?

markblinkhorn commented 7 months ago

I can't replicate this problem with my copy of easy_abc.py on Windows. I can select any part of the tune (2 or more notes) and it plays as expected.

I did comment out the particular kludge some time ago. I think it may have been added because of a problem with a previous version of abc2midi which is no longer an issue. - So I uncommented it again, but still can't reproduce your selection play problem with the above tune...

revad commented 7 months ago

What version of abc2midi - on Windows and Linux? The guy who reported this is using the latest on Windows. Mine is from November 22, which is the latest packaged one for openSuSE.

markblinkhorn commented 7 months ago

Linux: 4.84 January 20 2023 abc2midi Windows: 4.91 March 02 2024 abc2midi

revad commented 7 months ago

Me: SuSE Linux 4.80 Dec 30 2022 Windows 4.91 Mar 2 2024

Both versions of EasyABC are identical (and identical to the Windows vesion used by my correspondent) - the latest 1.3.8.7 from here and various changes collected from the issues that affect me - selection play and Python 3.10. But maybe not identical to yours.

I tried version 1.3.8.7 code without any subsequent changes and that shows the problem too: a selection of that tune will not play, but adding a blank line at the end makes it play. But my conclusion - that adding a blank line negates the effect of the %%MIDI control 7 0 A2 sequence - may be wrong. It was based on the fact that the blank link splits that sequence from the tune in the input to abc2midi. Here is the processes tune, playing the penultimate bar:

X:2
%%MIDI gchordoff
%%MIDI program 0
%%MIDI chordprog 24
%%MIDI bassprog 24
%%MIDI chordvol 96
%%MIDI bassvol 96
Q:120
T: Maggie in the Woods
C: v1
% v1  As copied from OCD Band original
M:4/4
L:1/4
K:G
%%MIDI control 7 96
%%gchordfont bold 16
   | "G"     | "Em"         | "G"         | "D"     |
"G"     | "Em"         | "G"  "D"       | "G"     
   | "G"  "Em"   | "C"      | "G"  "D"       | "G"  "D"   |
"G"  "Em"   | "C"      | "G"dB"D"A B/A/ | "G"     

%%MIDI control 7 0
A2

But that happens with any tune with a blank line on the end - and maybe the blank line before %%MIDI control 7 0 is ignored by ABC2MIDI. Nevertheless, when I comment out that %%MIDI control 7 0 sequence the selection does play.

I took a brief look at the change log for abcmidi but didn't see anything about the problem this kludge was meant to resolve. But it's 15,000 lines! https://github.com/sshlien/abcmidi/blob/master/doc/CHANGES I shall leave it commented out for now.

markblinkhorn commented 7 months ago

Dave, I don't think we are working with identical input ABC files. This is the output from my setup:

X:2
T: Maggie in the Woods:
C: v1
% v1  As copied from OCD Band original
M:4/4
L:1/4
K:G
%%gchordfont bold 16
|: D | "G"GDGA | "Em"Bee f/e/ | "G"dBA G/A/ | "D"BAAD |
"G"GDGA | "Em"Bee f/e/ | "G"dB"D"A B/A/ | "G"G2 G :|
|: d | "G"gf"Em"ed | "C"efg>e | "G"dB"D"A G/A/ | "G"BA"D"Ad |
"G"gf"Em"ed | "C"efg>e | "G"dB"D"A B/A/ | "G"G2 G :|

[--------------------------------------------]
X:2
T: Maggie in the Woods:
C: v1
% v1  As copied from OCD Band original
M:4/4
L:1/4
K:G
%%gchordfont bold 16
|:   | "G"     | "Em"         | "G"         | "D"     |
"G"     | "Em"         | "G"  "D"       | "G"     :|
|:   | "G"  "Em"   | "C"      | "G"  "D"       | "G"  "D"   |
"G"  "Em"   | "C"      | "G"dB"D"A B/A/ | "G"     :|

(It does play correctly)

I note the following differences in your printed output:

  1. Extra %%MIDI commands
  2. Presence of Q: directive
  3. Absence of repeat marks

If I manually insert the %%MIDI commands it doesn't prevent the selection from playing - with the massive exception of the %%MIDI control AFTER the K: directive - this causes a major failure in abc2midi which generates an error dialog box and then nothing plays, irrespective of selection.

The absence of the repeat marks piques my curiosity. I can see that this could have the possibility of preventing it playing a section. So the question then becomes; why are the repeat marks missing?

Just in case something got lost from an earlier post - here is the GetAbcToPlay (complete with ugly commented out bits) from my currently running copy:

   def GetAbcToPlay(self):
        def _get_end_offset(note_offset):
            #found = False
            for (start_offset, end_offset, _) in notes:
                if start_offset == note_offset:
                    #found = True
                    break
            #if found == True:
            #    return(end_offset)
            #else:
            #    return(found)
            return(end_offset)

        tune = self.GetSelectedTune()
        if tune:
            position, end_position = tune.offset_start, tune.offset_end
            if end_position > position and len(self.selected_note_descs) > 2: ## and False:
                text = self.editor.GetTextRange(position, end_position)
                notes = get_notes_from_abc(text)
                num_header_lines, first_note_line_index = self.get_num_extra_header_lines(tune)
                #print(tune.offset_start, tune.offset_end, len(self.selected_note_descs))
                #print(notes)

                # workaround for the fact the abcm2ps returns incorrect row numbers
                # check the row number of the first note and if it doesn't agree with the actual value
                # then pretend that we have more or less extra header lines
                if self.music_pane.current_page.notes: # 1.3.6.2 [JWdJ] 2015-02
                    actual_first_row = self.music_pane.current_page.notes[0][2]-1
                    correction = (actual_first_row - first_note_line_index)
                    num_header_lines += correction

                temp = text.replace('\r\n', ' \n').replace('\r', '\n')  # re.sub(r'\r\n|\r', '\n', text)
                line_start_offset = [m.start(0) for m in re.finditer(r'(?m)^', temp)]

                selected_note_offsets = []
                #print(self.selected_note_descs)

                for (_, _, abc_row, abc_col, desc) in self.selected_note_descs:
                    abc_row -= num_header_lines
                    selected_note_offsets.append(line_start_offset[abc_row-1]+abc_col)
                    #print(text[line_start_offset[abc_row-1]+abc_col], abc_row, abc_col, desc)
                selected_note_offsets.sort()
                #print(selected_note_offsets)

                i = 0
                while i < len(selected_note_offsets):
                    note_offset = selected_note_offsets[i]
                    #print(i, note_offset)
                    if text[note_offset] == '[':
                        chord = True
                        note_offset += 1
                        while chord == True and i < len(selected_note_offsets):
                            selected_note_offsets[i] = note_offset
                            end_offset = _get_end_offset(note_offset)
                            #print(i, note_offset, end_offset, text[note_offset:end_offset], text[end_offset])
                            note_offset = end_offset
                            chord = text[note_offset] != ']'
                            if chord == True:
                                i += 1
                    #else:
                    #    end_offset = _get_end_offset(note_offset)
                    #    print(i, note_offset, end_offset, text[note_offset:end_offset]) 
                    i += 1
                #print(selected_note_offsets)

                unselected_note_offsets = [(start_offset, end_offset) for (start_offset, end_offset, _) in notes if not any(p for p in selected_note_offsets if start_offset <= p < end_offset)]
                unselected_note_offsets.sort()
                #print(unselected_note_offsets)

                pieces = []
                pos = 0
                for start_offset, end_offset in unselected_note_offsets:
                    #print(text[start_offset:end_offset], ":", start_offset > pos)
                    if start_offset > pos:
                        #print(text[pos:start_offset])
                        pieces.append(text[pos:start_offset])
                    pieces.append(' ' * (end_offset - start_offset))
                    pos = end_offset
                pieces.append(text[pos:])
                #print(pieces)
                text = ''.join(pieces)

                # for some strange reason the MIDI sequence seems to be cut-off in the end if the last note is short
                # adding a silent extra note seems to fix this
                #text = text + os.linesep + '%%MIDI control 7 0' + os.linesep + 'A2'
                print(temp)
                print("[--------------------------------------------]")
                print(text)
                return (tune, text)
            return (tune, self.editor.GetTextRange(position, end_position))
        return (tune, '')

Incidentally - I believe abc2midi assumes a blank line indicates EOF and will ignore any subsequent characters. I could be mistaken with this belief, but it does fit empirically observed behaviour.

Mark

revad commented 7 months ago

That test was on Linux - was your on Windows? My settings are all default.

There's a 'no repeats' parameter to the player which removes the colons. It's used if you click control-play, and if you play selections.

markblinkhorn commented 7 months ago

I've tried it on both Windows and Linux; both behave the same. I tried using ctrl-play, both with and without selections and I can't make it fail to play. I'd still like to know why your output omits the repeat colons and mine doesn't...

revad commented 7 months ago

Our code for GetAbcToPlay() is the same. (Almost - I forgot your index out of range fix).

I think the selection ABC you've quoted above is from the print statements you've added to GetAbcToPlay() whereas I am quoting what is shown by EasyABC using the Internals>Input processed tune menu item.

The repeat removal is done in PlayMidi():

        if remove_repeats or (len(self.selected_note_indices) > 2):
            abc = abc.replace('|:', '').replace(':|', '').replace('::', '')
            execmessages += '\n*removing repeats*'

The boolean 'remove_repeats' is set if you press control/command when clicking play. So control-play or playing a selection should ignore repeats.

One curiosity I've discovered is that if you play a selection which includes variant endings you get different results depending on how you define the variants.

|1 a b c :|2 d e f|             or
| [1 a b c :| [2 d e f |]

The first plays both variants, the second only plays the first variant. If this is a bug, it's a useful one :)

revad commented 7 months ago

I have discovered that a selection of Maggie in the Woods will play if I remove all the gchords. (I had another case where that fixed a non-playing selection.)

Or if I select 'Play chords' in the abc2midi settings. (Unfortunately if I then add %%MIDI gchordoff - it won't play.)

That %%MIDI control 7 0 kludge was included in EasyABC v1.3.6.3 in 2015 when this repo was set up. It seems reasonable to remove it.

markblinkhorn commented 7 months ago

That explains it - my Internals>Input processed tune does match yours. I also observe the same 'feature' with the 1st/2nd time repeats. I suspect it is really a bug, but it doesn't crash out and is potentially useful; so it must be a feature...

This may go some way towards explaining what it going on with the repeats -

Using the [1 syntax, I get:

=== follow score ===

Synchronization error in row 23 (SVG row 24):
SVG023:|: d | "G"gf"Em"ed | "C"efg>e | "G"dB"D"A G/A/ | "G"BA"D"Ad |
Errors:                                                       !!!
SVG024:"G"gf"Em"ed | "C"efg>e | "G"dB"D"A B/A/ | "G"G2 G | [1 abc :| [2 def |]
SVG024:   ^^    ^^      ^^^ ^      ^^   ^ ^ ^       ^  ^      ^^^       ^^^
MID023:"G"  "Em"   | "C"      | "G"  "D"       | "G"G2 G | [1 abc  [2 def |]
MID023:                                             ^  ^      ^^^

And, with the (more usual?) syntax:

=== follow score ===

Synchronization error in row 23 (SVG row 24):
SVG023:|: d | "G"gf"Em"ed | "C"efg>e | "G"dB"D"A G/A/ | "G"BA"D"Ad |
Errors:                                                           !!
SVG024:"G"gf"Em"ed | "C"efg>e | "G"dB"D"A B/A/ | "G"G2 G |1 abc :|2 def |]
SVG024:   ^^    ^^      ^^^ ^      ^^   ^ ^ ^       ^  ^    ^^^     ^^^
MID023:"G"  "Em"   | "C"      | "G"  "D"       | "G"G2 G |1 abc 2 def |]
MID023:                                             ^  ^    ^^^   ^^^

The output tunes (respectively) show the following ABC code being passed to abc2midi

| [1 abc [2 def |] - only plays 1st variant

|1 abc 2 def |] - plays both variants (abc2midi must be simply ignoring the numerals)

I'm still no clearer why your 'Maggie in the Woods' play selection fails though!

markblinkhorn commented 7 months ago

Crossed messages. I tend to leave 'Play chords' enabled all the time and use %%MIDI gchordoff to switch.

I think we've covered all the bases now...