audio-tuner-gui 0.9.1__py3-none-any.whl
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/__init__.py +39 -0
- audio_tuner_gui/analysis.py +717 -0
- audio_tuner_gui/common.py +350 -0
- audio_tuner_gui/display.py +605 -0
- audio_tuner_gui/export.py +563 -0
- audio_tuner_gui/file_selector.py +435 -0
- audio_tuner_gui/icons/audio_tuner_icon.ico +0 -0
- audio_tuner_gui/icons/audio_tuner_icon.svg +215 -0
- audio_tuner_gui/icons/audio_tuner_icon_hires.svg +215 -0
- audio_tuner_gui/icons/audio_tuner_logo_dark.svg +563 -0
- audio_tuner_gui/icons/audio_tuner_logo_light.svg +563 -0
- audio_tuner_gui/icons/black-logo.svg +24 -0
- audio_tuner_gui/icons/folder.png +0 -0
- audio_tuner_gui/icons/gpl-v3-logo_red.svg +223 -0
- audio_tuner_gui/icons/gpl-v3-logo_white.svg +224 -0
- audio_tuner_gui/icons/preferences-other.png +0 -0
- audio_tuner_gui/icons/process-stop.png +0 -0
- audio_tuner_gui/icons/white-logo.svg +24 -0
- audio_tuner_gui/log_viewer.py +154 -0
- audio_tuner_gui/option_panel.py +683 -0
- audio_tuner_gui/player.py +757 -0
- audio_tuner_gui/scripts/__init__.py +0 -0
- audio_tuner_gui/scripts/tuner_gui.py +863 -0
- audio_tuner_gui-0.9.1.dist-info/METADATA +93 -0
- audio_tuner_gui-0.9.1.dist-info/RECORD +28 -0
- audio_tuner_gui-0.9.1.dist-info/WHEEL +4 -0
- audio_tuner_gui-0.9.1.dist-info/entry_points.txt +2 -0
- audio_tuner_gui-0.9.1.dist-info/licenses/COPYING +674 -0
|
@@ -0,0 +1,605 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
#
|
|
3
|
+
# This file is part of Audio Tuner.
|
|
4
|
+
#
|
|
5
|
+
# Copyright 2025, 2026 Jessie Blue Cassell <bluesloth600@gmail.com>
|
|
6
|
+
#
|
|
7
|
+
# This program is free software: you can redistribute it and/or modify
|
|
8
|
+
# it under the terms of the GNU General Public License as published by
|
|
9
|
+
# the Free Software Foundation, either version 3 of the License, or
|
|
10
|
+
# (at your option) any later version.
|
|
11
|
+
#
|
|
12
|
+
# This program is distributed in the hope that it will be useful,
|
|
13
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
14
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
15
|
+
# GNU General Public License for more details.
|
|
16
|
+
#
|
|
17
|
+
# You should have received a copy of the GNU General Public License
|
|
18
|
+
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
"""Analysis display widget for the GUI."""
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
__author__ = 'Jessie Blue Cassell'
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
__all__ = [
|
|
28
|
+
'Display',
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
import math
|
|
33
|
+
|
|
34
|
+
from PyQt6.QtCore import (
|
|
35
|
+
Qt,
|
|
36
|
+
pyqtSignal,
|
|
37
|
+
QRectF,
|
|
38
|
+
QPointF,
|
|
39
|
+
)
|
|
40
|
+
from PyQt6.QtWidgets import (
|
|
41
|
+
QGraphicsView,
|
|
42
|
+
QGraphicsScene,
|
|
43
|
+
QGraphicsItem,
|
|
44
|
+
QGraphicsTextItem,
|
|
45
|
+
QGraphicsLineItem,
|
|
46
|
+
QGraphicsPolygonItem,
|
|
47
|
+
QGraphicsRectItem,
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
from PyQt6.QtGui import (
|
|
51
|
+
QColor,
|
|
52
|
+
QPainter,
|
|
53
|
+
QPen,
|
|
54
|
+
QBrush,
|
|
55
|
+
QTransform,
|
|
56
|
+
QPolygonF,
|
|
57
|
+
QLinearGradient,
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
import audio_tuner.common as com
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
DISPLAY_BG_COLOR = QColor(10, 10, 20)
|
|
64
|
+
_DISPLAY_HEADER_COLOR = QColor(200, 200, 200)
|
|
65
|
+
_DISPLAY_BRIGHT_COLOR = QColor(20, 180, 30)
|
|
66
|
+
DISPLAY_DATA_COLOR = QColor(10, 150, 20)
|
|
67
|
+
_METER_SCALE_COLOR = QColor(100, 100, 100)
|
|
68
|
+
_METER_ZERO_COLOR = QColor(240, 240, 240)
|
|
69
|
+
|
|
70
|
+
_TITLE_X = .015
|
|
71
|
+
_TITLE_Y = .033
|
|
72
|
+
_TITLE_H = .045
|
|
73
|
+
|
|
74
|
+
_HEADER_Y = .07
|
|
75
|
+
_HEADER_H = .035
|
|
76
|
+
_HEADER_DATA = (
|
|
77
|
+
('Note', .025),
|
|
78
|
+
('Standard', .142),
|
|
79
|
+
('Measured', .295),
|
|
80
|
+
('Discrepancy', .445),
|
|
81
|
+
('Correction', .87),
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
class _DisplayScene(QGraphicsScene):
|
|
86
|
+
def drawBackground(self, painter, rect):
|
|
87
|
+
w = self.width()
|
|
88
|
+
h = self.height()
|
|
89
|
+
margin = 1
|
|
90
|
+
x = margin
|
|
91
|
+
y = margin
|
|
92
|
+
w -= 2 * margin
|
|
93
|
+
h -= 2 * margin
|
|
94
|
+
painter.setBrush(DISPLAY_BG_COLOR)
|
|
95
|
+
painter.setPen(Qt.PenStyle.NoPen)
|
|
96
|
+
r = .02 * w
|
|
97
|
+
painter.drawRoundedRect(QRectF(x, y, w, h), r, r)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
class _Text(QGraphicsTextItem):
|
|
101
|
+
def __init__(self, string, color):
|
|
102
|
+
super().__init__(string)
|
|
103
|
+
self.setDefaultTextColor(color)
|
|
104
|
+
self.setTextInteractionFlags(
|
|
105
|
+
Qt.TextInteractionFlag.TextSelectableByMouse)
|
|
106
|
+
self.setCursor(Qt.CursorShape.IBeamCursor)
|
|
107
|
+
|
|
108
|
+
def set_geo(self, x, y, h=None, right=False):
|
|
109
|
+
self.orig_w = self.boundingRect().width()
|
|
110
|
+
self.orig_h = self.boundingRect().height()
|
|
111
|
+
parent = self.parentItem()
|
|
112
|
+
if parent:
|
|
113
|
+
parent_scale = parent.scale()
|
|
114
|
+
else:
|
|
115
|
+
parent_scale = 1.0
|
|
116
|
+
if h:
|
|
117
|
+
scalefactor = h / (self.orig_h * parent_scale)
|
|
118
|
+
self.setScale(scalefactor)
|
|
119
|
+
self.scalefactor = scalefactor
|
|
120
|
+
else:
|
|
121
|
+
scalefactor = self.scale() * parent_scale
|
|
122
|
+
h = self.orig_h * scalefactor
|
|
123
|
+
if right:
|
|
124
|
+
x -= self.orig_w * scalefactor
|
|
125
|
+
y -= h/2
|
|
126
|
+
self.setPos(x / parent_scale, y)
|
|
127
|
+
|
|
128
|
+
def set_squish(self, squish_factor):
|
|
129
|
+
self.setTransform(QTransform(squish_factor, 0, 0,
|
|
130
|
+
1, 0, 0),
|
|
131
|
+
combine=False)
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
class _TitleText():
|
|
135
|
+
def __init__(self, scene, string, color):
|
|
136
|
+
text = _Text(string, color)
|
|
137
|
+
scene.addItem(text)
|
|
138
|
+
self.text = text
|
|
139
|
+
|
|
140
|
+
def update_data(self, string):
|
|
141
|
+
self.text.setPlainText(string)
|
|
142
|
+
self.set_geo(self.x, self.y, self.h, self.max_w)
|
|
143
|
+
|
|
144
|
+
def set_geo(self, x, y, h, max_w=None):
|
|
145
|
+
self.x = x
|
|
146
|
+
self.y = y
|
|
147
|
+
self.h = h
|
|
148
|
+
self.max_w = max_w
|
|
149
|
+
self.text.set_geo(x, y, h)
|
|
150
|
+
if max_w is not None:
|
|
151
|
+
w = self.text.boundingRect().width() * self.text.scale()
|
|
152
|
+
squish_factor = max_w / w
|
|
153
|
+
if squish_factor < 1:
|
|
154
|
+
self.text.set_squish(squish_factor)
|
|
155
|
+
else:
|
|
156
|
+
self.text.set_squish(1)
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
class _Headers():
|
|
161
|
+
def __init__(self, scene, color):
|
|
162
|
+
self.data = _HEADER_DATA
|
|
163
|
+
|
|
164
|
+
self.headers = []
|
|
165
|
+
for x in self.data:
|
|
166
|
+
header = _Text(x[0], color)
|
|
167
|
+
self.headers.append(header)
|
|
168
|
+
scene.addItem(header)
|
|
169
|
+
header.setZValue(2)
|
|
170
|
+
|
|
171
|
+
def show(self):
|
|
172
|
+
for header in self.headers:
|
|
173
|
+
header.show()
|
|
174
|
+
|
|
175
|
+
def hide(self):
|
|
176
|
+
for header in self.headers:
|
|
177
|
+
header.hide()
|
|
178
|
+
|
|
179
|
+
def update_size(self, view_w, view_h):
|
|
180
|
+
for i, header in enumerate(self.headers):
|
|
181
|
+
x = self.data[i][1] * view_w
|
|
182
|
+
y = _HEADER_Y * view_w
|
|
183
|
+
h = _HEADER_H * view_w
|
|
184
|
+
header.set_geo(x, y, h)
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
class _Meter():
|
|
188
|
+
def __init__(self):
|
|
189
|
+
self.main_thickness = .4
|
|
190
|
+
needle_length = 6
|
|
191
|
+
needle_thickness = 2.6
|
|
192
|
+
needle_middle = 1
|
|
193
|
+
minor_tick_length = 1
|
|
194
|
+
major_tick_length = 2
|
|
195
|
+
|
|
196
|
+
main_color = _METER_SCALE_COLOR
|
|
197
|
+
zero_color = _METER_ZERO_COLOR
|
|
198
|
+
needle_color = _DISPLAY_BRIGHT_COLOR
|
|
199
|
+
|
|
200
|
+
main_pen = QPen(main_color, self.main_thickness)
|
|
201
|
+
zero_pen = QPen(zero_color, self.main_thickness)
|
|
202
|
+
needle_brush = QBrush(needle_color)
|
|
203
|
+
self.mainline = QGraphicsLineItem(-50, 0, 50, 0)
|
|
204
|
+
self.mainline.setPen(main_pen)
|
|
205
|
+
|
|
206
|
+
needlepoly = QPolygonF((
|
|
207
|
+
QPointF(-needle_thickness / 2, needle_length / 2),
|
|
208
|
+
QPointF(needle_thickness / 2, needle_length / 2),
|
|
209
|
+
QPointF(needle_middle / 2, 0),
|
|
210
|
+
QPointF(needle_thickness / 2, -needle_length / 2),
|
|
211
|
+
QPointF(-needle_thickness / 2, -needle_length / 2),
|
|
212
|
+
QPointF(-needle_middle / 2, 0),
|
|
213
|
+
))
|
|
214
|
+
needle = QGraphicsPolygonItem(needlepoly)
|
|
215
|
+
needle.setPen(QPen(Qt.PenStyle.NoPen))
|
|
216
|
+
needle.setBrush(needle_brush)
|
|
217
|
+
needle.setParentItem(self.mainline)
|
|
218
|
+
needle.setFlag(QGraphicsItem.GraphicsItemFlag.ItemStacksBehindParent)
|
|
219
|
+
self.needle = needle
|
|
220
|
+
|
|
221
|
+
ghostneedle = QGraphicsPolygonItem(needlepoly)
|
|
222
|
+
ghostneedle.setPen(QPen(needle_color, self.main_thickness / 2))
|
|
223
|
+
ghostneedle.setBrush(QBrush(Qt.BrushStyle.NoBrush))
|
|
224
|
+
ghostneedle.setParentItem(self.needle)
|
|
225
|
+
ghostneedle.setFlag(
|
|
226
|
+
QGraphicsItem.GraphicsItemFlag.ItemStacksBehindParent)
|
|
227
|
+
self.ghostneedle = ghostneedle
|
|
228
|
+
|
|
229
|
+
for x in range(-50, 51, 25):
|
|
230
|
+
if x == 0:
|
|
231
|
+
pen = zero_pen
|
|
232
|
+
else:
|
|
233
|
+
pen = main_pen
|
|
234
|
+
if x % 50 == 0:
|
|
235
|
+
y = major_tick_length
|
|
236
|
+
else:
|
|
237
|
+
y = minor_tick_length
|
|
238
|
+
QGraphicsLineItem(x, 0,
|
|
239
|
+
x, y,
|
|
240
|
+
self.mainline).setPen(pen)
|
|
241
|
+
|
|
242
|
+
for x in range(975, 1026, 5):
|
|
243
|
+
if x == 1000:
|
|
244
|
+
pen = zero_pen
|
|
245
|
+
else:
|
|
246
|
+
pen = main_pen
|
|
247
|
+
if x % 10 == 0:
|
|
248
|
+
y = major_tick_length
|
|
249
|
+
else:
|
|
250
|
+
y = minor_tick_length
|
|
251
|
+
x = (-1.0) * com.ratio_to_cents(x/1000)
|
|
252
|
+
QGraphicsLineItem(x, 0,
|
|
253
|
+
x, -y,
|
|
254
|
+
self.mainline).setPen(pen)
|
|
255
|
+
|
|
256
|
+
def setParentItem(self, parent):
|
|
257
|
+
self.parent = parent
|
|
258
|
+
self.mainline.setParentItem(parent)
|
|
259
|
+
|
|
260
|
+
def set_data(self, cents):
|
|
261
|
+
self.needle.setPos(cents, 0)
|
|
262
|
+
|
|
263
|
+
def set_ghost_offset(self, cents):
|
|
264
|
+
self.ghostneedle.setPos(cents, 0)
|
|
265
|
+
|
|
266
|
+
def set_geo(self, x, y, w):
|
|
267
|
+
scalefactor = w / 100
|
|
268
|
+
self.mainline.setScale(scalefactor)
|
|
269
|
+
self.mainline.setPos(x, y)
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
class _Row():
|
|
273
|
+
pass
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
class _ReferencePoint(QGraphicsItem):
|
|
277
|
+
def boundingRect(self):
|
|
278
|
+
return QRectF(self.x(), self.y(), 0, 0)
|
|
279
|
+
|
|
280
|
+
def paint(self, a, b, c):
|
|
281
|
+
pass
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
class _ClipRect(QGraphicsRectItem):
|
|
285
|
+
def paint(self, a, b, c):
|
|
286
|
+
pass
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
class _Rows(list):
|
|
290
|
+
def __init__(self, scene):
|
|
291
|
+
super().__init__()
|
|
292
|
+
self.view_w = None
|
|
293
|
+
|
|
294
|
+
self.color1 = DISPLAY_DATA_COLOR
|
|
295
|
+
|
|
296
|
+
self.vertical_offset = 0.0
|
|
297
|
+
|
|
298
|
+
self.scene = scene
|
|
299
|
+
|
|
300
|
+
self.cliprect = _ClipRect()
|
|
301
|
+
self.cliprect.setFlag(
|
|
302
|
+
QGraphicsItem.GraphicsItemFlag.ItemClipsChildrenToShape)
|
|
303
|
+
self.cliprect.setFlag(
|
|
304
|
+
QGraphicsItem.GraphicsItemFlag.ItemHasNoContents)
|
|
305
|
+
scene.addItem(self.cliprect)
|
|
306
|
+
|
|
307
|
+
self.headers = _Headers(scene, _DISPLAY_HEADER_COLOR)
|
|
308
|
+
self.headers.hide()
|
|
309
|
+
|
|
310
|
+
self.top_fade = QGraphicsRectItem()
|
|
311
|
+
self.bot_fade = QGraphicsRectItem()
|
|
312
|
+
self.top_fade.setZValue(1)
|
|
313
|
+
self.bot_fade.setZValue(1)
|
|
314
|
+
self.top_fade.setPen(QPen(Qt.PenStyle.NoPen))
|
|
315
|
+
self.bot_fade.setPen(QPen(Qt.PenStyle.NoPen))
|
|
316
|
+
top_grad = QLinearGradient()
|
|
317
|
+
top_grad.setCoordinateMode(QLinearGradient.CoordinateMode.ObjectMode)
|
|
318
|
+
transparent = QColor(DISPLAY_BG_COLOR)
|
|
319
|
+
transparent.setAlpha(0)
|
|
320
|
+
top_grad.setColorAt(1, transparent)
|
|
321
|
+
top_grad.setColorAt(0, DISPLAY_BG_COLOR)
|
|
322
|
+
top_grad.setStart(QPointF(0, 0))
|
|
323
|
+
top_grad.setFinalStop(QPointF(0, 1))
|
|
324
|
+
bot_grad = QLinearGradient(top_grad)
|
|
325
|
+
bot_grad.setStart(QPointF(0, 1))
|
|
326
|
+
bot_grad.setFinalStop(QPointF(0, 0))
|
|
327
|
+
self.top_fade.setBrush(QBrush(top_grad))
|
|
328
|
+
self.bot_fade.setBrush(QBrush(bot_grad))
|
|
329
|
+
scene.addItem(self.top_fade)
|
|
330
|
+
scene.addItem(self.bot_fade)
|
|
331
|
+
|
|
332
|
+
self.bar = QGraphicsRectItem()
|
|
333
|
+
self.bar.setZValue(1.5)
|
|
334
|
+
self.bar.setBrush(QBrush(self.color1))
|
|
335
|
+
self.bar.setPen(QPen(Qt.PenStyle.NoPen))
|
|
336
|
+
self.bar.hide()
|
|
337
|
+
scene.addItem(self.bar)
|
|
338
|
+
|
|
339
|
+
def add_row(self):
|
|
340
|
+
row = _Row()
|
|
341
|
+
row.parent = _ReferencePoint()
|
|
342
|
+
row.parent.setFlag(
|
|
343
|
+
QGraphicsItem.GraphicsItemFlag.ItemHasNoContents)
|
|
344
|
+
if len(self) == 0:
|
|
345
|
+
self.headers.show()
|
|
346
|
+
row.parent.setParentItem(self.cliprect)
|
|
347
|
+
row.parent.setFlag(
|
|
348
|
+
QGraphicsItem.GraphicsItemFlag.ItemIsMovable)
|
|
349
|
+
else:
|
|
350
|
+
row.parent.setParentItem(self[0].parent)
|
|
351
|
+
row.note = _Text(' ', self.color1)
|
|
352
|
+
row.note.setParentItem(row.parent)
|
|
353
|
+
row.standard = _Text(' ', self.color1)
|
|
354
|
+
row.standard.setParentItem(row.parent)
|
|
355
|
+
row.measured = _Text(' ', self.color1)
|
|
356
|
+
row.measured.setParentItem(row.parent)
|
|
357
|
+
row.cents = _Text(' ', self.color1)
|
|
358
|
+
row.cents.setParentItem(row.parent)
|
|
359
|
+
row.meter = _Meter()
|
|
360
|
+
row.meter.setParentItem(row.parent)
|
|
361
|
+
row.correction = _Text(' ', self.color1)
|
|
362
|
+
row.correction.setParentItem(row.parent)
|
|
363
|
+
self.append(row)
|
|
364
|
+
|
|
365
|
+
def remove_row(self):
|
|
366
|
+
self.scene.removeItem(self[-1].parent)
|
|
367
|
+
del self[-1]
|
|
368
|
+
if len(self) == 0:
|
|
369
|
+
self.headers.hide()
|
|
370
|
+
|
|
371
|
+
def update_data(self, result_rows):
|
|
372
|
+
"""Update the data displayed in the rows.
|
|
373
|
+
|
|
374
|
+
Parameters
|
|
375
|
+
----------
|
|
376
|
+
result_rows : list[audio_tuner_gui.common.RowData]
|
|
377
|
+
The row data for each row.
|
|
378
|
+
"""
|
|
379
|
+
|
|
380
|
+
while len(self) > len(result_rows):
|
|
381
|
+
self.remove_row()
|
|
382
|
+
while len(self) < len(result_rows):
|
|
383
|
+
self.add_row()
|
|
384
|
+
|
|
385
|
+
for i, row in enumerate(result_rows):
|
|
386
|
+
self[i].note.setPlainText(row['note'])
|
|
387
|
+
self[i].standard.setPlainText(f'{row["standard"]:>8.2f} Hz')
|
|
388
|
+
self[i].measured.setPlainText(f'{row["measured"]:>8.2f} Hz')
|
|
389
|
+
self[i].cents.setPlainText(f'{row["cents"]:>+3.0f} c')
|
|
390
|
+
self[i].meter.set_data(row['cents'])
|
|
391
|
+
self[i].correction.setPlainText(f'{row["correction"]:>8.3f}')
|
|
392
|
+
self.update_size(self.view_w, self.view_h, force=True)
|
|
393
|
+
|
|
394
|
+
def update_ghost_offset(self, cents):
|
|
395
|
+
for row in self:
|
|
396
|
+
row.meter.set_ghost_offset(cents)
|
|
397
|
+
|
|
398
|
+
def update_size(self, view_w, view_h, force=False):
|
|
399
|
+
old_view_w = self.view_w
|
|
400
|
+
self.view_w = view_w
|
|
401
|
+
self.view_h = view_h
|
|
402
|
+
clip_x = .023 * view_w
|
|
403
|
+
clip_y = .08 * view_w - 2
|
|
404
|
+
self.clip_y = clip_y
|
|
405
|
+
clip_w = .962 * view_w
|
|
406
|
+
clip_h = view_h - (clip_y + 6)
|
|
407
|
+
self.clip_h = clip_h
|
|
408
|
+
fade_x = clip_x
|
|
409
|
+
fade_w = clip_w - 5
|
|
410
|
+
fade_h = view_w * .015
|
|
411
|
+
self.cliprect.setRect(QRectF(clip_x, clip_y,
|
|
412
|
+
clip_w, clip_h))
|
|
413
|
+
self.top_fade.setRect(QRectF(fade_x, clip_y - 2,
|
|
414
|
+
fade_w, fade_h))
|
|
415
|
+
self.bot_fade.setRect(QRectF(fade_x, clip_y + clip_h - fade_h + 2,
|
|
416
|
+
fade_w, fade_h))
|
|
417
|
+
for i, row in enumerate(self):
|
|
418
|
+
h = .04 * view_w
|
|
419
|
+
if i == 0:
|
|
420
|
+
row.parent.setPos(clip_x,
|
|
421
|
+
.1 * view_w + self.vertical_offset)
|
|
422
|
+
elif old_view_w != view_w or force:
|
|
423
|
+
row.parent.setPos(0, .03 * i * view_w)
|
|
424
|
+
self.headers.update_size(view_w, view_h)
|
|
425
|
+
if old_view_w != view_w or force:
|
|
426
|
+
row.note.set_geo(0, 0, h)
|
|
427
|
+
row.standard.set_geo(.24 * view_w, 0, h, right=True)
|
|
428
|
+
row.measured.set_geo(.4 * view_w, 0, h, right=True)
|
|
429
|
+
row.cents.set_geo(.486 * view_w, 0, h, right=True)
|
|
430
|
+
row.meter.set_geo(.68 * view_w, 0, .37 * view_w)
|
|
431
|
+
row.correction.set_geo(.855 * view_w, 0, h, right=False)
|
|
432
|
+
self.max_drag = (len(self) * .03 * self.view_w
|
|
433
|
+
- self.view_h
|
|
434
|
+
+ .11 * self.view_w)
|
|
435
|
+
self.vertical_drag(0)
|
|
436
|
+
self.update_bar()
|
|
437
|
+
|
|
438
|
+
def update_bar(self):
|
|
439
|
+
try:
|
|
440
|
+
bar_h_factor = ((self.view_h - .11 * self.view_w)
|
|
441
|
+
/ (len(self) * .03 * self.view_w))
|
|
442
|
+
except ZeroDivisionError:
|
|
443
|
+
bar_h_factor = 2
|
|
444
|
+
if bar_h_factor < 1:
|
|
445
|
+
bar_h = bar_h_factor * self.clip_h
|
|
446
|
+
self.bar_h = bar_h
|
|
447
|
+
bar_offset = ((-self.vertical_offset / self.max_drag)
|
|
448
|
+
* (self.clip_h - bar_h))
|
|
449
|
+
self.bar.setRect(QRectF(.985 * self.view_w,
|
|
450
|
+
self.clip_y + bar_offset,
|
|
451
|
+
.005 * self.view_w,
|
|
452
|
+
bar_h))
|
|
453
|
+
self.bar.show()
|
|
454
|
+
else:
|
|
455
|
+
self.bar.hide()
|
|
456
|
+
|
|
457
|
+
def vertical_drag(self, event, scroll_bar_click=False):
|
|
458
|
+
if scroll_bar_click:
|
|
459
|
+
event = -((event / (self.clip_h - self.bar_h)) * self.max_drag)
|
|
460
|
+
delta = max(event, -self.max_drag - self.vertical_offset)
|
|
461
|
+
delta = min(delta, -self.vertical_offset)
|
|
462
|
+
try:
|
|
463
|
+
self[0].parent.moveBy(0, delta)
|
|
464
|
+
except IndexError:
|
|
465
|
+
return
|
|
466
|
+
self.vertical_offset += delta
|
|
467
|
+
self.update_bar()
|
|
468
|
+
|
|
469
|
+
def one_up(self):
|
|
470
|
+
self.vertical_drag(.03 * self.view_w)
|
|
471
|
+
|
|
472
|
+
def one_down(self):
|
|
473
|
+
self.vertical_drag(-.03 * self.view_w)
|
|
474
|
+
|
|
475
|
+
def page_up(self):
|
|
476
|
+
n = math.floor((self.view_h - .1 * self.view_w) / (.03 * self.view_w))
|
|
477
|
+
self.vertical_drag(.03 * self.view_w * n)
|
|
478
|
+
|
|
479
|
+
def page_down(self):
|
|
480
|
+
n = math.floor((self.view_h - .1 * self.view_w) / (.03 * self.view_w))
|
|
481
|
+
self.vertical_drag(-.03 * self.view_w * n)
|
|
482
|
+
|
|
483
|
+
|
|
484
|
+
class Display(QGraphicsView):
|
|
485
|
+
"""A widget that displays the results of the analysis. Inherits
|
|
486
|
+
from QGraphicsView.
|
|
487
|
+
"""
|
|
488
|
+
|
|
489
|
+
VerticalDrag = pyqtSignal(float, bool)
|
|
490
|
+
"""
|
|
491
|
+
"""
|
|
492
|
+
|
|
493
|
+
def __init__(self):
|
|
494
|
+
self._w = 101
|
|
495
|
+
self._h = 101
|
|
496
|
+
self._old_w = 101
|
|
497
|
+
self._old_h = 101
|
|
498
|
+
self._scroll_bar_click = False
|
|
499
|
+
|
|
500
|
+
self._margin = 10
|
|
501
|
+
|
|
502
|
+
self._scene = _DisplayScene(0, 0, self._w, self._h)
|
|
503
|
+
|
|
504
|
+
super().__init__(self._scene)
|
|
505
|
+
|
|
506
|
+
self._init_display(' ')
|
|
507
|
+
|
|
508
|
+
self.setRenderHint(QPainter.RenderHint.Antialiasing)
|
|
509
|
+
|
|
510
|
+
self.setAcceptDrops(False)
|
|
511
|
+
|
|
512
|
+
def _init_display(self, title):
|
|
513
|
+
self._title = _TitleText(self._scene, title, DISPLAY_DATA_COLOR)
|
|
514
|
+
|
|
515
|
+
self._rows = _Rows(self._scene)
|
|
516
|
+
self.VerticalDrag.connect(self._rows.vertical_drag,
|
|
517
|
+
Qt.ConnectionType.QueuedConnection)
|
|
518
|
+
|
|
519
|
+
def mousePressEvent(self, event):
|
|
520
|
+
xpos = ((event.pos().x() - self._margin)
|
|
521
|
+
/ (self._total_w - 2 * self._margin))
|
|
522
|
+
self._scroll_bar_click = xpos > .983
|
|
523
|
+
self._prev_mouse_pos = event.globalPosition().y()
|
|
524
|
+
super().mousePressEvent(event)
|
|
525
|
+
|
|
526
|
+
def mouseMoveEvent(self, event):
|
|
527
|
+
if event.buttons() & Qt.MouseButton.LeftButton:
|
|
528
|
+
mouse_pos = event.globalPosition().y()
|
|
529
|
+
mouse_delta = mouse_pos - self._prev_mouse_pos
|
|
530
|
+
self._prev_mouse_pos = mouse_pos
|
|
531
|
+
self.VerticalDrag.emit(mouse_delta, self._scroll_bar_click)
|
|
532
|
+
super().mouseMoveEvent(event)
|
|
533
|
+
|
|
534
|
+
def wheelEvent(self, event):
|
|
535
|
+
self._rows.vertical_drag(event.angleDelta().y())
|
|
536
|
+
|
|
537
|
+
def keyPressEvent(self, event):
|
|
538
|
+
if event.key() == Qt.Key.Key_Up:
|
|
539
|
+
self._rows.one_up()
|
|
540
|
+
if event.key() == Qt.Key.Key_Down:
|
|
541
|
+
self._rows.one_down()
|
|
542
|
+
if event.key() == Qt.Key.Key_PageUp:
|
|
543
|
+
self._rows.page_up()
|
|
544
|
+
if event.key() == Qt.Key.Key_PageDown:
|
|
545
|
+
self._rows.page_down()
|
|
546
|
+
super().keyPressEvent(event)
|
|
547
|
+
|
|
548
|
+
def resizeEvent(self, event):
|
|
549
|
+
margin = self._margin
|
|
550
|
+
self._total_w = event.size().width()
|
|
551
|
+
self._total_h = event.size().height()
|
|
552
|
+
w = self._total_w - 2 * margin
|
|
553
|
+
h = self._total_h - 2 * margin
|
|
554
|
+
# force even numbers to prevent jitter in the position of the
|
|
555
|
+
# display while resizing
|
|
556
|
+
h = (h // 2) * 2
|
|
557
|
+
w = (w // 2) * 2
|
|
558
|
+
self._w = w
|
|
559
|
+
self._h = h
|
|
560
|
+
if self._old_w != w:
|
|
561
|
+
self.setMinimumHeight(int(.12 * event.size().width() + 2 * margin))
|
|
562
|
+
if self._old_w != w or self._old_h != h:
|
|
563
|
+
self._old_w = w
|
|
564
|
+
self._old_h = h
|
|
565
|
+
self.setSceneRect(0, 0, w, h)
|
|
566
|
+
super().resizeEvent(event)
|
|
567
|
+
self.update()
|
|
568
|
+
self._scene.setSceneRect(0, 0, w, h)
|
|
569
|
+
self._title.set_geo(_TITLE_X * w,
|
|
570
|
+
_TITLE_Y * w,
|
|
571
|
+
_TITLE_H * w, w * .98)
|
|
572
|
+
self._rows.update_size(w, h)
|
|
573
|
+
self._scene.update()
|
|
574
|
+
|
|
575
|
+
def _add_row(self):
|
|
576
|
+
pass
|
|
577
|
+
|
|
578
|
+
def update_ghost_offset(self, cents):
|
|
579
|
+
"""Set the offset of the ghost needles away from the main
|
|
580
|
+
needles.
|
|
581
|
+
|
|
582
|
+
Parameters
|
|
583
|
+
----------
|
|
584
|
+
cents : float
|
|
585
|
+
The offset in cents.
|
|
586
|
+
"""
|
|
587
|
+
|
|
588
|
+
self._rows.update_ghost_offset(cents)
|
|
589
|
+
|
|
590
|
+
def update_data(self, filename, result_rows):
|
|
591
|
+
"""Update the displayed data.
|
|
592
|
+
|
|
593
|
+
Parameters
|
|
594
|
+
----------
|
|
595
|
+
filename : str
|
|
596
|
+
The name of the song.
|
|
597
|
+
result_rows : list[audio_tuner_gui.common.RowData]
|
|
598
|
+
The row data for each row.
|
|
599
|
+
"""
|
|
600
|
+
|
|
601
|
+
try:
|
|
602
|
+
self._title.update_data(filename)
|
|
603
|
+
except AttributeError:
|
|
604
|
+
self._init_display(filename)
|
|
605
|
+
self._rows.update_data(result_rows)
|