Well, there is python-midi, which is stable enough:
You can easily create a script that automatically "shifts" the timing of events by decreasing the tick of the first event.
By "easily" I imply some basic rules:
- all events which have to be shifted must be on the same midi track
- the first Note event in any of those tracks should not be at the very beginning of the file
- tempo changes, if any, should not be too big
Of course, it's possible to do the same even with these limitations, but it will be much more difficult to implement it in the right way.
Timing in midi files works with "ticks" or "pulses", and each file has its own resolution, which represents the tick resolution per beat, or the smallest possible quantization: if a midi file has a resolution equal to 128 it means that the smallest possible division is a 128th of a quarter note.
The python-midi module treats midi files as nested lists. The main object, representing the midi file read (or to be written), is a Pattern, which is a list of Tracks, which in turn is a list of MidiEvents.
A very basic example, assuming you know the exact structure of your midi file and you want to shift the second track:
import midi
pattern = midi.read_midifile('myfile.mid')
strings = pattern[1]
#let's see what's the next event with a positive relative tick
eventId = 0
event = strings[eventId]
while not event.tick:
eventId += 1
event = strings[eventId]
#ensure that the resulting tick is >= 0
firstEvent.tick = max(0, firstEvent.tick - 150)
midi.write_midifile('newfile.mid', pattern)
Sometimes midi files contain an "invisible" track which includes some metadata; if you know the track name (and there are no duplicate names), you can find it like this:
import midi
pattern = midi.read_midifile('myfile.mid')
#the TrackName event _should_ be the first event in the track...
for track in pattern:
for eventId, event in enumerate(track):
if isinstance(event, midi.TrackNameEvent) and event.text == 'Strings':
break
eventId += 1
event = track[eventId]
while not event.tick:
eventId += 1
event = track[eventId]
event.tick = max(0, event.tick - 150)
midi.write_midifile('newfile.mid', pattern)
These are very simple examples, with no error checks at all.
Also, the 150 subtraction is arbitrary, and it should be computed according to the tempo and the tick resolution, which is accessible using pattern.resolution; supposing that your file has a resolution of 600 ticks per beat, the above examples will anticipate every event by a 16th, or 0.125 seconds if the tempo is set to 120bpm.
Once you've found out what's the preferred anticipation in seconds of your patch, you could compute the right timing in the script also: Tempo events are usually in the first track, so just cycle through it until you find a SetTempoEvent, and compute the "delta tick" like this:
deltaSecs = 0.125
deltaTick = int(deltaSecs * pattern.resolution * tempoEvent.bpm / 60.)
[...]
firstEvent.tick = max(0, event.tick - deltaTick)
I hope this helps!
MaurizioB