def __init__(self, mc, name, config, member_cls): super().__init__(mc, name, config, member_cls) self._track = None self._simultaneous_limit = None self._stealing_method = SoundStealingMethod.oldest self._key = None self.log = logging.getLogger('SoundPool') config.setdefault('track', None) config.setdefault('key', None) # Make sure a legal track name has been specified (unless only one track exists) if config['track']: # Track name specified in config, validate it track = self.machine.sound_system.audio_interface.get_track_by_name( self.config['track']) if track is None: self.log.error( "'%s' is not a valid track name. " "Could not create sound '%s' asset.", self.config['track'], name) raise AudioException( "'{}' is not a valid track name. " "Could not create sound '{}' asset".format( self.config['track'], name)) else: # Track not specified, determine track count if self.machine.sound_system.audio_interface.get_track_count( ) == 1: # Only one track exists, assign default track track = self.machine.sound_system.audio_interface.get_track(0) config['track'] = track.name self.log.debug( "Sound '%s' has been assigned a default track value of '%s' " "(only one track has been configured)", name, track.name) else: raise AssertionError( "SoundPool {} does not have a valid track.".format(name)) self._track = config['track'] self._key = config['key'] if 'simultaneous_limit' in self.config and self.config[ 'simultaneous_limit'] is not None: self._simultaneous_limit = int(self.config['simultaneous_limit']) if 'stealing_method' in self.config and self.config[ 'stealing_method'] is not None: method = str(self.config['stealing_method']).lower() if method == 'skip': self._stealing_method = SoundStealingMethod.skip elif method == 'oldest': self._stealing_method = SoundStealingMethod.oldest elif method == 'newest': self._stealing_method = SoundStealingMethod.newest else: raise AudioException( "Illegal value for sound_pool.stealing_method. " "Could not create sound pool '{}' asset".format(name))
def validate(self, sound_length): """ Validates the ducking settings against the length of the sound to ensure all settings are valid. Args: sound_length: The length of the sound in samples Return: True if all settings are valid, otherwise an exception will be thrown """ if sound_length is None or sound_length == 0: raise AudioException( "ducking may not be applied to an empty/zero length sound") if self._attack > sound_length: raise AudioException( "'ducking.attack' value may not be longer than the " "length of the sound") if self._release_point >= sound_length: raise AudioException( "'ducking.release_point' value may not occur before the " "beginning of the sound") if self._release_point + self._attack >= sound_length: raise AudioException( "'ducking.release_point' value may not occur before " "the ducking attack segment has completed") return True
def load_markers(config, sound_name): """ Load and validate the markers config section Args: config: The 'markers' configuration file section for the sound sound_name: The name of the sound Returns: List of sound marker dictionary objects """ markers = list() if isinstance(config, dict): config_markers = list(config) elif isinstance(config, list): config_markers = config else: raise AudioException("Sound %s has an invalid markers section", sound_name) last_marker_time = 0 # Loop over all markers in the list for settings in config_markers: marker = dict() # Set marker parameters marker['time'] = AudioInterface.string_to_secs(settings['time']) if marker['time'] < last_marker_time: raise AudioException( "Sound markers for sound %s must be in ascending time order", sound_name) last_marker_time = marker['time'] if 'events' in settings and settings['events'] is not None: marker['events'] = Util.string_to_list(settings['events']) else: raise AudioException( "Sound markers for sound %s must specify at least one event", sound_name) if 'name' in settings and settings['name'] is not None: marker['name'] = settings['name'].lower() else: marker['name'] = None if len(markers) == AudioInterface.get_max_markers(): raise AudioException( "Cannot add marker - the limit of %d sound markers has been " "reached for sound %s.", AudioInterface.get_max_markers(), sound_name) markers.append(marker) return markers
def _create_track(self, name, config=None): # noqa """Create a track in the audio system with the specified name and configuration. Args: name: The track name (which will be used to reference the track, such as "voice" or "sfx". config: A Python dictionary containing the configuration settings for this track. """ if self.audio_interface is None: raise AudioException( "Could not create '{}' track - the sound_system has " "not been initialized".format(name)) # Validate track config parameters if name in self.tracks: raise AudioException( "Could not create '{}' track - a track with that name " "already exists".format(name)) if config is None: config = {} if 'volume' not in config: config['volume'] = DEFAULT_TRACK_VOLUME if 'type' not in config: config['type'] = 'standard' if config['type'] not in ['standard', 'playlist', 'sound_loop']: raise AudioException( "Could not create '{}' track - an illegal value for " "'type' was found".format(name)) # Validate type-specific parameters and create the track track = None if config['type'] == 'standard': if 'simultaneous_sounds' not in config: config[ 'simultaneous_sounds'] = DEFAULT_TRACK_MAX_SIMULTANEOUS_SOUNDS track = self.audio_interface.create_standard_track( self.mc, name, config['simultaneous_sounds'], config['volume']) elif config['type'] == 'playlist': config.setdefault('crossfade_time', 0.0) config['crossfade_time'] = Util.string_to_secs( config['crossfade_time']) track = self.audio_interface.create_playlist_track( self.mc, name, config['crossfade_time'], config['volume']) elif config['type'] == 'sound_loop': if 'max_layers' not in config: config['max_layers'] = 8 track = self.audio_interface.create_sound_loop_track( self.mc, name, config['max_layers'], config['volume']) if track is None: raise AudioException( "Could not create '{}' track due to an error".format(name)) self.tracks[name] = track if 'events_when_stopped' in config and config[ 'events_when_stopped'] is not None: track.events_when_stopped = Util.string_to_event_list( config['events_when_stopped']) if 'events_when_played' in config and config[ 'events_when_played'] is not None: track.events_when_played = Util.string_to_event_list( config['events_when_played']) if 'events_when_paused' in config and config[ 'events_when_paused'] is not None: track.events_when_paused = Util.string_to_event_list( config['events_when_paused']) if 'events_when_resumed' in config and config[ 'events_when_resumed'] is not None: track.events_when_resumed = Util.string_to_event_list( config['events_when_resumed'])
def __init__(self, mc, name, file, config): """ Constructor""" super().__init__(mc, name, file, config) self._track = None self._streaming = False self._volume = DEFAULT_VOLUME self.priority = DEFAULT_PRIORITY self._max_queue_time = DEFAULT_MAX_QUEUE_TIME self._loops = DEFAULT_LOOPS self._start_at = 0 self._fade_in = 0 self._fade_out = 0 self._simultaneous_limit = None self._stealing_method = SoundStealingMethod.oldest self._events_when_played = None self._events_when_stopped = None self._events_when_looping = None self._mode_end_action = ModeEndAction.stop_looping self._markers = list() self._container = None # holds the actual sound samples in memory self._ducking = None self._key = None self.log = logging.getLogger('SoundAsset') config.setdefault('track', None) # Make sure a legal track name has been specified (unless only one track exists) if config['track']: # Track name specified in config, validate it track = self.machine.sound_system.audio_interface.get_track_by_name( self.config['track']) if track is None: self.log.error( "'%s' is not a valid track name. " "Could not create sound '%s' asset.", self.config['track'], name) raise AudioException( "'{}' is not a valid track name. " "Could not create sound '{}' asset".format( self.config['track'], name)) else: # Track not specified, determine track count if self.machine.sound_system.audio_interface.get_track_count( ) == 1: # Only one track exists, assign default track track = self.machine.sound_system.audio_interface.get_track(0) config['track'] = track.name self.log.debug( "Sound '%s' has been assigned a default track value of '%s' " "(only one track has been configured)", name, track.name) else: raise AssertionError( "Sound {} does not have a valid track.".format(name)) self._track = config['track'] # Validate sound attributes and provide default values self.config.setdefault('streaming', False) self._streaming = self.config['streaming'] self.config.setdefault('volume', 0.5) self._volume = min(max(float(self.config['volume']), 0.0), 1.0) self.config.setdefault('priority', 0) self.priority = int(self.config['priority']) self.config.setdefault('max_queue_time', None) if self.config['max_queue_time'] is None: self._max_queue_time = None else: self._max_queue_time = AudioInterface.string_to_secs( self.config['max_queue_time']) self.config.setdefault('loops', 0) self._loops = int(self.config['loops']) self.config.setdefault('simultaneous_limit', None) if self.config['simultaneous_limit'] is None: self._simultaneous_limit = None else: self._simultaneous_limit = int(self.config['simultaneous_limit']) self.config.setdefault('start_at', 0) self._start_at = AudioInterface.string_to_secs(self.config['start_at']) self.config.setdefault('fade_in', 0) self._fade_in = AudioInterface.string_to_secs(self.config['fade_in']) self.config.setdefault('fade_out', 0) self._fade_out = AudioInterface.string_to_secs(self.config['fade_out']) self.config.setdefault('stealing_method', 'oldest') method = str(self.config['stealing_method']).lower() if method == 'skip': self._stealing_method = SoundStealingMethod.skip elif method == 'oldest': self._stealing_method = SoundStealingMethod.oldest elif method == 'newest': self._stealing_method = SoundStealingMethod.newest else: raise AudioException( "Illegal value for sound.stealing_method. " "Could not create sound '{}' asset".format(name)) if 'events_when_played' in self.config and isinstance( self.config['events_when_played'], str): self._events_when_played = Util.string_to_list( self.config['events_when_played']) if 'events_when_stopped' in self.config and isinstance( self.config['events_when_stopped'], str): self._events_when_stopped = Util.string_to_list( self.config['events_when_stopped']) if 'events_when_looping' in self.config and isinstance( self.config['events_when_looping'], str): self._events_when_looping = Util.string_to_list( self.config['events_when_looping']) if 'mode_end_action' in self.config and self.config[ 'mode_end_action'] is not None: action = str(self.config['mode_end_action']).lower() if action == 'stop': self._mode_end_action = ModeEndAction.stop elif action == 'stop_looping': self._mode_end_action = ModeEndAction.stop_looping else: raise AudioException( "Illegal value for sound.mode_end_action. " "Could not create sound '{}' asset".format(name)) if 'key' in self.config: self._key = self.config['key'] if 'markers' in self.config: self._markers = SoundAsset.load_markers(self.config['markers'], self.name) if 'ducking' in self.config: try: self._ducking = DuckingSettings(self.machine, self.config['ducking']) except AudioException: raise AudioException( "Error in ducking settings: {}. " "Could not create sound '{}' asset".format( sys.exc_info()[1], self.name)) # An attenuation value of exactly 1.0 does absolutely nothing so # there is no point in keeping the ducking settings for this # sound when attenuation is 1.0. if self._ducking.attenuation == 1.0: self._ducking = None
def __init__(self, mc, config): """ Constructor Args: mc: The media controller instance. config: The ducking configuration file section that contains all the ducking settings for the sound. Notes: The config section should contain the following attributes: target: A list of track names to apply the ducking to when the sound is played. delay: The duration (in seconds) of the delay period (time before attack starts) attack: The duration (in seconds) of the attack stage of the ducking envelope attenuation: The attenuation (gain) (0.0 to 1.0) to apply to the target track while ducking release_point: The point (in seconds) relative to the end of the sound at which to start the release stage. A positive value indicates prior to the end of the sound while a negative value indicates to start the release after the end of the sound. release: The duration (in seconds) of the release stage of the ducking process. """ if config is None: raise AudioException( "The 'ducking' configuration must include the following " "attributes: track, delay, attack, attenuation, " "release_point, and release") if 'target' not in config: raise AudioException("'ducking.target' must contain at least one " "valid audio track name") # Target can contain a list of track names - convert string to list and validate self._targets = Util.string_to_list(config['target']) if len(self._targets) == 0: raise AudioException("'ducking.target' must contain at least one " "valid audio track name") # Create a bit mask of target tracks based on their track number (will be used to pass # the target data to the audio library). self._track_bit_mask = 0 for target in self._targets: track = mc.sound_system.audio_interface.get_track_by_name(target) if track is None: raise AudioException( "'ducking.target' contains an invalid track name '%s'", target) self._track_bit_mask += (1 << track.number) # Delay is optional (defaults to 0, must be >= 0) if 'delay' in config: self._delay = max( mc.sound_system.audio_interface.string_to_secs( config['delay']), 0) else: self._delay = 0 if 'attack' not in config: raise AudioException( "'ducking.attack' must contain a valid attack value (time " "string)") self._attack = max( mc.sound_system.audio_interface.string_to_secs(config['attack']), mc.sound_system.audio_interface.string_to_secs( MINIMUM_DUCKING_DURATION)) if 'attenuation' not in config: raise AudioException( "'ducking.attenuation' must contain valid attenuation " "value (0.0 to 1.0)") self._attenuation = min( max(float(AudioInterface.string_to_gain(config['attenuation'])), 0.0), 1.0) if 'release_point' not in config: raise AudioException( "'ducking.release_point' must contain a valid release point " "value (time string)") # Release point cannot be negative (must be before or at the end of the sound) self._release_point = max( mc.sound_system.audio_interface.string_to_secs( config['release_point']), 0) if 'release' not in config: raise AudioException( "'ducking.release' must contain a valid release " "value (time string)") self._release = max( mc.sound_system.audio_interface.string_to_secs(config['release']), mc.sound_system.audio_interface.string_to_secs( MINIMUM_DUCKING_DURATION))