Esempio n. 1
0
    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))
Esempio n. 2
0
    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
Esempio n. 3
0
    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
Esempio n. 4
0
    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'])
Esempio n. 5
0
    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
Esempio n. 6
0
    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))