Actorify mixers
This commit is contained in:
parent
3a1de6578d
commit
b88d8d5d8a
@ -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():
|
||||||
|
|||||||
@ -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
|
||||||
|
|
||||||
|
|||||||
@ -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.
|
||||||
|
|||||||
@ -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()
|
||||||
|
|||||||
@ -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):
|
||||||
|
|||||||
@ -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()
|
||||||
|
|||||||
@ -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)
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user