voicemeeter-api-python/voicemeeterlib/recorder.py

243 lines
6.3 KiB
Python
Raw Normal View History

import re
from . import kinds
2022-06-16 14:07:12 +01:00
from .error import VMError
from .iremote import IRemote
2023-06-23 03:39:07 +01:00
from .meta import action_fn, bool_prop
2022-06-16 14:07:12 +01:00
class Recorder(IRemote):
"""
Implements the common interface
Defines concrete implementation for recorder
"""
@classmethod
def make(cls, remote):
"""
Factory function for recorder.
Returns a Recorder class of a kind.
"""
2022-06-18 11:12:33 +01:00
CHANNELOUTMIXIN_cls = _make_channelout_mixins[remote.kind.name]
ARMCHANNELMIXIN_cls = _make_armchannel_mixins(remote)[remote.kind.name]
2022-06-16 14:07:12 +01:00
REC_cls = type(
2025-01-15 12:40:31 +00:00
f'Recorder{remote.kind}',
(cls, CHANNELOUTMIXIN_cls, ARMCHANNELMIXIN_cls),
2022-06-16 14:07:12 +01:00
{
**{
2023-06-23 03:39:07 +01:00
param: action_fn(param)
2022-06-16 14:07:12 +01:00
for param in [
2025-01-15 12:40:31 +00:00
'play',
'stop',
'pause',
'replay',
'record',
'ff',
'rew',
2022-06-16 14:07:12 +01:00
]
},
2025-01-15 12:40:31 +00:00
'mode': RecorderMode(remote),
2022-06-16 14:07:12 +01:00
},
)
return REC_cls(remote)
def __str__(self):
2025-01-15 12:40:31 +00:00
return f'{type(self).__name__}'
2022-06-16 14:07:12 +01:00
@property
def identifier(self) -> str:
2025-01-15 12:40:31 +00:00
return 'recorder'
2022-06-16 14:07:12 +01:00
@property
def samplerate(self) -> int:
2025-01-15 12:40:31 +00:00
return int(self.getter('samplerate'))
2022-06-16 14:07:12 +01:00
@samplerate.setter
def samplerate(self, val: int):
opts = (22050, 24000, 32000, 44100, 48000, 88200, 96000, 176400, 192000)
if val not in opts:
2025-01-15 12:40:31 +00:00
self.logger.warning(f'samplerate got: {val} but expected a value in {opts}')
self.setter('samplerate', val)
2022-06-16 14:07:12 +01:00
@property
def bitresolution(self) -> int:
2025-01-15 12:40:31 +00:00
return int(self.getter('bitresolution'))
@bitresolution.setter
def bitresolution(self, val: int):
opts = (8, 16, 24, 32)
if val not in opts:
self.logger.warning(
2025-01-15 12:40:31 +00:00
f'bitresolution got: {val} but expected a value in {opts}'
)
2025-01-15 12:40:31 +00:00
self.setter('bitresolution', val)
@property
def channel(self) -> int:
2025-01-15 12:40:31 +00:00
return int(self.getter('channel'))
@channel.setter
def channel(self, val: int):
if not 1 <= val <= 8:
2025-01-15 12:40:31 +00:00
self.logger.warning(f'channel got: {val} but expected a value from 1 to 8')
self.setter('channel', val)
@property
def kbps(self):
2025-01-15 12:40:31 +00:00
return int(self.getter('kbps'))
@kbps.setter
def kbps(self, val: int):
opts = (32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320)
if val not in opts:
2025-01-15 12:40:31 +00:00
self.logger.warning(f'kbps got: {val} but expected a value in {opts}')
self.setter('kbps', val)
@property
def gain(self) -> float:
2025-01-15 12:40:31 +00:00
return round(self.getter('gain'), 1)
@gain.setter
def gain(self, val: float):
2025-01-15 12:40:31 +00:00
self.setter('gain', val)
def load(self, file: str):
try:
2025-01-15 12:40:31 +00:00
self.setter('load', file)
except UnicodeError:
2025-01-15 12:40:31 +00:00
raise VMError('File full directory must be a raw string')
# loop forwarder methods, for backwards compatibility
@property
def loop(self):
return self.mode.loop
@loop.setter
def loop(self, val: bool):
self.mode.loop = val
def goto(self, time_str):
def get_sec():
"""Get seconds from time string"""
2025-01-15 12:40:31 +00:00
h, m, s = time_str.split(':')
return int(h) * 3600 + int(m) * 60 + int(s)
time_str = str(time_str) # coerce the type
if (
re.match(
2025-01-15 12:40:31 +00:00
r'^(?:[01]\d|2[0123]):(?:[012345]\d):(?:[012345]\d)$',
time_str,
)
is not None
):
2025-01-15 12:40:31 +00:00
self.setter('goto', get_sec())
else:
self.logger.warning(
"goto expects a string that matches the format 'hh:mm:ss'"
)
def filetype(self, val: str):
2025-01-15 12:40:31 +00:00
opts = {'wav': 1, 'aiff': 2, 'bwf': 3, 'mp3': 100}
try:
2025-01-15 12:40:31 +00:00
self.setter('filetype', opts[val.lower()])
except KeyError:
self.logger.warning(
2025-01-15 12:40:31 +00:00
f'filetype got: {val} but expected a value in {list(opts.keys())}'
)
class RecorderMode(IRemote):
@property
def identifier(self):
2025-01-15 12:40:31 +00:00
return 'recorder.mode'
@property
def recbus(self) -> bool:
2025-01-15 12:40:31 +00:00
return self.getter('recbus') == 1
@recbus.setter
def recbus(self, val: bool):
2025-01-15 12:40:31 +00:00
self.setter('recbus', 1 if val else 0)
@property
def playonload(self) -> bool:
2025-01-15 12:40:31 +00:00
return self.getter('playonload') == 1
@playonload.setter
def playonload(self, val: bool):
2025-01-15 12:40:31 +00:00
self.setter('playonload', 1 if val else 0)
@property
def loop(self) -> bool:
2025-01-15 12:40:31 +00:00
return self.getter('loop') == 1
@loop.setter
def loop(self, val: bool):
2025-01-15 12:40:31 +00:00
self.setter('loop', 1 if val else 0)
@property
def multitrack(self) -> bool:
2025-01-15 12:40:31 +00:00
return self.getter('multitrack') == 1
@multitrack.setter
def multitrack(self, val: bool):
2025-01-15 12:40:31 +00:00
self.setter('multitrack', 1 if val else 0)
class RecorderArmChannel(IRemote):
def __init__(self, remote, i):
super().__init__(remote)
self._i = i
def set(self, val: bool):
2025-01-15 12:40:31 +00:00
self.setter('', 1 if val else 0)
class RecorderArmStrip(RecorderArmChannel):
@property
def identifier(self):
2025-01-15 12:40:31 +00:00
return f'recorder.armstrip[{self._i}]'
class RecorderArmBus(RecorderArmChannel):
@property
def identifier(self):
2025-01-15 12:40:31 +00:00
return f'recorder.armbus[{self._i}]'
def _make_armchannel_mixin(remote, kind):
"""Creates an armchannel out mixin"""
return type(
2025-01-15 12:40:31 +00:00
f'ArmChannelMixin{kind}',
(),
{
2025-01-15 12:40:31 +00:00
'armstrip': tuple(
RecorderArmStrip(remote, i) for i in range(kind.num_strip)
),
2025-01-15 12:40:31 +00:00
'armbus': tuple(RecorderArmBus(remote, i) for i in range(kind.num_bus)),
},
)
def _make_armchannel_mixins(remote):
return {kind.name: _make_armchannel_mixin(remote, kind) for kind in kinds.all}
2022-06-16 14:07:12 +01:00
2022-06-18 11:12:33 +01:00
def _make_channelout_mixin(kind):
"""Creates a channel out mixin"""
2022-06-16 14:07:12 +01:00
return type(
2025-01-15 12:40:31 +00:00
f'ChannelOutMixin{kind}',
2022-06-16 14:07:12 +01:00
(),
{
2025-01-15 12:40:31 +00:00
**{f'A{i}': bool_prop(f'A{i}') for i in range(1, kind.phys_out + 1)},
**{f'B{i}': bool_prop(f'B{i}') for i in range(1, kind.virt_out + 1)},
2022-06-16 14:07:12 +01:00
},
)
2022-06-18 11:12:33 +01:00
_make_channelout_mixins = {
kind.name: _make_channelout_mixin(kind) for kind in kinds.all
2022-06-18 11:12:33 +01:00
}