Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remove audio player thread to fix crash on exit #681

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion requirements.build.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ pyserial==3.5
sliplib==0.6.2
bitarray==2.9.2
simpleaudio==1.0.4
wave==0.0.2
numpy==1.26.4
charset-normalizer<3.0.0
requests==2.31.0
62 changes: 16 additions & 46 deletions src/main/python/main/ayab/audio.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,73 +20,43 @@
"""Standalone audio player."""

from __future__ import annotations
import logging
from os import path

import simpleaudio as sa
import wave

from PySide6.QtCore import QObject, QThread
from typing import TYPE_CHECKING, Optional
from typing import TYPE_CHECKING

if TYPE_CHECKING:
from .ayab import GuiMain


class AudioWorker(QObject):
def __init__(self, parent: GuiMain):
super().__init__()
self.__dir = parent.app_context.get_resource("assets")
self.__prefs = parent.prefs
class AudioPlayer:
def __init__(self, gui: GuiMain):
self.__dir = gui.app_context.get_resource("assets")
self.__prefs = gui.prefs
self.__cache: dict[str, sa.WaveObject] = {}

def play(self, sound: str, blocking: bool = False) -> None:
"""Play audio and wait until finished."""
# thread remains open in quiet mode but sound does not play
def play(self, sound: str) -> None:
"""Play audio."""
if self.__prefs.value("quiet_mode"):
return
# else
wave_obj = self.__wave(sound)
if wave_obj is None:
return
# else
play_obj = wave_obj.play()
if blocking:
# wait until sound has finished before returning
play_obj.wait_done()
wave_obj.play()

def __wave(self, sound: str) -> Optional[sa.WaveObject]:
def __wave(self, sound: str) -> sa.WaveObject | None:
X-sam marked this conversation as resolved.
Show resolved Hide resolved
"""Get and cache audio."""
if sound not in self.__cache:
self.__cache[sound] = self.__load_wave(sound)
wave_object = self.__load_wave(sound)
if wave_object is None:
return None
self.__cache[sound] = wave_object
return self.__cache[sound]

def __load_wave(self, sound: str) -> Optional[sa.WaveObject]:
def __load_wave(self, sound: str) -> sa.WaveObject | None:
X-sam marked this conversation as resolved.
Show resolved Hide resolved
"""Get audio from file."""
filename = sound + ".wav"
try:
wave_read = wave.open(path.join(self.__dir, filename), "rb")
except FileNotFoundError:
logging.warning("File " + filename + " not found.")
return None
except OSError:
logging.warning("Error loading " + filename + ".")
return None
else:
return sa.WaveObject.from_wave_read(wave_read)


class AudioPlayer(QThread):
"""Audio controller in its own thread."""

def __init__(self, parent: GuiMain):
super().__init__(parent)
self.__worker = AudioWorker(parent)
self.__worker.moveToThread(self)
self.start()

def __del__(self) -> None:
self.wait()

def play(self, sound: str) -> None:
self.__worker.play(sound, blocking=False)
filename = path.join(self.__dir, sound + ".wav")
return sa.WaveObject.from_wave_file(filename)
6 changes: 2 additions & 4 deletions src/main/python/main/ayab/signal_receiver.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
# https://github.com/AllYarnsAreBeautiful/ayab-desktop

from __future__ import annotations
from PySide6.QtCore import QObject, Signal, Qt
from PySide6.QtCore import QObject, Signal
from .engine.status import Status
from .engine.options import Alignment
from .engine.engine_fsm import Operation
Expand Down Expand Up @@ -73,9 +73,7 @@ def activate_signals(self, parent: GuiMain) -> None:
# self.statusbar_updater.connect(parent.statusbar.update)
self.blocking_popup_displayer.connect(display_blocking_popup)
self.popup_displayer.connect(display_blocking_popup)
self.audio_player.connect(
parent.audio.play, type=Qt.ConnectionType.BlockingQueuedConnection
)
self.audio_player.connect(parent.audio.play)
self.needles_updater.connect(parent.scene.update_needles)
self.alignment_updater.connect(parent.scene.update_alignment)
self.image_resizer.connect(parent.set_image_dimensions)
Expand Down