pythontimepygamemidi

Write MIDI file


I want to write a MIDI file with the inputs I receive from the digital piano I have connected. I am using pygame.midi to open an input port and midiutil to write the MIDI file. What I can't wrap my head around is the timing. For example, in addNote(track, channel, pitch, time, duration, volume), how do I know what time and duration of a note is? When reading a note, I get pitch, and volume just fine, but the others I have no idea... I tried using the timestamp but to no avail, it places the note really far away in the MIDI file.

So, how do I compute the 'time' and 'duration' of a note?


Solution

  • time dictates the position in musical time the note should be played. Exactly what the parameter should be depends in part how the Midi file object was constructed (more on that soon)

    In practice, MIDI asks for two messages per note: a NOTE On message and a NOTE Off message. The duration will indicate when the Note Off message should be sent, relative to the start of the note. Again, how the parameter should be formed depends on how the file object is constructed.

    From the MIDIUtil docs:

    • time – the time at which the note sounds. The value can be either quarter notes [Float], or ticks [Integer]. Ticks may be specified by passing eventtime_is_ticks=True to the MIDIFile constructor. The default is quarter notes.
    • duration – the duration of the note. Like the time argument, the value can be either quarter notes [Float], or ticks [Integer]

    A complete example that plays the C major scale

    from midiutil import MIDIFile
    degrees = [60, 62, 64, 65, 67, 69, 71, 72] # MIDI note number
    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
    # automatically created)
    MyMIDI.addTempo(track,time, tempo)
    for pitch in degrees:
        MyMIDI.addNote(track, channel, pitch, time, duration, volume)
        time = time + 1
    with open("major-scale.mid", "wb") as output_file:
        MyMIDI.writeFile(output_file)
    

    The combination of the file tempo (at the current time) combined with the note's position (time) and duration (in terms of how many beats), the library can synthesize all the midi messages needed to play (start/stop) the note at the correct time.

    Another Example

    Let's try applying this to the following musical phrase:

    musical phrase

    First, set everything up.

    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
    # automatically created)
    MyMIDI.addTempo(track,time, tempo)
    

    To add the first half note on E and quarter note on G:

    time = 0  # it's the first beat of the piece
    quarter_note = 1  # equal to one beat, assuming x/4 time
    half_note = 2 # Half notes are 2x value of a single quarter note
    E3 = 64  # MIDI note value for E3
    G3 = 67
    
    # Add half note
    MyMIDI.addNote(track, channel, pitch=E3, duration=half_note, time=0, volume=volume)
    # Add quarter note
    MyMIDI.addNote(track, channel, pitch=G3, duration=quarter_note, time=0, volume=volume)
    

    Now let's add the remaining notes:

    A3 = 69
    C3 = 60
    B3 = 71
    C4 = 72
    
    # add the remaining notes
    for time, pitch, duration in [(1,A3, quarter_note),
                                  (2,B3, quarter_note), (2, C3, half_note), 
                                  (3,C4, quarter_note)]:
        MyMIDI.addNote(track, channel, 
                       duration=duration, 
                       pitch=pitch, time=time, volume=volume)