class Channel(object): """ **pyj2d.mixer.Channel** * Channel.play * Channel.stop * Channel.pause * Channel.unpause * Channel.fadeout * Channel.set_volume * Channel.get_volume * Channel.get_busy * Channel.get_sound * Channel.queue * Channel.get_queue * Channel.set_endevent * Channel.get_endevent """ _mixer = None def __init__(self, id): self._id = id self._sound = None self._stream = None self._len = self._mixer._bufferSize self._data = jarray.zeros(self._len, 'b') self._data_len = 0 self._data_sum = 0 self._data_rate = self._mixer._byteRate / 1000 self._active = AtomicBoolean(False) self._pause = False self._loops = 0 self._volume = 1.0 self._lvolume = 1.0 self._rvolume = 1.0 self._queue = None self._endevent = None self._maxtime = 0 self._fadein = 0 self._fadeout = 0 self._dvol = 1.0 self._process = False self._mixer._register_channel(self) def _set_sound(self, sound): self._sound = sound self._stream = sound._get_stream() def _reset_sound(self): self._active.set(False) restart = not self._pause if not self._sound: return try: sound = self._sound self._stream.close() self._set_sound(self._sound) except AttributeError: restart = False if restart: self._active.set(True) def _get(self): try: self._data_len = self._stream.read(self._data, 0, self._len) except IOException: self._data_len = 0 if self._data_len > 0: self._data_sum += self._data_len if not self._process: return (self._data, self._data_len, self._lvolume*self._sound._volume, self._rvolume*self._sound._volume) if self._maxtime: self._dvol = 1.0 if self._data_sum > self._maxtime: self._data_len -= (self._data_sum-self._maxtime) self._maxtime = 0 self._loops = 0 self._onended() if self._fadein: if self._data_sum < self._fadein: self._dvol = self._data_sum / self._fadein else: self._dvol = 1.0 self._fadein = 0 if not (self._maxtime or self._fadeout): self._process = False elif self._fadeout: if self._data_sum < self._fadeout: self._dvol = 1.0 - (self._data_sum / self._fadeout) else: self._dvol = 0.01 self._fadeout = 0 self._loops = 0 self._onended() return (self._data, self._data_len, self._lvolume*self._sound._volume*self._dvol, self._rvolume*self._sound._volume*self._dvol) else: self._data_sum = 0 self._onended() return (self._data, self._data_len, 1.0, 1.0) def _play(self, sound, loops, maxtime, fade_ms): self._set_sound(sound) self._loops = loops if maxtime: self._maxtime = int(maxtime * self._data_rate) self._process = True if fade_ms: self._fadein = fade_ms * self._data_rate self._process = True self._data_sum = 0 self._active.set(True) def play(self, sound, loops=0, maxtime=0, fade_ms=0): """ Play sound on channel. Argument sound to play, loops is repeat number or -1 for continuous, maxtime is maximum play time, and fade_ms is fade-in time. """ if self._sound: lv, rv = self._lvolume, self._rvolume self.stop() self.set_volume(lv, rv) self._set_sound(sound) self._loops = loops if maxtime: self._maxtime = int(maxtime * self._data_rate) self._process = True if fade_ms: self._fadein = fade_ms * self._data_rate self._process = True self._data_sum = 0 self._active.set(True) self._mixer._activate_channel(self._id) return None def _onended(self): if not self._loops: if not self._queue: self.stop() else: self.play(self._queue) else: self._stream.close() self._set_sound(self._sound) self._loops -= 1 def stop(self): """ Stop sound on channel. """ if not self._active.get() and not self._pause: return None self._active.set(False) self._mixer._deactivate_channel(self._id) try: self._stream.close() self._stream = None except AttributeError: pass self._sound = None self._queue = None self._pause = False self._loops = 0 self._maxtime = 0 self._fadein = 0 self._fadeout = 0 self._volume = 1.0 self._lvolume = 1.0 self._rvolume = 1.0 self._process = False self._mixer._restore_channel(self._id) if self._endevent is not None: env.event.post(self._endevent) return None def pause(self): """ Pause sound on channel. """ if self._active.get(): self._active.set(False) self._pause = True return None def unpause(self): """ Unpause sound on channel. """ if self._pause: self._active.set(True) self._pause = False return None def fadeout(self, time): """ Stop sound after fade out time. """ if self._active.get() or self._pause: self._fadeout = self._data_sum + (time * self._data_rate) self._process = True return None def set_volume(self, volume, volume2=None): """ Set channel volume of sound playing. Argument volume of value 0.0 to 1.0, setting for both speakers when single, stereo l/r speakers with second value. """ if volume < 0.0: volume = 0.0 elif volume > 1.0: volume = 1.0 self._lvolume = volume if volume2: if volume2 < 0.0: volume2 = 0.0 elif volume2 > 1.0: volume2 = 1.0 self._rvolume = volume2 else: self._rvolume = self._lvolume self._volume = volume return None def get_volume(self): """ Get channel volume for current sound. """ return self._volume def get_busy(self): """ Check if channel is processing sound. """ return self._active.get() or self._pause def get_sound(self): """ Get sound open by channel. """ return self._sound def queue(self, sound): """ Queue sound to play after current sound ends. """ if not self._sound: self.play(sound) else: self._queue = sound def get_queue(self): """ Return queue sound. """ return self._queue def set_endevent(self, eventType=None): """ Set endevent for sound channel. Argument eventType is event type (eg. USEREVENT+num). Without an argument resets endevent to NOEVENT type. """ if eventType is not None: if ( self._endevent is None or self._endevent.type != eventType ): self._endevent = env.event.Event(eventType) else: self._endevent = None def get_endevent(self): """ Get endevent type for sound channel. """ if self._endevent is not None: return self._endevent.type else: return Const.NOEVENT
class AbstractTimedThroughputWorker(object): """ Send throughput across a time period. """ def __init__(self, period=60, chunks=100, throughput=0): self.period = period # The time period over which to produce throughput self.chunks = chunks self.throughput = AtomicInteger(throughput) self.throughput_success = AtomicInteger(0) self.resume = AtomicBoolean(True) self.curr_tick = AtomicInteger(0) def stop(self): self.resume.set(False) self.thread.join() def start(self): self.thread = Thread(target=self.loop) self.thread.start() def loop(self): while self.resume.get(): self.throughput_success.set(self.tick(self.throughput.get())) def action(self, throughput): """ This method fires Args: throughput (int): The throughput in bytes Returns (bool): Indicating success. """ raise NotImplementedError("Please implement this method") def next_tick(self, period): """ Returns the next multiple of a time period """ curr_time = time.time() return curr_time + (period - curr_time % period) def tick(self, throughput): """ Fires throughput over this time period """ # The next 60 second time period next_tick = self.next_tick(self.period) # Every mini_tick, we will fire a thoughput of this size throughput_per_chunk = throughput / self.chunks # The size of a mini_tick (e.g. 0.6 seconds) mini_tick_period = self.period / float(self.chunks) chunks_sent, successes = 0, 0 while time.time() < next_tick and chunks_sent < self.chunks: if not self.resume.get(): break # Fire action and record time taken # time_taken, success = time_it(self.action, throughput_per_chunk) success = self.action(throughput_per_chunk) # Count successes if success: successes += 1 chunks_sent += 1 # The time remaining to reach the next mini tick time_till_next_mini_tick = max( 0, self.next_tick(mini_tick_period) - time.time()) # sleep to next mini tick to ensure actions happen evenly time.sleep(time_till_next_mini_tick) return successes * throughput_per_chunk
class Mixer(Runnable): """ **pyj2d.mixer** * pyj2d.mixer.init * pyj2d.mixer.quit * pyj2d.mixer.get_init * pyj2d.mixer.stop * pyj2d.mixer.pause * pyj2d.mixer.unpause * pyj2d.mixer.fadeout * pyj2d.mixer.set_num_channels * pyj2d.mixer.get_num_channels * pyj2d.mixer.set_reserved * pyj2d.mixer.find_channel * pyj2d.mixer.get_busy * pyj2d.mixer.Sound * pyj2d.mixer.Channel * pyj2d.mixer.music """ def __init__(self): self._mixer = None Sound._mixer = self Channel._mixer = self self.Sound = Sound self.Channel = self._get_channel self.music = None self._channel_max = 8 self._channels = {} self._channel_available = ConcurrentLinkedDeque() self._channel_available.addAll(list(range(self._channel_max))) self._channel_active = ConcurrentLinkedDeque() self._channel_reserved = ConcurrentLinkedDeque() self._channel_reserved_num = 0 self._thread = None self.run = self._process self._active = AtomicBoolean(False) self._initialized = False def init(self, frequency=22050, size=-16, channels=2, buffer=4096): """ Mixer initialization. Argument sampled frequency, bit size, channels, and buffer. Currently implements PCM 16-bit audio. Plays WAV, AIFF, and AU sampled audio. To specify BigEndian format of AIFF and AU, use size of float type. The mixing is done by Mixer.class, compiled with 'javac Mixer.java'. For JAR creation include with 'jar uvf App.jar pyj2d/Mixer.class'. """ if not self._initialized: encoding = {True: AudioFormat.Encoding.PCM_SIGNED, False: AudioFormat.Encoding.PCM_UNSIGNED}[size<0] channels = {True:1, False:2}[channels<=1] framesize = int((abs(size)/8) * channels) isBigEndian = isinstance(size, float) self._audio_format = AudioFormat(encoding, int(frequency), int(abs(size)), channels, framesize, int(frequency), isBigEndian) self._bufferSize = buffer try: self._mixer = AudioMixer(self._audio_format, self._bufferSize) except TypeError: self._mixer = None return None if not self._mixer.isInitialized(): return None self._byteRate = ( self._audio_format.getSampleRate() * self._audio_format.getChannels() * (self._audio_format.getSampleSizeInBits()/8) ) self._bufferSize = self._mixer.getBufferSize() self._byteArray = jarray.zeros(self._bufferSize, 'b') for id in range(self._channel_max): self._get_channel(id) self.music = Music() self._initialized = True self._thread = Thread(self) self._thread.start() return None def pre_init(self, frequency=22050, size=-16, channels=2, buffer=4096): """ Mixer initialization. """ self.init(frequency, size, channels, buffer) return None def quit(self): """ Stop mixer processing and release resources. """ self._initialized = False return None def _quit(self): self.stop() self.music._channel.stop() try: self._mixer.quit() except AttributeError: pass self._mixer = None def get_init(self): """ Get the audio format initialized. """ if self._initialized: frequency = int(self._audio_format.sampleRate) format = ( self._audio_format.sampleSizeInBits * {True:1,False:-1}[self._audio_format.bigEndian] ) channels = self._audio_format.channels return (frequency, format, channels) else: return None def stop(self): """ Stop mixer channels. """ for id in self._channel_active.iterator(): if id > -1: self._channels[id].stop() return None def fadeout(self, time): """ Fadeout mixer channels in given time. """ for id in self._channel_active.iterator(): if id > -1: self._channels[id].fadeout(time) return None def pause(self): """ Pause mixer channels. """ for id in self._channel_active.iterator(): if id > -1: self._channels[id].pause() return None def unpause(self): """ Unpause mixer channels. """ for id in self._channel_active.iterator(): if id > -1: self._channels[id].unpause() return None def set_num_channels(self, count): """ Set maximum mixer channels. Argument channel count. """ if count >= self._channel_max: for id in range(self._channel_max, count): self._get_channel(id) self._channel_available.add(id) self._channel_max = count elif count >= 0: for id in range(count, self._channel_max): if id in self._channels: if self._channels[id] is not None: self._channels[id].stop() del self._channels[id] self._channel_available.remove(id) self._channel_max = count return None def get_num_channels(self): """ Get maximum mixer channels. """ return self._channel_max def set_reserved(self, count): """ Reserve channel. Argument reserved channel count. """ if count > self._channel_max: count = self._channel_max elif count < 0: count = 0 self._channel_reserved_num = count self._channel_reserved.clear() for id in range(self._channel_reserved_num): self._channel_reserved.add(id) self._channel_available.remove(id) return None def find_channel(self, force=False): """ Get an inactive mixer channel. Optional force attribute return longest running channel if all active. """ try: id = self._channel_available.pop() self._channel_available.add(id) return self._channels[id] except NoSuchElementException: pass try: if self._channel_reserved_num: id = self._channel_reserved.pop() self._channel_reserved.add(id) return self._channels[id] except NoSuchElementException: pass if not force: return None longest = None longest_reserved = None for id in self._channel_active.iterator(): if id > self._channel_reserved_num-1: longest = id break elif id > -1: if longest_reserved is None: longest_reserved = id if longest is not None: channel = longest else: if longest_reserved is not None: channel = longest_reserved else: channel = 0 return self._channels[channel] def get_busy(self): """ Check if mixer channels are actively processing. """ for id in self._channel_active.iterator(): if id > -1: if self._channels[id]._active: return True return False def _process(self): while self._initialized: if not self._active.get(): self._idle() continue if self._channel_active.size() > 1: data, data_len = self._mix(self._channel_active) if data_len > 0: self._write(data, data_len) else: try: channel = self._channel_active.getFirst() data, data_len = self._read(channel) except NoSuchElementException: data_len = 0 if data_len > 0: self._write(data, data_len) self._quit() def _idle(self): try: self._thread.sleep(10) except InterruptedException: Thread.currentThread().interrupt() self.quit() def _mix(self, channels): for id in channels.iterator(): channel = self._channels[id] if not channel._active.get(): continue try: data, data_len, lvol, rvol = channel._get() except AttributeError: continue self._mixer.setAudioData(data, data_len, lvol, rvol) data_len = self._mixer.getAudioData(self._byteArray) return self._byteArray, data_len def _read(self, channel): channel = self._channels[channel] if not channel._active.get(): data, data_len = None, 0 else: try: data, data_len, lvol, rvol = channel._get() except AttributeError: data, data_len = None, 0 if data_len: if lvol < 1.0 or rvol < 1.0: data = self._mixer.processVolume(data, data_len, lvol, rvol) return data, data_len def _write(self, data, data_len): try: self._mixer.write(data, 0, data_len) except IllegalArgumentException: nonIntegralByte = data_len % self._audio_format.getFrameSize() if nonIntegralByte: data_len -= nonIntegralByte try: self._mixer.write(data, 0, data_len) except (IllegalArgumentException, LineUnavailableException): pass except LineUnavailableException: pass def _activate_channel(self, id): if id > self._channel_reserved_num-1: self._channel_available.remove(id) else: self._channel_reserved.remove(id) self._channel_active.add(id) self._active.set(True) def _deactivate_channel(self, id): self._channel_active.remove(id) if self._channel_active.isEmpty(): self._active.set(False) def _restore_channel(self, id): if id > self._channel_reserved_num-1: self._channel_available.add(id) elif id > -1: self._channel_reserved.add(id) def _retrieve_channel(self): try: id = self._channel_available.pop() self._channel_active.add(id) self._active.set(True) return self._channels[id] except NoSuchElementException: return None def _get_channel(self, id): if id in self._channels: return self._channels[id] else: return Channel(id) def _register_channel(self, channel): id = channel._id if id < self._channel_max: self._channels[id] = channel else: raise AttributeError("Channel not available.")