Initial commit from version 1e-99
[beatscape.git] / keyfile.py
1 import pygame
2 import imp
3 import os.path
4 from bmevent import *
5
6 class KeyFile:
7         bmelist = None
8         numkeys = 0
9
10         # Loaded from a file
11         player = None
12         genre = None
13         title = None
14         artist = None
15         stagefile = None
16         playlevel = -1
17         rank = 3
18         stagefile = None
19         volwav = 1.0
20         wavs = None
21         bmps = None
22         offset = 0
23
24         # Allows us to continuously map time to the current beat
25         beatfunc = None
26
27         # Keymapping is a sequence of keycodes, mapping the index to the
28         # key
29         def __init__(self):
30                 self.bmelist = []
31                 self.wavs = {}
32                 self.bmps = {}
33
34         def add(self,bme):
35                 self.bmelist.append(bme)
36
37         def remove(self,bme):
38                 return self.bmelist.remove(bme)
39
40         def sort(self):
41                 def sortfun(a,b):
42                         if a.beat == b.beat:
43                                 return 0
44                         elif a.beat > b.beat:
45                                 return 1
46                         elif a.beat < b.beat:
47                                 return -1
48                 self.bmelist.sort(sortfun)
49
50         def dump(self):
51                 for b in self.bmelist:
52                         print str(b)
53
54         # AWFUL DIRTY NO GOOD HACK
55         # (but does find the end of the song within 10ms)
56         def length(self):
57                 if len(self.beatfunc) >= 2:
58                         t = self.beatfunc[-2][0]
59                 else:
60                         t = 0
61                 last_beat = self.bmelist[-1].beat
62                 b = self.eval_beatfunc(t)
63                 while b < last_beat:
64                         t+= 10
65                         b = self.eval_beatfunc(t)
66                 return t
67
68         # It's time to get func-y
69         def generate_beatfunc(self):
70                 self.beatfunc = []
71                 bpms = filter(lambda x: x.type & (BME_TEMPO | BME_LONGMEASURE | BME_STOP), self.bmelist)
72                 self.beatfunc = self.generate_beatfunc_r(bpms)
73
74         def generate_beatfunc_r(self, bpms, ct=0):
75                 if len(bpms) == 0:
76                         return [(3600000.0,0)]
77                 beat = bpms[0].beat
78                 type = bpms[0].type
79
80                 if type == BME_TEMPO:
81                         ms_per_beat = 60000.0 / bpms[0].dataref
82                         func = lambda t: (t - ct) / ms_per_beat + beat
83                         if len(bpms) == 1:
84                                 next = 4000
85                         else:
86                                 next = bpms[1].beat
87                         duration = (next - beat) * ms_per_beat
88                         self.lastbpm = bpms[0].dataref
89                 elif type == BME_LONGMEASURE:
90                         # Long measures only last one measure, so we do
91                         # the slow measure just like a tempo change,
92                         # then add a tempo change back at the end.
93                         ms_per_beat = (60000.0 / self.lastbpm) * bpms[0].dataref
94                         func = lambda t: (t - ct) / ms_per_beat + beat
95
96                         print "Last BPM:",self.lastbpm
97                         duration = 4 * ms_per_beat
98                         bpms.insert(1,BMEvent(beat+4,BME_TEMPO,0,self.lastbpm))
99                 elif type == BME_STOP:
100                         func = lambda t: beat
101                         duration = bpms[0].dataref
102                 else:
103                         raise Exception("WTF? Invalid type in generate_beatfunc_r")
104                 l = self.generate_beatfunc_r(bpms[1:], ct + duration)
105                 l.insert(0,(ct,func))
106                 return l
107
108         def eval_beatfunc(self,t):
109                 for n in range(0,len(self.beatfunc)-1):
110                         if self.beatfunc[n][0] <= t and self.beatfunc[n+1][0] > t:
111                                 return self.beatfunc[n][1](t)
112
113         def show_beatfunc(self,surface):
114                 end = self.length()
115                 xscale = end / surface.get_width()
116                 yscale = self.bmelist[-1].beat / surface.get_height()
117
118                 for t in range(0,end,100):
119                         beat = self.eval_beatfunc(t)
120                         if beat:
121                                 pygame.draw.circle(surface,(255,255,255),(t/xscale,480 - beat / yscale),1)
122                         else:
123                                 pygame.draw.line(surface,(255,0,0), (t/140.625,0), (t/140.625,480))
124
125
126 class BMEListIter:
127         bmelist = None
128
129         def __init__(self,bmelist):
130                 self.bmelist = bmelist 
131                 self.b = 0.0
132                 self.i = 0
133
134         def goto(self,b):
135                 l = len(self.bmelist) - 1
136                 self.b = b
137
138                 while self.i > 0 and b < self.bmelist[self.i].beat:
139                         self.i -= 1
140                 while self.i < l and b > self.bmelist[self.i].beat:
141                         self.i += 1
142
143         def window(self,db,type=None):
144                 l = len(self.bmelist)
145                 eb = self.b + db
146                 ei = self.i
147
148                 while ei < l and eb >= self.bmelist[ei].beat:
149                         ei += 1
150
151                 if type:
152                         return filter(lambda x: x.type & type, self.bmelist[self.i:ei])
153                 else:
154                         return self.bmelist[self.i:ei]
155
156
157 screen = None
158 font = None
159
160 loaders = []
161 for x in ["BMloader","SMloader"]:
162         f = open("loaders/" + x + ".py")
163         loaders.append(imp.load_module(x,f,x + ".py",(".py",'r',imp.PY_SOURCE)))
164         #f.close()
165
166 def vmessage(message):
167         fs = font.render(message, 0, (255,255,255),(0,0,0))
168         screen.fill((0,0,0),(0,450,640,30))
169         screen.blit(fs,(0,450))
170
171 def vstatus(type,arg):
172         if type == "STAGEFILE":
173                 screen.blit(arg,(0,0))
174         elif type == "WAV":
175                 vmessage("Loaded WAV " + arg)
176         elif type == "BMP":
177                 vmessage("Loaded BMP " + arg)
178         elif type == "TRACK":
179                 vmessage("Parsing track " + str(arg))
180         elif type == "ERROR":
181                 vmessage("ERROR: " + arg)
182         pygame.display.flip()
183
184 def likelihood(file):
185         likelihoods = map(lambda l: l.detect(file), loaders)
186         gl = 0.0
187         gn = None
188         for n in range(0,len(likelihoods)):
189                 if likelihoods[n] > gl:
190                         gl = likelihoods[n]
191                         gn = n
192         return gn
193
194 def kf_load(file):
195         global screen,font
196         screen = pygame.display.get_surface()
197         font = pygame.font.SysFont("Helvetica Normal",30)
198         gn = likelihood(file)
199         if gn != None:
200                 print "Load..."
201                 kf = loaders[gn].load(file,vstatus)
202                 kf.generate_beatfunc()
203                 return kf
204         else:
205                 return None
206         font = None
207
208 def kf_info(file):
209         gn = likelihood(file)
210         d = loaders[gn].info(file)
211         d['loader.name'] = loaders[gn].name
212         d['loader.version'] = loaders[gn].version
213
214         return d