コード例 #1
0
def generate_mono(filename='generated_mono.gp5', tempo=120, pitches=None):
    if pitches is None:
        pitches = set(range(MIN_PITCH, MAX_PITCH + 1))

    tracks = [
        Track(
            "Electric Guitar",
            len(TUNING), TUNING + (-1,),
            1, 1, 2, N_FRETS, 0, (200, 55, 55, 0), 27
        ),
    ]
    measures = []
    beats = []
    onset_times = []
    list_of_pitches = []
    onset_time = START_OFFSET_SECONDS
    quarter_note_seconds = 60 / tempo
    for pitch in range(MIN_PITCH, MAX_PITCH + 1):
        if pitch in pitches:
            for string, fret in get_string_fret_possibilities(pitch):
                print('pitch={}, string={}, fret={}'.format(pitch, string, fret))

                if len(measures) == 0:
                    measures.append(Measure(4, 4, beam8notes=(2, 2, 2, 2)))
                else:
                    measures.append(Measure())

                beats_measure = create_measure()
                beats_measure[0][0].append(Beat([None] * 7, pause=True))
                onset_time += quarter_note_seconds

                notes = [None] * 7
                notes[string] = Note(fret)
                beats_measure[0][0].append(Beat(notes))
                onset_times.append(onset_time)
                list_of_pitches.append([pitch])
                onset_time += quarter_note_seconds

                beats_measure[0][0].append(Beat([None] * 7, pause=True))
                onset_time += quarter_note_seconds
                beats_measure[0][0].append(Beat([None] * 7, pause=True))
                onset_time += quarter_note_seconds
                beats.append(beats_measure)

    path_to_gp5_file = os.path.join(r'..\tmp', filename)
    write_gp5(measures, tracks, beats, tempo=tempo, outfile=path_to_gp5_file)
    path_to_truth_file = os.path.join(r'..\tmp', GP5_ENDING_REGEX.sub('', filename) + '.xml')
    write_truth_file([path_to_truth_file], [onset_times], [list_of_pitches])
コード例 #2
0
    recording_name = recording_name[:-4]
if args.track_title is None:
    track_title = recording_name
else:
    track_title = args.track_title

if args.path_to_gp5 is None:
    path_to_gp5 = recording_name + '.gp5'
else:
    path_to_gp5 = args.path_to_gp5

path_to_midi = recording_name + '.mid'

write_gp5(measures,
          tracks,
          beats,
          tempo=tempo,
          outfile=path_to_gp5,
          header=Header(title=track_title))

print('Exporting to Midi file')

from midiutil import MIDIFile

track = 0
channel = 0
time = 0  # In beats
duration = 1  # In beats
#tempo    = 60   # In BPM
volume = 100  # 0-127, as per the MIDI standard

