1 """
2 Interface to the PyGame mixer module, which is able to play fully
3 formed midi files using Timidity.
4 We play midi streams simply by outputing to a temporary midi file and
5 playing that.
6
7 The alternative approach explored in sequencer_portmidi is much nicer
8 in principle, but doesn't work.
9
10 @note: Requires PyGame to be installed
11 @note: PyGame requires SDL to be installed and configured to play
12 midi files. It uses a nasty old version of Timidity and requires you
13 to have GUS-compatible patches installed.
14
15 """
16 """
17 Copyright 2011 Giles Hall, Mark Granroth-Wilding
18
19 This file is part of Pymidi2.
20
21 Pymidi2 is free software: you can redistribute it and/or modify
22 it under the terms of the GNU General Public License as published by
23 the Free Software Foundation, either version 3 of the License, or
24 (at your option) any later version.
25
26 Pymidi2 is distributed in the hope that it will be useful,
27 but WITHOUT ANY WARRANTY; without even the implied warranty of
28 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
29 GNU General Public License for more details.
30
31 You should have received a copy of the GNU General Public License
32 along with Pymidi2. If not, see <http://www.gnu.org/licenses/>.
33
34 """
35
36 try:
37 import pygame
38 except ImportError:
39 raise ImportError, "PyGame needs to be installed before you can load the sequencer"
40 import pygame.midi
41
42 from midi import write_midifile, read_midifile, NoteOnEvent, NoteOffEvent, \
43 ProgramChangeEvent, MetaEvent, SysExEvent
44
45 import time
46 from tempfile import TemporaryFile
47 from threading import Thread, Event
48 from pygame import mixer, event, USEREVENT
49
50 pygame.init()
51 mixer.init()
54 """
55 Midi sequencer that outputs midi to a temporary file and plays it
56 using PyGame's interface to the SDL mixer.
57
58 """
60 self.playing = False
61 self._music_temp = None
62 if stream is not None:
63 self.load_stream(stream)
64
66 """
67 Loads the whole of an L{EventStream<midi.EventStream>}.
68 Call L{play} to start playback.
69
70 """
71 temp_file = TemporaryFile(suffix=".mid")
72 write_midifile(stream, temp_file)
73 temp_file.seek(0)
74 mixer.music.load(temp_file)
75 self._music_loaded = True
76
80
81 - def play(self, block=False):
82 if not self.playing and self._music_loaded:
83 if block:
84
85 ev = Event()
86 else:
87 ev = None
88 on_music_end(self._cleanup, event=ev)
89 mixer.music.play()
90 self.playing = True
91
92 if block:
93
94
95
96
97 while not ev.wait(0.3):
98 pass
99
103
107
109 """ Called once playback is finished """
110
111 if self._music_temp is not None:
112 self._music_temp.close()
113 self._music_temp = None
114 self.playing = False
115
116 @property
118 return self._music_temp is not None
119
121 """
122 Sets an endevent on the PyGame mixer so that it gets notified when
123 the music comes to an end. Executes a callback function when this
124 happens.
125
126 """
127 - def __init__(self, callback, event=None):
128 super(MusicEndWaiter, self).__init__()
129 self.daemon = True
130 self.callback = callback
131
132 self.event = event
133
135 mixer.music.set_endevent(USEREVENT)
136 while True:
137 error = False
138 try:
139 ev = event.wait()
140 except:
141
142 error = True
143
144 if error or ev.type == USEREVENT:
145
146
147 time.sleep(0.1)
148 self.__dict__["callback"]()
149 if self.event is not None:
150 self.event.set()
151 return
152
156
158 """
159 Sends realtime midi events to midi devices through Pygame.
160
161 This class takes care of converting our native event representation into
162 what PyGame needs.
163
164 """
166 self.device = device
167
168
169 pygame.midi.init()
170
171
172 self.output = pygame.midi.Output(device)
173
204
206 """
207 Returns a list of tuples with information about available midi devices,
208 as returned by PyGame's get_device_info() function.
209
210 The list indices correspond to pygame's device ids.
211
212 """
213 pygame.midi.init()
214 return [
215 pygame.midi.get_device_info(i) for i in range(pygame.midi.get_count())
216 ]
217