Skip to content

Commit b7f0e0e

Browse files
committed
Threaded every task with better progress bar handling to improve stability, various fixes
1 parent 42dcade commit b7f0e0e

19 files changed

+662
-454
lines changed

__init__.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,4 @@
66
:date: 2024.05
77
"""
88

9-
__version__ = '1.1.0'
9+
__version__ = '1.2.0'

plt_cfg/YMM_plt_cfg.json

+36
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
{
2+
"textColor": {
3+
"0": "ffffffff"
4+
},
5+
"trackBackgroundColor": {
6+
"0": "ffffffff"
7+
},
8+
"control": {
9+
"expression": "ffeb3526",
10+
"modulation": "ffeb3526",
11+
"reverb": "ffeb3526"
12+
},
13+
"other": {
14+
"ampVelTrack": "ffeb3526",
15+
"release": "ffeb3526"
16+
},
17+
"backgroundRamp": {
18+
"0": "fff3c43f",
19+
"1": "ffe3b258",
20+
"2": "fff3c43f"
21+
},
22+
"group": {
23+
"0": "ffeb3526"
24+
},
25+
"muteButton": {
26+
"0": "ffeb3526"
27+
},
28+
"backgroundText": {
29+
"0": "ffffffff",
30+
"1": "80ffffff"
31+
},
32+
"keyboard": {
33+
"0": "fff3c43f",
34+
"1": "ffffffff"
35+
}
36+
}

tools/audio_player.py

+22-16
Original file line numberDiff line numberDiff line change
@@ -14,41 +14,47 @@
1414
import soundfile as sf
1515
from common_ui_utils import resource_path
1616

17+
from PyQt5.QtCore import QObject, pyqtSignal
18+
19+
20+
class AudioPlayerSignals(QObject):
21+
message = pyqtSignal(str)
22+
1723

1824
class AudioPlayer:
1925
def __init__(self):
2026
self.is_playing = threading.Event()
2127
self.stream = None
28+
self.signals = AudioPlayerSignals()
2229

23-
def play(self, data, sr, loop_start, loop_end, msg=None):
30+
def play(self, data, sr, loop_start, loop_end):
2431
if not self.is_playing.is_set():
2532
callback = AudioCallback(data, loop_start, loop_end, player=self)
2633
self.stream = sd.OutputStream(samplerate=sr, channels=data.ndim, callback=callback)
2734
self.is_playing.set()
28-
if msg:
29-
if loop_start is None or loop_start is None:
30-
msg(f'▶ Press space bar to stop')
31-
else:
32-
msg(f'▶ {loop_start}-{loop_end} Press space bar to stop')
35+
36+
if loop_start is None or loop_start is None:
37+
self.signals.message.emit(f'▶ Press space bar to stop')
38+
else:
39+
self.signals.message.emit(f'▶ {loop_start}-{loop_end} Press space bar to stop')
40+
3341
with self.stream:
3442
while self.is_playing.is_set():
3543
sd.sleep(100)
36-
if msg:
37-
if loop_start is None or loop_start is None:
38-
try:
39-
msg(f'■ Press space bar to play')
40-
except RuntimeError as e:
41-
pass
42-
43-
def stop(self, msg=None):
44+
if loop_start is None or loop_start is None:
45+
try:
46+
self.signals.message.emit(f'■ Press space bar to play')
47+
except RuntimeError as e:
48+
print(f'Error: {e}')
49+
50+
def stop(self):
4451
if self.is_playing.is_set():
4552
if self.stream is not None:
4653
# self._stream.stop()
4754
self.stream.close()
4855
self.stream = None
4956
self.is_playing.clear()
50-
if msg:
51-
msg('■ Press space bar to play')
57+
self.signals.message.emit('■ Press space bar to play')
5258

5359

5460
class AudioCallback:

tools/base_tool_UI.py

+46-15
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,8 @@
3030
from audio_player import AudioPlayer
3131
from common_prefs_utils import get_settings, set_settings, read_settings, write_settings
3232
from common_ui_utils import FilePathLabel, replace_widget, resource_path
33-
from common_ui_utils import Worker, Node, KeyPressHandler, sample_to_name
33+
from common_ui_utils import Node, KeyPressHandler, sample_to_name
34+
from tools.worker import Worker
3435
from sample_utils import Sample
3536
from waveform_widgets import WaveformDialog, LoopPointDialog
3637

@@ -47,11 +48,16 @@ def __init__(self, parent=None):
4748
self.options = Node()
4849

4950
self.player = AudioPlayer()
51+
self.player.signals.message.connect(self.update_message)
52+
5053
self.temp_audio = Node()
5154
self.playback_thread = None
5255

5356
self.threadpool = QtCore.QThreadPool(parent=self)
5457
self.worker = None
58+
self.active_workers = []
59+
self.worker_result = None
60+
self.event_loop = QtCore.QEventLoop()
5561

5662
self.current_dir = Path(__file__).parent
5763
self.base_dir = self.current_dir.parent
@@ -155,11 +161,21 @@ def get_options(self):
155161
self.options.suffix = ''
156162

157163
def as_worker(self, cmd):
158-
if self.worker:
159-
if self.worker.running:
160-
return False
161-
self.worker = Worker(cmd)
162-
self.threadpool.start(self.worker)
164+
if not any(worker.running for worker in self.active_workers):
165+
self.worker = Worker(cmd)
166+
167+
# Worker signals
168+
self.worker.signals.progress.connect(self.update_progress)
169+
self.worker.signals.progress_range.connect(self.update_range)
170+
self.worker.signals.message.connect(self.update_message)
171+
self.worker.signals.result.connect(self.handle_result)
172+
173+
self.worker.signals.finished.connect(lambda: self.cleanup_worker(self.worker))
174+
175+
self.active_workers.append(self.worker)
176+
self.threadpool.start(self.worker)
177+
else:
178+
print('Task is already running!')
163179

164180
def browse_files(self):
165181
self.refresh_lw_items()
@@ -254,9 +270,7 @@ def play_lw_item(self, *args):
254270
audio_file = args[0].data(Qt.Qt.UserRole)
255271
if os.path.isfile(audio_file):
256272
data, sr = sf.read(audio_file)
257-
self.playback_thread = threading.Thread(target=self.player.play,
258-
args=(data, sr, None, None, self.progress_pb.setFormat),
259-
daemon=True)
273+
self.playback_thread = threading.Thread(target=self.player.play, args=(data, sr, None, None), daemon=True)
260274
self.playback_thread.start()
261275

262276
def key_lw_items_event(self, event):
@@ -295,7 +309,7 @@ def key_lw_items_event(self, event):
295309

296310
def play_stop_toggle(self):
297311
if self.player.is_playing.is_set():
298-
self.player.stop(msg=self.progress_pb.setFormat)
312+
self.player.stop()
299313
else:
300314
items = self.files_lw.selectedItems()
301315
if items:
@@ -304,8 +318,7 @@ def play_stop_toggle(self):
304318
data, sr = sf.read(audio_file)
305319
info = Sample(audio_file)
306320
self.playback_thread = threading.Thread(target=self.player.play,
307-
args=(data, sr, info.loopStart, info.loopEnd,
308-
self.progress_pb.setFormat), daemon=True)
321+
args=(data, sr, info.loopStart, info.loopEnd), daemon=True)
309322
self.playback_thread.start()
310323

311324
@staticmethod
@@ -341,7 +354,7 @@ def lw_drop_event(self, event):
341354
event.ignore()
342355

343356
def closeEvent(self, event):
344-
self.player.stop(msg=None)
357+
self.player.stop()
345358
self.removeEventFilter(self)
346359
print(f'{self.objectName()} closed')
347360
event.accept()
@@ -393,14 +406,14 @@ def load_settings(self):
393406
result = read_settings(widget=self, filepath=None, startdir=p, ext=self.settings_ext)
394407
if result:
395408
os.chdir(result.parent)
396-
self.progress_pb.setFormat(f'{result.name} loaded')
409+
self.update_message(f'{result.name} loaded')
397410

398411
def save_settings(self):
399412
self.set_settings_path()
400413
result = write_settings(widget=self, filepath=None, startdir=self.settings_path, ext=self.settings_ext)
401414
if result:
402415
os.chdir(result.parent)
403-
self.progress_pb.setFormat(f'{result.name} saved')
416+
self.update_message(f'{result.name} saved')
404417

405418
def get_defaults(self):
406419
get_settings(self, self.default_settings)
@@ -409,6 +422,24 @@ def restore_defaults(self):
409422
set_settings(widget=self, node=self.default_settings)
410423
self.progress_pb.setFormat(f'Default settings restored')
411424

425+
def update_progress(self, value):
426+
self.progress_pb.setValue(value)
427+
428+
def update_message(self, message):
429+
self.progress_pb.setFormat(message)
430+
431+
def update_range(self, mn, mx):
432+
self.progress_pb.setRange(mn, mx)
433+
self.progress_pb.update()
434+
435+
def handle_result(self, value):
436+
self.worker_result = value
437+
self.event_loop.quit()
438+
439+
def cleanup_worker(self, worker):
440+
if worker in self.active_workers:
441+
self.active_workers.remove(worker)
442+
412443

413444
def launch(mw, app_id=''):
414445
"""

