-
Notifications
You must be signed in to change notification settings - Fork 0
/
songinfo.py
303 lines (250 loc) · 8.9 KB
/
songinfo.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
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
import os
import sys
import struct
from .configparser import ConfigParser
from .midi import MidiOutStream, MidiInFile
from .midireader import difficulties, noteSet, noteMap
class MidiInfoReader(MidiOutStream):
__slots__ = ("difficulties", "nTracks", "ignored", "trackNo")
# We exit via this exception so that we don't need to read the whole file in
class Done(Exception): pass
def __init__(self):
super().__init__()
self.difficulties = set()
self.ignored = False
self.nTracks = 0
self.trackNo = -1
def start_of_track(self, track):
self.trackNo = track
def header(self, format, nTracks, division):
self.nTracks = nTracks
def sequence_name(self, val):
name = ''.join(list(map(chr, val)))
self.ignored = name != "PART GUITAR" and self.nTracks > 2
if self.difficulties:
raise MidiInfoReader.Done()
return self.ignored
def note_on(self, channel, note, velocity):
if not self.ignored:
if not note in noteMap:
return
self.difficulties.add(difficulties[noteMap[note][0]])
if len(self.difficulties) == len(difficulties):
raise MidiInfoReader.Done()
class Score():
accuracy = None
streak = None
badnotes = None
def __init__(self, score = None):
if score is not None:
self.accuracy = score.accuracy
self.streak = score.streak
self.badnotes = score.badnotes
def __eq__(self, score):
return self.accuracy == score.accuracy and self.streak == score.streak and self.badnotes == score.badnotes
def empty(self):
return self.accuracy is None or self.streak is None or self.badnotes is None
def __repr__(self):
if self.empty():
return "Score()"
return f"Score(acc: {self.accuracy}, str: {self.streak}, bn: {self.badnotes})"
class ScoreSet():
def __init__(self):
self.max_acc = Score()
self.max_streak = Score()
def empty(self):
return self.max_acc.empty() and self.max_streak.empty()
def __repr__(self):
if self.empty():
return "ScoreSet()"
return f"ScoreSet(max_acc: {str(self.max_acc)}, max_str: {str(self.max_streak)})"
class SongInfo(object):
def __init__(self, dirName):
infoFileName = dirName + "/song.ini"
self.dirName = dirName
self.songName = os.path.basename(os.path.dirname(infoFileName))
self.fileName = infoFileName
self.info = ConfigParser()
self._difficulties = None
self._scores = None
try:
self.info.read(infoFileName)
except Exception as e:
print(f"Exception while reading {infoFileName}: {e}")
def load(self):
return self
def _set(self, attr, value):
if not self.info.has_section("song"):
self.info.add_section("song")
if type(value) == str:
value = value.encode(Config.encoding)
else:
value = str(value)
self.info.set("song", attr, value)
def _get(self, attr, type = None, default = ""):
try:
v = self.info.get("song", attr)
except:
v = default
if v is not None and type:
v = type(v)
return v
def readDifficulties(self):
if self._difficulties is not None:
return self._difficulties
diffFileName = os.path.join(os.path.dirname(self.fileName), ".diff.pet")
try:
diffs = []
with open(diffFileName, "rb") as f:
magic = f.read(6)
assert(magic == b"PHDIFF")
ver = f.read(1)
assert(ver == b"\0")
length = f.read(1)[0]
difflist = f.read(length)
assert(len(difflist) == length)
for b in difflist:
diffs.append(difficulties[b])
diffs.sort(key = lambda a: a.id, reverse=True)
self._difficulties = diffs
return self._difficulties
except AssertionError as e:
print(f"Assertion failed while processing {self.fileName}")
sys.print_exception(e)
os.unlink(diffFileName)
except Exception as e:
print(f"Error while processing {self.fileName}")
sys.print_exception(e)
def getDifficulties(self):
if self._difficulties is not None:
return self._difficulties
diffFileName = os.path.join(os.path.dirname(self.fileName), ".diff.pet")
if os.path.exists(diffFileName):
self.readDifficulties()
if self._difficulties is not None:
return self._difficulties
# See which difficulties are available
noteFileName = os.path.join(os.path.dirname(self.fileName), "notes.mid")
info = MidiInfoReader()
midiIn = MidiInFile(info, noteFileName)
try:
midiIn.read()
except MidiInfoReader.Done:
pass
self._difficulties = list(info.difficulties)
self._difficulties.sort(key = lambda a: a.id, reverse=True)
self.saveDifficulties()
return self._difficulties
def saveDifficulties(self):
if self._difficulties is None:
return
try:
diffFileName = os.path.join(os.path.dirname(self.fileName), ".diff.pet")
with open(diffFileName, "wb") as f:
f.write(b"PHDIFF\0")
f.write(bytes((len(self._difficulties),)))
for diff in self._difficulties:
f.write(bytes([diff.id]))
except Exception as e:
sys.print_exception(e)
def readScores(self):
self._scores = {}
for diff in difficulties.values():
self._scores[diff] = ScoreSet()
scoreFileName = os.path.join(os.path.dirname(self.fileName), ".score.pet")
if not os.path.exists(scoreFileName):
return self._scores
try:
with open(scoreFileName, "rb") as f:
magic = f.read(7)
assert(magic == b"PHSCORE")
ver = f.read(1)
assert(ver == b"\0")
length = f.read(1)[0]
for i in range(length):
diff = f.read(1)[0]
assert(diff in difficulties)
assert(difficulties[diff] in self._scores)
score = self._scores[difficulties[diff]]
score.max_acc.accuracy, score.max_acc.streak, score.max_acc.badnotes = struct.unpack('!fII', f.read(12))
score.max_streak.accuracy, score.max_streak.streak, score.max_streak.badnotes = struct.unpack('!fII', f.read(12))
except Exception as e:
sys.print_exception(e)
return self._scores
def saveScores(self):
try:
scoreFileName = os.path.join(os.path.dirname(self.fileName), ".score.pet")
with open(scoreFileName, "wb") as f:
f.write(b"PHSCORE\0")
diffs = tuple(filter(lambda x: not self._scores[x].empty(), self._scores.keys()))
f.write(bytes((len(diffs),)))
for diff in diffs:
score = self._scores[diff]
f.write(bytes((diff.id,)))
f.write(struct.pack("!fII", score.max_acc.accuracy, score.max_acc.streak, score.max_acc.badnotes))
f.write(struct.pack("!fII", score.max_streak.accuracy, score.max_streak.streak, score.max_streak.badnotes))
except Exception as e:
sys.print_exception(e)
def recordScore(self, difficulty, accuracy, streak, badnotes):
if self._scores is None:
self.readScores()
max_acc = self._scores[difficulty].max_acc
max_streak = self._scores[difficulty].max_streak
save = False
if max_acc.empty() or accuracy > max_acc.accuracy:
max_acc.accuracy = accuracy
max_acc.streak = streak
max_acc.badnotes = badnotes
save = True
elif accuracy == max_acc.accuracy:
if streak > max_acc.streak:
max_acc.streak = streak
max_acc.badnotes = badnotes
save = True
elif badnotes < max_acc.badnotes:
max_acc.badnotes = badnotes
save = True
if max_streak.empty() or streak > max_streak.streak:
max_streak.streak = streak
max_streak.accuracy = accuracy
max_streak.badnotes = badnotes
save = True
elif streak == max_streak.streak:
if accuracy > max_streak.accuracy:
max_streak.accuracy = accuracy
max_streak.badnotes = badnotes
save = True
elif badnotes < max_streak.badnotes:
max_streak.badnotes = badnotes
save = True
if save:
self.saveScores()
return save
def getName(self):
return self._get("name")
def setName(self, value):
self._set("name", value)
def getArtist(self):
return self._get("artist")
def setArtist(self, value):
self._set("artist", value)
def getDelay(self):
return self._get("delay", int, 0)
def setDelay(self, value):
return self._set("delay", value)
def getPreview(self):
preview = self._get("preview_start_time", int, None)
length = self._get("song_length", int, None)
if preview is None or preview < 0 or length is None or length <= 0:
return 0.1
return preview / length
def getScores(self):
if self._scores is None:
self.readScores()
return self._scores
name = property(getName, setName)
artist = property(getArtist, setArtist)
delay = property(getDelay, setDelay)
difficulties = property(getDifficulties)
preview = property(getPreview)
scores = property(getScores)