MyMIDI = MIDIFile(1)  # One track, defaults to format 1 (tempo track is created
コード例 #3
0
def generate_poly(chord_type, filename='generated_poly.gp5', n_measures_per_file=10000, tempo=120, pitches=None):
    if pitches is None:
        pitches = set(range(MIN_PITCH, MAX_PITCH + 1))

    files = []
    tracks, measures, beats, onset_times, list_of_pitches, onset_time = init_file()

    # chords with 2 pitches
    if chord_type == 2:
        for pitch_1 in range(MIN_PITCH, MAX_PITCH + 1):
            if pitch_1 in pitches:
                for pitch_2 in range(pitch_1 + 1, MAX_PITCH + 1):
                    if pitch_1 != pitch_2:
                        possibilities = plausibility.get_all_fret_possibilities((pitch_1, pitch_2), tuning=TUNING, n_frets=N_FRETS)
                        if len(possibilities) > 0:
                            measure, beats_measure, onset_after_seconds, measure_duration_seconds, printable_notes = create_measure_beats_measure_printable_notes(
                                len(measures), possibilities[0], tempo
                            )
                            measures.append(measure)
                            beats.append(beats_measure)
                            onset_times.append(onset_time + onset_after_seconds)
                            onset_time += measure_duration_seconds
                            list_of_pitches.append([pitch_1, pitch_2])
                            print('pitch_1={}, pitch_2={}, notes={}'.format(
                                pitch_1, pitch_2, printable_notes
                            ))

                            if len(measures) >= n_measures_per_file:
                                if len(files) == 0:
                                    current_filename = filename
                                else:
                                    current_filename = GP5_ENDING_REGEX.sub('', filename) + '.' + str(len(files)) + '.gp5'
                                files.append((current_filename, measures, tracks, beats, onset_times, list_of_pitches))
                                tracks, measures, beats, onset_times, list_of_pitches, onset_time = init_file()

    # chords with 3 pitches
    elif chord_type == 3:
        for pitch_1 in range(MIN_PITCH, MAX_PITCH + 1):
            if pitch_1 in pitches:
                for pitch_2 in range(pitch_1 + 1, MAX_PITCH + 1):
                    for pitch_3 in range(pitch_2 + 1, MAX_PITCH + 1):
                        if pitch_1 != pitch_2 and pitch_1 != pitch_3 and pitch_2 != pitch_3:
                            possibilities = plausibility.get_all_fret_possibilities((pitch_1, pitch_2, pitch_3),
                                                                                    tuning=TUNING, n_frets=N_FRETS)
                            if len(possibilities) > 0:
                                measure, beats_measure, onset_after_seconds, measure_duration_seconds, printable_notes = create_measure_beats_measure_printable_notes(
                                    len(measures), possibilities[0], tempo
                                )
                                measures.append(measure)
                                beats.append(beats_measure)
                                onset_times.append(onset_time + onset_after_seconds)
                                onset_time += measure_duration_seconds
                                list_of_pitches.append([pitch_1, pitch_2, pitch_3])
                                print('pitch_1={}, pitch_2={}, pitch_3={}, notes={}'.format(
                                    pitch_1, pitch_2, pitch_3, printable_notes
                                ))

                                if len(measures) >= n_measures_per_file:
                                    if len(files) == 0:
                                        current_filename = filename
                                    else:
                                        current_filename = GP5_ENDING_REGEX.sub('', filename) + '.' + str(len(files)) + '.gp5'
                                    files.append((current_filename, measures, tracks, beats, onset_times, list_of_pitches))
                                    tracks, measures, beats, onset_times, list_of_pitches, onset_time = init_file()

    # chords with 4 pitches
    elif chord_type == 4:
        for pitch_1 in range(MIN_PITCH, MAX_PITCH + 1):
            if pitch_1 in pitches:
                for pitch_2 in range(pitch_1 + 1, MAX_PITCH + 1):
                    for pitch_3 in range(pitch_2 + 1, MAX_PITCH + 1):
                        for pitch_4 in range(pitch_3 + 1, MAX_PITCH + 1):
                            if (pitch_1 != pitch_2 and pitch_1 != pitch_3 and pitch_1 != pitch_4
                                and pitch_2 != pitch_3 and pitch_2 != pitch_4
                                and pitch_3 != pitch_4):
                                possibilities = plausibility.get_all_fret_possibilities((pitch_1, pitch_2, pitch_3, pitch_4),
                                                                                        tuning=TUNING, n_frets=N_FRETS)
                                if len(possibilities) > 0:
                                    measure, beats_measure, onset_after_seconds, measure_duration_seconds, printable_notes = create_measure_beats_measure_printable_notes(
                                        len(measures), possibilities[0], tempo
                                    )
                                    measures.append(measure)
                                    beats.append(beats_measure)
                                    onset_times.append(onset_time + onset_after_seconds)
                                    onset_time += measure_duration_seconds
                                    list_of_pitches.append([pitch_1, pitch_2, pitch_3, pitch_4])
                                    print('pitch_1={}, pitch_2={}, pitch_3={}, pitch_4={}, notes={}'.format(
                                        pitch_1, pitch_2, pitch_3, pitch_4, printable_notes
                                    ))

                                    if len(measures) >= n_measures_per_file:
                                        if len(files) == 0:
                                            current_filename = filename
                                        else:
                                            current_filename = GP5_ENDING_REGEX.sub('', filename) + '.' + str(len(files)) + '.gp5'
                                        files.append((current_filename, measures, tracks, beats, onset_times, list_of_pitches))
                                        tracks, measures, beats, onset_times, list_of_pitches, onset_time = init_file()

    else:
        raise ValueError('Unsupported chord type {}'.format(chord_type))

    if len(measures) > 0:
        if len(files) == 0:
            current_filename = filename
        else:
            current_filename = GP5_ENDING_REGEX.sub('', filename) + '.' + str(len(files)) + '.gp5'
        files.append((current_filename, measures, tracks, beats, onset_times, list_of_pitches))

    for filename, measures, tracks, beats, onset_times, list_of_pitches in files:
        path_to_gp5_file = os.path.join(r'..\tmp', filename)
        write_gp5(measures, tracks, beats, tempo=tempo, outfile=path_to_gp5_file)
        path_to_truth_file = os.path.join(r'..\tmp', GP5_ENDING_REGEX.sub('', filename) + '.xml')
        write_truth_file([path_to_truth_file], [onset_times], [list_of_pitches])
