-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathparsers.py
169 lines (155 loc) · 5.92 KB
/
parsers.py
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
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
# -*- coding: utf-8 -*-
"""
Created on Sat May 18 21:47:44 2019
@author: wmy
"""
import midi
import numpy as np
import glob
from tqdm import tqdm
import matplotlib.pyplot as plt
import os
class MidiParser(object):
'''Midi Parser'''
def __init__(self, name=None):
self.__lowest_pitch = 21
self.__highest_pitch = 108
self.__pitch_span = self.__highest_pitch - self.__lowest_pitch + 1
self.name = name
pass
@property
def lowest_pitch(self):
return self.__lowest_pitch
@lowest_pitch.setter
def lowest_pitch(self, pitch):
if isinstance(pitch, int):
if pitch >= 0:
if pitch <= self.__highest_pitch:
self.__lowest_pitch = pitch
self.__pitch_span = self.__highest_pitch - self.__lowest_pitch + 1
else:
raise ValueError("lowest pitch must be lower than highest pitch")
else:
raise ValueError("expected lowest pitch >= 0")
else:
raise ValueError("lowest pitch must be the type of int")
@property
def highest_pitch(self):
return self.__highest_pitch
@highest_pitch.setter
def highest_pitch(self, pitch):
if isinstance(pitch, int):
if pitch <= 127:
if pitch >= self.__lowest_pitch:
self.__highest_pitch = pitch
self.__pitch_span = self.__highest_pitch - self.__lowest_pitch + 1
else:
raise ValueError("highest pitch must be higher than lowest pitch")
else:
raise ValueError("expected highest pitch <= 127")
else:
raise ValueError("highest pitch must be the type of int")
@property
def pitch_span(self):
return self.__pitch_span
def parse(self, fp, tracks=None):
pattern = midi.read_midifile(fp)
if tracks != None:
if not isinstance(tracks, list):
raise ValueError("tracks must be a list.")
new_pattern = midi.Pattern()
new_pattern.resolution = 480
for index in tracks:
if not isinstance(index, int):
raise ValueError("element in tracks must be int.")
new_pattern.append(pattern[index])
pass
pattern = new_pattern
pass
sequence = []
state = [0 for x in range(self.__pitch_span)]
sequence.append(state)
time_left = [track[0].tick for track in pattern]
posns = [0 for track in pattern]
time = 0
while True:
# duration: 1/64
if time % (pattern.resolution//16) == (pattern.resolution//32):
last_state = state
state = [last_state[x] for x in range(self.__pitch_span)]
sequence.append(state)
pass
for i in range(len(time_left)):
while time_left[i] == 0:
track = pattern[i]
pos = posns[i]
evt = track[pos]
if isinstance(evt, midi.NoteEvent):
if (evt.pitch >= self.__lowest_pitch) or (evt.pitch <= self.__highest_pitch):
if isinstance(evt, midi.NoteOffEvent) or evt.velocity == 0:
state[evt.pitch-self.__lowest_pitch] = 0
else:
state[evt.pitch-self.__lowest_pitch] = 1
pass
try:
time_left[i] = track[pos + 1].tick
posns[i] += 1
except IndexError:
time_left[i] = None
pass
if time_left[i] is not None:
time_left[i] -= 1
if all(t is None for t in time_left):
break
time += 1
pass
sequence = np.array(sequence)
return sequence
def unparse(self, sequence, sp, tickscale=24, velocity=80):
sequence = np.array(sequence)
pattern = midi.Pattern()
pattern.resolution = 480
track = midi.Track()
pattern.append(track)
tickscale = tickscale
lastcmdtime = 0
prevstate = [0 for x in range(self.__pitch_span)]
for time, state in enumerate(sequence):
offNotes = []
onNotes = []
for i in range(self.__pitch_span):
n = state[i]
p = prevstate[i]
if p == 1 and n == 0:
offNotes.append(i)
elif p == 0 and n == 1:
onNotes.append(i)
pass
for note in offNotes:
tick = (time - lastcmdtime) * tickscale
pitch = note + self.__lowest_pitch
event = midi.NoteOffEvent(tick=tick, pitch=pitch)
track.append(event)
lastcmdtime = time
for note in onNotes:
tick = (time - lastcmdtime) * tickscale
pitch = note + self.__lowest_pitch
event = midi.NoteOnEvent(tick=tick, velocity=velocity, pitch=pitch)
track.append(event)
lastcmdtime = time
pass
prevstate = state
pass
eot = midi.EndOfTrackEvent(tick=1)
track.append(eot)
midi.write_midifile(sp, pattern)
pass
def plot(self, fp, sp):
sequence = self.parse(fp)
plt.rcParams['figure.dpi'] = 300
plt.imshow(sequence, aspect='auto')
plt.savefig(sp)
plt.rcParams['figure.dpi'] = 100
plt.close()
pass
pass