def limiting_notes(block):
    r"""
    Given a block, generate all the limiting notes and the index of the chord
    they belong to.

    Note that, for the sake of predictability, notes within the same chord are
    generated based on their string order: limiting notes on a high string are
    yielded before limiting notes on a low string within the same chord.
    >>> limiting_notes(['E|-0', 'B|-0', 'G|-0', 'D|-0', 'A|-0', 'E|-0']).next()
    (('E', 2), 0)
    """
    chord_index = 0
    for chord in chords(block):
        for string_num in sorted(chord.keys()):
            note = fret2note(chord[string_num], string_num)
            if is_limiting(note):
                yield (note, chord_index)
        chord_index += 1
def midi_contexts(midi_path):
    r"""
    For each chord in a midi file, yield a context.

    Note that these contexts differ from those provided by the
    `chord_contexts` function, because data about physical notes
    is unavailable.
    
    Specifically, the information provided is as follows:

       #. the current theoretical chord
       #. the following theoretical chords
       #. the chord index of the current chord
       #. the next limiting note along with its chord index

    **Also note that only notes occur in contexts.
    Rests are ignored, and only the melody section is examined,
    so chords are not relevant.**
    """
    piece = music21.converter.parse(midi_path)
    melody_part = _find_melody_part(piece)
    pitches = [n.pitch for n in melody_part if isinstance(n, Note)]
    for pitch_index in range(0, len(pitches)):
        if "-" in pitches[pitch_index].name:
            pitches[pitch_index] = pitches[pitch_index].getEnharmonic()
    notes = [(p.name, p.octave) for p in pitches]

    following_indices = range(1, CHORDS_AFTER_CURRENT + 1)  # assuming slack

    for current_index in range(0, len(notes)):
        limiting = None
        for search_index in range(current_index + 1, len(notes)):
            if is_limiting(notes[search_index]):
                limiting = (notes[search_index], search_index)
                break

        current = Counter({notes[current_index]: 1})  # standard rep
        following = [Counter({notes[index]: 1}) for index in following_indices]
        guesstimate = [current, following, current_index, limiting]
        yield tuple(guesstimate)
        if following_indices:
            del (following_indices[0])
        if following_indices and following_indices[-1] < len(notes) - 1:
            following_indices.append(following_indices[-1] + 1)
 def test_middle_c_not_limiting(self, mock_frets):
     note = ('C', 3)
     mock_frets.return_value = [(8, ('E', 2)), (3, ('A', 3))]
     self.assertFalse(notemappings.is_limiting(note))
 def test_highest_a_two_positions(self, mock_frets):
     note = ('A', 6)
     mock_frets.return_value = [(22, ('B', 4)), (17, ('E', 4))]
     self.assertTrue(notemappings.is_limiting(note))
 def test_lowest_e_one_position(self, mock_frets):
     note = ('E', 2)
     mock_frets.return_value = [(0, ('E', 2))]
     self.assertTrue(notemappings.is_limiting(note))