コード例 #4
0
assert args.track2 > 0, 'Invalid track number for track 2, should be > 0'

filename1 = os.path.basename(args.file1).rstrip('.gp5')
filename2 = os.path.basename(args.file2).rstrip('.gp5')

if args.outfile is None:
    out_path = filename1 + ' VS ' + filename2 + '.gp5'
else:
    out_path = args.outfile

gp5file1 = GP5File(args.file1)
gp5file2 = GP5File(args.file2)

assert args.track1 <= gp5file1.nTracks, 'Invalid track number for track 1, should be <= number of tracks'
assert args.track2 <= gp5file2.nTracks, 'Invalid track number for track 2, should be <= number of tracks'
assert [gp5file1.tracks[args.track1 - 1].channel, gp5file2.tracks[args.track2 - 1].channel].count(10) != 1, \
    'Invalid tracks, cannot compare a melodic to a percussion track'

common_markers = meta_comparison(gp5file1, gp5file2)
measures, beats = compare(gp5file1, gp5file2, args.track1, args.track2, TUNING,
                          common_markers, args.compare_positions)
tracks = [
    gp5file1.tracks[args.track1 - 1],
    copy(gp5file1.tracks[args.track1 - 1]), gp5file2.tracks[args.track2 - 1]
]
tracks[0].name = 'common notes'
tracks[1].name = 'differences file 1'
tracks[2].name = 'differences file 2'

