from dataclasses import dataclass
from .exceptions import FiltersError
import typing as t
from inspect import signature
# https://stackoverflow.com/questions/55099243/python3-dataclass-with-kwargsasterisk
class BaseObject:
@classmethod
def from_kwargs(cls, **kwargs):
# fetch the constructor's signature
cls_fields = {field for field in signature(cls).parameters}
# split the kwargs into native ones and new ones
native_args, new_args = {}, {}
for name, val in kwargs.items():
if name in cls_fields:
native_args[name] = val
else:
new_args[name] = val
# use the native ones to create the class ...
ret = cls(**native_args)
# ... and add the new ones by hand
for new_name, new_val in new_args.items():
setattr(ret, new_name, new_val)
return ret
[docs]@dataclass
class Memory(BaseObject):
free: int
used: int
allocated: int
reservable: int
[docs]@dataclass
class Cpu(BaseObject):
cores: int
systemLoad: float
lavalinkLoad: float
[docs]@dataclass
class FrameStats(BaseObject):
sent: int
nulled: int
deficit: int
[docs]@dataclass
class Stats(BaseObject):
"""
Info websocket for connection
"""
players: int
playingPlayers: int
uptime: int
memory: Memory
cpu: Cpu
frameStats: t.Optional[FrameStats] = None
[docs]@dataclass(repr=True)
class Track(BaseObject):
"""
Info track object.
"""
encoded: str
identifier: str
is_seekable: bool
author: str
length: int
is_stream: bool
position: int
title: str
uri: str
artworkUrl: str
isrc: str
plugin_info: str
load_type: str
requester: t.Union[str, None] = None
source_name: t.Optional[str] = None
timestamp: t.Optional[t.Any] = None
"""
optional option to save a requester for the track
"""
def __repr__(self) -> str:
return self.title
[docs]@dataclass
class ConnectionInfo(BaseObject):
"""
A info for Connection just use to save the connection information.
"""
guild_id: int
session_id: str
channel_id: t.Optional[int]
[docs]@dataclass
class VoiceInfo(BaseObject):
"""
Info of a voice.
"""
token: str
endpoint: str
[docs]@dataclass
class PlayList(BaseObject):
name: str
selected_track: int
tracks: t.List[Track]
[docs]@dataclass
class PlayerState(BaseObject):
"""
Event on player update.
"""
time: int
connected: bool
ping: int
position: t.Optional[int] = None
[docs]@dataclass
class Version(BaseObject):
"""
Version info
"""
semver: str
major: int
minor: int
patch: int
preRelease: str = None
build: str = None
[docs]@dataclass
class Git(BaseObject):
"""
Git info
"""
branch: str
commit: str
commitTime: int
[docs]@dataclass
class Plugin(BaseObject):
"""
Plugin info
"""
name: str
version: str
[docs]@dataclass
class Info(BaseObject):
"""
"""
version: Version
buildTime: int
git: Git
jvm: str
lavaplayer: str
sourceManagers: t.List[str]
filters: t.List[str]
plugins: t.List[Plugin]
[docs]@dataclass(init=True)
class Filters:
"""
All the filters are optional, and leaving them out of this message will disable them.
Adding a filter can have adverse effects on performance. These filters force Lavaplayer to decode all audio to PCM, even if the input was already in the Opus format that Discord uses. This means decoding and encoding audio that would normally require very little processing. This is often the case with YouTube videos.
Parameters
---------
volume: :class:`int` | :class:`float`
Float value where 1.0 is 100%. Values >1.0 may cause clipping
"""
def __init__(self, volume: t.Union[int, float] = 1.0) -> None:
self._payload: dict = {"op": "filters", "volume": volume}
[docs] def equalizer(self, bands: t.List[t.Tuple[int, t.Union[float, int]]]):
"""
There are 15 bands (0-14) that can be changed.
"gain" is the multiplier for the given band. The default value is 0. Valid values range from -0.25 to 1.0,
where -0.25 means the given band is completely muted, and 0.25 means it is doubled. Modifying the gain could
also change the volume of the output.
"""
update_list = []
for v in bands:
if not -1 < v[0] < 15:
raise FiltersError("Invalid band, must be 0-14")
gain = max(min(float(v[1]), 1.0), -0.25)
band = v[1]
update_list.append({'band': band, 'gain': gain})
self._payload["equalizer"] = update_list
[docs] def karaoke(self, level: t.Union[int, float], mono_level: t.Union[int, float], filter_band: t.Union[int, float], filter_width: t.Union[int, float]):
"""
Uses equalization to eliminate part of a band, usually targeting vocals.
"""
self._payload["karaoke"] = {"level": level, "monoLevel": mono_level, "filterBand": filter_band, "filterWidth": filter_width}
[docs] def timescale(self, speed: t.Union[int, float], pitch: t.Union[int, float], rate: t.Union[int, float]):
"""
Changes the speed, pitch, and rate. All default to 1.
"""
self._payload["timescale"] = {"speed": speed, "pitch": pitch, "rate": rate}
[docs] def tremolo(self, frequency: t.Union[int, float], depth: t.Union[int, float]):
"""
Uses amplification to create a shuddering effect, where the volume quickly oscillates.
Example: https://en.wikipedia.org/wiki/File:Fuse_Electronics_Tremolo_MK-III_Quick_Demo.ogv
"""
self._payload["tremolo"] = {"frequency": frequency, "depth": depth}
[docs] def vibrato(self, frequency: t.Union[int, float], depth: t.Union[int, float]):
"""
Similar to tremolo. While tremolo oscillates the volume, vibrato oscillates the pitch.
"""
self._payload["vibrato"] = {"frequency": frequency, "depth": depth}
[docs] def rotation(self, rotation_hz: t.Union[int, float]):
"""
Rotates the sound around the stereo channels/user headphones aka Audio Panning.
It can produce an effect similar to: https://youtu.be/QB9EB8mTKcc (without the reverb)
"""
self._payload["rotation"] = {"rotationHz": rotation_hz}
[docs] def distortion(self, sin_offset: t.Union[int, float], sin_scale: t.Union[int, float], cos_offset: t.Union[int, float], cos_scale: t.Union[int, float], tan_offset: t.Union[int, float], tan_scale: t.Union[int, float], offset: t.Union[int, float], scale: t.Union[int, float]):
"""
Distortion effect. It can generate some pretty unique audio effects.
"""
self._payload["distortion"] = {"sinOffset": sin_offset, "sinScale": sin_scale, "cosOffset": cos_offset, "cosScale": cos_scale, "tanOffset": tan_offset, "tanScale": tan_scale, "offset": offset, "scale": scale}
[docs] def channel_mix(self, left_to_left: t.Union[int, float], left_to_right: t.Union[int, float], right_to_left: t.Union[int, float], right_to_right: t.Union[int, float]):
"""
Mixes both channels (left and right), with a configurable factor on how much each channel affects the other.
With the defaults, both channels are kept independent from each other.
Setting all factors to 0.5 means both channels get the same audio.
"""
self._payload["channelMix"] = {"leftToLeft": left_to_left, "leftToRight": left_to_right, "rightToLeft": right_to_left, "rightToRight": right_to_right}
[docs] def low_pass(self, smoothing: t.Union[int, float]):
"""
Higher frequencies get suppressed, while lower frequencies pass through this filter, thus the name low pass.
"""
self._payload["lowPass"] = {"smoothing": smoothing}
[docs] def plugin_filters(self, map: dict):
"""
Filter plugin configurations
"""
self._payload["pluginFilters"] = map