Exemple #1
0
def end_track(track):
    track.events.append(DeltaTime(track=track, time=0))

    end_of_track = MidiEvent(track=track, type='END_OF_TRACK')
    end_of_track.data = b''
    track.events.append(end_of_track)

    return track
Exemple #2
0
def write_starting_messages(track):
    no_time_spacing = DeltaTime(track, time=0)

    track.events.append(no_time_spacing)

    channel_prefix = MidiEvent(track=track, type=midi.MetaEvents.MIDI_CHANNEL_PREFIX)
    channel_prefix.data = b'\x00'
    track.events.append(channel_prefix)

    track.events.append(no_time_spacing)

    track_name = MidiEvent(track=track, type=MetaEvents.SEQUENCE_TRACK_NAME)
    track_name.data = bytes("Quantized Midi", 'ascii')
    track.events.append(track_name)

    # track.events.append(no_time_spacing)

    # smtpe_offset = MidiEvent(track=track, type='SMTPE_OFFSET')
    # smtpe_offset.data = b'\x00\x00\x00\x00'
    # track.events.append(smtpe_offset)
    #
    # track.events.append(no_time_spacing)

    # SET_TEMPO should be MetaEvents.SET_TEMPO
    # tempo = MidiEvent(track=track, type='SET_TEMPO')
    # tempo.data = b'\x05\xe8\x19'#b'\x07\xA1\x20'
    # track.events.append(tempo)

    # track.events.append(no_time_spacing)
    #
    # time_signature = MidiEvent(track=track, type='TIME_SIGNATURE')
    # time_signature.data = b'\x04\x02\x18\x08'
    # track.events.append(time_signature)

    track.events.append(no_time_spacing)

    program_change = MidiEvent(track=track, type=ChannelVoiceMessages.PROGRAM_CHANGE)
    program_change.channel = 1
    program_change.data = 1
    track.events.append(program_change)

    # track.events.append(no_time_spacing)
    #
    # Controller change should be ChannelVoiceMessages.CONTROLLER_CHANGE now
    # controller_change = MidiEvent(track=track, type='CONTROLLER_CHANGE')
    # controller_change.channel = 1
    # controller_change.parameter1 = 64
    # controller_change.parameter2 = 127
    # track.events.append(controller_change)
    # track.events.append(no_time_spacing)

    return track
Exemple #3
0
def quantized_to_midi(quantized_data):
    resulting_midi = MidiFile()
    resulting_midi.format = 0
    resulting_midi.ticksPerQuarterNote = 400

    track = MidiTrack(0)
    track.events.append(MidiEvent())
    track.setChannel(1)
Exemple #4
0
def end_notes(notes_to_end, track, first_delta_time_to_use):
    first = True
    time_written = 0

    for note in notes_to_end:
        if first:
            track.events.append(DeltaTime(track=track, time=first_delta_time_to_use))
            time_written = first_delta_time_to_use
            first = False
        else:
            track.events.append(DeltaTime(track=track, time=0))

        end_note = MidiEvent(track, type=ChannelVoiceMessages.NOTE_OFF)
        end_note.pitch = note[0]
        end_note.channel = 1
        end_note.parameter2 = 0

        track.events.append(end_note)

    return time_written
