diff --git a/emmental/audio/filter.py b/emmental/audio/filter.py new file mode 100644 index 0000000..a959fef --- /dev/null +++ b/emmental/audio/filter.py @@ -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 diff --git a/tests/audio/test_filter.py b/tests/audio/test_filter.py new file mode 100644 index 0000000..734abc3 --- /dev/null +++ b/tests/audio/test_filter.py @@ -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")