feat: add Robot (ring modulation) and Dźwięk z tostera (8-bit bitcrusher) player audio effects
This commit is contained in:
7 files changed
+137
No files matched your search
@@ -9,6 +9,8 @@ from zyrarbot.audio.effects.registry import (
|
||||
create_default_effects,
|
||||
get_available_effects_metadata,
|
||||
)
|
||||
from zyrarbot.audio.effects.robot import RobotEffect
|
||||
from zyrarbot.audio.effects.toaster import ToasterEffect
|
||||
from zyrarbot.audio.effects.tremolo import TremoloEffect
|
||||
|
||||
__all__ = [
|
||||
@@ -17,6 +19,8 @@ __all__ = [
|
||||
"DistortionEffect",
|
||||
"FourthDensityEffect",
|
||||
"MuffledEffect",
|
||||
"RobotEffect",
|
||||
"ToasterEffect",
|
||||
"TremoloEffect",
|
||||
"create_default_effects",
|
||||
"get_available_effects_metadata",
|
||||
|
||||
@@ -6,6 +6,8 @@ from zyrarbot.audio.effects.base import AudioEffect
|
||||
from zyrarbot.audio.effects.distortion import DistortionEffect
|
||||
from zyrarbot.audio.effects.fourth_density import FourthDensityEffect
|
||||
from zyrarbot.audio.effects.muffled import MuffledEffect
|
||||
from zyrarbot.audio.effects.robot import RobotEffect
|
||||
from zyrarbot.audio.effects.toaster import ToasterEffect
|
||||
from zyrarbot.audio.effects.tremolo import TremoloEffect
|
||||
|
||||
|
||||
@@ -14,6 +16,8 @@ def create_default_effects() -> list[AudioEffect]:
|
||||
return [
|
||||
MuffledEffect(),
|
||||
FourthDensityEffect(),
|
||||
RobotEffect(),
|
||||
ToasterEffect(),
|
||||
DistortionEffect(),
|
||||
TremoloEffect(),
|
||||
]
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import array
|
||||
import math
|
||||
|
||||
from zyrarbot.audio.effects.base import AudioEffect
|
||||
|
||||
|
||||
class RobotEffect(AudioEffect):
|
||||
"""Efekt metalicznego, robotycznego głosu oparty na ring modulation (modulacja pierścieniowa ~65Hz)."""
|
||||
|
||||
id = "robot"
|
||||
name = "Robot"
|
||||
description = "Metaliczny, robotyczny głos cyborga (ring modulation ~65Hz)"
|
||||
badge = "CYBORG"
|
||||
icon = "bot"
|
||||
|
||||
def __init__(self, freq_hz: float = 65.0, mix: float = 0.85) -> None:
|
||||
self.freq_hz = freq_hz
|
||||
self.mix = mix
|
||||
self._phase = 0.0
|
||||
self._phase_inc = 2.0 * math.pi * freq_hz / 48000.0
|
||||
|
||||
def process(self, pcm_data: bytes, sample_rate: int = 48000) -> bytes:
|
||||
if not pcm_data:
|
||||
return pcm_data
|
||||
|
||||
samples = array.array("h", pcm_data)
|
||||
phase = self._phase
|
||||
phase_inc = self._phase_inc
|
||||
mix = self.mix
|
||||
dry = 1.0 - mix
|
||||
|
||||
for i in range(0, len(samples), 2):
|
||||
carrier = math.sin(phase)
|
||||
phase += phase_inc
|
||||
if phase >= 2.0 * math.pi:
|
||||
phase -= 2.0 * math.pi
|
||||
|
||||
mod_factor = dry + mix * carrier
|
||||
samples[i] = max(-32768, min(32767, int(samples[i] * mod_factor)))
|
||||
samples[i + 1] = max(-32768, min(32767, int(samples[i + 1] * mod_factor)))
|
||||
|
||||
self._phase = phase
|
||||
return samples.tobytes()
|
||||
|
||||
def reset(self) -> None:
|
||||
self._phase = 0.0
|
||||
@@ -0,0 +1,56 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import array
|
||||
|
||||
from zyrarbot.audio.effects.base import AudioEffect
|
||||
|
||||
|
||||
class ToasterEffect(AudioEffect):
|
||||
"""Efekt 'dźwięku z tostera / mikrofalówki' – memiczny lo-fi bitcrusher (5-bit quant + downsampling)."""
|
||||
|
||||
id = "toaster"
|
||||
name = "Dźwięk z tostera"
|
||||
description = "Memiczny dźwięk z mikrofalówki / kalkulatora (bitcrusher 5-bit, aliasing)"
|
||||
badge = "8-BIT"
|
||||
icon = "radio"
|
||||
|
||||
def __init__(self, bit_depth: int = 5, downsample: int = 6) -> None:
|
||||
self.bit_depth = bit_depth
|
||||
self.downsample = downsample
|
||||
self.shift = 16 - bit_depth
|
||||
self._counter = 0
|
||||
self._hold_l = 0
|
||||
self._hold_r = 0
|
||||
|
||||
def process(self, pcm_data: bytes, sample_rate: int = 48000) -> bytes:
|
||||
if not pcm_data:
|
||||
return pcm_data
|
||||
|
||||
samples = array.array("h", pcm_data)
|
||||
shift = self.shift
|
||||
down = self.downsample
|
||||
hold_l = self._hold_l
|
||||
hold_r = self._hold_r
|
||||
cnt = self._counter
|
||||
|
||||
for i in range(0, len(samples), 2):
|
||||
if cnt == 0:
|
||||
# Obniżenie rozdzielczości bitowej (bitcrush / kwantyzacja do 5 bitów)
|
||||
sl = samples[i]
|
||||
sr = samples[i + 1]
|
||||
hold_l = (sl >> shift) << shift
|
||||
hold_r = (sr >> shift) << shift
|
||||
|
||||
samples[i] = hold_l
|
||||
samples[i + 1] = hold_r
|
||||
cnt = (cnt + 1) % down
|
||||
|
||||
self._counter = cnt
|
||||
self._hold_l = hold_l
|
||||
self._hold_r = hold_r
|
||||
return samples.tobytes()
|
||||
|
||||
def reset(self) -> None:
|
||||
self._counter = 0
|
||||
self._hold_l = 0
|
||||
self._hold_r = 0
|
||||
@@ -117,6 +117,8 @@ class ControlCog(MusicCogBase, name="Control"):
|
||||
nazwa=[
|
||||
app_commands.Choice(name="Przester (1000%)", value="distortion"),
|
||||
app_commands.Choice(name="Czwarta gęstość (~1s pitch)", value="fourth_density"),
|
||||
app_commands.Choice(name="Robot (cyborg voice)", value="robot"),
|
||||
app_commands.Choice(name="Dźwięk z tostera (8-bit lo-fi)", value="toaster"),
|
||||
app_commands.Choice(name="Pod wodą (lowpass)", value="muffled"),
|
||||
app_commands.Choice(name="Tremolo (pulsowanie)", value="tremolo"),
|
||||
]
|
||||
|
||||
Reference in new issue
Block a user