audio-tuner-gui 0.10.0__tar.gz → 0.11.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {audio_tuner_gui-0.10.0 → audio_tuner_gui-0.11.1}/PKG-INFO +4 -3
- {audio_tuner_gui-0.10.0 → audio_tuner_gui-0.11.1}/README.rst +1 -1
- {audio_tuner_gui-0.10.0 → audio_tuner_gui-0.11.1}/audio_tuner_gui/__init__.py +1 -1
- {audio_tuner_gui-0.10.0 → audio_tuner_gui-0.11.1}/audio_tuner_gui/analysis.py +107 -81
- {audio_tuner_gui-0.10.0 → audio_tuner_gui-0.11.1}/audio_tuner_gui/common.py +77 -25
- {audio_tuner_gui-0.10.0 → audio_tuner_gui-0.11.1}/audio_tuner_gui/file_selector.py +71 -21
- audio_tuner_gui-0.11.1/audio_tuner_gui/icons/fallback/README +4 -0
- audio_tuner_gui-0.11.1/audio_tuner_gui/icons/fallback/application-exit.svg +457 -0
- audio_tuner_gui-0.11.1/audio_tuner_gui/icons/fallback/audio-card.svg +434 -0
- audio_tuner_gui-0.11.1/audio_tuner_gui/icons/fallback/audio-x-generic.svg +180 -0
- audio_tuner_gui-0.11.1/audio_tuner_gui/icons/fallback/dialog-warning.svg +373 -0
- audio_tuner_gui-0.11.1/audio_tuner_gui/icons/fallback/document-save.svg +619 -0
- audio_tuner_gui-0.11.1/audio_tuner_gui/icons/fallback/edit-delete.svg +896 -0
- audio_tuner_gui-0.11.1/audio_tuner_gui/icons/fallback/folder.svg +424 -0
- audio_tuner_gui-0.11.1/audio_tuner_gui/icons/fallback/format-justify-left.svg +271 -0
- audio_tuner_gui-0.11.1/audio_tuner_gui/icons/fallback/go-home.svg +445 -0
- audio_tuner_gui-0.11.1/audio_tuner_gui/icons/fallback/go-next.svg +192 -0
- audio_tuner_gui-0.11.1/audio_tuner_gui/icons/fallback/go-previous.svg +854 -0
- audio_tuner_gui-0.11.1/audio_tuner_gui/icons/fallback/go-up.svg +196 -0
- audio_tuner_gui-0.11.1/audio_tuner_gui/icons/fallback/media-playback-pause.svg +641 -0
- audio_tuner_gui-0.11.1/audio_tuner_gui/icons/fallback/media-playback-start.svg +319 -0
- audio_tuner_gui-0.11.1/audio_tuner_gui/icons/fallback/media-playback-stop.svg +651 -0
- audio_tuner_gui-0.11.1/audio_tuner_gui/icons/fallback/media-seek-backward.svg +374 -0
- audio_tuner_gui-0.11.1/audio_tuner_gui/icons/fallback/media-seek-forward.svg +383 -0
- audio_tuner_gui-0.11.1/audio_tuner_gui/icons/fallback/preferences-system.svg +398 -0
- audio_tuner_gui-0.11.1/audio_tuner_gui/icons/fallback/process-stop.svg +336 -0
- {audio_tuner_gui-0.10.0 → audio_tuner_gui-0.11.1}/audio_tuner_gui/option_panel.py +49 -21
- {audio_tuner_gui-0.10.0 → audio_tuner_gui-0.11.1}/audio_tuner_gui/scripts/tuner_gui.py +137 -21
- {audio_tuner_gui-0.10.0 → audio_tuner_gui-0.11.1}/pyproject.toml +2 -1
- audio_tuner_gui-0.10.0/audio_tuner_gui/icons/folder.png +0 -0
- audio_tuner_gui-0.10.0/audio_tuner_gui/icons/preferences-other.png +0 -0
- audio_tuner_gui-0.10.0/audio_tuner_gui/icons/process-stop.png +0 -0
- {audio_tuner_gui-0.10.0 → audio_tuner_gui-0.11.1}/.gitignore +0 -0
- {audio_tuner_gui-0.10.0 → audio_tuner_gui-0.11.1}/COPYING +0 -0
- {audio_tuner_gui-0.10.0 → audio_tuner_gui-0.11.1}/audio_tuner_gui/display.py +0 -0
- {audio_tuner_gui-0.10.0 → audio_tuner_gui-0.11.1}/audio_tuner_gui/export.py +0 -0
- {audio_tuner_gui-0.10.0 → audio_tuner_gui-0.11.1}/audio_tuner_gui/icons/audio_tuner_icon.ico +0 -0
- {audio_tuner_gui-0.10.0 → audio_tuner_gui-0.11.1}/audio_tuner_gui/icons/audio_tuner_icon.svg +0 -0
- {audio_tuner_gui-0.10.0 → audio_tuner_gui-0.11.1}/audio_tuner_gui/icons/audio_tuner_icon_hires.svg +0 -0
- {audio_tuner_gui-0.10.0 → audio_tuner_gui-0.11.1}/audio_tuner_gui/icons/audio_tuner_logo_dark.svg +0 -0
- {audio_tuner_gui-0.10.0 → audio_tuner_gui-0.11.1}/audio_tuner_gui/icons/audio_tuner_logo_light.svg +0 -0
- {audio_tuner_gui-0.10.0 → audio_tuner_gui-0.11.1}/audio_tuner_gui/icons/black-logo.svg +0 -0
- {audio_tuner_gui-0.10.0 → audio_tuner_gui-0.11.1}/audio_tuner_gui/icons/gpl-v3-logo_red.svg +0 -0
- {audio_tuner_gui-0.10.0 → audio_tuner_gui-0.11.1}/audio_tuner_gui/icons/gpl-v3-logo_white.svg +0 -0
- {audio_tuner_gui-0.10.0 → audio_tuner_gui-0.11.1}/audio_tuner_gui/icons/white-logo.svg +0 -0
- {audio_tuner_gui-0.10.0 → audio_tuner_gui-0.11.1}/audio_tuner_gui/log_viewer.py +0 -0
- {audio_tuner_gui-0.10.0 → audio_tuner_gui-0.11.1}/audio_tuner_gui/player.py +0 -0
- {audio_tuner_gui-0.10.0 → audio_tuner_gui-0.11.1}/audio_tuner_gui/scripts/__init__.py +0 -0
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: audio-tuner-gui
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.11.1
|
|
4
4
|
Summary: Graphical interface for Audio Tuner
|
|
5
5
|
Project-URL: Homepage, https://codeberg.org/bluesloth/audio_tuner
|
|
6
6
|
Project-URL: Repository, https://codeberg.org/bluesloth/audio_tuner.git
|
|
7
7
|
Project-URL: Issues, https://codeberg.org/bluesloth/audio_tuner/issues
|
|
8
|
-
Project-URL: Documentation, https://audio-tuner.readthedocs.io/
|
|
8
|
+
Project-URL: Documentation, https://audio-tuner.readthedocs.io/
|
|
9
|
+
Project-URL: News, https://codeberg.org/bluesloth/audio_tuner/src/branch/master/NEWS
|
|
9
10
|
Author-email: Jessie Blue Cassell <bluesloth600@gmail.com>
|
|
10
11
|
License-File: COPYING
|
|
11
12
|
Keywords: audio,music,pitch shift,player,tempo shift,tuning
|
|
@@ -61,7 +62,7 @@ for details.
|
|
|
61
62
|
Audio Tuner can be configured to use **ffmpeg** and **ffprobe** instead of
|
|
62
63
|
libmpv2, at the cost of reduced functionality in the GUI.
|
|
63
64
|
|
|
64
|
-
.. _documentation: https://audio-tuner.readthedocs.io/
|
|
65
|
+
.. _documentation: https://audio-tuner.readthedocs.io/
|
|
65
66
|
.. _audio-tuner: https://pypi.org/project/audio-tuner/
|
|
66
67
|
.. _PyQt6: https://www.riverbankcomputing.com/software/pyqt/
|
|
67
68
|
|
|
@@ -41,7 +41,7 @@ for details.
|
|
|
41
41
|
Audio Tuner can be configured to use **ffmpeg** and **ffprobe** instead of
|
|
42
42
|
libmpv2, at the cost of reduced functionality in the GUI.
|
|
43
43
|
|
|
44
|
-
.. _documentation: https://audio-tuner.readthedocs.io/
|
|
44
|
+
.. _documentation: https://audio-tuner.readthedocs.io/
|
|
45
45
|
.. _audio-tuner: https://pypi.org/project/audio-tuner/
|
|
46
46
|
.. _PyQt6: https://www.riverbankcomputing.com/software/pyqt/
|
|
47
47
|
|
|
@@ -38,6 +38,7 @@ from PyQt6.QtCore import (
|
|
|
38
38
|
pyqtSignal,
|
|
39
39
|
QThread,
|
|
40
40
|
QPersistentModelIndex,
|
|
41
|
+
QModelIndex,
|
|
41
42
|
)
|
|
42
43
|
from PyQt6.QtWidgets import (
|
|
43
44
|
QWidget,
|
|
@@ -84,6 +85,32 @@ class _AudioView(QTableView):
|
|
|
84
85
|
self.setShowGrid(False)
|
|
85
86
|
|
|
86
87
|
|
|
88
|
+
class _Result(anal.Analysis):
|
|
89
|
+
"""A class that inherits from audio_tuner.analysis.Analysis and
|
|
90
|
+
extends it with a few more attributes necessary for the GUI.
|
|
91
|
+
|
|
92
|
+
Attributes
|
|
93
|
+
----------
|
|
94
|
+
result_rows : list[audio_tuner_gui.common.RowData] | None
|
|
95
|
+
A list of RowData objects that can be passed to the
|
|
96
|
+
`update_data` method of audio_tuner_gui.display.Display, or None
|
|
97
|
+
if it hasn't been set yet.
|
|
98
|
+
options : audio_tuner_gui.common.Options | None
|
|
99
|
+
The options used to do the analysis, or None if it hasn't been
|
|
100
|
+
set yet.
|
|
101
|
+
index : PyQt6.QtCore.QPersistentModelIndex | None
|
|
102
|
+
The index of the results entry in the analyzed audio widget, or
|
|
103
|
+
None if it hasn't been set yet.
|
|
104
|
+
"""
|
|
105
|
+
|
|
106
|
+
def __init__(self, *args, **kwargs):
|
|
107
|
+
super().__init__(*args, **kwargs)
|
|
108
|
+
|
|
109
|
+
self.result_rows = None
|
|
110
|
+
self.options = None
|
|
111
|
+
self.index = None
|
|
112
|
+
|
|
113
|
+
|
|
87
114
|
class _Worker(QObject):
|
|
88
115
|
"""A worker class to be run in it's own thread that handles
|
|
89
116
|
analysis. Inherits from QObject.
|
|
@@ -107,23 +134,23 @@ class _Worker(QObject):
|
|
|
107
134
|
The amount of progress, from 0 to 1.
|
|
108
135
|
"""
|
|
109
136
|
|
|
110
|
-
ResultReady = pyqtSignal(
|
|
137
|
+
ResultReady = pyqtSignal(_Result)
|
|
111
138
|
"""Signal emitted when analysis is finished and the result is ready.
|
|
112
139
|
|
|
113
140
|
Parameters
|
|
114
141
|
----------
|
|
115
|
-
|
|
116
|
-
The
|
|
142
|
+
_Result
|
|
143
|
+
The result of the analysis.
|
|
117
144
|
"""
|
|
118
145
|
|
|
119
|
-
ProcessingError = pyqtSignal(
|
|
120
|
-
"""Signal emitted when the
|
|
121
|
-
|
|
146
|
+
ProcessingError = pyqtSignal(_Result)
|
|
147
|
+
"""Signal emitted when the _Result object raises an exception during
|
|
148
|
+
analysis.
|
|
122
149
|
|
|
123
150
|
Parameters
|
|
124
151
|
----------
|
|
125
|
-
|
|
126
|
-
The
|
|
152
|
+
_Result
|
|
153
|
+
The _Result object.
|
|
127
154
|
"""
|
|
128
155
|
|
|
129
156
|
AddToLog = pyqtSignal(str, int)
|
|
@@ -147,9 +174,6 @@ class _Worker(QObject):
|
|
|
147
174
|
The name of the option, as defined in audio_tuner_gui.common.
|
|
148
175
|
"""
|
|
149
176
|
|
|
150
|
-
def __init__(self):
|
|
151
|
-
super().__init__()
|
|
152
|
-
|
|
153
177
|
def _progress_check(self, progress: float) -> bool:
|
|
154
178
|
self.UpdateProgbar.emit(progress)
|
|
155
179
|
return not QThread.currentThread().isInterruptionRequested()
|
|
@@ -168,24 +192,17 @@ class _Worker(QObject):
|
|
|
168
192
|
self.OptionError.emit(gcom.OPTION_END)
|
|
169
193
|
|
|
170
194
|
def analyze(self,
|
|
171
|
-
|
|
195
|
+
result: _Result,
|
|
172
196
|
options: gcom.Options,
|
|
173
197
|
reread_requested: bool) -> None:
|
|
174
|
-
"""Do the analysis. This gets passed an instance of
|
|
175
|
-
|
|
176
|
-
the
|
|
177
|
-
ProcessingError signal if it went wrong). The Analysis instance
|
|
178
|
-
will have `result_rows` and `options` in addition to it's usual
|
|
179
|
-
attributes. `result_rows` is a list of
|
|
180
|
-
audio_tuner_gui.common.RowData objects that can be passed to the
|
|
181
|
-
`update_data` method of audio_tuner_gui.display.Display, and
|
|
182
|
-
`options` is the audio_tuner_gui.common.Options object that was
|
|
183
|
-
passed to the `options` parameter of this method.
|
|
198
|
+
"""Do the analysis. This gets passed an instance of _Result,
|
|
199
|
+
and then spits it out again via the ResultReady signal when it's
|
|
200
|
+
done (or via the ProcessingError signal if it went wrong).
|
|
184
201
|
|
|
185
202
|
Parameters
|
|
186
203
|
----------
|
|
187
|
-
|
|
188
|
-
The
|
|
204
|
+
result : _Result
|
|
205
|
+
The _Result instance to put the results in.
|
|
189
206
|
options : audio_tuner_gui.common.Options
|
|
190
207
|
The analysis options.
|
|
191
208
|
reread_requested : bool
|
|
@@ -195,44 +212,41 @@ class _Worker(QObject):
|
|
|
195
212
|
|
|
196
213
|
if QThread.currentThread().isInterruptionRequested():
|
|
197
214
|
return
|
|
198
|
-
|
|
199
|
-
|
|
215
|
+
result.try_sequence = options[gcom.OPTION_BACKENDS]
|
|
216
|
+
result.print_msg = self._print_msg
|
|
200
217
|
if (reread_requested
|
|
201
|
-
and (
|
|
202
|
-
or
|
|
203
|
-
|
|
204
|
-
|
|
218
|
+
and (result.pitch != options[gcom.OPTION_PITCH]
|
|
219
|
+
or result.tempo != options[gcom.OPTION_TEMPO])):
|
|
220
|
+
result.pitch = options[gcom.OPTION_PITCH]
|
|
221
|
+
result.tempo = options[gcom.OPTION_TEMPO]
|
|
205
222
|
redo_level = gcom.REDO_LEVEL_ALL
|
|
206
223
|
else:
|
|
207
|
-
redo_level = options.redo_level(
|
|
208
|
-
basename = os.path.basename(
|
|
224
|
+
redo_level = options.redo_level(result.options)
|
|
225
|
+
basename = os.path.basename(result.inputfile)
|
|
209
226
|
self.UpdateStatusbar.emit('Analyzing ' + basename + '...')
|
|
210
227
|
if redo_level >= gcom.REDO_LEVEL_ALL:
|
|
211
228
|
try:
|
|
212
229
|
s = '\nLoading "' + basename + '"'
|
|
213
230
|
self.AddToLog.emit(s, lv.LOG_LEVEL_NORMAL)
|
|
214
231
|
size = 2**options[gcom.OPTION_SIZE_EXP]
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
end=options[gcom.OPTION_END],
|
|
221
|
-
samplerate=options[gcom.OPTION_SAMPLERATE],
|
|
222
|
-
pad=pad_amounts)
|
|
232
|
+
pad_amounts = (size * 2, size * 2)
|
|
233
|
+
result.load_data(start=options[gcom.OPTION_START],
|
|
234
|
+
end=options[gcom.OPTION_END],
|
|
235
|
+
samplerate=options[gcom.OPTION_SAMPLERATE],
|
|
236
|
+
pad=pad_amounts)
|
|
223
237
|
except eh.LoadError:
|
|
224
238
|
s = 'Error processing ' + basename
|
|
225
239
|
self.UpdateStatusbar.emit(s)
|
|
226
|
-
self.ProcessingError.emit(
|
|
240
|
+
self.ProcessingError.emit(result)
|
|
227
241
|
return
|
|
228
242
|
try:
|
|
229
243
|
self.AddToLog.emit('Analyzing "' + basename + '"',
|
|
230
244
|
lv.LOG_LEVEL_NORMAL)
|
|
231
|
-
|
|
245
|
+
result.fft(size=size, progress_hook=self._progress_check)
|
|
232
246
|
except eh.ShortError:
|
|
233
247
|
s = 'Error processing ' + basename
|
|
234
248
|
self.UpdateStatusbar.emit(s)
|
|
235
|
-
self.ProcessingError.emit(
|
|
249
|
+
self.ProcessingError.emit(result)
|
|
236
250
|
self.AddToLog.emit(eh.ERRMSG_SHORT,
|
|
237
251
|
lv.LOG_LEVEL_ERROR)
|
|
238
252
|
return
|
|
@@ -243,16 +257,16 @@ class _Worker(QObject):
|
|
|
243
257
|
lv.LOG_LEVEL_WARNING)
|
|
244
258
|
return
|
|
245
259
|
if redo_level >= gcom.REDO_LEVEL_FIND_PEAKS:
|
|
246
|
-
|
|
247
|
-
low_cut=options[gcom.OPTION_LOW_CUT] *
|
|
248
|
-
high_cut=options[gcom.OPTION_HIGH_CUT] *
|
|
260
|
+
result.find_peaks(
|
|
261
|
+
low_cut=options[gcom.OPTION_LOW_CUT] * result.pitch,
|
|
262
|
+
high_cut=options[gcom.OPTION_HIGH_CUT] * result.pitch,
|
|
249
263
|
max_peaks=options[gcom.OPTION_MAX_PEAKS],
|
|
250
264
|
dB_range=options[gcom.OPTION_DB_RANGE])
|
|
251
265
|
if redo_level >= gcom.REDO_LEVEL_TUNING_SYSTEM:
|
|
252
|
-
peaks =
|
|
266
|
+
peaks = result.peaks
|
|
253
267
|
tuning_system = options.tuning_system
|
|
254
268
|
result_rows = []
|
|
255
|
-
pitch_offset = options[gcom.OPTION_PITCH] /
|
|
269
|
+
pitch_offset = options[gcom.OPTION_PITCH] / result.pitch
|
|
256
270
|
for note in tuning_system(
|
|
257
271
|
options[gcom.OPTION_LOW_CUT] * options[gcom.OPTION_PITCH],
|
|
258
272
|
options[gcom.OPTION_HIGH_CUT] * options[gcom.OPTION_PITCH]):
|
|
@@ -268,11 +282,11 @@ class _Worker(QObject):
|
|
|
268
282
|
result_row['cents'] = -com.ratio_to_cents(freq_ratio)
|
|
269
283
|
result_row['correction'] = freq_ratio * pitch_offset
|
|
270
284
|
result_rows.append(result_row)
|
|
271
|
-
|
|
285
|
+
result.result_rows = result_rows
|
|
272
286
|
|
|
273
|
-
|
|
287
|
+
result.options = options
|
|
274
288
|
|
|
275
|
-
self.ResultReady.emit(
|
|
289
|
+
self.ResultReady.emit(result)
|
|
276
290
|
self.UpdateProgbar.emit(-1)
|
|
277
291
|
self.AddToLog.emit('Finished analyzing "' + basename + '"',
|
|
278
292
|
lv.LOG_LEVEL_NORMAL)
|
|
@@ -326,13 +340,13 @@ class AnalyzedAudio(QWidget):
|
|
|
326
340
|
The amount of progress, from 0 to 1.
|
|
327
341
|
"""
|
|
328
342
|
|
|
329
|
-
AnalyzeAudio = pyqtSignal(
|
|
343
|
+
AnalyzeAudio = pyqtSignal(_Result, gcom.Options, bool)
|
|
330
344
|
"""Signal emitted to tell the worker thread to run an analysis.
|
|
331
345
|
|
|
332
346
|
Parameters
|
|
333
347
|
----------
|
|
334
|
-
|
|
335
|
-
The
|
|
348
|
+
_Result
|
|
349
|
+
The _Result object to use.
|
|
336
350
|
audio_tuner_gui.common.Options
|
|
337
351
|
The options.
|
|
338
352
|
bool
|
|
@@ -405,7 +419,7 @@ class AnalyzedAudio(QWidget):
|
|
|
405
419
|
self.current_duration = None
|
|
406
420
|
self.current_metadata = None
|
|
407
421
|
|
|
408
|
-
self.
|
|
422
|
+
self._results = {}
|
|
409
423
|
self._in_progress = {}
|
|
410
424
|
self._cancelled = False
|
|
411
425
|
|
|
@@ -460,6 +474,7 @@ class AnalyzedAudio(QWidget):
|
|
|
460
474
|
view.setColumnWidth(0, 20)
|
|
461
475
|
|
|
462
476
|
view.selectionModel().currentRowChanged.connect(self._audio_selected)
|
|
477
|
+
view.clicked.connect(self._clicked)
|
|
463
478
|
model.itemChanged.connect(self._item_changed)
|
|
464
479
|
|
|
465
480
|
self._model = model
|
|
@@ -495,6 +510,14 @@ class AnalyzedAudio(QWidget):
|
|
|
495
510
|
self._remove_button = remove_button
|
|
496
511
|
self._set_removal_enabled(False)
|
|
497
512
|
|
|
513
|
+
def _clicked(self, index: QModelIndex):
|
|
514
|
+
item2 = self._model.item(index.row(), 2)
|
|
515
|
+
if item2:
|
|
516
|
+
options = self._results[item2.data()].options
|
|
517
|
+
# FIXME: PushOptions gets emitted twice when the user
|
|
518
|
+
# changes the selection by clicking.
|
|
519
|
+
self.PushOptions.emit(options)
|
|
520
|
+
|
|
498
521
|
def _audio_selected(self, new, prev):
|
|
499
522
|
title = None
|
|
500
523
|
item1 = self._model.item(new.row(), 1)
|
|
@@ -510,11 +533,11 @@ class AnalyzedAudio(QWidget):
|
|
|
510
533
|
self.current_title = title
|
|
511
534
|
self.current_listed_title = listed_title
|
|
512
535
|
self.current_selection = item2.data()
|
|
513
|
-
self.current_options = self.
|
|
514
|
-
self.current_duration = self.
|
|
515
|
-
self.current_metadata = self.
|
|
536
|
+
self.current_options = self._results[item2.data()].options
|
|
537
|
+
self.current_duration = self._results[item2.data()].file_duration
|
|
538
|
+
self.current_metadata = self._results[item2.data()].file_metadata
|
|
516
539
|
self.DisplayResult.emit(title,
|
|
517
|
-
self.
|
|
540
|
+
self._results[item2.data()].result_rows)
|
|
518
541
|
self.PushOptions.emit(self.current_options)
|
|
519
542
|
self.SomethingSelected.emit()
|
|
520
543
|
|
|
@@ -523,7 +546,7 @@ class AnalyzedAudio(QWidget):
|
|
|
523
546
|
if data is not None:
|
|
524
547
|
if item.text() == '':
|
|
525
548
|
self._model.itemChanged.disconnect(self._item_changed)
|
|
526
|
-
file_title = self.
|
|
549
|
+
file_title = self._results[data].file_title
|
|
527
550
|
item.setText(file_title if file_title else ' ')
|
|
528
551
|
self._model.itemChanged.connect(self._item_changed)
|
|
529
552
|
if data == self.current_selection:
|
|
@@ -540,7 +563,7 @@ class AnalyzedAudio(QWidget):
|
|
|
540
563
|
if title:
|
|
541
564
|
self.current_title = title
|
|
542
565
|
self.DisplayResult.emit(title,
|
|
543
|
-
self.
|
|
566
|
+
self._results[item2.data()].result_rows)
|
|
544
567
|
|
|
545
568
|
def add_audio(self, path, options):
|
|
546
569
|
"""Analyze an audio file asynchronously and add it to the list
|
|
@@ -560,8 +583,8 @@ class AnalyzedAudio(QWidget):
|
|
|
560
583
|
canonical_path = os.path.realpath(path)
|
|
561
584
|
if canonical_path in self._in_progress:
|
|
562
585
|
return
|
|
563
|
-
if canonical_path in self.
|
|
564
|
-
index = self.
|
|
586
|
+
if canonical_path in self._results:
|
|
587
|
+
index = self._results[canonical_path].index
|
|
565
588
|
self._view.selectRow(index.row())
|
|
566
589
|
return
|
|
567
590
|
|
|
@@ -574,8 +597,11 @@ class AnalyzedAudio(QWidget):
|
|
|
574
597
|
self.Starting.emit()
|
|
575
598
|
|
|
576
599
|
self._in_progress[canonical_path] = True
|
|
577
|
-
|
|
578
|
-
|
|
600
|
+
|
|
601
|
+
# This is a misnomer at this point because there's no results in
|
|
602
|
+
# "result" yet, but that will soon change.
|
|
603
|
+
result = _Result(canonical_path)
|
|
604
|
+
self.AnalyzeAudio.emit(result, options, False)
|
|
579
605
|
|
|
580
606
|
def change_options(self, new_options, reread_requested):
|
|
581
607
|
"""Change the options of the currently selected analyzed audio.
|
|
@@ -602,19 +628,19 @@ class AnalyzedAudio(QWidget):
|
|
|
602
628
|
self.Starting.emit()
|
|
603
629
|
|
|
604
630
|
self._in_progress[canonical_path] = True
|
|
605
|
-
|
|
606
|
-
self.AnalyzeAudio.emit(
|
|
631
|
+
result = self._results[canonical_path]
|
|
632
|
+
self.AnalyzeAudio.emit(result, new_options, reread_requested)
|
|
607
633
|
self.current_options = new_options
|
|
608
634
|
|
|
609
|
-
def _handle_result(self,
|
|
610
|
-
canonical_path =
|
|
611
|
-
file_title =
|
|
612
|
-
if canonical_path in self.
|
|
635
|
+
def _handle_result(self, result):
|
|
636
|
+
canonical_path = result.inputfile
|
|
637
|
+
file_title = result.file_title
|
|
638
|
+
if canonical_path in self._results:
|
|
613
639
|
if canonical_path == self.current_selection:
|
|
614
640
|
self.DisplayResult.emit(self.current_title,
|
|
615
|
-
self.
|
|
641
|
+
self._results[canonical_path].result_rows)
|
|
616
642
|
else:
|
|
617
|
-
file_track =
|
|
643
|
+
file_track = result.file_track
|
|
618
644
|
|
|
619
645
|
track_num = QStandardItem(file_track.partition('/')[0]
|
|
620
646
|
if file_track
|
|
@@ -630,8 +656,8 @@ class AnalyzedAudio(QWidget):
|
|
|
630
656
|
|
|
631
657
|
index = QPersistentModelIndex(self._model.indexFromItem(filename))
|
|
632
658
|
self._model.setVerticalHeaderItem(index.row(), QStandardItem(' '))
|
|
633
|
-
|
|
634
|
-
self.
|
|
659
|
+
result.index = index
|
|
660
|
+
self._results[canonical_path] = result
|
|
635
661
|
|
|
636
662
|
del self._in_progress[canonical_path]
|
|
637
663
|
if len(self._in_progress) == 0:
|
|
@@ -655,9 +681,9 @@ class AnalyzedAudio(QWidget):
|
|
|
655
681
|
|
|
656
682
|
canonical_path = self.current_selection
|
|
657
683
|
if canonical_path is not None:
|
|
658
|
-
pitch = self.
|
|
684
|
+
pitch = self._results[canonical_path].options[gcom.OPTION_PITCH]
|
|
659
685
|
try:
|
|
660
|
-
self.
|
|
686
|
+
self._results[canonical_path].show_plot(plot_type=plot_type,
|
|
661
687
|
asynchronous=True,
|
|
662
688
|
title=self.current_title,
|
|
663
689
|
pitch=pitch,
|
|
@@ -669,8 +695,8 @@ class AnalyzedAudio(QWidget):
|
|
|
669
695
|
def _handle_log_message(self, message, level):
|
|
670
696
|
self.AddToLog.emit(message, level)
|
|
671
697
|
|
|
672
|
-
def _handle_error(self,
|
|
673
|
-
del self._in_progress[
|
|
698
|
+
def _handle_error(self, result):
|
|
699
|
+
del self._in_progress[result.inputfile]
|
|
674
700
|
if len(self._in_progress) == 0:
|
|
675
701
|
self.Finished.emit()
|
|
676
702
|
|
|
@@ -692,9 +718,9 @@ class AnalyzedAudio(QWidget):
|
|
|
692
718
|
except IndexError:
|
|
693
719
|
return
|
|
694
720
|
path = self._model.item(row, 2).data()
|
|
695
|
-
del self.
|
|
721
|
+
del self._results[path]
|
|
696
722
|
self._model.removeRows(row, 1)
|
|
697
|
-
if len(self.
|
|
723
|
+
if len(self._results) == 0:
|
|
698
724
|
self.DisplayResult.emit(' ', [])
|
|
699
725
|
self.NothingSelected.emit()
|
|
700
726
|
self.current_title = None
|
|
@@ -34,7 +34,6 @@ __all__ = [
|
|
|
34
34
|
'OPTION_HIGH_CUT',
|
|
35
35
|
'OPTION_DB_RANGE',
|
|
36
36
|
'OPTION_MAX_PEAKS',
|
|
37
|
-
'OPTION_PAD',
|
|
38
37
|
'OPTION_SIZE_EXP',
|
|
39
38
|
'OPTION_SAMPLERATE',
|
|
40
39
|
'OPTION_PITCH',
|
|
@@ -65,6 +64,8 @@ __all__ = [
|
|
|
65
64
|
'ICON_PLAYER_BACK',
|
|
66
65
|
'ICON_PLAYER_FORWARD',
|
|
67
66
|
'ICON_AUDIO_DEVICE',
|
|
67
|
+
'ICON_SAVE',
|
|
68
|
+
'ICON_DEL',
|
|
68
69
|
'RowData',
|
|
69
70
|
'SplitAction',
|
|
70
71
|
'Options',
|
|
@@ -73,6 +74,7 @@ __all__ = [
|
|
|
73
74
|
|
|
74
75
|
import os
|
|
75
76
|
from typing import TypedDict
|
|
77
|
+
from argparse import Namespace
|
|
76
78
|
|
|
77
79
|
from PyQt6.QtGui import (
|
|
78
80
|
QIcon,
|
|
@@ -94,7 +96,6 @@ OPTION_LOW_CUT = 'Low cut'
|
|
|
94
96
|
OPTION_HIGH_CUT = 'High cut'
|
|
95
97
|
OPTION_DB_RANGE = 'dB range'
|
|
96
98
|
OPTION_MAX_PEAKS = 'Max peaks'
|
|
97
|
-
OPTION_PAD = 'Pad input'
|
|
98
99
|
OPTION_SIZE_EXP = 'FFT size exponent'
|
|
99
100
|
OPTION_SAMPLERATE = 'Sample rate'
|
|
100
101
|
OPTION_BACKENDS = 'backends'
|
|
@@ -111,11 +112,8 @@ REDO_LEVEL_NONE = 0
|
|
|
111
112
|
APP_ICON = os.path.join(PKGDIR, 'icons/audio_tuner_icon.svg')
|
|
112
113
|
|
|
113
114
|
# These are missing from ThemeIcon
|
|
114
|
-
ICON_OPTIONS = os.path.join(PKGDIR, 'icons/preferences-
|
|
115
|
-
ICON_FILES = os.path.join(PKGDIR, 'icons/folder.
|
|
116
|
-
|
|
117
|
-
# This one is in ThemeIcon but not on Windows for some reason
|
|
118
|
-
ICON_CANCEL = os.path.join(PKGDIR, 'icons/process-stop.png')
|
|
115
|
+
ICON_OPTIONS = os.path.join(PKGDIR, 'icons/fallback/preferences-system.svg')
|
|
116
|
+
ICON_FILES = os.path.join(PKGDIR, 'icons/fallback/folder.svg')
|
|
119
117
|
|
|
120
118
|
LOGO_LIGHT = os.path.join(PKGDIR, 'icons/audio_tuner_logo_light.svg')
|
|
121
119
|
LOGO_DARK = os.path.join(PKGDIR, 'icons/audio_tuner_logo_dark.svg')
|
|
@@ -133,7 +131,7 @@ try:
|
|
|
133
131
|
ICON_CLEAR = QIcon.ThemeIcon.EditDelete
|
|
134
132
|
ICON_EXIT = QIcon.ThemeIcon.ApplicationExit
|
|
135
133
|
# ICON_OPTIONS = 'preferences-other'
|
|
136
|
-
|
|
134
|
+
ICON_CANCEL = QIcon.ThemeIcon.ProcessStop
|
|
137
135
|
ICON_ABOUT = QIcon.ThemeIcon.HelpAbout
|
|
138
136
|
ICON_MESSAGE_LOG = QIcon.ThemeIcon.FormatJustifyLeft
|
|
139
137
|
# ICON_FILES = 'folder'
|
|
@@ -145,6 +143,8 @@ try:
|
|
|
145
143
|
ICON_PLAYER_BACK = QIcon.ThemeIcon.MediaSeekBackward
|
|
146
144
|
ICON_PLAYER_FORWARD = QIcon.ThemeIcon.MediaSeekForward
|
|
147
145
|
ICON_AUDIO_DEVICE = QIcon.ThemeIcon.AudioCard
|
|
146
|
+
ICON_SAVE = QIcon.ThemeIcon.DocumentSave
|
|
147
|
+
ICON_DEL = QIcon.ThemeIcon.EditDelete
|
|
148
148
|
except AttributeError:
|
|
149
149
|
ICON_BACK = 'go-previous'
|
|
150
150
|
ICON_FORWARD = 'go-next'
|
|
@@ -154,7 +154,7 @@ except AttributeError:
|
|
|
154
154
|
ICON_CLEAR = 'edit-delete'
|
|
155
155
|
ICON_EXIT = 'application-exit'
|
|
156
156
|
# ICON_OPTIONS = 'preferences-other'
|
|
157
|
-
|
|
157
|
+
ICON_CANCEL = 'process-stop'
|
|
158
158
|
ICON_ABOUT = 'help-about'
|
|
159
159
|
ICON_MESSAGE_LOG = 'format-justify-left'
|
|
160
160
|
# ICON_FILES = 'folder'
|
|
@@ -166,6 +166,8 @@ except AttributeError:
|
|
|
166
166
|
ICON_PLAYER_BACK = 'media-seek-backward'
|
|
167
167
|
ICON_PLAYER_FORWARD = 'media-seek-forward'
|
|
168
168
|
ICON_AUDIO_DEVICE = 'audio-card'
|
|
169
|
+
ICON_SAVE = 'document-save'
|
|
170
|
+
ICON_DEL = 'edit-delete'
|
|
169
171
|
|
|
170
172
|
|
|
171
173
|
class RowData(TypedDict):
|
|
@@ -275,25 +277,76 @@ class Options(dict):
|
|
|
275
277
|
|
|
276
278
|
def __init__(self, args):
|
|
277
279
|
super().__init__()
|
|
278
|
-
|
|
279
|
-
self
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
280
|
+
|
|
281
|
+
self._options_and_args = (
|
|
282
|
+
(OPTION_TUNING_SYSTEM, 'tuning'),
|
|
283
|
+
(OPTION_REF_FREQ, 'ref_freq'),
|
|
284
|
+
(OPTION_REF_NOTE, 'ref_note'),
|
|
285
|
+
(OPTION_START, 'start'),
|
|
286
|
+
(OPTION_END, 'end'),
|
|
287
|
+
(OPTION_LOW_CUT, 'low_cut'),
|
|
288
|
+
(OPTION_HIGH_CUT, 'high_cut'),
|
|
289
|
+
(OPTION_DB_RANGE, 'dB_range'),
|
|
290
|
+
(OPTION_MAX_PEAKS, 'max_peaks'),
|
|
291
|
+
(OPTION_SIZE_EXP, 'size_exp'),
|
|
292
|
+
(OPTION_SAMPLERATE, 'samplerate'),
|
|
293
|
+
(OPTION_BACKENDS, 'backends'),
|
|
294
|
+
(OPTION_PITCH, 'pitch'),
|
|
295
|
+
(OPTION_TEMPO, 'tempo'),
|
|
296
|
+
)
|
|
297
|
+
|
|
298
|
+
for opt, arg in self._options_and_args:
|
|
299
|
+
if arg == 'tuning':
|
|
300
|
+
val = getattr(args, arg)
|
|
301
|
+
self[opt] = self._guify_tuning_system(val)
|
|
302
|
+
else:
|
|
303
|
+
self[opt] = getattr(args, arg)
|
|
293
304
|
|
|
294
305
|
def _guify_tuning_system(self, tuning_system):
|
|
295
306
|
return tuning_system.replace('_', ' ').title()
|
|
296
307
|
|
|
308
|
+
def _unguify_tuning_system(self, tuning_system):
|
|
309
|
+
return tuning_system.replace(' ', '_').lower()
|
|
310
|
+
|
|
311
|
+
def argparse_namespace(self) -> Namespace:
|
|
312
|
+
"""Return an argparse.Namespace object containing the options.
|
|
313
|
+
|
|
314
|
+
Returns
|
|
315
|
+
-------
|
|
316
|
+
argparse.Namespace
|
|
317
|
+
The options.
|
|
318
|
+
"""
|
|
319
|
+
|
|
320
|
+
args = Namespace()
|
|
321
|
+
|
|
322
|
+
for opt, arg in self._options_and_args:
|
|
323
|
+
if arg == 'tuning':
|
|
324
|
+
val = self[opt]
|
|
325
|
+
setattr(args, arg, self._unguify_tuning_system(val))
|
|
326
|
+
else:
|
|
327
|
+
setattr(args, arg, self[opt])
|
|
328
|
+
|
|
329
|
+
return args
|
|
330
|
+
|
|
331
|
+
def merge_args(self, args: Namespace):
|
|
332
|
+
"""Modify the options according to the arguments in an
|
|
333
|
+
argparse.Namespace object.
|
|
334
|
+
|
|
335
|
+
Parameters
|
|
336
|
+
----------
|
|
337
|
+
args : argparse.Namespace
|
|
338
|
+
The arguments.
|
|
339
|
+
"""
|
|
340
|
+
|
|
341
|
+
for opt, arg in self._options_and_args:
|
|
342
|
+
if arg == 'tuning':
|
|
343
|
+
if (val := getattr(args, arg, None)) is not None:
|
|
344
|
+
self[opt] = self._guify_tuning_system(val)
|
|
345
|
+
else:
|
|
346
|
+
if (val := getattr(args, arg, None)) is not None:
|
|
347
|
+
self[opt] = val
|
|
348
|
+
|
|
349
|
+
|
|
297
350
|
def redo_level(self, old_options):
|
|
298
351
|
"""Compare the options stored in this instance of Options to the
|
|
299
352
|
options stored in an older one to find out how much analysis
|
|
@@ -313,7 +366,6 @@ class Options(dict):
|
|
|
313
366
|
if (old_options is None
|
|
314
367
|
or self[OPTION_START] != old_options[OPTION_START]
|
|
315
368
|
or self[OPTION_END] != old_options[OPTION_END]
|
|
316
|
-
or self[OPTION_PAD] != old_options[OPTION_PAD]
|
|
317
369
|
or self[OPTION_SIZE_EXP] != old_options[OPTION_SIZE_EXP]
|
|
318
370
|
or self[OPTION_BACKENDS] != old_options[OPTION_BACKENDS]
|
|
319
371
|
or self[OPTION_SAMPLERATE] != old_options[OPTION_SAMPLERATE]):
|