Add clap detection sensitivity settings (Tools menu)

Threshold and min-interval (the librosa delta/wait knobs) were only
ever tunable via the CLI's -t/-m flags. Added a Clap Detection
Settings dialog (Tools menu) with both as spin boxes, an explicit
Reset to Defaults button, and Cancel that leaves MainWindow's current
settings untouched.

Exposed DEFAULT_THRESHOLD=0.7/DEFAULT_MIN_INTERVAL=0.3 as named
constants in clap_detection.py so the CLI script, DetectWorker's
defaults, and the dialog's reset button all reference the same source
of truth instead of three copies of the same magic numbers that could
drift apart.
This commit is contained in:
ThePendulum
2026-07-26 05:56:50 +02:00
parent 97cbf38d97
commit ff127a8c21
4 changed files with 186 additions and 10 deletions
+5 -5
View File
@@ -11,7 +11,7 @@ from datetime import timedelta
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
from clap_detection import detect_claps, ClapDetectionUnavailable
from clap_detection import detect_claps, ClapDetectionUnavailable, DEFAULT_THRESHOLD, DEFAULT_MIN_INTERVAL
def format_timestamp(seconds):
@@ -43,10 +43,10 @@ Threshold guide:
)
parser.add_argument('input_file', help='Audio or video file')
parser.add_argument('-t', '--threshold', type=float, default=0.7,
help='Detection threshold (0-1, default: 0.7, higher = less sensitive)')
parser.add_argument('-m', '--min-interval', type=float, default=0.3,
help='Minimum seconds between detections (default: 0.3)')
parser.add_argument('-t', '--threshold', type=float, default=DEFAULT_THRESHOLD,
help=f'Detection threshold (0-1, default: {DEFAULT_THRESHOLD}, higher = less sensitive)')
parser.add_argument('-m', '--min-interval', type=float, default=DEFAULT_MIN_INTERVAL,
help=f'Minimum seconds between detections (default: {DEFAULT_MIN_INTERVAL})')
parser.add_argument('-s', '--show-strength', action='store_true',
help='Show onset strength values')
+95
View File
@@ -0,0 +1,95 @@
"""Verifies the Clap Detection Settings dialog: defaults match
clap_detection.py's DEFAULT_THRESHOLD/DEFAULT_MIN_INTERVAL, changing values
and accepting actually updates MainWindow state and gets used by the next
detection run, Cancel leaves state untouched, and Reset to Defaults in the
dialog restores the shared constants (not just whatever MainWindow happened
to start with).
"""
import sys
import time
from pathlib import Path
from PyQt6.QtWidgets import QApplication, QDialog
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
import main as app_main
from clap_detection import DEFAULT_THRESHOLD, DEFAULT_MIN_INTERVAL
ROOT = Path(__file__).resolve().parent.parent
CLIP = str(ROOT / "test_media" / "sync_test_INSYNC.mp4")
def wait_for(predicate, qapp, timeout=20):
deadline = time.time() + timeout
while not predicate() and time.time() < deadline:
qapp.processEvents()
time.sleep(0.05)
return predicate()
def main():
qapp = QApplication(sys.argv)
win = app_main.MainWindow()
win.show()
qapp.processEvents()
assert win.clap_threshold == DEFAULT_THRESHOLD
assert win.clap_min_interval == DEFAULT_MIN_INTERVAL
print(f"PASS: MainWindow starts with defaults ({DEFAULT_THRESHOLD}, {DEFAULT_MIN_INTERVAL})")
# -- Dialog opens pre-filled with current settings
dialog = app_main.ClapSettingsDialog(win.clap_threshold, win.clap_min_interval)
assert dialog.threshold_spin.value() == DEFAULT_THRESHOLD
assert dialog.min_interval_spin.value() == DEFAULT_MIN_INTERVAL
# -- Changing values and cancelling shouldn't touch MainWindow state
dialog.threshold_spin.setValue(0.3)
dialog.min_interval_spin.setValue(1.5)
dialog.reject()
assert win.clap_threshold == DEFAULT_THRESHOLD, "cancel should not have changed MainWindow state"
print("PASS: Cancel leaves settings untouched")
# -- Changing values and accepting (simulated via the real handler) does
# update MainWindow state and gets used by the actual worker.
dialog2 = app_main.ClapSettingsDialog(win.clap_threshold, win.clap_min_interval)
dialog2.threshold_spin.setValue(0.3) # very sensitive
dialog2.min_interval_spin.setValue(0.1)
win.clap_threshold, win.clap_min_interval = dialog2.values()
assert win.clap_threshold == 0.3
assert win.clap_min_interval == 0.1
print("PASS: Accepting new values updates MainWindow state")
# -- Reset to Defaults button restores the shared constants regardless
# of what was passed in (even if MainWindow itself had non-default
# settings already).
dialog3 = app_main.ClapSettingsDialog(0.3, 0.1)
assert dialog3.threshold_spin.value() == 0.3
dialog3._reset_to_defaults()
assert dialog3.threshold_spin.value() == DEFAULT_THRESHOLD
assert dialog3.min_interval_spin.value() == DEFAULT_MIN_INTERVAL
print("PASS: Reset to Defaults restores DEFAULT_THRESHOLD/DEFAULT_MIN_INTERVAL")
# -- End-to-end: a changed (very sensitive) threshold actually reaches
# the real DetectWorker/detect_claps() call for a real detection run.
win._load(CLIP)
qapp.processEvents()
time.sleep(0.2)
qapp.processEvents()
win.clap_threshold = 0.3
win.clap_min_interval = 0.1
win.detect_all_button.click()
qapp.processEvents()
assert win._detect_worker.threshold == 0.3
assert win._detect_worker.min_interval == 0.1
ok = wait_for(lambda: win.detect_all_button.isEnabled(), qapp)
assert ok, "detection did not finish in time"
print("PASS: changed settings actually reach the DetectWorker for a real run")
win.close()
qapp.processEvents()
print("\nCLAP SETTINGS SMOKE TEST PASSED")
if __name__ == "__main__":
main()