feat: read-only waveform strip + manual refresh in the Audio tab
This commit is contained in:
@@ -3937,6 +3937,39 @@ def main():
|
||||
sys.exit(ret)
|
||||
|
||||
|
||||
class AudioWaveform(QWidget):
|
||||
"""Read-only waveform strip: paints normalized peak bars for the current
|
||||
audio region. Populated on demand via manual refresh (no implicit UI-thread
|
||||
decode)."""
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
self._peaks: list[float] = []
|
||||
self.setFixedHeight(48)
|
||||
self.setToolTip("Waveform of the current audio area (↻ to refresh)")
|
||||
|
||||
def set_peaks(self, peaks: list[float]) -> None:
|
||||
self._peaks = list(peaks or [])
|
||||
self.update()
|
||||
|
||||
def clear(self) -> None:
|
||||
self._peaks = []
|
||||
self.update()
|
||||
|
||||
def paintEvent(self, _ev):
|
||||
p = QPainter(self)
|
||||
p.fillRect(self.rect(), QColor(30, 30, 30))
|
||||
if not self._peaks:
|
||||
return
|
||||
w = self.width(); h = self.height(); mid = h / 2
|
||||
n = len(self._peaks)
|
||||
p.setPen(QPen(QColor(0, 220, 190)))
|
||||
for i, v in enumerate(self._peaks):
|
||||
x = int(i * w / n)
|
||||
bar_h = v * (h - 4) / 2
|
||||
p.drawLine(x, int(mid - bar_h), x, int(mid + bar_h))
|
||||
|
||||
|
||||
class MainWindow(QMainWindow):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
@@ -4495,6 +4528,11 @@ class MainWindow(QMainWindow):
|
||||
self._spn_gain.setSingleStep(0.5)
|
||||
self._spn_gain.setSuffix(" dB")
|
||||
self._spn_gain.setToolTip("Gain applied to the extracted audio (0 = unchanged)")
|
||||
self._wave = AudioWaveform()
|
||||
self._btn_wave_refresh = QPushButton("↻ Waveform")
|
||||
self._btn_wave_refresh.setToolTip(
|
||||
"Decode the current audio area and draw its waveform")
|
||||
self._btn_wave_refresh.clicked.connect(self._on_wave_refresh)
|
||||
self._transport_row = transport_row
|
||||
|
||||
# Row 1b — subcategory (subprofile) export buttons live on their own
|
||||
@@ -4797,10 +4835,12 @@ class MainWindow(QMainWindow):
|
||||
g.addWidget(self._chk_normalize, 3, 0, 1, 2)
|
||||
g.addWidget(QLabel("Gain:"), 3, 2)
|
||||
g.addWidget(self._spn_gain, 3, 3)
|
||||
# Row 4 reserved for the waveform strip (added in a later task).
|
||||
g.addWidget(self._btn_extract_audio, 5, 0, 1, 4)
|
||||
# Row 4 — read-only waveform strip for the current audio area.
|
||||
g.addWidget(self._wave, 4, 0, 1, 4)
|
||||
g.addWidget(self._btn_wave_refresh, 5, 0, 1, 2)
|
||||
g.addWidget(self._btn_extract_audio, 6, 0, 1, 4)
|
||||
g.setColumnStretch(4, 1) # spare gutter column clusters label/field pairs to the left
|
||||
g.setRowStretch(6, 1) # anchor content to the top (row 4 stays reserved for the waveform)
|
||||
g.setRowStretch(7, 1) # anchor content to the top
|
||||
tb.addItem(extract_page, "Extract & Edit")
|
||||
|
||||
# ── Page 2: Scan / Classify (moved verbatim) ─────────────
|
||||
@@ -6463,6 +6503,21 @@ class MainWindow(QMainWindow):
|
||||
start = self._cursor
|
||||
self._timeline.set_audio_region(start, start + self._spn_audio_len.value())
|
||||
|
||||
def _on_wave_refresh(self) -> None:
|
||||
"""Decode the current audio area and repaint the waveform strip."""
|
||||
if not self._file_path:
|
||||
self._wave.clear()
|
||||
return
|
||||
from core.waveform import load_region_samples, peaks
|
||||
start = self._cursor
|
||||
dur = self._spn_audio_len.value()
|
||||
QApplication.setOverrideCursor(Qt.CursorShape.WaitCursor)
|
||||
try:
|
||||
samples = load_region_samples(self._file_path, start, dur)
|
||||
finally:
|
||||
QApplication.restoreOverrideCursor()
|
||||
self._wave.set_peaks(peaks(samples))
|
||||
|
||||
def _on_extract_audio(self) -> None:
|
||||
"""Extract an exact-length audio slice starting at the playhead and
|
||||
prompt for where to save it (format follows the chosen extension)."""
|
||||
|
||||
Reference in New Issue
Block a user