Remove all of mopidy.mixers and tests.mixers modules.

This commit is contained in:
Thomas Adamcik 2012-09-04 00:54:28 +02:00
parent 4ffd06736e
commit c71202c2be
13 changed files with 0 additions and 577 deletions

View File

@ -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()

View File

@ -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)

View File

@ -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')

View File

@ -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()

View File

@ -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

View File

@ -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()

View File

@ -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

View File

@ -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())

View File

@ -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)

View File

@ -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())

View File

@ -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)