Actorify mixers
This commit is contained in:
parent
3a1de6578d
commit
b88d8d5d8a
@ -20,6 +20,7 @@ def main():
|
||||
setup_settings()
|
||||
setup_gobject_loop()
|
||||
setup_output()
|
||||
setup_mixer()
|
||||
setup_backend()
|
||||
setup_frontends()
|
||||
|
||||
@ -50,11 +51,13 @@ def setup_gobject_loop():
|
||||
return gobject_loop
|
||||
|
||||
def setup_output():
|
||||
output = get_class(settings.OUTPUT)()
|
||||
output.start()
|
||||
return output
|
||||
return get_class(settings.OUTPUT).start_proxy()
|
||||
|
||||
def setup_mixer():
|
||||
return get_class(settings.MIXER).start_proxy()
|
||||
|
||||
def setup_backend():
|
||||
# XXX Convert backend to one or more actors?
|
||||
return get_class(settings.BACKENDS[0])()
|
||||
|
||||
def setup_frontends():
|
||||
|
||||
@ -1,12 +1,14 @@
|
||||
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(BaseMixer):
|
||||
class AlsaMixer(ThreadingActor, BaseMixer):
|
||||
"""
|
||||
Mixer which uses the Advanced Linux Sound Architecture (ALSA) to control
|
||||
volume.
|
||||
@ -20,8 +22,8 @@ class AlsaMixer(BaseMixer):
|
||||
- :attr:`mopidy.settings.MIXER_ALSA_CONTROL`
|
||||
"""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(AlsaMixer, self).__init__(*args, **kwargs)
|
||||
def __init__(self):
|
||||
# XXX Do mixer detection after actor starts?
|
||||
self._mixer = alsaaudio.Mixer(self._get_mixer_control())
|
||||
assert self._mixer is not None
|
||||
|
||||
|
||||
@ -2,17 +2,12 @@ from mopidy import settings
|
||||
|
||||
class BaseMixer(object):
|
||||
"""
|
||||
:param backend: a backend instance
|
||||
:type backend: :class:`mopidy.backends.base.Backend`
|
||||
|
||||
**Settings:**
|
||||
|
||||
- :attr:`mopidy.settings.MIXER_MAX_VOLUME`
|
||||
"""
|
||||
|
||||
def __init__(self, backend, *args, **kwargs):
|
||||
self.backend = backend
|
||||
self.amplification_factor = settings.MIXER_MAX_VOLUME / 100.0
|
||||
amplification_factor = settings.MIXER_MAX_VOLUME / 100.0
|
||||
|
||||
@property
|
||||
def volume(self):
|
||||
@ -35,9 +30,6 @@ class BaseMixer(object):
|
||||
volume = 100
|
||||
self._set_volume(volume)
|
||||
|
||||
def destroy(self):
|
||||
pass
|
||||
|
||||
def _get_volume(self):
|
||||
"""
|
||||
Return volume as integer in range [0, 100]. :class:`None` if unknown.
|
||||
|
||||
@ -1,12 +1,13 @@
|
||||
import logging
|
||||
from threading import Lock
|
||||
|
||||
from pykka.actor import ThreadingActor
|
||||
|
||||
from mopidy import settings
|
||||
from mopidy.mixers.base import BaseMixer
|
||||
|
||||
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
|
||||
protocol.
|
||||
@ -24,12 +25,12 @@ class DenonMixer(BaseMixer):
|
||||
- :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
|
||||
specification: 9600bps 8N1.
|
||||
"""
|
||||
super(DenonMixer, self).__init__(*args, **kwargs)
|
||||
# XXX Do setup after actor starts?
|
||||
device = kwargs.get('device', None)
|
||||
if device:
|
||||
self._device = device
|
||||
@ -38,14 +39,11 @@ class DenonMixer(BaseMixer):
|
||||
self._device = Serial(port=settings.MIXER_EXT_PORT, timeout=0.2)
|
||||
self._levels = ['99'] + ["%(#)02d" % {'#': v} for v in range(0, 99)]
|
||||
self._volume = 0
|
||||
self._lock = Lock()
|
||||
|
||||
def _get_volume(self):
|
||||
self._lock.acquire()
|
||||
self.ensure_open_device()
|
||||
self._ensure_open_device()
|
||||
self._device.write('MV?\r')
|
||||
vol = str(self._device.readline()[2:4])
|
||||
self._lock.release()
|
||||
logger.debug(u'_get_volume() = %s' % vol)
|
||||
return self._levels.index(vol)
|
||||
|
||||
@ -53,14 +51,12 @@ class DenonMixer(BaseMixer):
|
||||
# Clamp according to Denon-spec
|
||||
if volume > 99:
|
||||
volume = 99
|
||||
self._lock.acquire()
|
||||
self.ensure_open_device()
|
||||
self._ensure_open_device()
|
||||
self._device.write('MV%s\r'% self._levels[volume])
|
||||
vol = self._device.readline()[2:4]
|
||||
self._lock.release()
|
||||
self._volume = self._levels.index(vol)
|
||||
|
||||
def ensure_open_device(self):
|
||||
def _ensure_open_device(self):
|
||||
if not self._device.isOpen():
|
||||
logger.debug(u'(re)connecting to Denon device')
|
||||
self._device.open()
|
||||
|
||||
@ -1,10 +1,11 @@
|
||||
from pykka.actor import ThreadingActor
|
||||
|
||||
from mopidy.mixers.base import BaseMixer
|
||||
|
||||
class DummyMixer(BaseMixer):
|
||||
class DummyMixer(ThreadingActor, BaseMixer):
|
||||
"""Mixer which just stores and reports the chosen volume."""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(DummyMixer, self).__init__(*args, **kwargs)
|
||||
def __init__(self):
|
||||
self._volume = None
|
||||
|
||||
def _get_volume(self):
|
||||
|
||||
@ -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
|
||||
|
||||
class GStreamerSoftwareMixer(BaseMixer):
|
||||
class GStreamerSoftwareMixer(ThreadingActor, BaseMixer):
|
||||
"""Mixer which uses GStreamer to control volume in software."""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(GStreamerSoftwareMixer, self).__init__(*args, **kwargs)
|
||||
def __init__(self):
|
||||
# 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):
|
||||
return self.backend.output.get_volume()
|
||||
return self.output.get_volume().get()
|
||||
|
||||
def _set_volume(self, volume):
|
||||
self.backend.output.set_volume(volume)
|
||||
self.output.set_volume(volume).get()
|
||||
|
||||
@ -2,13 +2,15 @@ import logging
|
||||
from serial import Serial
|
||||
from multiprocessing import Pipe
|
||||
|
||||
from pykka.actor import ThreadingActor
|
||||
|
||||
from mopidy import settings
|
||||
from mopidy.mixers.base import BaseMixer
|
||||
from mopidy.utils.process import BaseThread
|
||||
|
||||
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
|
||||
protocol.
|
||||
@ -36,21 +38,19 @@ class NadMixer(BaseMixer):
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(NadMixer, self).__init__(*args, **kwargs)
|
||||
self._volume = None
|
||||
self._pipe, other_end = Pipe()
|
||||
NadTalker(self.backend.core_queue, pipe=other_end).start()
|
||||
def __init__(self):
|
||||
self._volume_cache = None
|
||||
self._nad_talker = NadTalker.start_proxy()
|
||||
|
||||
def _get_volume(self):
|
||||
return self._volume
|
||||
return self._volume_cache
|
||||
|
||||
def _set_volume(self, volume):
|
||||
self._volume = volume
|
||||
self._pipe.send({'command': 'set_volume', 'volume': volume})
|
||||
self._volume_cache = volume
|
||||
self._nad_talker.set_volume(volume)
|
||||
|
||||
|
||||
class NadTalker(BaseThread):
|
||||
class NadTalker(ThreadingActor):
|
||||
"""
|
||||
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.
|
||||
_nad_volume = None
|
||||
|
||||
def __init__(self, core_queue, pipe=None):
|
||||
super(NadTalker, self).__init__(core_queue)
|
||||
self.name = u'NadTalker'
|
||||
self.pipe = pipe
|
||||
def __init__(self):
|
||||
self._device = None
|
||||
|
||||
def run_inside_try(self):
|
||||
# XXX Do after actor starts?
|
||||
self._setup()
|
||||
|
||||
def _setup(self):
|
||||
self._open_connection()
|
||||
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):
|
||||
# Opens serial connection to the device.
|
||||
@ -164,7 +158,7 @@ class NadTalker(BaseThread):
|
||||
self._nad_volume = 0
|
||||
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
|
||||
# target volume.
|
||||
logger.debug(u'Setting volume to %d' % volume)
|
||||
|
||||
@ -1,9 +1,11 @@
|
||||
from subprocess import Popen, PIPE
|
||||
import time
|
||||
|
||||
from pykka.actor import ThreadingActor
|
||||
|
||||
from mopidy.mixers.base import BaseMixer
|
||||
|
||||
class OsaMixer(BaseMixer):
|
||||
class OsaMixer(ThreadingActor, BaseMixer):
|
||||
"""
|
||||
Mixer which uses ``osascript`` on OS X to control volume.
|
||||
|
||||
@ -14,7 +16,6 @@ class OsaMixer(BaseMixer):
|
||||
**Settings:**
|
||||
|
||||
- None
|
||||
|
||||
"""
|
||||
|
||||
CACHE_TTL = 30
|
||||
|
||||
Loading…
Reference in New Issue
Block a user