3
# Python midifile package -- parse, load and play MIDI files.
4
# Copyright (C) 2011 by Wilbert Berendsen
6
# This program is free software; you can redistribute it and/or
7
# modify it under the terms of the GNU General Public License
8
# as published by the Free Software Foundation; either version 2
9
# of the License, or (at your option) any later version.
11
# This program is distributed in the hope that it will be useful,
12
# but WITHOUT ANY WARRANTY; without even the implied warranty of
13
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
# GNU General Public License for more details.
16
# You should have received a copy of the GNU General Public License
17
# along with this program; if not, write to the Free Software
18
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
19
# See http://www.gnu.org/licenses/ for more information.
25
from __future__ import unicode_literals
35
"""The base class for a MIDI player.
37
Use set_output() to set a MIDI output instance (see midioutput.py).
38
You can override: timer_midi_time(), timer_start() and timer_stop()
39
to use another timing source than the Python threading.Timer instances.
49
self._tempo_factor = 1.0
51
self._last_exception = None
53
def set_output(self, output):
54
"""Sets an Output instance that handles the MIDI events.
56
Use None to disable all output.
62
"""Returns the currently set Output instance."""
65
def load(self, filename, time=1000, beat=True):
66
"""Convenience function, loads a MIDI file.
68
See set_song() for the other arguments.
71
self.set_song(song.load(filename), time, beat)
73
def set_song(self, song, time=1000, beat=True):
74
"""Loads the specified Song (see song.py).
76
If time is not None, it specifies at which interval (in msec) the
77
time() method will be called. Default: 1000.
78
If beat is True (default), the beat() method will be called on every
82
playing = self._playing
84
self.timer_stop_playing()
86
self._events = make_event_list(song, time, beat)
90
self.timer_start_playing()
93
"""Returns the current Song."""
97
"""Unloads a loaded Song."""
105
def total_time(self):
106
"""Returns the length in msec of the current song."""
108
return self._events[-1][0]
111
def current_time(self):
112
"""Returns the current time position."""
113
if self._position >= len(self._events):
114
time = self.total_time()
116
time = self._events[self._position][0]
118
return time - self.timer_offset()
119
return time - self._offset
122
"""Starts playing."""
123
if self.has_events():
124
self.timer_start_playing()
128
self.timer_stop_playing()
130
def is_playing(self):
131
"""Returns True if the player is playing, else False."""
134
def set_tempo_factor(self, factor):
135
"""Sets the tempo factor as a floating point value (1.0 is normal)."""
136
self._tempo_factor = float(factor)
138
def tempo_factor(self):
139
"""Returns the tempo factor (by default: 1.0)."""
140
return self._tempo_factor
142
def seek(self, time):
143
"""Goes to the specified time (in msec)."""
147
# bisect our way in the events list.
148
end = len(self._events)
150
mid = (pos + end) // 2
151
if time > self._events[mid][0]:
155
if pos < len(self._events):
156
offset = self._events[pos][0] - time
157
self.set_position(pos, offset)
159
def seek_measure(self, measnum, beat=1):
160
"""Goes to the specified measure and beat (beat defaults to 1).
162
Returns whether the measure position could be found (True or False).
166
for i, (t, e) in enumerate(self._events):
168
if e.beat[0] == measnum:
171
if e.beat[1] >= beat:
173
if e.beat[0] > measnum:
176
self.set_position(position)
180
def set_position(self, position, offset=0):
181
"""(Private) Goes to the specified position in the internal events list.
183
The default implementation does nothing with the time offset,
184
but inherited implementations may wait that many msec before
185
triggering the event at that position.
187
This method is called by seek() and seek_measure().
190
old, self._position = self._position, position
193
if old != self._position:
194
self.position_event(old, self._position)
195
self.timer_schedule(offset, False)
197
self._offset = offset
199
def has_events(self):
200
"""Returns True if there are events left to play."""
201
return bool(self._events) and self._position < len(self._events)
203
def next_event(self):
204
"""(Private) Handles the current event and advances to the next.
206
Returns the time in ms (not adjusted by tempo factor!) before
207
next_event should be called again.
209
If there is no event to handle anymore, returns 0.
210
If this event was the last, calls finish() and returns 0.
213
if self.has_events():
214
time, event = self._events[self._position]
215
self.handle_event(time, event)
217
if self._position < len(self._events):
218
return self._events[self._position][0] - time
221
def handle_event(self, time, event):
222
"""(Private) Called for every event."""
224
self.midi_event(event.midi)
226
self.time_event(time)
228
self.beat_event(*event.beat)
230
def midi_event(self, midi):
231
"""(Private) Plays the specified MIDI events.
233
The format depends on the way MIDI events are stored in the Song.
238
self._output.midi_event(midi)
239
except BaseException as e:
240
self.exception_event(e)
242
def time_event(self, msec):
243
"""(Private) Called on every time update."""
245
def beat_event(self, measnum, beat, num, den):
246
"""(Private) Called on every beat."""
248
def start_event(self):
249
"""Called when playback is started."""
251
def stop_event(self):
252
"""Called when playback is stopped by the user."""
254
self._output.all_sounds_off()
256
def finish_event(self):
257
"""Called when a song reaches the end by itself."""
259
def position_event(self, old, new):
260
"""Called when the user seeks while playing and the position changes.
262
This means MIDI events are skipped and it might be necessary to
263
issue an all notes off command to the MIDI output.
267
self._output.all_sounds_off()
269
def exception_event(self, exception):
270
"""Called when an exception occurs while writing to a MIDI output.
272
The default implementation stores the exception in self._last_exception
273
and sets the output to None.
276
self._last_exception = exception
277
self.set_output(None)
279
def timer_midi_time(self):
280
"""Should return a continuing time value in msec, used while playing.
282
The default implementation returns the time in msec from the Python
286
return int(time.time() * 1000)
288
def timer_schedule(self, delay, sync=True):
289
"""Schedules the upcoming event.
291
If sync is False, don't look at the previous synchronisation time.
294
msec = delay / self._tempo_factor
296
self._sync_time += msec
297
msec = self._sync_time - self.timer_midi_time()
299
self._sync_time = self.timer_midi_time() + msec
300
self.timer_start(max(0, msec))
302
def timer_start(self, msec):
303
"""Starts the timer to fire once, the specified msec from now."""
305
self._timer = threading.Timer(msec / 1000.0, self.timer_timeout)
308
def timer_stop(self):
309
"""Stops the timer."""
314
def timer_offset(self):
315
"""Returns the time before the next event.
317
This value is only useful while playing.
320
return int((self._sync_time - self.timer_midi_time()) * self._tempo_factor)
322
def timer_start_playing(self):
323
"""Starts playing by starting the timer for the first upcoming event."""
324
reset = self.current_time() == 0
327
if reset and self._output:
330
except BaseException as e:
331
self.exception_event(e)
332
self.timer_schedule(self._offset, False)
334
def timer_timeout(self):
335
"""Called when the timer times out.
337
Handles an event and schedules the next.
338
If the end of a song is reached, calls finish_event()
341
offset = self.next_event()
343
self.timer_schedule(offset)
346
self._playing = False
349
def timer_stop_playing(self):
351
self._offset = self.timer_offset()
352
self._playing = False
357
"""Any event (MIDI, Time and/or Beat).
359
Has three attributes that determine what the Player does:
361
time: if True, time_event() is caled with the current music time.
362
beat: None or (measnum, beat, num, den), then beat_event() is called.
363
midi: If not None, midi_event() is called with the midi.
366
__slots__ = ['midi', 'time', 'beat']
377
l.append('beat({0}:{1})'.format(self.beat[0], self.beat[1]))
380
return '<Event ' + ', '.join(l) + '>'
383
def make_event_list(song, time=None, beat=None):
384
"""Returns a list of all the events in Song.
386
Each item is a two-tuple(time, Event).
388
If time is given, a time event is generated every that many microseconds
389
If beat is True, beat events are generated as well.
390
MIDI events are always created.
393
d = collections.defaultdict(Event)
395
for t, evs in song.music:
399
for t in range(0, song.length+1, time):
406
return [(t, d[t]) for t in sorted(d)]