tools/common_prefs_utils.py

+17-10
Original file line numberDiff line numberDiff line change
@@ -29,15 +29,18 @@ def get_settings(widget, node):
2929
if not name or name.startswith('qt_'):
3030
continue
3131

32-
match type(child):
33-
case QtWidgets.QCheckBox:
32+
match child.__class__.__name__:
33+
case 'QCheckBox':
3434
value = child.isChecked()
35-
case QtWidgets.QComboBox:
35+
case 'QComboBox':
3636
value = child.currentText()
37-
case QtWidgets.QSpinBox | QtWidgets.QDoubleSpinBox:
37+
case 'QSpinBox' | 'QDoubleSpinBox':
3838
value = child.value()
39-
case QtWidgets.QLineEdit:
39+
case 'QLineEdit' | 'UrlPathLineEdit':
4040
value = child.text()
41+
case 'FilePathLabel':
42+
p = child.fullPath()
43+
value = Path(p).as_posix() if p else ''
4144
case _:
4245
value = None
4346

@@ -61,20 +64,24 @@ def set_settings(widget, node):
6164
name = child.objectName()
6265
if not name or name.startswith('qt_') or not hasattr(node, name):
6366
continue
67+
6468
value = getattr(node, name)
65-
match type(child):
66-
case QtWidgets.QCheckBox:
69+
match child.__class__.__name__:
70+
case 'QCheckBox':
6771
child.setChecked(value)
68-
case QtWidgets.QComboBox:
72+
case 'QComboBox':
6973
values = [child.itemText(i) for i in range(child.count())]
7074
if value in values:
7175
child.setCurrentText(value)
7276
else:
7377
print(f'{name} QComboxBox skipped : {value} not found in items')
74-
case QtWidgets.QSpinBox | QtWidgets.QDoubleSpinBox:
78+
case 'QSpinBox' | 'QDoubleSpinBox':
7579
child.setValue(value)
76-
case QtWidgets.QLineEdit:
80+
case 'QLineEdit' | 'UrlPathLineEdit':
7781
child.setText(value)
82+
case 'FilePathLabel':
83+
child.setFullPath(value)
84+
7885
return True
7986

8087

0 commit comments

Comments
 (0)