Package midi :: Package sequencer_pygame
[hide private]
[frames] | no frames]

Source Code for Package midi.sequencer_pygame

  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  # Initialize the PyGame mixer module 
 50  pygame.init() 
 51  mixer.init() 
52 53 -class Sequencer(object):
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 """
59 - def __init__(self, stream=None):
60 self.playing = False 61 self._music_temp = None 62 if stream is not None: 63 self.load_stream(stream)
64
65 - def load_stream(self, stream):
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
77 - def stop(self):
78 if self.playing: 79 mixer.music.stop()
80
81 - def play(self, block=False):
82 if not self.playing and self._music_loaded: 83 if block: 84 # Create a condition that will be notified when the music stops 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 # Don't just wait, because then we'd fail to receive signals 94 # in this thread (this is a Python bug) 95 # Instead, wake up a few times a second and start waiting again 96 # wait() returns False if the timeout fired 97 while not ev.wait(0.3): 98 pass
99
100 - def pause(self):
101 if self.playing: 102 mixer.music.pause()
103
104 - def unpause(self):
105 if self.playing: 106 mixer.music.unpause()
107
108 - def _cleanup(self):
109 """ Called once playback is finished """ 110 # Get rid of the temporary file 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
117 - def music_loaded(self):
118 return self._music_temp is not None
119
120 -class MusicEndWaiter(Thread):
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
134 - def run(self):
135 mixer.music.set_endevent(USEREVENT) 136 while True: 137 error = False 138 try: 139 ev = event.wait() 140 except: 141 # If anything went wrong with the queue, just give up waiting 142 error = True 143 144 if error or ev.type == USEREVENT: 145 # This usually gets fired a bit too early, so wait a 146 # bit before we call the cleanup 147 time.sleep(0.1) 148 self.__dict__["callback"]() 149 if self.event is not None: 150 self.event.set() 151 return
152
153 -def on_music_end(callback, event=None):
154 waiter = MusicEndWaiter(callback, event=event) 155 waiter.start()
156
157 -class RealtimeSequencer(object):
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 """
165 - def __init__(self, device=0):
166 self.device = device 167 168 # Initialize the midi module 169 pygame.midi.init() 170 171 # Prepare an output to the requested device 172 self.output = pygame.midi.Output(device)
173
174 - def send_event(self, event):
175 """ 176 Sends the midi event (given in our representation) to the midi device. 177 178 """ 179 # Ignore meta events 180 if isinstance(event, MetaEvent): 181 return 182 # Handle certain types of events using PyGame's special methods 183 if type(event) == NoteOnEvent: 184 self.output.note_on(event.pitch, 185 velocity=event.velocity, 186 channel=event.channel) 187 elif type(event) == NoteOffEvent: 188 self.output.note_off(event.pitch, 189 velocity=event.velocity, 190 channel=event.channel) 191 elif type(event) == ProgramChangeEvent: 192 self.output.set_instrument(event.value, 193 channel=event.channel) 194 elif type(event) == SysExEvent: 195 # Send the sysex data 196 data = [0xF0] + [ord(b) for b in event.encode_data()] + [0xF7] 197 self.output.write_sys_ex(0, data) 198 else: 199 # Convert this to binary form to write to the device 200 data = event.encode_data() 201 status = (event.statusmsg & 0xF0) | (0x0F & event.channel) 202 midi_bytes = [ [[status] + [ord(b) for b in data], 0] ] 203 self.output.write(midi_bytes)
204
205 -def get_midi_devices():
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