def testMultiVoice(self):
   tunes, exceptions = abc_parser.parse_abc_tunebook_file(
       os.path.join(testing_lib.get_testdata_dir(),
                    'zocharti_loch.abc'))
   self.assertEmpty(tunes)
   self.assertLen(exceptions, 1)
   self.assertIsInstance(exceptions[0], abc_parser.MultiVoiceError)
Example #2
0
    def compare_directory(self, directory):
        self.maxDiff = None  # pylint: disable=invalid-name

        files_in_dir = tf.gfile.ListDirectory(directory)
        files_parsed = 0
        for file_in_dir in files_in_dir:
            if not file_in_dir.endswith('.abc'):
                continue
            abc = os.path.join(directory, file_in_dir)
            midis = {}
            ref_num = 1
            while True:
                midi = re.sub(r'\.abc$',
                              str(ref_num) + '.mid',
                              os.path.join(directory, file_in_dir))
                if not tf.gfile.Exists(midi):
                    break
                midis[ref_num] = midi
                ref_num += 1

            print('parsing {}: {}'.format(files_parsed, abc))
            tunes, exceptions = abc_parser.parse_abc_tunebook_file(abc)
            files_parsed += 1
            self.assertEqual(len(tunes), len(midis) - len(exceptions))

            for tune in tunes.values():
                expanded_tune = sequences_lib.expand_section_groups(tune)
                midi_ns = midi_io.midi_file_to_sequence_proto(
                    midis[tune.reference_number])
                # abc2midi adds a 1-tick delay to the start of every note, but we don't.
                tick_length = ((1 / (midi_ns.tempos[0].qpm / 60)) /
                               midi_ns.ticks_per_quarter)
                for note in midi_ns.notes:
                    note.start_time -= tick_length
                    # For now, don't compare velocities.
                    note.velocity = 90
                if len(midi_ns.notes) != len(expanded_tune.notes):
                    pdb.set_trace()
                    self.assertProtoEquals(midi_ns, expanded_tune)
                for midi_note, test_note in zip(midi_ns.notes,
                                                expanded_tune.notes):
                    try:
                        self.assertProtoEquals(midi_note, test_note)
                    except Exception as e:  # pylint: disable=broad-except
                        print(e)
                        pdb.set_trace()
                self.assertEqual(midi_ns.total_time, expanded_tune.total_time)