Exemple #5
0
def make_midi(quantized_data, ticks_per_sixteenth_note):
    result = MidiFile()
    result.format = 0
    result.ticksPerQuarterNote = ticks_per_sixteenth_note * 4
    track = write_starting_messages()

    beats_passed = 0

    notes_to_end = []

    leading_delta_time_written = False

    last_event_written_at_beat = 0

    for beat in quantized_data:
        to_write_end = find_notes_to_remove(notes_to_end)
        last_event_written_at_beat += int(
            end_notes(to_write_end, track,
                      (beats_passed - last_event_written_at_beat) *
                      ticks_per_sixteenth_note) / ticks_per_sixteenth_note)

        for note in beat:
            if note[1] > 0:
                time = (beats_passed -
                        last_event_written_at_beat) * ticks_per_sixteenth_note
                spacing = DeltaTime(track=track, time=time)
                track.events.append(spacing)

                last_event_written_at_beat = beats_passed

                note_on = MidiEvent(track=track, type='NOTE_ON')
                note_on.velocity = 50
                note_on.pitch = note[0]
                note_on.channel = 1
                track.events.append(note_on)
                notes_to_end.append(note)

        beats_passed += 1

        # toRemove = []
        #
        # for note in notes_to_end:
        #     if note[1] == 0:
        #         time = (beats_passed - last_event_written_at_beat) * ticks_per_sixteenth_note
        #         spacing = DeltaTime(track=track, time=time)
        #         track.events.append(spacing)
        #
        #         last_event_written_at_beat = beats_passed
        #
        #         note_off = MidiEvent(track=track, type='NOTE_OFF')
        #         note_off.pitch = note[0]
        #         note_off.channel = 1
        #         note_off.parameter2 = 0
        #         toRemove.append(note)
        #
        #     note[1] -= 1
        #     beats_passed += 1
        #
        # for note in toRemove:
        #     if note[1] <= 0:
        #         notes_to_end.remove(note)

    end_notes(notes_to_end, track, ticks_per_sixteenth_note * 2)

    end_track(track)
    result.tracks.append(track)

    return result
Exemple #6
0
def write_midi(pitch_block,
               duration_block,
               outfile="out.mid",
               qpm_multiplier=1024,
               tempo_multiplier=1.0):
    # Assumes any element with
    from music21.midi import MidiTrack, MidiFile, MidiEvent, DeltaTime
    # duration, pitch, velocity

    qpm_mult = qpm_multiplier
    all_mt = []

    for i in range(pitch_block.shape[0]):
        mt = MidiTrack(1)
        t = 0
        t_last = 0
        pitch_slice = pitch_block[i, :]
        duration_slice = duration_block[i, :]
        beat_slice = list((qpm_mult * duration_slice).astype("int32"))
        pitch_slice = list(pitch_slice.astype("int32"))
        for d, p in zip(beat_slice, pitch_slice):
            if (p == -1) or (d == -1):
                # bypass
                continue
            dt = DeltaTime(mt)
            dt.time = t - t_last
            mt.events.append(dt)

            me = MidiEvent(mt)
            me.type = "NOTE_ON"
            me.channel = 1
            me.time = None
            me.pitch = p
            me.velocity = 90
            mt.events.append(me)

            # add note off / velocity zero message
            dt = DeltaTime(mt)
            dt.time = d
            # add to track events
            mt.events.append(dt)

            me = MidiEvent(mt)
            me.type = "NOTE_ON"
            me.channel = 1
            me.time = None
            me.pitch = p
            me.velocity = 0
            mt.events.append(me)
            t_last = t + d
            t += d

        # add end of track
        dt = DeltaTime(mt)
        dt.time = 0
        mt.events.append(dt)

        me = MidiEvent(mt)
        me.type = "END_OF_TRACK"
        me.channel = 1
        me.data = ''
        mt.events.append(me)
        all_mt.append(mt)

    mf = MidiFile()
    mf.ticksPerQuarterNote = int(tempo_multiplier * qpm_mult)
    for mt in all_mt:
        mf.tracks.append(mt)

    mf.open(outfile, 'wb')
    mf.write()
    mf.close()
