audio: Create a new audio filter for ReplayGain and Background Mode

This links together our ReplayGain filter with a volume element that is
set to the user configured background volume when background listening
mode is enabled, and 100% when background listening mode is disabled.

Implements: #50 ("Background Music Mode")
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
This commit is contained in:
Anna Schumaker 2023-05-28 16:32:33 -04:00
parent 7155fa9db5
commit 84fbd94aa1
2 changed files with 117 additions and 0 deletions

45
emmental/audio/filter.py Normal file
View File

@ -0,0 +1,45 @@
# Copyright 2023 (c) Anna Schumaker.
"""A custom Gst.Bin with our audio filter effects."""
from gi.repository import GObject
from gi.repository import Gst
from . import replaygain
class Filter(Gst.Bin):
"""The audio filter element."""
bg_enabled = GObject.Property(type=bool, default=False)
bg_volume = GObject.Property(type=float, default=0.5)
rg_mode = GObject.Property(type=str, default="disabled")
def __init__(self):
"""Initialize the audio filter."""
super().__init__()
self._replaygain = replaygain.Filter()
self._volume = Gst.ElementFactory.make("volume")
self.add(self._replaygain)
self.add(self._volume)
rg_pad = self._replaygain.get_static_pad("src")
rg_pad.link(self._volume.get_static_pad("sink"))
self.__add_ghost_pad("sink", self._replaygain)
self.__add_ghost_pad("src", self._volume)
self.connect("notify", self.__notify)
def __add_ghost_pad(self, pad: str, elm: Gst.Element) -> None:
self.add_pad(Gst.GhostPad.new(pad, elm.get_static_pad(pad)))
def __notify(self, filter: Gst.Bin, param: GObject.ParamSpec) -> None:
match param.name:
case "bg-enabled" | "bg-volume":
vol = self.bg_volume if self.bg_enabled else 1.0
if vol != self._volume.get_property("volume"):
vs = f"{round(vol * 100)}%" if self.bg_enabled else "off"
print(f"audio: setting background listening to {vs}")
self._volume.set_property("volume", vol)
case "rg-mode":
if self.rg_mode != self._replaygain.mode:
self._replaygain.mode = self.rg_mode

View File

@ -0,0 +1,72 @@
# Copyright 2023 (c) Anna Schumaker.
"""Tests our combined Filter element."""
import io
import unittest
import emmental.audio.filter
from gi.repository import Gst
class TestFilter(unittest.TestCase):
"""Tests our custom Filter element."""
def setUp(self):
"""Set up common variables."""
self.filter = emmental.audio.filter.Filter()
def test_init(self):
"""Test that the filter is set up correctly."""
self.assertIsInstance(self.filter, Gst.Bin)
self.assertIsInstance(self.filter.get_static_pad("src"), Gst.GhostPad)
self.assertIsInstance(self.filter.get_static_pad("sink"), Gst.GhostPad)
@unittest.mock.patch("sys.stdout", new_callable=io.StringIO)
def test_replaygain(self, mock_stdout: io.StringIO):
"""Test the ReplayGain element in the filter."""
self.assertIsInstance(self.filter._replaygain,
emmental.audio.replaygain.Filter)
self.assertEqual(self.filter.rg_mode, "disabled")
self.filter.rg_mode = "track"
self.assertEqual(self.filter._replaygain.mode, "track")
self.filter.rg_mode = "track"
self.assertEqual(mock_stdout.getvalue(),
"audio: setting ReplayGain mode to 'track'\n")
@unittest.mock.patch("sys.stdout", new_callable=io.StringIO)
def test_volume(self, mock_stdout: io.StringIO):
"""Test the Volume element in the filter."""
self.assertIsInstance(self.filter._volume, Gst.Element)
self.assertRegex(self.filter._volume.name, r"volume\d+")
self.assertEqual(self.filter._volume.get_property("volume"), 1.0)
self.assertFalse(self.filter.bg_enabled)
self.assertEqual(self.filter.bg_volume, 0.5)
self.filter.bg_enabled = True
self.assertEqual(self.filter._volume.get_property("volume"), 0.5)
self.assertEqual(mock_stdout.getvalue(),
"audio: setting background listening to 50%\n")
self.filter.bg_volume = 0.50
self.assertEqual(mock_stdout.getvalue(),
"audio: setting background listening to 50%\n")
self.filter.bg_volume = 0.75
self.assertEqual(self.filter._volume.get_property("volume"), 0.75)
self.assertEqual(mock_stdout.getvalue(),
"audio: setting background listening to 50%\n"
"audio: setting background listening to 75%\n")
self.filter.bg_enabled = False
self.assertEqual(self.filter._volume.get_property("volume"), 1.0)
self.assertEqual(mock_stdout.getvalue(),
"audio: setting background listening to 50%\n"
"audio: setting background listening to 75%\n"
"audio: setting background listening to off\n")
self.filter.bg_volume = 0.5
self.assertEqual(self.filter._volume.get_property("volume"), 1.0)
self.assertEqual(mock_stdout.getvalue(),
"audio: setting background listening to 50%\n"
"audio: setting background listening to 75%\n"
"audio: setting background listening to off\n")