-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmidi_process.py
More file actions
122 lines (106 loc) · 4.33 KB
/
midi_process.py
File metadata and controls
122 lines (106 loc) · 4.33 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
from math import inf
from py_midicsv import midi_to_csv
class Song:
def __init__(self, title):
self.title = title
self.notes = []
self.minPitch = inf
self.maxPitch = -inf
self.minTrack = inf
self.maxTrack = -inf
self.startTime = inf
self.endTime = -inf
def add_note(self, track, tick_on, tick_off, pitch, velocity, tempo_map):
note = self.Note(track, tick_on, tick_off, pitch, velocity, tempo_map)
self.notes.append(note)
self.minPitch = min(self.minPitch, note.pitch)
self.maxPitch = max(self.maxPitch, note.pitch)
self.minTrack = min(self.minTrack, note.track)
self.maxTrack = max(self.maxTrack, note.track)
self.startTime = min(self.startTime, note.time)
self.endTime = max(self.endTime, note.time + note.duration)
class Note:
def __init__(self, track, tick_on, tick_off, pitch, velocity, tempo_map):
MICROS_PER_SECOND = 1000000
MAX_VELOCITY = 127
self.track = track
start = tempo_map.micros_at_tick(tick_on) / MICROS_PER_SECOND
end = tempo_map.micros_at_tick(tick_off) / MICROS_PER_SECOND
self.duration = end - start
self.time = start
self.pitch = pitch
self.velocity = velocity / MAX_VELOCITY
class TempoMap:
def __init__(self):
self.ticks_per_quarter_note = 480
self.tempo_map = []
def add_tempo(self, tick, tempo):
tempo_event = self.TempoEvent(tick, tempo, self.micros_at_tick(tick))
self.tempo_map.append(tempo_event)
def micros_at_tick(self, tick):
tempo_event_at_tick = self.TempoEvent()
for tempo_event in self.tempo_map:
if tempo_event.tick > tick:
break
tempo_event_at_tick = tempo_event
micros_offset = (
(tick - tempo_event_at_tick.tick)
/ self.ticks_per_quarter_note
* tempo_event_at_tick.tempo
)
return tempo_event_at_tick.micros + micros_offset
class TempoEvent:
def __init__(self, tick=0, tempo=500000, micros=0):
self.tick = tick
self.tempo = tempo
self.micros = micros
def process_midi(midiFile):
if isinstance(midiFile, str):
filename = midiFile.rsplit("/", 1)[-1]
else:
filename = midiFile.filename
filename = filename.rsplit(".", 1)
title = filename[0]
extension = filename[1].lower() if 1 < len(filename) else ""
if extension not in ["mid", "midi", "kar"]:
raise ValueError("Oops! Your file seems to have the wrong extension!")
try:
csv = midi_to_csv(midiFile)
except Exception:
raise ValueError("Oh no! There was a problem processing your MIDI file!")
song = Song(title)
tempo_map = TempoMap()
rows = "".join(csv).splitlines()
for i, row in enumerate(rows):
cells = row.split(", ")
event = cells[2]
if event == "Header":
tempo_map.ticks_per_quarter_note = int(cells[5])
elif event == "Title_t" and (track := int(cells[0])) == 1:
song.title = cells[3][1:-1]
elif event == "Tempo":
tick = int(cells[1])
tempo = int(cells[3])
tempo_map.add_tempo(tick, tempo)
elif event == "Note_on_c" and (velocity := int(cells[5])) != 0:
track_on = int(cells[0])
tick_on = int(cells[1])
pitch_on = int(cells[4])
velocity_on = velocity
for row in rows[i:]:
cells = row.split(", ")
event = cells[2]
if (
(
(event == "Note_on_c" and (velocity := int(cells[5])) == 0)
or event == "Note_off_c"
)
and track_on == (track_off := int(cells[0]))
and pitch_on == (pitch_off := int(cells[4]))
):
tick_off = int(cells[1])
song.add_note(track_on, tick_on, tick_off, pitch_on, velocity_on, tempo_map)
break
if not song.notes:
raise ValueError("Uh oh! Looks like your MIDI file doesn't have any notes!")
return song