write_gp5(measures, tracks, beats, tempo=gp5file1.tempo, outfile=out_path)
コード例 #5
0
def convert_midi2gp5(path_to_midi,
                     outfile,
                     shortest_note=0.25,
                     init_tempo=120,
                     time_signature=(4, 4),
                     force_drums=False,
                     default_instrument=30,
                     verbose=False):
    """ Convert a MIDI file to GP5
    
    Parameters
    ----------
    path_to_midi: str
        path to MIDI file
    outfile: str
        path to GP5 output file
    shortest_note: float, optional
        shortest note to be considered, in quarters. Default: 0.25 (16th notes)
    init_tempo: int, optional
        Tempo in beats per minute (bpm) to assume, if the file doesn't specify a tempo. Ignored otherwise.
    time_signature: (int, int), optional
        Time signature (numerator, denominator). Default: 4/4
    force_drums: bool, optional
        Force to be considered a drum track, even if the track is not on MIDI channel 10 as it should be due to
        the MIDI standard. Default: False
    default_instrument: int, optional
        Instrument to use if not specified in the MIDI file. Default: 30 (distortion guitar)
    verbose: bool, optional
        Print raw info from the MIDI track. Default: False
    """

    gp5_measures = []
    gp5_tracks = []
    gp5_beats = []

    event_queue = []

    track_mapping = {}
    track_name = {}
    track_instrument = {}
    track_min_note = {}
    track_max_note = {}

    unused_midi_channels = list(range(1, 65))
    unused_midi_channels.remove(10)  # remove drum track

    # read file
    with open(path_to_midi, 'rb') as f:
        midi = f.read()
    score = midi2score(midi)
    ticks_per_quarter = score[0]

    for (i, track) in enumerate(score[1:]):
        # determine track mapping
        track_mapping[i] = -1
        track_name[i] = "Track" + str(i)
        track_instrument[i] = default_instrument
        track_max_note[i] = -1
        track_min_note[i] = 128
        elements_to_push = []
        for event in track:
            if event[0] in midi_events:
                if event[0] == 'patch_change' and event[1] == 0:
                    track_instrument[i] = event[3]
                    if event[
                            2] == 9:  # channel 10 (0-based here) indicates drum track! (midi standard)
                        track_instrument[i] = -1 - track_instrument[
                            i]  # save drums as negative number (-1 bc -0 == 0)
                    if verbose:
                        print(
                            'initial patch [raw track:{}]: chan:{}, patch:{}'.
                            format(i, event[2], event[3]))
                else:
                    elements_to_push.append(event)
                    if event[
                            0] == 'note':  # start_time, duration, channel, note, velocity
                        track_max_note[i] = max(track_max_note[i], event[4])
                        track_min_note[i] = min(track_min_note[i], event[4])
                track_mapping[i] = i
            else:
                if event[0] == 'track_name':
                    track_name[i] = event[2].decode(
                        'ISO-8859-1'
                    )  # decode('ascii','ignore') / decode('UTF-8')
                else:
                    elements_to_push.append(event)
        for event in elements_to_push:
            heappush(event_queue, (event[1], track_mapping[i], event))

    heappush(event_queue,
             (float('inf'), -1, ['song_end', float('inf')
                                 ]))  # add end of song event

    # rearrange track indices to fill gaps of tracks that only contain meta-events
    idx = 0
    for key, value in sorted(track_mapping.items()):
        if value > -1:  # actual track, no meta-track
            track_mapping[key] = idx
            idx += 1

            # default values, assume drum track
            tuning = (0, 0, 0, 0, 0, 0, 0
                      )  # apparently file format writes this tuning for drums
            n_strings = 6  # apparently file format writes drum tracks have 6 strings
            channel1 = channel2 = 10  # default assume drum track

            if track_instrument[
                    key] >= 0 and not force_drums:  # not a drum track
                channel1 = min(unused_midi_channels)
                unused_midi_channels.remove(channel1)
                channel2 = min(unused_midi_channels)
                unused_midi_channels.remove(channel2)
                tuning = determine_tuning(track_min_note[key])
                n_strings = 7 - tuning.count(-1)
            elif track_instrument[key] < 0:
                track_instrument[key] = -1 - track_instrument[
                    key]  # get back correct instrument number
            elif force_drums:
                track_instrument[key] = 0
            gp5_tracks.append(
                Track(
                    track_name[key],  # track name
                    n_strings,  # number of strings
                    tuning,  # tuning (7 strings: highest to lowest)
                    1,  # midi port (default always 1)
                    channel1,  # channel 1
                    channel2,  # channel 2 (effects channel)
                    87 if channel1 == 10 else
                    30,  # frets: use some reserve frets in case the tuning was determined wrongly
                    0,  # capo
                    (0 if channel1 == 10 else 255, 0, 0,
                     0),  # color (we use black for drums, red for guitar)
                    track_instrument[key]  # instrument
                ))

    track_struct = []
    for t in gp5_tracks:
        track_struct.append(
            ([], []))  # add t tuples with two empty lists (for each voice)
        if verbose:
            print(t)

    numerator, denominator = time_signature
    note_accuracy = 1 / shortest_note  # number of notes per quarter
    gp5_duration = int(log2(note_accuracy))  # calculate gp5-duration

    time_signature_changed = True  # first measure needs to contain time signature
    cur_marker_name = ""

    cur_beat_start_ticks = 0.0  # current beat started at tick 0.0
    next_beat_start_ticks = (4 * numerator / denominator
                             )  # next beat starts at tick
    cur_measure = 0  # start with measure 0
    gp5_beats.append(deepcopy(track_struct))  # append empty measure 0

    gp5_note_overflows = [(0, None)] * len(
        gp5_tracks
    )  # number of notes (G_GP5DURATION) that didn't fit in last measure

    for i in range(len(event_queue)):
        (time, track, event) = heappop(event_queue)
        ticks = event[1] / ticks_per_quarter

        if ticks >= next_beat_start_ticks:
            nn = numerator if time_signature_changed else 0  # numerator
            dd = denominator if time_signature_changed else 0  # denominator
            time_signature_changed = False

            # fill measure up with pauses
            for t in range(len(gp5_tracks)):
                past_notes = round(
                    note_accuracy *
                    (next_beat_start_ticks - cur_beat_start_ticks))
                cur_gp5_beats = gp5_beats[cur_measure][t][0]
                for b in range(len(cur_gp5_beats),
                               past_notes):  # insert pauses
                    cur_gp5_beats.append(
                        Beat([None] * 7, duration=gp5_duration, pause=True))

            # repeat_open repeat_close repeat_alt m_name marker_color maj_key min_key double_bar beam8notes triplet_feel
            gp5_measures.append(Measure(nn, dd, marker_name=cur_marker_name))
            cur_measure += 1

            if event[0] != 'song_end':  # don't create new measure in the end TODO write overflow notes at end
                gp5_beats.append(
                    deepcopy(track_struct))  # append empty measure 0
                for j in range(
                        len(gp5_note_overflows)):  # write overflowing notes
                    of_cur_notes = min(
                        gp5_note_overflows[j][0],
                        int(4 * numerator / denominator * note_accuracy))
                    for b in range(of_cur_notes):
                        gp5_beats[cur_measure][j][0].append(
                            Beat(gp5_note_overflows[j][1],
                                 duration=gp5_duration))
                    gp5_note_overflows[j] = (gp5_note_overflows[j][0] -
                                             of_cur_notes,
                                             gp5_note_overflows[j][1])

                cur_marker_name = ""  # reset name
                cur_beat_start_ticks = next_beat_start_ticks
                next_beat_start_ticks += (4 * numerator / denominator)

        if event[0] == 'note':  # start_time, duration, channel, note, velocity
            dur = event[
                2] / ticks_per_quarter  # duration of the midi note [in quarters]
            dur_remaining = next_beat_start_ticks - ticks  # remaining quarters that fit in the current measure
            dur_past = ticks - cur_beat_start_ticks  # past quarters in current measure before current note
            n_notes = max(1, round(dur *
                                   note_accuracy))  # total notes to be written
            remaining_notes = round(
                dur_remaining * note_accuracy
            )  # max notes that can be written to current measure
            past_notes = round(dur_past *
                               note_accuracy)  # note offset from measure start
            cur_notes = min(
                n_notes,
                remaining_notes)  # notes to write into current measure

            # at this point every note should be exactly of length note_accuracy
            cur_track = track_mapping[
                track]  # real track index (without meta-tracks)
            cur_gp5_beats = gp5_beats[cur_measure][cur_track][0]
            for b in range(len(cur_gp5_beats), past_notes):  # insert pauses
                cur_gp5_beats.append(
                    Beat([None] * 7, duration=gp5_duration, pause=True))

            overflow_notes = [None] * 7
            is_tied = False  # first beat untied
            for cur_beat_idx in range(past_notes, past_notes + cur_notes):
                if len(cur_gp5_beats
                       ) <= cur_beat_idx:  # insert new beat if needed
                    cur_gp5_beats.append(Beat([None] * 7))
                assert len(cur_gp5_beats
                           ) > cur_beat_idx, "ERR: len:{}, idx:{}".format(
                               len(cur_gp5_beats), cur_beat_idx)
                notes = cur_gp5_beats[cur_beat_idx].notes
                tuning = gp5_tracks[cur_track].tuning
                for t in range(6, -1, -1):
                    if 0 <= tuning[t] <= event[4] and t < gp5_tracks[
                            cur_track].nStrings and notes[t] is None:
                        notes[t] = Note(event[4] - tuning[t], tied=is_tied)
                        overflow_notes = notes
                        break  # TODO this doesnt make sure a note is written!
                        # better: get all prev notes, list all possible positions for each note -> get most plausible
                        # right now a high E is written on lowest string, when followed by a low E -> impossible!

                cur_gp5_beats[cur_beat_idx] = Beat(notes,
                                                   duration=gp5_duration)
                is_tied = True  # following beats tied!

            gp5_note_overflows[cur_track] = [
                max(0, n_notes - cur_notes), overflow_notes
            ]
            if verbose:
                print('{}[{}]: note:{}, dur:{}, chan:{}, v:{}'.format(
                    ticks, cur_track, event[4], dur, event[3], event[5]))
        elif event[0] == 'set_tempo':
            tempo = round(1 / (event[2] / 60000000))
            init_tempo = tempo if ticks == 0 else init_tempo  # update init tempo if necessary
            if verbose:
                print('{}: set tempo: {}'.format(ticks, tempo))
        elif event[
                0] == 'time_signature':  # event, time, nn, dd, metronome_clicks, speed (num of 32ths to the quarter)
            numerator = event[2]  # nn / numerator
            denominator = pow(
                2,
                event[3])  # dd / log_denominator -> 2=quarter, 3=eights, etc.
            time_signature_changed = True
            next_beat_start_ticks = cur_beat_start_ticks + (4 * numerator /
                                                            denominator)
            if verbose:
                print('{}: time signature: {} {} {} {}'.format(
                    ticks, event[2], event[3], event[4], event[5]))
        elif event[0] == 'marker':
            cur_marker_name = event[2].decode(
                'ISO-8859-1')  # decode('ascii','ignore') / decode('UTF-8')
            if verbose:
                print('{}: set marker: {}'.format(ticks, cur_marker_name))
        elif event[0] == 'patch_change' and verbose:  # instrument change
            print('{}: patch change: chan:{}, patch:{}'.format(
                ticks, event[2], event[3]))
        # elif event[0] == 'control_change':  # track volume, pan, usw. - not needed here
        #     print('{}: control change: chan:{}, control:{}, val:{}'.format(ticks, event[2],event[3],event[4]))
        # elif event[0] == 'pitch_wheel_change':  # bend-release - not needed here
        #     print('{}: pitch wheel change: chan:{}, pitch wheel:{}'.format(ticks, event[2], event[3]))
        elif verbose:
            print('{}: -- unknown event: {}'.format(ticks, event[0]))

    if False and verbose:
        for m in gp5_measures:
            print(m)

    collapse_beats(gp5_beats, gp5_duration)

    assert len(gp5_measures) == len(gp5_beats), "ERR: Mlen {}!={}".format(
        len(gp5_measures), len(gp5_beats))
    if False and verbose:
        for m in range(len(gp5_beats)):
            print('Measure {}'.format(m + 1))
            assert len(gp5_tracks) == len(
                gp5_beats[m]), "ERR: Tlen {}!={}".format(
                    len(gp5_tracks), len(gp5_beats[m]))
            for t in range(len(gp5_beats[m])):
                print('\tTrack {}'.format(t + 1))
                for b in gp5_beats[m][t][0]:
                    print("\t\tBeat")
                    if b.pause or b.empty:
                        print("\t\t\t", b.notes)
                    else:
                        for n in b.notes:
                            print("\t\t\t", n)
                    print("\t\t\t dur:{}, dot:{}, pause:{}, empty:{}".format(
                        b.duration, b.dotted, b.pause, b.empty))

    write_gp5(gp5_measures, gp5_tracks, gp5_beats, init_tempo, outfile=outfile)
