def set(self, key, value): """Write a setting into memory and ~/.config/alsacontrol/config.""" if key not in _defaults: logger.error('Unknown setting %s', key) return None self.check_mtime() if key in self._config and self._config[key] == value: logger.debug('Setting "%s" is already "%s"', key, value) return False self._config[key] = value with open(self._path, 'r+') as config_file: config_contents = config_file.read() config_contents = _modify_config(config_contents, key, value) # overwrite completely with open(self._path, 'w') as config_file: if not config_contents.endswith('\n'): config_contents += '\n' config_file.write(config_contents) self.mtime = os.path.getmtime(self._path) return True
def get_current_card(source): """Get a tuple describing the current card selection based on config. Parameters ---------- source : string one of 'pcm_input' or 'pcm_output' Returns ------- A tuple of (d, card) with d being the index in the list of options from get_cards. """ pcm_name = get_config().get(source) if pcm_name == 'null': logger.debug('No input selected') return None, None cards = get_cards() if len(cards) == 0: logger.error('Could not find any card') return None, None card = get_card(pcm_name) if card not in cards: logger.warning('Found unknown %s "%s" in config', source, pcm_name) return None, card index = cards.index(card) return index, card
def get(self, key): """Read a value from the configuration or get the default.""" self.check_mtime() if key not in _defaults: logger.error('Unknown setting %s', key) return None return self._config.get(key, _defaults[key])
def set_mute(mixer_name, state): """Set if the mixer should be muted or not.""" if mixer_name not in alsaaudio.mixers(): logger.error('Could not find mixer %s', mixer_name) return mixer = alsaaudio.Mixer(mixer_name) mixer.setmute(state)
def set_volume(volume, pcm_type, nonlinear=False): """Change the mixer volume. Parameters ---------- volume : float New value between 0 and 1 pcm_type : int 0 for output (PCM_PLAYBACK), 1 for input (PCM_CAPTURE) nonlinear : bool if True, will apply to_mixer_volume """ if pcm_type == alsaaudio.PCM_PLAYBACK: mixer_name = OUTPUT_VOLUME elif pcm_type == alsaaudio.PCM_CAPTURE: mixer_name = INPUT_VOLUME else: raise ValueError(f'Unsupported PCM {pcm_type}') if mixer_name not in alsaaudio.mixers(): logger.error('Could not find mixer %s', mixer_name) return if nonlinear: volume = to_mixer_volume(volume) mixer_volume = min(100, max(0, round(volume * 100))) mixer = alsaaudio.Mixer(mixer_name) current_mixer_volume = mixer.getvolume(pcm_type)[0] if mixer_volume == current_mixer_volume: return mixer.setvolume(mixer_volume)
def get_volume(pcm, nonlinear=False): """Get the current mixer volume between 0 and 1. Parameters ---------- pcm : int 0 for output (PCM_PLAYBACK), 1 for input (PCM_CAPTURE) nonlinear : bool if True, will apply to_perceived_volume """ if pcm == alsaaudio.PCM_PLAYBACK: mixer_name = OUTPUT_VOLUME elif pcm == alsaaudio.PCM_CAPTURE: mixer_name = INPUT_VOLUME else: raise ValueError(f'Unsupported PCM {pcm}') if mixer_name not in alsaaudio.mixers(): logger.error('Could not find mixer %s', mixer_name) # might be due to configuration return 100 mixer = alsaaudio.Mixer(mixer_name) mixer_volume = mixer.getvolume(pcm)[0] / 100 if nonlinear: return to_perceived_volume(mixer_volume) return mixer_volume
def get_cards(): """List all cards, including options such as jack.""" cards = alsaaudio.cards() if services.is_jack_running(): cards.append('jack') if len(cards) == 0: logger.error('Could not find any card') return cards
def is_muted(mixer_name=OUTPUT_MUTE): """Figure out if the output is muted or not.""" if mixer_name not in alsaaudio.mixers(): logger.error('Could not find mixer %s', mixer_name) return False mixer = alsaaudio.Mixer(mixer_name) return mixer.getmute()[0] == 1
def toggle_mute(mixer_name): """Mute or unmute. Returns None if it fails. """ if mixer_name not in alsaaudio.mixers(): logger.error('Could not find mixer %s', mixer_name) return None mixer = alsaaudio.Mixer(mixer_name) if mixer.getmute()[0]: mixer.setmute(0) return False mixer.setmute(1) return True
def output_exists(func, testcard=True, testmixer=True): """Check if the configured output card and mixer is available.""" # might be a pcm name with plugin and device card = get_card(get_config().get('pcm_output')) if testcard: if card is None: logger.debug('%s, No output selected', func) return None if not card in get_cards(): logger.error('%s, Could not find the output card "%s"', func, card) return False if testmixer and get_config().get('output_use_softvol'): if 'alsacontrol-output-volume' not in alsaaudio.mixers(): logger.error('%s, Could not find the output softvol mixer', func) play_silence() return False return True
def input_exists(func, testcard=True, testmixer=True): """Check if the configured input card and mixer is available. Returns None if no card is configured, because the existance of 'no' card cannot be determined. """ # might be a pcm name with plugin and device card = get_card(get_config().get('pcm_input')) if testcard: if card is None: logger.debug('%s, No input selected', func) return None if not card in alsaaudio.cards(): logger.error('%s, Could not find the input card "%s"', func, card) return False if testmixer and get_config().get('input_use_softvol'): if 'alsacontrol-input-volume' not in alsaaudio.mixers(): logger.error('%s, Could not find the input softvol mixer', func) record_to_nowhere() return False return True
def check_speaker_test(self): """Return a tuple of (running, error) for the speaker test.""" if self.speaker_test_process is None: return False, None return_code = self.speaker_test_process.poll() if return_code is not None: if return_code == -15: # the test was stopped by hand return False, None if return_code != 0: # speaker-test had an error, try to read it from its output logger.error('speaker-test failed (code %d):', return_code) msg = [] stderr = self._read_from_std(self.speaker_test_process.stderr) stdout = self._read_from_std(self.speaker_test_process.stdout) self.speaker_test_process = None if len(stderr) > 0: msg.append('Errors:') for line in stderr: logger.error('speaker-test stderr: %s', line) msg.append(line) if len(stdout) > 0: msg += ['', 'Output:'] for line in stdout: logger.error('speaker-test stdout: %s', line) msg.append(line) if len(msg) == 0: msg = f'Unknown error. Exit code {return_code}' else: msg = '\n'.join(msg) return False, msg if return_code == 0: self.speaker_test_process = None return False, None if return_code is None: return True, None return True, None
def record_to_nowhere(): """Similar problem as in play_silence with the input mixer. Otherwise 'Unable to find mixer control alsacontrol-input-mute' will be thrown at the start. """ logger.debug('Trying to capture sound to make the input mixers visible') try: pcm = alsaaudio.PCM(type=alsaaudio.PCM_CAPTURE, device='default') pcm.read() except alsaaudio.ALSAAudioError as error: error = str(error) logger.error(error) if 'resource busy' in error: logger.error( 'Your specified input is currently busy, are jack or pulse' 'using it?') logger.error('Could not initialize input mixer. ' 'Try setting a different device.')
def play_silence(): """In order to make alsa see the mixers, play some silent audio. Otherwise 'Unable to find mixer control alsacontrol-output-mute' will be thrown at the start. """ logger.debug('Trying to play sound to make the output mixers visible') try: pcm = alsaaudio.PCM(type=alsaaudio.PCM_PLAYBACK, channels=1, periodsize=32, device='default') data = b'\x00' * 32 pcm.write(data) except alsaaudio.ALSAAudioError as error: error = str(error) logger.error(error) if 'resource busy' in error: logger.error( 'Your specified output is currently busy, is jack using it?') logger.error('Could not initialize output mixer, ' 'try setting a different device.')