Actorify mixers

This commit is contained in:
Stein Magnus Jodal 2011-03-07 22:03:11 +01:00
parent 3a1de6578d
commit b88d8d5d8a
8 changed files with 54 additions and 59 deletions

View File

@ -20,6 +20,7 @@ def main():
setup_settings() setup_settings()
setup_gobject_loop() setup_gobject_loop()
setup_output() setup_output()
setup_mixer()
setup_backend() setup_backend()
setup_frontends() setup_frontends()
@ -50,11 +51,13 @@ def setup_gobject_loop():
return gobject_loop return gobject_loop
def setup_output(): def setup_output():
output = get_class(settings.OUTPUT)() return get_class(settings.OUTPUT).start_proxy()
output.start()
return output def setup_mixer():
return get_class(settings.MIXER).start_proxy()
def setup_backend(): def setup_backend():
# XXX Convert backend to one or more actors?
return get_class(settings.BACKENDS[0])() return get_class(settings.BACKENDS[0])()
def setup_frontends(): def setup_frontends():

View File

@ -1,12 +1,14 @@
import alsaaudio import alsaaudio
import logging import logging
from pykka.actor import ThreadingActor
from mopidy import settings from mopidy import settings
from mopidy.mixers.base import BaseMixer from mopidy.mixers.base import BaseMixer
logger = logging.getLogger('mopidy.mixers.alsa') logger = logging.getLogger('mopidy.mixers.alsa')
class AlsaMixer(BaseMixer): class AlsaMixer(ThreadingActor, BaseMixer):
""" """
Mixer which uses the Advanced Linux Sound Architecture (ALSA) to control Mixer which uses the Advanced Linux Sound Architecture (ALSA) to control
volume. volume.
@ -20,8 +22,8 @@ class AlsaMixer(BaseMixer):
- :attr:`mopidy.settings.MIXER_ALSA_CONTROL` - :attr:`mopidy.settings.MIXER_ALSA_CONTROL`
""" """
def __init__(self, *args, **kwargs): def __init__(self):
super(AlsaMixer, self).__init__(*args, **kwargs) # XXX Do mixer detection after actor starts?
self._mixer = alsaaudio.Mixer(self._get_mixer_control()) self._mixer = alsaaudio.Mixer(self._get_mixer_control())
assert self._mixer is not None assert self._mixer is not None

View File

@ -2,17 +2,12 @@ from mopidy import settings
class BaseMixer(object): class BaseMixer(object):
""" """
:param backend: a backend instance
:type backend: :class:`mopidy.backends.base.Backend`
**Settings:** **Settings:**
- :attr:`mopidy.settings.MIXER_MAX_VOLUME` - :attr:`mopidy.settings.MIXER_MAX_VOLUME`
""" """
def __init__(self, backend, *args, **kwargs): amplification_factor = settings.MIXER_MAX_VOLUME / 100.0
self.backend = backend
self.amplification_factor = settings.MIXER_MAX_VOLUME / 100.0
@property @property
def volume(self): def volume(self):
@ -35,9 +30,6 @@ class BaseMixer(object):
volume = 100 volume = 100
self._set_volume(volume) self._set_volume(volume)
def destroy(self):
pass
def _get_volume(self): def _get_volume(self):
""" """
Return volume as integer in range [0, 100]. :class:`None` if unknown. Return volume as integer in range [0, 100]. :class:`None` if unknown.

View File