コード例 #6
0
onset_times_seconds = read_onset_times(path_to_truth, 1, 'xml', 0.05)
onset_times_grouped, list_of_pitch_sets = _read_onset_times_pitches(path_to_truth, 40, 88, 1, 0.05)
assert onset_times_seconds == onset_times_grouped

string_fret_detector = SequenceStringFretDetection(tuning, n_frets)
list_of_string_lists, list_of_fret_lists = string_fret_detector.predict_strings_and_frets(
    None, onset_times_seconds, list_of_pitch_sets
)

beat_transformer = SimpleBeatTransformer(shortest_note=shortest_note, beats_per_measure=float(4.0))
beats = beat_transformer.transform(path_to_wav, onset_times_seconds,
                                   list_of_string_lists, list_of_fret_lists, tempo)

measures = []
for i, measure in enumerate(beats):
    if i == 0:
        measures.append(Measure(4, 4, beam8notes=(2, 2, 2, 2)))
    else:
        measures.append(Measure())

tracks = [Track("Electric Guitar", len(tuning), tuning + (-1,), 1, 1, 2, n_frets, 0, (200, 55, 55, 0), 25)]


recording_name = os.path.basename(path_to_wav)
if recording_name.endswith('.wav'):
    recording_name = recording_name[:-4]
path_to_gp5 = '..\\tmp\\' + recording_name + '.gp5'

write_gp5(measures, tracks, beats, tempo=tempo, outfile=path_to_gp5)
コード例 #7
0
        (  # track 1
            [  # voice 1: notes duration pause empty dotted ntuple_feel chord text effect mix_change
                Beat([None, None, None, None,
                      Note(3), Note(0), None],
                     duration=-2)
            ],
            []  # voice 2
        )
    ],
    [  # measure 8
        (  # track 1
            [  # voice 1: notes duration pause empty dotted ntuple_feel chord text effect mix_change
                Beat([None, None, None, None,
                      Note(3), Note(0), None],
                     duration=-2)
            ],
            []  # voice 2
        )
    ],
]

write_gp5(
    measures,
    tracks,
    beats,
    tempo=133,
    outfile="out.gp5",
    # header=Header('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'),
    # lyrics=Lyrics(1, [(1, "I have no"), (4, "Idea what you talk"), (1, "about, wtf")]),
)