class AbstractAudioDriver(with_metaclass(ABCMeta, object)): @abstractmethod def create_audio_player(self, source, player): pass @abstractmethod def get_listener(self): pass @abstractmethod def delete(self): pass
class AbstractListener(with_metaclass(ABCMeta, object)): """The listener properties for positional audio. You can obtain the singleton instance of this class by calling :meth:`AbstractAudioDriver.get_listener`. """ _volume = 1.0 _position = (0, 0, 0) _forward_orientation = (0, 0, -1) _up_orientation = (0, 1, 0) @abstractmethod def _set_volume(self, volume): pass volume = property(lambda self: self._volume, lambda self, volume: self._set_volume(volume), doc="""The master volume for sound playback. All sound volumes are multiplied by this master volume before being played. A value of 0 will silence playback (but still consume resources). The nominal volume is 1.0. :type: float """) @abstractmethod def _set_position(self, position): pass position = property(lambda self: self._position, lambda self, position: self._set_position(position), doc="""The position of the listener in 3D space. The position is given as a tuple of floats (x, y, z). The unit defaults to meters, but can be modified with the listener properties. :type: 3-tuple of float """) @abstractmethod def _set_forward_orientation(self, orientation): pass forward_orientation = property( lambda self: self._forward_orientation, lambda self, o: self._set_forward_orientation(o), doc="""A vector giving the direction the listener is facing. The orientation is given as a tuple of floats (x, y, z), and has no unit. The forward orientation should be orthagonal to the up orientation. :type: 3-tuple of float """) @abstractmethod def _set_up_orientation(self, orientation): pass up_orientation = property(lambda self: self._up_orientation, lambda self, o: self._set_up_orientation(o), doc="""A vector giving the "up" orientation of the listener. The orientation is given as a tuple of floats (x, y, z), and has no unit. The up orientation should be orthagonal to the forward orientation. :type: 3-tuple of float """)
class AbstractAudioPlayer(with_metaclass(ABCMeta, object)): """Base class for driver audio players. """ # Audio synchronization constants AUDIO_DIFF_AVG_NB = 20 # no audio correction is done if too big error AV_NOSYNC_THRESHOLD = 10.0 def __init__(self, source, player): """Create a new audio player. :Parameters: `source` : `Source` Source to play from. `player` : `Player` Player to receive EOS and video frame sync events. """ # We only keep weakref to the player and its source to avoid # circular references. It's the player who owns the source and # the audio_player self.source = source self.player = weakref.proxy(player) # Audio synchronization self.audio_diff_avg_count = 0 self.audio_diff_cum = 0.0 self.audio_diff_avg_coef = math.exp(math.log10(0.01) / self.AUDIO_DIFF_AVG_NB) self.audio_diff_threshold = 0.1 # Experimental. ffplay computes it differently @abstractmethod def play(self): """Begin playback.""" @abstractmethod def stop(self): """Stop (pause) playback.""" @abstractmethod def delete(self): """Stop playing and clean up all resources used by player.""" def _play_group(self, audio_players): """Begin simultaneous playback on a list of audio players.""" # This should be overridden by subclasses for better synchrony. for player in audio_players: player.play() def _stop_group(self, audio_players): """Stop simultaneous playback on a list of audio players.""" # This should be overridden by subclasses for better synchrony. for player in audio_players: player.stop() @abstractmethod def clear(self): """Clear all buffered data and prepare for replacement data. The player should be stopped before calling this method. """ self.audio_diff_avg_count = 0 self.audio_diff_cum = 0.0 @abstractmethod def get_time(self): """Return approximation of current playback time within current source. Returns ``None`` if the audio player does not know what the playback time is (for example, before any valid audio data has been read). :rtype: float :return: current play cursor time, in seconds. """ # TODO determine which source within group @abstractmethod def prefill_audio(self): """Prefill the audio buffer with audio data. This method is called before the audio player starts in order to reduce the time it takes to fill the whole audio buffer. """ def get_audio_time_diff(self): """Queries the time difference between the audio time and the `Player` master clock. The time difference returned is calculated using a weighted average on previous audio time differences. The algorithms will need at least 20 measurements before returning a weighted average. :rtype: float :return: weighted average difference between audio time and master clock from `Player` """ audio_time = self.get_time() or 0 p_time = self.player.time diff = audio_time - p_time if abs(diff) < self.AV_NOSYNC_THRESHOLD: self.audio_diff_cum = diff + self.audio_diff_cum * self.audio_diff_avg_coef if self.audio_diff_avg_count < self.AUDIO_DIFF_AVG_NB: self.audio_diff_avg_count += 1 else: avg_diff = self.audio_diff_cum * (1 - self.audio_diff_avg_coef) if abs(avg_diff) > self.audio_diff_threshold: return avg_diff else: self.audio_diff_avg_count = 0 self.audio_diff_cum = 0.0 return 0.0 def set_volume(self, volume): """See `Player.volume`.""" pass def set_position(self, position): """See :py:attr:`~pyglet.media.Player.position`.""" pass def set_min_distance(self, min_distance): """See `Player.min_distance`.""" pass def set_max_distance(self, max_distance): """See `Player.max_distance`.""" pass def set_pitch(self, pitch): """See :py:attr:`~pyglet.media.Player.pitch`.""" pass def set_cone_orientation(self, cone_orientation): """See `Player.cone_orientation`.""" pass def set_cone_inner_angle(self, cone_inner_angle): """See `Player.cone_inner_angle`.""" pass def set_cone_outer_angle(self, cone_outer_angle): """See `Player.cone_outer_angle`.""" pass def set_cone_outer_gain(self, cone_outer_gain): """See `Player.cone_outer_gain`.""" pass @property def source(self): "Source to play from." return self._source @source.setter def source(self, value): self._source = weakref.proxy(value)