@ -1,12 +1,13 @@
import logging import logging
from threading import Lock
from pykka.actor import ThreadingActor
from mopidy import settings from mopidy import settings
from mopidy.mixers.base import BaseMixer from mopidy.mixers.base import BaseMixer
logger = logging.getLogger(u'mopidy.mixers.denon') logger = logging.getLogger(u'mopidy.mixers.denon')
class DenonMixer(BaseMixer): class DenonMixer(ThreadingActor, BaseMixer):
""" """
Mixer for controlling Denon amplifiers and receivers using the RS-232 Mixer for controlling Denon amplifiers and receivers using the RS-232
protocol. protocol.
@ -24,12 +25,12 @@ class DenonMixer(BaseMixer):
- :attr:`mopidy.settings.MIXER_EXT_PORT` -- Example: ``/dev/ttyUSB0`` - :attr:`mopidy.settings.MIXER_EXT_PORT` -- Example: ``/dev/ttyUSB0``
""" """
def __init__(self, *args, **kwargs): def __init__(self):
""" """
Connects using the serial specifications from Denon's RS-232 Protocol Connects using the serial specifications from Denon's RS-232 Protocol
specification: 9600bps 8N1. specification: 9600bps 8N1.
""" """
super(DenonMixer, self).__init__(*args, **kwargs) # XXX Do setup after actor starts?
device = kwargs.get('device', None) device = kwargs.get('device', None)
if device: if device:
self._device = device self._device = device
@ -38,14 +39,11 @@ class DenonMixer(BaseMixer):
self._device = Serial(port=settings.MIXER_EXT_PORT, timeout=0.2) self._device = Serial(port=settings.MIXER_EXT_PORT, timeout=0.2)
self._levels = ['99'] + ["%(#)02d" % {'#': v} for v in range(0, 99)] self._levels = ['99'] + ["%(#)02d" % {'#': v} for v in range(0, 99)]
self._volume = 0 self._volume = 0
self._lock = Lock()
def _get_volume(self): def _get_volume(self):
self._lock.acquire() self._ensure_open_device()
self.ensure_open_device()
self._device.write('MV?\r') self._device.write('MV?\r')
vol = str(self._device.readline()[2:4]) vol = str(self._device.readline()[2:4])
self._lock.release()
logger.debug(u'_get_volume() = %s' % vol) logger.debug(u'_get_volume() = %s' % vol)
return self._levels.index(vol) return self._levels.index(vol)
@ -53,14 +51,12 @@ class DenonMixer(BaseMixer):
# Clamp according to Denon-spec # Clamp according to Denon-spec
if volume > 99: if volume > 99:
volume = 99 volume = 99
self._lock.acquire() self._ensure_open_device()
self.ensure_open_device()
self._device.write('MV%s\r'% self._levels[volume]) self._device.write('MV%s\r'% self._levels[volume])
vol = self._device.readline()[2:4] vol = self._device.readline()[2:4]
self._lock.release()
self._volume = self._levels.index(vol) self._volume = self._levels.index(vol)
def ensure_open_device(self): def _ensure_open_device(self):
if not self._device.isOpen(): if not self._device.isOpen():
logger.debug(u'(re)connecting to Denon device') logger.debug(u'(re)connecting to Denon device')
self._device.open() self._device.open()

View File

@ -1,10 +1,11 @@
from pykka.actor import ThreadingActor
from mopidy.mixers.base import BaseMixer from mopidy.mixers.base import BaseMixer
class DummyMixer(BaseMixer): class DummyMixer(ThreadingActor, BaseMixer):
"""Mixer which just stores and reports the chosen volume.""" """Mixer which just stores and reports the chosen volume."""
def __init__(self, *args, **kwargs): def __init__(self):
super(DummyMixer, self).__init__(*args, **kwargs)
self._volume = None self._volume = None
def _get_volume(self): def _get_volume(self):

View File

@ -1,13 +1,19 @@
from pykka.actor import ThreadingActor
from pykka.proxy import ActorProxy
from pykka.registry import ActorRegistry
from mopidy.mixers.base import BaseMixer from mopidy.mixers.base import BaseMixer
class GStreamerSoftwareMixer(BaseMixer): class GStreamerSoftwareMixer(ThreadingActor, BaseMixer):
"""Mixer which uses GStreamer to control volume in software.""" """Mixer which uses GStreamer to control volume in software."""
def __init__(self, *args, **kwargs): def __init__(self):
super(GStreamerSoftwareMixer, self).__init__(*args, **kwargs) # XXX Get reference to output without hardcoding GStreamerOutput
output_refs = ActorRegistry.get_by_class_name('GStreamerOutput')
self.output = ActorProxy(output_refs[0])
def _get_volume(self): def _get_volume(self):
return self.backend.output.get_volume() return self.output.get_volume().get()
def _set_volume(self, volume): def _set_volume(self, volume):
self.backend.output.set_volume(volume) self.output.set_volume(volume).get()

