Work on sound positioning for objects. Enable in sounds in preferences.
This commit is contained in:
@@ -16,6 +16,7 @@ speech_stub.speak = mock.Mock()
|
||||
sys.modules.setdefault("cthulhu.speech", speech_stub)
|
||||
|
||||
from cthulhu import settings
|
||||
from cthulhu.sound_generator import SoundGenerator
|
||||
from cthulhu.sound_theme_manager import SoundThemeManager
|
||||
from cthulhu.speech_generator import SpeechGenerator
|
||||
from cthulhu.script_utilities import Utilities
|
||||
@@ -146,6 +147,74 @@ class LinkIndicatorPresentationRegressionTests(unittest.TestCase):
|
||||
self.assertEqual(spoken, "Docs link")
|
||||
self.assertEqual(icons, [icon])
|
||||
|
||||
def test_sound_pan_uses_object_position_inside_active_frame(self):
|
||||
utility = object.__new__(Utilities)
|
||||
obj = mock.Mock()
|
||||
frame = mock.Mock()
|
||||
icon = SimpleNamespace(path="/tmp/link.wav")
|
||||
|
||||
fakeApp = mock.Mock()
|
||||
fakeApp.settingsManager.getSetting.return_value = True
|
||||
|
||||
def get_rect(target):
|
||||
if target is frame:
|
||||
return SimpleNamespace(x=0, y=0, width=1000, height=800)
|
||||
if target is obj:
|
||||
return SimpleNamespace(x=495, y=0, width=10, height=20)
|
||||
return SimpleNamespace(x=0, y=0, width=0, height=0)
|
||||
|
||||
utility.frameAndDialog = mock.Mock(return_value=[frame, None])
|
||||
|
||||
with (
|
||||
mock.patch("cthulhu.script_utilities.cthulhu.cthulhuApp", fakeApp),
|
||||
mock.patch("cthulhu.script_utilities.AXComponent.get_rect", side_effect=get_rect),
|
||||
):
|
||||
utility.spatializeSoundIcon(icon, obj)
|
||||
|
||||
self.assertEqual(icon.pan, 0.0)
|
||||
|
||||
def test_sound_pan_hard_pans_only_at_extreme_edges(self):
|
||||
utility = object.__new__(Utilities)
|
||||
obj = mock.Mock()
|
||||
frame = mock.Mock()
|
||||
fakeApp = mock.Mock()
|
||||
fakeApp.settingsManager.getSetting.return_value = True
|
||||
|
||||
utility.frameAndDialog = mock.Mock(return_value=[frame, None])
|
||||
|
||||
with (
|
||||
mock.patch("cthulhu.script_utilities.cthulhu.cthulhuApp", fakeApp),
|
||||
mock.patch(
|
||||
"cthulhu.script_utilities.AXComponent.get_rect",
|
||||
side_effect=[
|
||||
SimpleNamespace(x=0, y=0, width=10, height=20),
|
||||
SimpleNamespace(x=0, y=0, width=1000, height=800),
|
||||
],
|
||||
),
|
||||
):
|
||||
self.assertEqual(utility.getSoundPanForObject(obj), -1.0)
|
||||
|
||||
|
||||
class SoundGeneratorSpatializationRegressionTests(unittest.TestCase):
|
||||
def test_generated_icons_and_tones_are_spatialized(self):
|
||||
generator = object.__new__(SoundGenerator)
|
||||
icon = SimpleNamespace(path="/tmp/link.wav")
|
||||
tone = SimpleNamespace(duration=0.1, frequency=440, volume=0.5, wave=0)
|
||||
generator._script = mock.Mock()
|
||||
generator._script.utilities.spatializeSoundIcon.side_effect = lambda sound, obj: sound
|
||||
|
||||
fakeApp = mock.Mock()
|
||||
fakeApp.settingsManager.getSetting.return_value = True
|
||||
obj = mock.Mock()
|
||||
|
||||
with mock.patch("cthulhu.sound_generator.cthulhu.cthulhuApp", fakeApp):
|
||||
result = generator._spatializeSounds([icon, [tone]], obj)
|
||||
|
||||
self.assertEqual(result, [icon, [tone]])
|
||||
generator._script.utilities.spatializeSoundIcon.assert_has_calls(
|
||||
[mock.call(icon, obj), mock.call(tone, obj)]
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
import sys
|
||||
import unittest
|
||||
import importlib
|
||||
from pathlib import Path
|
||||
from unittest import mock
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "src"))
|
||||
|
||||
from cthulhu import speech
|
||||
sys.modules.pop("cthulhu.speech", None)
|
||||
speech = importlib.import_module("cthulhu.speech")
|
||||
|
||||
|
||||
class PresentationInterruptSoundRegressionTests(unittest.TestCase):
|
||||
|
||||
@@ -20,36 +20,48 @@ class _FakePlaybin:
|
||||
class SoundHelperBackendTests(unittest.TestCase):
|
||||
def test_create_file_player_uses_playbin_default_sink_for_auto(self):
|
||||
fakePlaybin = _FakePlaybin()
|
||||
fakePanorama = object()
|
||||
|
||||
with (
|
||||
mock.patch.object(
|
||||
sound_helper.Gst.ElementFactory,
|
||||
"make",
|
||||
return_value=fakePlaybin,
|
||||
side_effect=[fakePlaybin, fakePanorama],
|
||||
) as makeElement,
|
||||
mock.patch.object(
|
||||
sound_helper.sound_sink,
|
||||
"create_audio_sink",
|
||||
) as createAudioSink,
|
||||
):
|
||||
player, sinkName, error = sound_helper._create_file_player("worker-file", settings.SOUND_SINK_AUTO)
|
||||
player, panorama, sinkName, error = sound_helper._create_file_player(
|
||||
"worker-file",
|
||||
settings.SOUND_SINK_AUTO,
|
||||
)
|
||||
|
||||
self.assertIs(player, fakePlaybin)
|
||||
self.assertIs(panorama, fakePanorama)
|
||||
self.assertEqual(sinkName, "playbin-default")
|
||||
self.assertIsNone(error)
|
||||
createAudioSink.assert_not_called()
|
||||
makeElement.assert_called_once_with("playbin", "worker-file")
|
||||
makeElement.assert_has_calls(
|
||||
[
|
||||
mock.call("playbin", "worker-file"),
|
||||
mock.call("audiopanorama", "worker-file-panorama"),
|
||||
]
|
||||
)
|
||||
self.assertNotIn("audio-sink", fakePlaybin.properties)
|
||||
self.assertIs(fakePlaybin.properties["audio-filter"], fakePanorama)
|
||||
|
||||
def test_create_file_player_sets_explicit_sink_when_requested(self):
|
||||
fakePlaybin = _FakePlaybin()
|
||||
fakePanorama = object()
|
||||
fakeSink = object()
|
||||
|
||||
with (
|
||||
mock.patch.object(
|
||||
sound_helper.Gst.ElementFactory,
|
||||
"make",
|
||||
return_value=fakePlaybin,
|
||||
side_effect=[fakePlaybin, fakePanorama],
|
||||
) as makeElement,
|
||||
mock.patch.object(
|
||||
sound_helper.sound_sink,
|
||||
@@ -57,14 +69,24 @@ class SoundHelperBackendTests(unittest.TestCase):
|
||||
return_value=(fakeSink, "pulsesink", None),
|
||||
) as createAudioSink,
|
||||
):
|
||||
player, sinkName, error = sound_helper._create_file_player("worker-file", settings.SOUND_SINK_PULSE)
|
||||
player, panorama, sinkName, error = sound_helper._create_file_player(
|
||||
"worker-file",
|
||||
settings.SOUND_SINK_PULSE,
|
||||
)
|
||||
|
||||
self.assertIs(player, fakePlaybin)
|
||||
self.assertIs(panorama, fakePanorama)
|
||||
self.assertEqual(sinkName, "pulsesink")
|
||||
self.assertIsNone(error)
|
||||
makeElement.assert_called_once_with("playbin", "worker-file")
|
||||
makeElement.assert_has_calls(
|
||||
[
|
||||
mock.call("playbin", "worker-file"),
|
||||
mock.call("audiopanorama", "worker-file-panorama"),
|
||||
]
|
||||
)
|
||||
createAudioSink.assert_called_once()
|
||||
self.assertIs(fakePlaybin.properties["audio-sink"], fakeSink)
|
||||
self.assertIs(fakePlaybin.properties["audio-filter"], fakePanorama)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -41,6 +41,7 @@ class SoundPreferencesBuilderTests(unittest.TestCase):
|
||||
self.assertIn("soundsTabLabel", objectIds)
|
||||
self.assertIn("enableSoundCheckButton", objectIds)
|
||||
self.assertIn("soundVolumeScale", objectIds)
|
||||
self.assertIn("spatializeObjectSoundsCheckButton", objectIds)
|
||||
self.assertIn("progressBarBeepIntervalSpinButton", objectIds)
|
||||
|
||||
def test_notebook_tab_positions_keep_sounds_page_and_label_aligned(self):
|
||||
@@ -90,6 +91,7 @@ class SoundPreferencesControllerTests(unittest.TestCase):
|
||||
"playSoundForState": True,
|
||||
"playSoundForPositionInSet": False,
|
||||
"playSoundForValue": False,
|
||||
"spatializeObjectSounds": True,
|
||||
}
|
||||
widgets = {
|
||||
"soundSinkCombo": mock.Mock(),
|
||||
@@ -102,6 +104,7 @@ class SoundPreferencesControllerTests(unittest.TestCase):
|
||||
"playSoundForStateCheckButton": mock.Mock(),
|
||||
"playSoundForPositionInSetCheckButton": mock.Mock(),
|
||||
"playSoundForValueCheckButton": mock.Mock(),
|
||||
"spatializeObjectSoundsCheckButton": mock.Mock(),
|
||||
"beepProgressBarUpdatesCheckButton": mock.Mock(),
|
||||
}
|
||||
gui.get_widget = widgets.__getitem__
|
||||
@@ -128,6 +131,7 @@ class SoundPreferencesControllerTests(unittest.TestCase):
|
||||
widgets["playSoundForStateCheckButton"].set_active.assert_called_once_with(True)
|
||||
widgets["playSoundForPositionInSetCheckButton"].set_active.assert_called_once_with(False)
|
||||
widgets["playSoundForValueCheckButton"].set_active.assert_called_once_with(False)
|
||||
widgets["spatializeObjectSoundsCheckButton"].set_active.assert_called_once_with(True)
|
||||
widgets["beepProgressBarUpdatesCheckButton"].set_active.assert_called_once_with(True)
|
||||
widgets["progressBarBeepIntervalSpinButton"].set_value.assert_called_once_with(0)
|
||||
|
||||
|
||||
@@ -127,6 +127,31 @@ class PlayerRecoveryTests(unittest.TestCase):
|
||||
|
||||
sendCommand.assert_called_once_with({"action": "stop"}, waitForResponse=False)
|
||||
|
||||
def test_icon_pan_is_sent_to_worker(self):
|
||||
player = sound.Player()
|
||||
icon = sound.Icon("/tmp", "link.wav")
|
||||
icon.pan = -0.5
|
||||
|
||||
with (
|
||||
mock.patch.object(icon, "isValid", return_value=True),
|
||||
mock.patch.object(player, "_sendWorkerCommand", return_value=(True, None)) as sendCommand,
|
||||
):
|
||||
player.play(icon)
|
||||
|
||||
command = sendCommand.call_args.args[0]
|
||||
self.assertEqual(command["pan"], -0.5)
|
||||
|
||||
def test_tone_pan_is_sent_to_worker(self):
|
||||
player = sound.Player()
|
||||
tone = sound.Tone(0.1, 440)
|
||||
tone.pan = 0.5
|
||||
|
||||
with mock.patch.object(player, "_sendWorkerCommand", return_value=(True, None)) as sendCommand:
|
||||
player.play(tone)
|
||||
|
||||
command = sendCommand.call_args.args[0]
|
||||
self.assertEqual(command["pan"], 0.5)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
Reference in New Issue
Block a user