1# Copyright 2014 Patrick Dawson <pat@dw.is> 2# 3# This software is provided 'as-is', without any express or implied 4# warranty. In no event will the authors be held liable for any damages 5# arising from the use of this software. 6# 7# Permission is granted to anyone to use this software for any purpose, 8# including commercial applications, and to alter it and redistribute it 9# freely, subject to the following restrictions: 10# 11# 1. The origin of this software must not be misrepresented; you must not 12# claim that you wrote the original software. If you use this software 13# in a product, an acknowledgment in the product documentation would be 14# appreciated but is not required. 15# 2. Altered source versions must be plainly marked as such, and must not be 16# misrepresented as being the original software. 17# 3. This notice may not be removed or altered from any source distribution. 18 19from sdl2 cimport * 20from sdl2_mixer cimport * 21from pygame_sdl2.rwobject cimport to_rwops 22from libc.string cimport memset 23 24import sys 25import threading 26from pygame_sdl2.error import error 27 28import pygame_sdl2.mixer_music as music 29import pygame_sdl2 30 31cdef object preinit_args = None 32cdef object output_spec = None 33 34cdef dict channel_events = {} 35cdef dict channel_queued = {} 36cdef dict current_sounds = {} 37 38# The lock protects channel_queued and current_sounds. 39_lock = threading.Lock() 40 41def _play_current(int channel): 42 """ 43 Caled by channel_callback to play the next sound. This has to be called 44 from a different thread, as the channel callback isn't allowed to call 45 MIX functions. 46 """ 47 48 cdef Sound next_sound 49 50 with _lock: 51 next_sound = channel_queued[channel] 52 current_sounds[channel] = next_sound 53 channel_queued[channel] = None 54 55 if next_sound: 56 with nogil: 57 Mix_PlayChannelTimed(channel, next_sound.chunk, 0, -1) 58 59 60cdef void channel_callback(int channel) with gil: 61 62 cdef int etype = 0 63 cdef SDL_Event e 64 65 etype = channel_events.get(channel, 0) 66 if etype != 0: 67 memset(&e, 0, sizeof(SDL_Event)) 68 e.type = etype 69 SDL_PushEvent(&e) 70 71 with _lock: 72 next_sound = channel_queued.get(channel) 73 if next_sound: 74 threading.Thread(target=_play_current, args=(channel,)).start() 75 76# A list of errors that occured during mixer initialization. 77errors = [ ] 78 79@pygame_sdl2.register_init 80def init(frequency=22050, size=MIX_DEFAULT_FORMAT, channels=2, buffer=4096): 81 if get_init() is not None: 82 return 83 84 for flag in (MIX_INIT_FLAC, MIX_INIT_MP3, MIX_INIT_OGG): 85 86 if Mix_Init(flag) != flag: 87 errors.append("{}\n".format(SDL_GetError())) 88 89 if preinit_args: 90 frequency, size, channels, buffer = preinit_args 91 92 if Mix_OpenAudio(frequency, size, channels, buffer) != 0: 93 raise error() 94 95 global output_spec 96 output_spec = get_init() 97 98 Mix_ChannelFinished(channel_callback) 99 100def pre_init(frequency=22050, size=MIX_DEFAULT_FORMAT, channels=2, buffersize=4096): 101 global preinit_args 102 preinit_args = (frequency, size, channels, buffersize) 103 104@pygame_sdl2.register_quit 105def quit(): # @ReservedAssignment 106 Mix_CloseAudio() 107 Mix_Quit() 108 109def get_init(): 110 cdef int frequency 111 cdef Uint16 format 112 cdef int channels 113 114 if Mix_QuerySpec(&frequency, &format, &channels) == 0: 115 return None 116 else: 117 return frequency, format, channels 118 119def stop(): 120 with nogil: 121 Mix_HaltChannel(-1) 122 123def pause(): 124 with nogil: 125 Mix_Pause(-1) 126 127def unpause(): 128 with nogil: 129 Mix_Resume(-1) 130 131def fadeout(time): 132 cdef int ms = time 133 with nogil: 134 Mix_FadeOutChannel(-1, ms) 135 136def set_num_channels(count): 137 Mix_AllocateChannels(count) 138 139def get_num_channels(): 140 return Mix_AllocateChannels(-1) 141 142def set_reserved(count): 143 Mix_ReserveChannels(count) 144 145def find_channel(force=False): 146 cdef int chan 147 chan = Mix_GroupAvailable(-1) 148 if chan == -1: 149 if not force: 150 return None 151 chan = Mix_GroupOldest(-1) 152 if chan == -1: 153 raise error() 154 return Channel(chan) 155 156def get_busy(): 157 return Mix_GroupNewer(-1) != -1 158 159 160cdef class Sound: 161 cdef Mix_Chunk *chunk 162 163 def __cinit__(self): 164 self.chunk = NULL 165 166 def __dealloc__(self): 167 if self.chunk: 168 Mix_FreeChunk(self.chunk) 169 170 def __init__(self, fi): 171 self.chunk = Mix_LoadWAV_RW(to_rwops(fi), 1) 172 if self.chunk == NULL: 173 raise error() 174 175 def play(self, loops=0, maxtime=-1, fade_ms=0): 176 cdef int cid 177 cdef int _loops = loops 178 cdef int _maxtime = maxtime 179 cdef int _fade_ms = fade_ms 180 181 with nogil: 182 if _fade_ms != 0: 183 cid = Mix_FadeInChannelTimed(-1, self.chunk, _loops, _fade_ms, _maxtime) 184 else: 185 cid = Mix_PlayChannelTimed(-1, self.chunk, _loops, _maxtime) 186 187 if cid == -1: 188 raise error() 189 return Channel(cid) 190 191 def stop(self): 192 cdef int i = 0 193 while i < Mix_AllocateChannels(-1): 194 if Mix_GetChunk(i) == self.chunk: 195 with nogil: 196 Mix_HaltChannel(i) 197 i += 1 198 199 def pause(self): 200 cdef int i = 0 201 while i < Mix_AllocateChannels(-1): 202 if Mix_GetChunk(i) == self.chunk: 203 with nogil: 204 Mix_Pause(i) 205 i += 1 206 207 def unpause(self): 208 cdef int i = 0 209 while i < Mix_AllocateChannels(-1): 210 if Mix_GetChunk(i) == self.chunk: 211 with nogil: 212 Mix_Resume(i) 213 i += 1 214 215 def fadeout(self, time): 216 cdef int i = 0 217 cdef int ms = time 218 while i < Mix_AllocateChannels(-1): 219 if Mix_GetChunk(i) == self.chunk: 220 with nogil: 221 Mix_FadeOutChannel(i, ms) 222 i += 1 223 224 def set_volume(self, value): 225 Mix_VolumeChunk(self.chunk, MIX_MAX_VOLUME * value) 226 227 def get_volume(self): 228 return Mix_VolumeChunk(self.chunk, -1) 229 230 def get_num_channels(self): 231 cdef int i = 0 232 cdef int n = 0 233 while i < Mix_AllocateChannels(-1): 234 if Mix_GetChunk(i) == self.chunk: 235 n += 1 236 i += 1 237 return n 238 239 def get_length(self): 240 # TODO: Adjust for actual format, rather than assuming 16-bit. 241 return <double>self.chunk.alen / output_spec[0] / 2 / output_spec[2] 242 243 def get_raw(self): 244 # return self.chunk.abuf 245 raise error("Not implemented.") 246 247 248cdef class Channel(object): 249 cdef int cid 250 251 def __init__(self, cid): 252 self.cid = cid 253 254 def play(self, Sound sound not None, loops=0, maxtime=-1, fade_ms=0): 255 cdef int _loops = loops 256 cdef int _maxtime = maxtime 257 cdef int _fade_ms = fade_ms 258 259 with nogil: 260 if _fade_ms != 0: 261 cid = Mix_FadeInChannelTimed(self.cid, sound.chunk, _loops, _fade_ms, _maxtime) 262 else: 263 cid = Mix_PlayChannelTimed(self.cid, sound.chunk, _loops, _maxtime) 264 265 if cid == -1: 266 raise error() 267 268 with _lock: 269 current_sounds[self.cid] = sound 270 271 def stop(self): 272 with nogil: 273 Mix_HaltChannel(self.cid) 274 275 def pause(self): 276 with nogil: 277 Mix_Pause(self.cid) 278 279 def unpause(self): 280 with nogil: 281 Mix_Resume(self.cid) 282 283 def fadeout(self, time): 284 cdef int ms = time 285 with nogil: 286 Mix_FadeOutChannel(self.cid, ms) 287 288 def set_volume(self, volume): 289 Mix_Volume(self.cid, int(MIX_MAX_VOLUME * volume)) 290 291 def get_volume(self): 292 cdef int vol = Mix_Volume(self.cid, -1) 293 return vol / <double>MIX_MAX_VOLUME 294 295 def get_busy(self): 296 return Mix_Playing(self.cid) != 0 297 298 def get_sound(self): 299 with _lock: 300 return current_sounds.get(self.cid) 301 302 def queue(self, Sound sound): 303 if self.get_busy(): 304 with _lock: 305 channel_queued[self.cid] = sound 306 else: 307 self.play(sound) 308 309 def get_queue(self): 310 with _lock: 311 return channel_queued.get(self.cid) 312 313 def set_endevent(self, type=None): 314 channel_events[self.cid] = type or 0 315 316 def get_endevent(self): 317 return channel_events.get(self.cid, 0) 318