View File

@ -2,13 +2,15 @@ import logging
from serial import Serial from serial import Serial
from multiprocessing import Pipe from multiprocessing import Pipe
from pykka.actor import ThreadingActor
from mopidy import settings from mopidy import settings
from mopidy.mixers.base import BaseMixer from mopidy.mixers.base import BaseMixer
from mopidy.utils.process import BaseThread from mopidy.utils.process import BaseThread
logger = logging.getLogger('mopidy.mixers.nad') logger = logging.getLogger('mopidy.mixers.nad')
class NadMixer(BaseMixer): class NadMixer(ThreadingActor, BaseMixer):
""" """
Mixer for controlling NAD amplifiers and receivers using the NAD RS-232 Mixer for controlling NAD amplifiers and receivers using the NAD RS-232
protocol. protocol.
@ -36,21 +38,19 @@ class NadMixer(BaseMixer):
""" """
def __init__(self, *args, **kwargs): def __init__(self):
super(NadMixer, self).__init__(*args, **kwargs) self._volume_cache = None
self._volume = None self._nad_talker = NadTalker.start_proxy()
self._pipe, other_end = Pipe()
NadTalker(self.backend.core_queue, pipe=other_end).start()
def _get_volume(self): def _get_volume(self):
return self._volume return self._volume_cache
def _set_volume(self, volume): def _set_volume(self, volume):
self._volume = volume self._volume_cache = volume
self._pipe.send({'command': 'set_volume', 'volume': volume}) self._nad_talker.set_volume(volume)
class NadTalker(BaseThread): class NadTalker(ThreadingActor):
""" """
Independent process which does the communication with the NAD device. Independent process which does the communication with the NAD device.
@ -72,21 +72,15 @@ class NadTalker(BaseThread):
# Volume in range 0..VOLUME_LEVELS. :class:`None` before calibration. # Volume in range 0..VOLUME_LEVELS. :class:`None` before calibration.
_nad_volume = None _nad_volume = None
def __init__(self, core_queue, pipe=None): def __init__(self):
super(NadTalker, self).__init__(core_queue)
self.name = u'NadTalker'
self.pipe = pipe
self._device = None self._device = None
def run_inside_try(self): # XXX Do after actor starts?
self._setup()
def _setup(self):
self._open_connection() self._open_connection()
self._set_device_to_known_state() self._set_device_to_known_state()
while self.pipe.poll(None):
message = self.pipe.recv()
if message['command'] == 'set_volume':
self._set_volume(message['volume'])
elif message['command'] == 'reset_device':
self._set_device_to_known_state()
def _open_connection(self): def _open_connection(self):
# Opens serial connection to the device. # Opens serial connection to the device.
@ -164,7 +158,7 @@ class NadTalker(BaseThread):
self._nad_volume = 0 self._nad_volume = 0
logger.info(u'Done calibrating NAD amplifier') logger.info(u'Done calibrating NAD amplifier')
def _set_volume(self, volume): def set_volume(self, volume):
# Increase or decrease the amplifier volume until it matches the given # Increase or decrease the amplifier volume until it matches the given
# target volume. # target volume.
logger.debug(u'Setting volume to %d' % volume) logger.debug(u'Setting volume to %d' % volume)

View File

@ -1,9 +1,11 @@
from subprocess import Popen, PIPE from subprocess import Popen, PIPE
import time import time
from pykka.actor import ThreadingActor
from mopidy.mixers.base import BaseMixer from mopidy.mixers.base import BaseMixer
class OsaMixer(BaseMixer): class OsaMixer(ThreadingActor, BaseMixer):
""" """
Mixer which uses ``osascript`` on OS X to control volume. Mixer which uses ``osascript`` on OS X to control volume.
@ -14,7 +16,6 @@ class OsaMixer(BaseMixer):
**Settings:** **Settings:**
- None - None
""" """
CACHE_TTL = 30 CACHE_TTL = 30