def abcToStructuredSong(abc_path):
    
    print('Loading abc songbook: ', abc_path)
    
    abcSongbook = abc_parser.parse_abc_tunebook_file(abc_path)
    new_structured_songs = []
    
    # iterate over all valid songs in the abc songbook
    # each song is a note_sequence element 
    for abcSong in abcSongbook[0].values():
        # ensure there is only one time signiture
        # ensure that the song is in 4/4

        time_signatures = abcSong.time_signatures
        tempos = abcSong.tempos
        if len(time_signatures) == 1 and len(tempos) <= 1 and time_signatures[0].numerator == 4 and time_signatures[0].denominator == 4:
    
            # DEFINE THE BASIC SONG PROPERTIES
            metadata = abcSong.sequence_metadata
            
            structured_song = {}
            structured_song['title'] = metadata.title
            structured_song['total time [sec]'] = abcSong.total_time
            structured_song['quantization [sec]'] = abcSong.quantization_info.steps_per_second
            structured_song['quantization [beat]'] = abcSong.quantization_info.steps_per_quarter
            
            print('     Loading song: ', structured_song['title'])
            
            if not abcSong.tempos:
                structured_song['tempo'] = 120
            else:
                structured_song['tempo'] = abcSong.tempos[0]

            beat_duration_sec = 1 / (structured_song['tempo'] / 60)
            
            # sampling of the measure
            unit = beat_duration_sec * 4 / 96.
            # possible note durations in seconds 
            # (it is possible to add representations - include 32nds, quintuplets...):
            # [full, half, quarter, 8th, 16th, dot half, dot quarter, dot 8th, dot 16th, half note triplet, quarter note triplet, 8th note triplet]
            possible_durations = [unit * 96, unit * 48, unit * 24, unit * 12, unit * 6, unit * 3,
                                  unit * 72, unit * 36, unit * 18, 
                                  unit * 32, unit * 16]
            
            quarter_durations = [4, 2, 1, 1/2, 1/4, 1/8,
                                  3, 3/2, 3/4,
                                  1/6, 1/12]

            # Define durations dictionary
            dur_dict = {}
            dur_dict[possible_durations[0]] = 'full'
            dur_dict[possible_durations[1]] = 'half'
            dur_dict[possible_durations[2]] = 'quarter'
            dur_dict[possible_durations[3]] = '8th'
            dur_dict[possible_durations[4]] = '16th'
            dur_dict[possible_durations[5]] = '32th'
            dur_dict[possible_durations[6]] = 'dot half'
            dur_dict[possible_durations[7]] = 'dot quarter'
            dur_dict[possible_durations[8]] = 'dot 8th'
            dur_dict[possible_durations[9]] = 'half note triplet'
            dur_dict[possible_durations[10]] = 'quarter note triplet'
            
            chords_times = []
            for textannotation in abcSong.text_annotations:
                if textannotation.annotation_type == 1:
                    chord = textannotation.text
                    chord_time = textannotation.time
                    chords_times.append([chord, chord_time])
            
            # check if the song has chords
            if chords_times:                
                # BASIC ARRAYS FOR structured_songs
                bars = []
                bar_num = 0 # bar count starts from 0 
                beats = []
                # trick: only works for NottinghamDB
                if chords_times[0][1] != 0:
                    beat_num = 3
                    bar_duration = 3
                else:
                    beat_num = 0 # beat count is [0,3]
                    bar_duration = 0
                beat_pitch = []
                beat_duration = []
                beat_offset = []
                offset_sec = 0
                beat_counter = 0
                
                next_beat_sec = (beat_counter + 1) * beat_duration_sec 
                # iterate over the note_sequence notes
                for i in range(len(abcSong.notes)):
                    
                    note = abcSong.notes[i]
                    beat_pitch.append(note.pitch)
                    if note.start_time:
                        duration_sec = note.end_time - note.start_time
                    else:
                        # first note does not have start time
                        duration_sec = note.end_time
                    # calculate distance from each duration
                    distance = np.abs(np.array(possible_durations) - duration_sec)
                    idx = distance.argmin()
                    beat_duration.append(dur_dict[possible_durations[idx]])
                    offset = int(bar_duration * 96 / 4)
                    beat_offset.append(offset)
                    bar_duration += quarter_durations[idx]
                    offset_sec += duration_sec
                    
                    # check for chords
                    nochord = True
                    for j in range(len(chords_times)-1):
                        if chords_times[j+1][1] > note.start_time and chords_times[j][1] <= note.start_time:
                            chord = chords_times[j][0]
                            nochord = False
                        elif chords_times[-1][1] <= note.start_time:
                            chord = chords_times[-1][0]
                            nochord = False
                            break
                    if nochord:
                        print('No chord at song %s, note %d' % (structured_song['title'], i))
                        chord = 'NC'
                    
                    # calculate at which second there is a new beat
                    #next_beat_sec = (bar_num * 4 + beat_num + 1) * beat_duration_sec + first_note_start
                    while offset_sec >= next_beat_sec:
                        if beat_num >= 3:
                            # end of bar
                            # append beat
                            beat = {}
                            beat['num beat'] = beat_num + 1
                            # check for chords
                            nochord = True
                            for j in range(len(chords_times)-1):
                                if chords_times[j+1][1] >= next_beat_sec and chords_times[j][1] < next_beat_sec:
                                    chord = chords_times[j][0]
                                    nochord = False
                                elif chords_times[-1][1] < next_beat_sec:
                                    chord = chords_times[-1][0]
                                    nochord = False
                                    break
                            if nochord:
                                chord = 'NC'
                            beat['chord'] = chord 
                            beat['pitch'] = beat_pitch 
                            beat['duration'] = beat_duration 
                            beat['offset'] = beat_offset
                            beat['scale'] = []
                            beat['bass'] = []
                            beats.append(beat)
                            beat_pitch = []
                            beat_duration = []
                            beat_offset = []
                            # append bar
                            bar = {}
                            bar['num bar'] = bar_num + 1 # over all song
                            bar['beats'] = beats # beats 1,2,3,4
                            bars.append(bar)
                            beats = []
                            beat_num = 0
                            bar_num += 1
                            beat_counter += 1
                            next_beat_sec = (beat_counter + 1) * beat_duration_sec
                            bar_duration = 0
                        else:
                            # end of beat
                            beat = {}
                            # number of beat in the bar [1,4]
                            beat['num beat'] = beat_num + 1
                            # at most one chord per beat
                            # check for chords
                            nochord = True
                            for j in range(len(chords_times)-1):
                                if chords_times[j+1][1] >= next_beat_sec and chords_times[j][1] < next_beat_sec:
                                    chord = chords_times[j][0]
                                    nochord = False
                                elif chords_times[-1][1] < next_beat_sec:
                                    chord = chords_times[-1][0]
                                    nochord = False
                                    break
                            if nochord:
                                chord = 'NC'
                            beat['chord'] = chord 
                            # pitch of notes which START in this beat
                            beat['pitch'] = beat_pitch 
                            # duration of notes which START in this beat
                            beat['duration'] = beat_duration 
                            # offset of notes which START in this beat wrt the start of the bar
                            beat['offset'] = beat_offset
                            # get from chord with m21
                            beat['scale'] = []
                            beat['bass'] = []
                            # append beat
                            beats.append(beat)
                            beat_pitch = []
                            beat_duration = []
                            beat_offset = []
                            beat_num += 1
                            beat_counter += 1
                            next_beat_sec = (beat_counter + 1) * beat_duration_sec 

                    
                    # check for rests
                    
                    if i != len(abcSong.notes)-1:
                        intra_note_time = abcSong.notes[i+1].start_time - abcSong.notes[i].end_time
                        # if the interval between notes is greater than the smallest duration ('16th')
                        # and smaller than the greatest duration ('full') then there is a rest
                        if intra_note_time >= possible_durations[5]:
                            # there is a rest!
                            # handle the possibility of pauses longer than a full note
                            while intra_note_time > possible_durations[0]:
                                
                                beat_pitch.append('R')
                                # calculate distance from each duration
                                distance = np.abs(np.array(possible_durations) - intra_note_time)
                                idx = distance.argmin()
                                beat_duration.append(dur_dict[possible_durations[idx]])
                                offset = int(bar_duration * 96 / 4)
                                beat_offset.append(offset)
                                bar_duration += quarter_durations[idx]

                                offset_sec += duration_sec
                                intra_note_time -= possible_durations[idx]
                                
                                # check for chords
                                nochord = True
                                for j in range(len(chords_times)-1):
                                    if chords_times[j+1][1] > note.start_time and chords_times[j][1] <= note.start_time:
                                        chord = chords_times[j][0]
                                        nochord = False
                                    elif chords_times[-1][1] <= note.start_time:
                                        chord = chords_times[-1][0]
                                        nochord = False
                                        break
                                if nochord:
                                    print('No chord at song %s, note %d' % (structured_song['title'], i))
                                    chord = 'NC'
                                
                                # calculate at which second there is a new beat
                                #next_beat_sec = (bar_num * 4 + beat_num + 1) * beat_duration_sec 
                                while offset_sec >= next_beat_sec:
                                    if beat_num >= 3:
                                        # end of bar
                                        # append beat
                                        beat = {}
                                        beat['num beat'] = beat_num + 1
                                        # check for chords
                                        nochord = True
                                        for j in range(len(chords_times)-1):
                                            if chords_times[j+1][1] >= next_beat_sec and chords_times[j][1] < next_beat_sec:
                                                chord = chords_times[j][0]
                                                nochord = False
                                            elif chords_times[-1][1] < next_beat_sec:
                                                chord = chords_times[-1][0]
                                                nochord = False
                                                break
                                        if nochord:
                                            chord = 'NC'
                                        beat['chord'] = chord 
                                        beat['pitch'] = beat_pitch 
                                        beat['duration'] = beat_duration 
                                        beat['offset'] = beat_offset
                                        beat['scale'] = []
                                        beat['bass'] = []
                                        beats.append(beat)
                                        beat_pitch = []
                                        beat_duration = []
                                        beat_offset = []
                                        # append bar
                                        bar = {}
                                        bar['num bar'] = bar_num + 1 # over all song
                                        bar['beats'] = beats # beats 1,2,3,4
                                        bars.append(bar)
                                        beats = []
                                        beat_num = 0
                                        bar_num += 1
                                        beat_counter += 1
                                        next_beat_sec = (beat_counter + 1) * beat_duration_sec 
                                    else:
                                        # end of beat
                                        beat = {}
                                        # number of beat in the bar [1,4]
                                        beat['num beat'] = beat_num + 1
                                        # at most one chord per beat
                                        # check for chords
                                        nochord = True
                                        for j in range(len(chords_times)-1):
                                            if chords_times[j+1][1] >= next_beat_sec and chords_times[j][1] < next_beat_sec:
                                                chord = chords_times[j][0]
                                                nochord = False
                                            elif chords_times[-1][1] < next_beat_sec:
                                                chord = chords_times[-1][0]
                                                nochord = False
                                                break
                                        if nochord:
                                            chord = 'NC'
                                        beat['chord'] = chord 
                                        # pitch of notes which START in this beat
                                        beat['pitch'] = beat_pitch 
                                        # duration of notes which START in this beat
                                        beat['duration'] = beat_duration 
                                        # offset of notes which START in this beat wrt the start of the bar
                                        beat['offset'] = beat_offset
                                        # get from chord with m21
                                        beat['scale'] = []
                                        beat['bass'] = []
                                        # append beat
                                        beats.append(beat)
                                        beat_pitch = []
                                        beat_duration = []
                                        beat_offset = []
                                        beat_num += 1
                                        beat_counter += 1
                                        next_beat_sec = (beat_counter + 1) * beat_duration_sec 
                            
                            beat_pitch.append('R')
                            # calculate distance from each duration
                            distance = np.abs(np.array(possible_durations) - intra_note_time)
                            idx = distance.argmin()
                            beat_duration.append(dur_dict[possible_durations[idx]])
                            offset = int(bar_duration * 96 / 4)
                            beat_offset.append(offset)
                            bar_duration += quarter_durations[idx]
                            offset_sec += duration_sec
                            # check for chords
                            nochord = True
                            for j in range(len(chords_times)-1):
                                if chords_times[j+1][1] > note.start_time and chords_times[j][1] <= note.start_time:
                                    chord = chords_times[j][0]
                                    nochord = False
                                elif chords_times[-1][1] <= note.start_time:
                                    chord = chords_times[-1][0]
                                    nochord = False
                                    break
                            if nochord:
                                print('No chord at song %s, note %d' % (structured_song['title'], i))
                                chord = 'NC'
                            
                            # calculate at which second there is a new beat
                            #next_beat_sec = (bar_num * 4 + beat_num + 1) * beat_duration_sec 
                            while offset_sec >= next_beat_sec:
                                if beat_num >= 3:
                                    # end of bar
                                    # append beat
                                    beat = {}
                                    beat['num beat'] = beat_num + 1
                                    # check for chords
                                    nochord = True
                                    for j in range(len(chords_times)-1):
                                        if chords_times[j+1][1] >= next_beat_sec and chords_times[j][1] < next_beat_sec:
                                            chord = chords_times[j][0]
                                            nochord = False
                                        elif chords_times[-1][1] < next_beat_sec:
                                            chord = chords_times[-1][0]
                                            nochord = False
                                            break
                                    if nochord:
                                        chord = 'NC'
                                    beat['chord'] = chord 
                                    beat['pitch'] = beat_pitch 
                                    beat['duration'] = beat_duration 
                                    beat['offset'] = beat_offset
                                    beat['scale'] = []
                                    beat['bass'] = []
                                    beats.append(beat)
                                    beat_pitch = []
                                    beat_duration = []
                                    beat_offset = []
                                    # append bar
                                    bar = {}
                                    bar['num bar'] = bar_num + 1 # over all song
                                    bar['beats'] = beats # beats 1,2,3,4
                                    bars.append(bar)
                                    beats = []
                                    beat_num = 0
                                    bar_num += 1
                                    beat_counter += 1
                                    next_beat_sec = (beat_counter + 1) * beat_duration_sec 
                                else:
                                    # end of beat
                                    beat = {}
                                    # number of beat in the bar [1,4]
                                    beat['num beat'] = beat_num + 1
                                    # at most one chord per beat
                                    # check for chords
                                    nochord = True
                                    for j in range(len(chords_times)-1):
                                        if chords_times[j+1][1] >= next_beat_sec and chords_times[j][1] < next_beat_sec:
                                            chord = chords_times[j][0]
                                            nochord = False
                                        elif chords_times[-1][1] < next_beat_sec:
                                            chord = chords_times[-1][0]
                                            nochord = False
                                            break
                                    if nochord:
                                        chord = 'NC'
                                    beat['chord'] = chord 
                                    # pitch of notes which START in this beat
                                    beat['pitch'] = beat_pitch 
                                    # duration of notes which START in this beat
                                    beat['duration'] = beat_duration 
                                    # offset of notes which START in this beat wrt the start of the bar
                                    beat['offset'] = beat_offset
                                    # get from chord with m21
                                    beat['scale'] = []
                                    beat['bass'] = []
                                    # append beat
                                    beats.append(beat)
                                    beat_pitch = []
                                    beat_duration = []
                                    beat_offset = []
                                    beat_num += 1
                                    beat_counter += 1
                                    next_beat_sec = (beat_counter + 1) * beat_duration_sec 
                            
                            
                # append last bar in case it doesn't have 4 beats
                if beats:
                    # end of bar
                    # append beat
                    beat = {}
                    beat['num beat'] = beat_num + 1
                    # check for chords
                    nochord = True
                    for j in range(len(chords_times)-1):
                        if chords_times[j+1][1] >= next_beat_sec and chords_times[j][1] < next_beat_sec:
                            chord = chords_times[j][0]
                            nochord = False
                        elif chords_times[-1][1] < next_beat_sec:
                            chord = chords_times[-1][0]
                            nochord = False
                            break
                    if nochord:
                        chord = 'NC'
                    beat['chord'] = chord 
                    beat['pitch'] = beat_pitch 
                    beat['duration'] = beat_duration 
                    beat['offset'] = beat_offset
                    beat['scale'] = []
                    beat['bass'] = []
                    beats.append(beat)
                    beat_pitch = []
                    beat_duration = []
                    beat_offset = []
                    # append bar
                    bar = {}
                    bar['num bar'] = bar_num + 1 # over all song
                    bar['beats'] = beats # beats 1,2,3,4
                    bars.append(bar)
                    beats = []
                    beat_num = 0
                    bar_num += 1
                    beat_counter += 1
                    next_beat_sec = (beat_counter + 1) * beat_duration_sec
                                    
                structured_song['bars'] = bars
                structured_song['beat duration [sec]'] = beat_duration_sec
                
                # compute chords array
                chord_array = []
                for bar in bars:
                    for beat in bar['beats']:
                        chord_array.append(beat['chord'])
                
                # compute next chord 
                last_chord = chord_array[0]
                next_chords = []
                for i in range(len(chord_array)):
                    if chord_array[i] != last_chord:
                        next_chords.append(chord_array[i])
                        last_chord = chord_array[i]
                
                # compute array of next chords
                next_chords.append('NC')
                next_chord_array = []
                next_chord_pointer = 0
                last_chord = chord_array[0]
                for i in range(len(chord_array)):
                    if chord_array[i] != last_chord:
                        last_chord = chord_array[i]
                        next_chord_pointer += 1
                    next_chord_array.append(next_chords[next_chord_pointer])
                
                # compute next chord 
                last_chord = bars[0]['beats'][0]['chord']
                next_chords2 = []
                for bar in bars:
                    for beat in bar['beats']:
                        if beat['chord'] != last_chord:
                            next_chords2.append(beat['chord'])
                            last_chord = beat['chord']
                
                # add next chord to the beats
                last_chord = bars[0]['beats'][0]['chord']
                next_chords2.append('NC')
                next_chord_pointer = 0
                for bar in bars:
                    for beat in bar['beats']:
                        if beat['chord'] != last_chord:
                            last_chord = beat['chord']
                            next_chord_pointer += 1
                        beat['next chord'] = next_chords2[next_chord_pointer]
            
                structured_song['bars'] = bars
                #songs.append(song)
                new_structured_songs.append(structured_song)

    return new_structured_songs
  def testParseEnglishAbc(self):
    tunes, exceptions = abc_parser.parse_abc_tunebook_file(
        os.path.join(testing_lib.get_testdata_dir(), 'english.abc'))
    self.assertLen(tunes, 1)
    self.assertLen(exceptions, 2)
    self.assertIsInstance(exceptions[0], abc_parser.VariantEndingError)
    self.assertIsInstance(exceptions[1], abc_parser.PartError)

    expected_metadata1 = testing_lib.parse_test_proto(
        music_pb2.NoteSequence,
        """
        ticks_per_quarter: 220
        source_info: {
          source_type: SCORE_BASED
          encoding_type: ABC
          parser: MAGENTA_ABC
        }
        reference_number: 1
        sequence_metadata {
          title: "Dusty Miller, The; Binny's Jig"
          artist: "Trad."
          composers: "Trad."
        }
        key_signatures {
          key: G
        }
        section_annotations {
          time: 0.0
          section_id: 0
        }
        section_annotations {
          time: 6.0
          section_id: 1
        }
        section_annotations {
          time: 12.0
          section_id: 2
        }
        section_groups {
          sections {
            section_id: 0
          }
          num_times: 2
        }
        section_groups {
          sections {
            section_id: 1
          }
          num_times: 2
        }
        section_groups {
          sections {
            section_id: 2
          }
          num_times: 2
        }
        """)
    expected_expanded_metadata1 = testing_lib.parse_test_proto(
        music_pb2.NoteSequence,
        """
        ticks_per_quarter: 220
        source_info: {
          source_type: SCORE_BASED
          encoding_type: ABC
          parser: MAGENTA_ABC
        }
        reference_number: 1
        sequence_metadata {
          title: "Dusty Miller, The; Binny's Jig"
          artist: "Trad."
          composers: "Trad."
        }
        key_signatures {
          key: G
        }
        section_annotations {
          time: 0.0
          section_id: 0
        }
        section_annotations {
          time: 6.0
          section_id: 0
        }
        section_annotations {
          time: 12.0
          section_id: 1
        }
        section_annotations {
          time: 18.0
          section_id: 1
        }
        section_annotations {
          time: 24.0
          section_id: 2
        }
        section_annotations {
          time: 30.0
          section_id: 2
        }
        """)
    self.compare_to_abc2midi_and_metadata(
        'english1.mid', expected_metadata1,
        expected_expanded_metadata1, tunes[1])