From c71202c2be48156264270a2af71d5ab100d865d4 Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Tue, 4 Sep 2012 00:54:28 +0200 Subject: [PATCH] Remove all of mopidy.mixers and tests.mixers modules. --- mopidy/core.py | 10 -- mopidy/mixers/__init__.py | 0 mopidy/mixers/alsa.py | 60 --------- mopidy/mixers/base.py | 63 --------- mopidy/mixers/denon.py | 58 -------- mopidy/mixers/dummy.py | 16 --- mopidy/mixers/gstreamer_software.py | 23 ---- mopidy/mixers/nad.py | 198 ---------------------------- mopidy/mixers/osa.py | 46 ------- tests/mixers/__init__.py | 0 tests/mixers/base_test.py | 38 ------ tests/mixers/denon_test.py | 42 ------ tests/mixers/dummy_test.py | 23 ---- 13 files changed, 577 deletions(-) delete mode 100644 mopidy/mixers/__init__.py delete mode 100644 mopidy/mixers/alsa.py delete mode 100644 mopidy/mixers/base.py delete mode 100644 mopidy/mixers/denon.py delete mode 100644 mopidy/mixers/dummy.py delete mode 100644 mopidy/mixers/gstreamer_software.py delete mode 100644 mopidy/mixers/nad.py delete mode 100644 mopidy/mixers/osa.py delete mode 100644 tests/mixers/__init__.py delete mode 100644 tests/mixers/base_test.py delete mode 100644 tests/mixers/denon_test.py delete mode 100644 tests/mixers/dummy_test.py diff --git a/mopidy/core.py b/mopidy/core.py index 7d2e01ff..ecded337 100644 --- a/mopidy/core.py +++ b/mopidy/core.py @@ -40,7 +40,6 @@ def main(): check_old_folders() setup_settings(options.interactive) setup_gstreamer() - setup_mixer() setup_backend() setup_frontends() loop.run() @@ -54,7 +53,6 @@ def main(): loop.quit() stop_frontends() stop_backend() - stop_mixer() stop_gstreamer() stop_remaining_actors() @@ -109,14 +107,6 @@ def setup_gstreamer(): def stop_gstreamer(): stop_actors_by_class(GStreamer) -def setup_mixer(): - # TODO: remove this hack which is just a stepping stone for our - # refactoring. - get_class('mopidy.mixers.gstreamer_software.GStreamerSoftwareMixer').start() - -def stop_mixer(): - stop_actors_by_class(get_class('mopidy.mixers.gstreamer_software.GStreamerSoftwareMixer')) - def setup_backend(): get_class(settings.BACKENDS[0]).start() diff --git a/mopidy/mixers/__init__.py b/mopidy/mixers/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/mopidy/mixers/alsa.py b/mopidy/mixers/alsa.py deleted file mode 100644 index acb12e66..00000000 --- a/mopidy/mixers/alsa.py +++ /dev/null @@ -1,60 +0,0 @@ -import alsaaudio -import logging - -from pykka.actor import ThreadingActor - -from mopidy import settings -from mopidy.mixers.base import BaseMixer - -logger = logging.getLogger('mopidy.mixers.alsa') - -class AlsaMixer(ThreadingActor, BaseMixer): - """ - Mixer which uses the Advanced Linux Sound Architecture (ALSA) to control - volume. - - **Dependencies:** - - - pyalsaaudio >= 0.2 (python-alsaaudio on Debian/Ubuntu) - - **Settings:** - - - :attr:`mopidy.settings.MIXER_ALSA_CONTROL` - """ - - def __init__(self): - super(AlsaMixer, self).__init__() - self._mixer = None - - def on_start(self): - self._mixer = alsaaudio.Mixer(self._get_mixer_control()) - assert self._mixer is not None - - def _get_mixer_control(self): - """Returns the first mixer control candidate that is known to ALSA""" - candidates = self._get_mixer_control_candidates() - for control in candidates: - if control in alsaaudio.mixers(): - logger.info(u'Mixer control in use: %s', control) - return control - else: - logger.debug(u'Mixer control not found, skipping: %s', control) - logger.warning(u'No working mixer controls found. Tried: %s', - candidates) - - def _get_mixer_control_candidates(self): - """ - A mixer named 'Master' does not always exist, so we fall back to using - 'PCM'. If this does not work for you, you may set - :attr:`mopidy.settings.MIXER_ALSA_CONTROL`. - """ - if settings.MIXER_ALSA_CONTROL: - return [settings.MIXER_ALSA_CONTROL] - return [u'Master', u'PCM'] - - def get_volume(self): - # FIXME does not seem to see external volume changes. - return self._mixer.getvolume()[0] - - def set_volume(self, volume): - self._mixer.setvolume(volume) diff --git a/mopidy/mixers/base.py b/mopidy/mixers/base.py deleted file mode 100644 index a387c143..00000000 --- a/mopidy/mixers/base.py +++ /dev/null @@ -1,63 +0,0 @@ -import logging - -from mopidy import listeners, settings - -logger = logging.getLogger('mopidy.mixers') - -class BaseMixer(object): - # TODO: remove completly - amplification_factor = 1.0 - - @property - def volume(self): - """ - The audio volume - - Integer in range [0, 100]. :class:`None` if unknown. Values below 0 is - equal to 0. Values above 100 is equal to 100. - """ - if not hasattr(self, '_user_volume'): - self._user_volume = 0 - volume = self.get_volume() - if volume is None or not self.amplification_factor < 1: - return volume - else: - user_volume = int(volume / self.amplification_factor) - if (user_volume - 1) <= self._user_volume <= (user_volume + 1): - return self._user_volume - else: - return user_volume - - @volume.setter - def volume(self, volume): - if not hasattr(self, '_user_volume'): - self._user_volume = 0 - volume = int(volume) - if volume < 0: - volume = 0 - elif volume > 100: - volume = 100 - self._user_volume = volume - real_volume = int(volume * self.amplification_factor) - self.set_volume(real_volume) - self._trigger_volume_changed() - - def get_volume(self): - """ - Return volume as integer in range [0, 100]. :class:`None` if unknown. - - *MUST be implemented by subclass.* - """ - raise NotImplementedError - - def set_volume(self, volume): - """ - Set volume as integer in range [0, 100]. - - *MUST be implemented by subclass.* - """ - raise NotImplementedError - - def _trigger_volume_changed(self): - logger.debug(u'Triggering volume changed event') - listeners.BackendListener.send('volume_changed') diff --git a/mopidy/mixers/denon.py b/mopidy/mixers/denon.py deleted file mode 100644 index b0abbdb9..00000000 --- a/mopidy/mixers/denon.py +++ /dev/null @@ -1,58 +0,0 @@ -import logging - -from pykka.actor import ThreadingActor - -from mopidy import settings -from mopidy.mixers.base import BaseMixer - -logger = logging.getLogger(u'mopidy.mixers.denon') - -class DenonMixer(ThreadingActor, BaseMixer): - """ - Mixer for controlling Denon amplifiers and receivers using the RS-232 - protocol. - - The external mixer is the authoritative source for the current volume. - This allows the user to use his remote control the volume without Mopidy - cancelling the volume setting. - - **Dependencies** - - - pyserial (python-serial on Debian/Ubuntu) - - **Settings** - - - :attr:`mopidy.settings.MIXER_EXT_PORT` -- Example: ``/dev/ttyUSB0`` - """ - - def __init__(self, device=None): - super(DenonMixer, self).__init__() - self._device = device - self._levels = ['99'] + ["%(#)02d" % {'#': v} for v in range(0, 99)] - self._volume = 0 - - def on_start(self): - if self._device is None: - from serial import Serial - self._device = Serial(port=settings.MIXER_EXT_PORT, timeout=0.2) - - def get_volume(self): - self._ensure_open_device() - self._device.write('MV?\r') - vol = str(self._device.readline()[2:4]) - logger.debug(u'_get_volume() = %s' % vol) - return self._levels.index(vol) - - def set_volume(self, volume): - # Clamp according to Denon-spec - if volume > 99: - volume = 99 - self._ensure_open_device() - self._device.write('MV%s\r'% self._levels[volume]) - vol = self._device.readline()[2:4] - self._volume = self._levels.index(vol) - - def _ensure_open_device(self): - if not self._device.isOpen(): - logger.debug(u'(re)connecting to Denon device') - self._device.open() diff --git a/mopidy/mixers/dummy.py b/mopidy/mixers/dummy.py deleted file mode 100644 index 7262e83c..00000000 --- a/mopidy/mixers/dummy.py +++ /dev/null @@ -1,16 +0,0 @@ -from pykka.actor import ThreadingActor - -from mopidy.mixers.base import BaseMixer - -class DummyMixer(ThreadingActor, BaseMixer): - """Mixer which just stores and reports the chosen volume.""" - - def __init__(self): - super(DummyMixer, self).__init__() - self._volume = None - - def get_volume(self): - return self._volume - - def set_volume(self, volume): - self._volume = volume diff --git a/mopidy/mixers/gstreamer_software.py b/mopidy/mixers/gstreamer_software.py deleted file mode 100644 index a38692db..00000000 --- a/mopidy/mixers/gstreamer_software.py +++ /dev/null @@ -1,23 +0,0 @@ -from pykka.actor import ThreadingActor -from pykka.registry import ActorRegistry - -from mopidy.mixers.base import BaseMixer -from mopidy.gstreamer import GStreamer - -class GStreamerSoftwareMixer(ThreadingActor, BaseMixer): - """Mixer which uses GStreamer to control volume in software.""" - - def __init__(self): - super(GStreamerSoftwareMixer, self).__init__() - self.output = None - - def on_start(self): - output_refs = ActorRegistry.get_by_class(GStreamer) - assert len(output_refs) == 1, 'Expected exactly one running output.' - self.output = output_refs[0].proxy() - - def get_volume(self): - return self.output.get_volume().get() - - def set_volume(self, volume): - self.output.set_volume(volume).get() diff --git a/mopidy/mixers/nad.py b/mopidy/mixers/nad.py deleted file mode 100644 index 78473308..00000000 --- a/mopidy/mixers/nad.py +++ /dev/null @@ -1,198 +0,0 @@ -import logging -import serial - -from pykka.actor import ThreadingActor - -from mopidy import settings -from mopidy.mixers.base import BaseMixer - -logger = logging.getLogger('mopidy.mixers.nad') - -class NadMixer(ThreadingActor, BaseMixer): - """ - Mixer for controlling NAD amplifiers and receivers using the NAD RS-232 - protocol. - - The NAD mixer was created using a NAD C 355BEE amplifier, but should also - work with other NAD amplifiers supporting the same RS-232 protocol (v2.x). - The C 355BEE does not give you access to the current volume. It only - supports increasing or decreasing the volume one step at the time. Other - NAD amplifiers may support more advanced volume adjustment than what is - currently used by this mixer. - - Sadly, this means that if you use the remote control to change the volume - on the amplifier, Mopidy will no longer report the correct volume. - - **Dependencies** - - - pyserial (python-serial on Debian/Ubuntu) - - **Settings** - - - :attr:`mopidy.settings.MIXER_EXT_PORT` -- Example: ``/dev/ttyUSB0`` - - :attr:`mopidy.settings.MIXER_EXT_SOURCE` -- Example: ``Aux`` - - :attr:`mopidy.settings.MIXER_EXT_SPEAKERS_A` -- Example: ``On`` - - :attr:`mopidy.settings.MIXER_EXT_SPEAKERS_B` -- Example: ``Off`` - - """ - - def __init__(self): - super(NadMixer, self).__init__() - self._volume_cache = None - self._nad_talker = NadTalker.start().proxy() - - def get_volume(self): - return self._volume_cache - - def set_volume(self, volume): - self._volume_cache = volume - self._nad_talker.set_volume(volume) - - -class NadTalker(ThreadingActor): - """ - Independent process which does the communication with the NAD device. - - Since the communication is done in an independent process, Mopidy won't - block other requests while doing rather time consuming work like - calibrating the NAD device's volume. - """ - - # Timeout in seconds used for read/write operations. - # If you set the timeout too low, the reads will never get complete - # confirmations and calibration will decrease volume forever. If you set - # the timeout too high, stuff takes more time. 0.2s seems like a good value - # for NAD C 355BEE. - TIMEOUT = 0.2 - - # Number of volume levels the device supports. 40 for NAD C 355BEE. - VOLUME_LEVELS = 40 - - # Volume in range 0..VOLUME_LEVELS. :class:`None` before calibration. - _nad_volume = None - - def __init__(self): - super(NadTalker, self).__init__() - self._device = None - - def on_start(self): - self._open_connection() - self._set_device_to_known_state() - - def _open_connection(self): - # Opens serial connection to the device. - # Communication settings: 115200 bps 8N1 - logger.info(u'Connecting to serial device "%s"', - settings.MIXER_EXT_PORT) - self._device = serial.Serial(port=settings.MIXER_EXT_PORT, - baudrate=115200, timeout=self.TIMEOUT) - self._get_device_model() - - def _set_device_to_known_state(self): - self._power_device_on() - self._select_speakers() - self._select_input_source() - self._unmute() - self._calibrate_volume() - - def _get_device_model(self): - model = self._ask_device('Main.Model') - logger.info(u'Connected to device of model "%s"', model) - return model - - def _power_device_on(self): - while self._ask_device('Main.Power') != 'On': - logger.info(u'Powering device on') - self._command_device('Main.Power', 'On') - - def _select_speakers(self): - if settings.MIXER_EXT_SPEAKERS_A is not None: - while (self._ask_device('Main.SpeakerA') - != settings.MIXER_EXT_SPEAKERS_A): - logger.info(u'Setting speakers A "%s"', - settings.MIXER_EXT_SPEAKERS_A) - self._command_device('Main.SpeakerA', - settings.MIXER_EXT_SPEAKERS_A) - if settings.MIXER_EXT_SPEAKERS_B is not None: - while (self._ask_device('Main.SpeakerB') != - settings.MIXER_EXT_SPEAKERS_B): - logger.info(u'Setting speakers B "%s"', - settings.MIXER_EXT_SPEAKERS_B) - self._command_device('Main.SpeakerB', - settings.MIXER_EXT_SPEAKERS_B) - - def _select_input_source(self): - if settings.MIXER_EXT_SOURCE is not None: - while self._ask_device('Main.Source') != settings.MIXER_EXT_SOURCE: - logger.info(u'Selecting input source "%s"', - settings.MIXER_EXT_SOURCE) - self._command_device('Main.Source', settings.MIXER_EXT_SOURCE) - - def _unmute(self): - while self._ask_device('Main.Mute') != 'Off': - logger.info(u'Unmuting device') - self._command_device('Main.Mute', 'Off') - - def _ask_device(self, key): - self._write('%s?' % key) - return self._readline().replace('%s=' % key, '') - - def _command_device(self, key, value): - if type(value) == unicode: - value = value.encode('utf-8') - self._write('%s=%s' % (key, value)) - self._readline() - - def _calibrate_volume(self): - # The NAD C 355BEE amplifier has 40 different volume levels. We have no - # way of asking on which level we are. Thus, we must calibrate the - # mixer by decreasing the volume 39 times. - logger.info(u'Calibrating NAD amplifier') - steps_left = self.VOLUME_LEVELS - 1 - while steps_left: - if self._decrease_volume(): - steps_left -= 1 - self._nad_volume = 0 - logger.info(u'Done calibrating NAD amplifier') - - def set_volume(self, volume): - # Increase or decrease the amplifier volume until it matches the given - # target volume. - logger.debug(u'Setting volume to %d' % volume) - target_nad_volume = int(round(volume * self.VOLUME_LEVELS / 100.0)) - if self._nad_volume is None: - return # Calibration needed - while target_nad_volume > self._nad_volume: - if self._increase_volume(): - self._nad_volume += 1 - while target_nad_volume < self._nad_volume: - if self._decrease_volume(): - self._nad_volume -= 1 - - def _increase_volume(self): - # Increase volume. Returns :class:`True` if confirmed by device. - self._write('Main.Volume+') - return self._readline() == 'Main.Volume+' - - def _decrease_volume(self): - # Decrease volume. Returns :class:`True` if confirmed by device. - self._write('Main.Volume-') - return self._readline() == 'Main.Volume-' - - def _write(self, data): - # Write data to device. Prepends and appends a newline to the data, as - # recommended by the NAD documentation. - if not self._device.isOpen(): - self._device.open() - self._device.write('\n%s\n' % data) - logger.debug('Write: %s', data) - - def _readline(self): - # Read line from device. The result is stripped for leading and - # trailing whitespace. - if not self._device.isOpen(): - self._device.open() - result = self._device.readline().strip() - if result: - logger.debug('Read: %s', result) - return result diff --git a/mopidy/mixers/osa.py b/mopidy/mixers/osa.py deleted file mode 100644 index bd97d790..00000000 --- a/mopidy/mixers/osa.py +++ /dev/null @@ -1,46 +0,0 @@ -from subprocess import Popen, PIPE -import time - -from pykka.actor import ThreadingActor - -from mopidy.mixers.base import BaseMixer - -class OsaMixer(ThreadingActor, BaseMixer): - """ - Mixer which uses ``osascript`` on OS X to control volume. - - **Dependencies:** - - - None - - **Settings:** - - - None - """ - - CACHE_TTL = 30 - - _cache = None - _last_update = None - - def _valid_cache(self): - return (self._cache is not None - and self._last_update is not None - and (int(time.time() - self._last_update) < self.CACHE_TTL)) - - def get_volume(self): - if not self._valid_cache(): - try: - self._cache = int(Popen( - ['osascript', '-e', - 'output volume of (get volume settings)'], - stdout=PIPE).communicate()[0]) - except ValueError: - self._cache = None - self._last_update = int(time.time()) - return self._cache - - def set_volume(self, volume): - Popen(['osascript', '-e', 'set volume output volume %d' % volume]) - self._cache = volume - self._last_update = int(time.time()) diff --git a/tests/mixers/__init__.py b/tests/mixers/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/mixers/base_test.py b/tests/mixers/base_test.py deleted file mode 100644 index 54cd8773..00000000 --- a/tests/mixers/base_test.py +++ /dev/null @@ -1,38 +0,0 @@ -class BaseMixerTest(object): - MIN = 0 - MAX = 100 - ACTUAL_MIN = MIN - ACTUAL_MAX = MAX - INITIAL = None - - mixer_class = None - - def setUp(self): - assert self.mixer_class is not None, \ - "mixer_class must be set in subclass" - # pylint: disable = E1102 - self.mixer = self.mixer_class() - # pylint: enable = E1102 - - def test_initial_volume(self): - self.assertEqual(self.mixer.volume, self.INITIAL) - - def test_volume_set_to_min(self): - self.mixer.volume = self.MIN - self.assertEqual(self.mixer.volume, self.ACTUAL_MIN) - - def test_volume_set_to_max(self): - self.mixer.volume = self.MAX - self.assertEqual(self.mixer.volume, self.ACTUAL_MAX) - - def test_volume_set_to_below_min_results_in_min(self): - self.mixer.volume = -10 - self.assertEqual(self.mixer.volume, self.ACTUAL_MIN) - - def test_volume_set_to_above_max_results_in_max(self): - self.mixer.volume = self.MAX + 10 - self.assertEqual(self.mixer.volume, self.ACTUAL_MAX) - - def test_volume_is_not_float(self): - self.mixer.volume = 1.0 / 3 * 100 - self.assertEqual(self.mixer.volume, 33) diff --git a/tests/mixers/denon_test.py b/tests/mixers/denon_test.py deleted file mode 100644 index cdfe0772..00000000 --- a/tests/mixers/denon_test.py +++ /dev/null @@ -1,42 +0,0 @@ -from mopidy.mixers.denon import DenonMixer -from tests.mixers.base_test import BaseMixerTest - -from tests import unittest - - -class DenonMixerDeviceMock(object): - def __init__(self): - self._open = True - self.ret_val = bytes('MV00\r') - - def write(self, x): - if x[2] != '?': - self.ret_val = bytes(x) - - def read(self, x): - return self.ret_val - - def readline(self): - return self.ret_val - - def isOpen(self): - return self._open - - def open(self): - self._open = True - - -class DenonMixerTest(BaseMixerTest, unittest.TestCase): - ACTUAL_MAX = 99 - INITIAL = 1 - - mixer_class = DenonMixer - - def setUp(self): - self.device = DenonMixerDeviceMock() - self.mixer = DenonMixer(device=self.device) - - def test_reopen_device(self): - self.device._open = False - self.mixer.volume = 10 - self.assertTrue(self.device.isOpen()) diff --git a/tests/mixers/dummy_test.py b/tests/mixers/dummy_test.py deleted file mode 100644 index f9418d7a..00000000 --- a/tests/mixers/dummy_test.py +++ /dev/null @@ -1,23 +0,0 @@ -from mopidy.mixers.dummy import DummyMixer - -from tests import unittest -from tests.mixers.base_test import BaseMixerTest - - -class DummyMixerTest(BaseMixerTest, unittest.TestCase): - mixer_class = DummyMixer - - def test_set_volume_is_capped(self): - self.mixer.amplification_factor = 0.5 - self.mixer.volume = 100 - self.assertEquals(self.mixer._volume, 50) - - def test_get_volume_does_not_show_that_the_volume_is_capped(self): - self.mixer.amplification_factor = 0.5 - self.mixer._volume = 50 - self.assertEquals(self.mixer.volume, 100) - - def test_get_volume_get_the_same_number_as_was_set(self): - self.mixer.amplification_factor = 0.5 - self.mixer.volume = 13 - self.assertEquals(self.mixer.volume, 13)