Exemple #7
0
def process_msg(msg):
    """
    Adds a new `music21.midi.Note` to `score` (`music21.stream.Score`) if it
    complete a note, otherwise just updates `curr_time_s`.

    Argument `msg` should be a `mido.Message`
    """
    global first_message_time_s
    global last_ticks
    global curr_time_s
    if first_message_time_s is None:
        first_message_time_s = msg.time

    # This is assuming that the time is tracked by adding all the delta times
    # that I ultimately get from rtmidi.
    # TODO need to check if the msg has this attribute?
    curr_time_s += msg.time

    # TODO also extend to those midi pads? requires that ~polytouch type, right?
    if msg.type not in ('note_on', 'note_off'):
        return

    # TODO TODO deal w/ pitch bends and stuff like that. music21 docs seem to
    # indicate some of its fns already deal with that, so i feel like i'm not
    # taking full advantage of the functionality already in the library...

    # TODO TODO TODO how to use deltatime w/o drifting away from true bpm due to
    # rounding????? (actually, how i'm doing it might be fine. check!!!)

    # TODO TODO TODO check assumption that delta time increments between each
    # msg (how do the ableton clock messages help then? how would i use any info
    # they have to improve current midi time est? is it just a matter of how
    # frequently they are received, or do they have other info that maybe i'm
    # just not getting?)
    # TODO TODO TODO if there is a quarter note in every position from 1.1.1,
    # this should equal ticks_per_quarter note at the note_off for the first
    # quarter note. why is it 4571 rather than 1024=ticks_per_quarter_note???
    ticks = secs2ticks(curr_time_s, offset_s=first_message_time_s)
    print('ticks:', ticks)
    d_ticks = ticks - last_ticks
    print('d_ticks:', d_ticks)

    # The conditional above should guarantee this `note` attribute exists.
    note_num = msg.note
    # Generating approprate input for `midiEventsToNote` as specified in
    # `music21` docs.
    # TODO TODO check, but it seems like rtmidi 0 indexes these, while ableton
    # and mido 1-index them?
    channel = msg.channel + 1
    if msg.type == 'note_on':
        assert note_num not in note_num2start_data
        # TODO any reason not to specify the channel?
        #delta1 = DeltaTime(midi_track, time=d_ticks, channel=msg.channel)
        delta1 = DeltaTime(midi_track, time=d_ticks)

        # TODO TODO leaving type=None of default OK? does that select some
        # appropriate version of the "ChannelVoiceMessages" the docs talk about?
        # TODO TODO del tim=0 here and below once i figure out whawt was causing
        # "type NoneType doesn't define __round__ method" error...
        on = MidiEvent(midi_track, type=CVM.NOTE_ON, channel=channel, time=0)
        # TODO refactor?
        print('msg.note:', msg.note)
        on.pitch = msg.note
        # TODO TODO TODO why is this None despite being set to an int?
        print('on.pitch:', on.pitch)
        print()
        on.velocity = msg.velocity

        note_num2start_data[note_num] = (ticks, delta1, on)

    elif msg.type == 'note_off':
        abs_on_ticks, delta1, on = note_num2start_data[note_num]
        # TODO TODO TODO or should this be from the last midi event???
        # that feels more consistent... (though it also seems harder for music21
        # to handle that...)
        # TODO TODO maybe test w/ single notes first
        # TODO delete ticks from start_data if not used
        #d_ticks = ticks - abs_on_ticks

        # TODO how are tracks used? will i only ever want one?

        # TODO see CVM.PITCH_BEND re: how to handle pitch bends (though prob.
        # not a complete solution right there...)

        #delta2 = DeltaTime(midi_track, time=d_ticks, channel=msg.channel)
        delta2 = DeltaTime(midi_track, time=d_ticks)

        # TODO why are there `time` fields for MidiEvent, if DeltaTime
        # is used for midiEventsToNote? (docs say it's non-essential as
        # DeltaTime is more important, so prob shouldn't worry)
        off = MidiEvent(midi_track, type=CVM.NOTE_OFF, channel=channel, time=0)
        # These variables aren't in the constructor (at least accoring to the
        # docs) for some reason.
        # TODO need to remap the range here, or good as-is?
        off.pitch = msg.note
        print('off.pitch:', off.pitch)
        off.velocity = msg.velocity

        # TODO need to set the ticksPerQuarter=None kwarg to this? and how
        # should i derive the appropriate input from the midi data i'm getting
        # from ableton? (i think i should set it, yes, but what happens if i
        # don't?)
        try:
            note = music21.midi.translate.midiEventsToNote(
                [delta1, on, delta2, off], ticksPerQuarter=ticks_per_quarter_note
                #[(delta1, on), (delta2, off)], ticksPerQuarter=ticks_per_quarter_note
            )
            print('WORKED!')
        except:
            print(delta1)
            print(on)
            print(delta2)
            print(off)
            print(ticks_per_quarter_note)
            raise
            import ipdb; ipdb.set_trace()

        del note_num2start_data[note_num]

    last_ticks = ticks