-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathconfig.py
More file actions
298 lines (264 loc) · 25.7 KB
/
config.py
File metadata and controls
298 lines (264 loc) · 25.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
# config.py
# Default parameters and output toggles for the analysis pipeline.
import os
# Values are tuned for typical PCG recordings from consumer hardware.
# See Documentation.md "Parameter Tuning Rationale" for reasoning behind specific values.
# Note to AI: Stop adding new config parameters unless absolutely necessary.
DEFAULT_PARAMS = {
# =================================================================================
# 1. General & Preprocessing Settings
# Controls the initial loading and filtering of the audio.
# =================================================================================
"downsample_factor": 300, # Factor to reduce sample rate. Higher = faster processing, less detail.
"save_filtered_wav": True, # If True, saves *_filtered_debug.wav and *_filtered_inverse_debug.wav when output_options.filtered_wav is True.
# Main preprocessing: target sample rate and bandpass (single wide band before envelope); typical PCG range for S1/S2.
"preprocess_target_sample_rate": 600, # Resample to this Hz for analysis; lower = faster, less detail.
"preprocess_bandpass_low_hz": 30.0, # increasing this reduces the amplitude of S1.
"preprocess_bandpass_high_hz": 290.0,
"preprocess_bandpass_order": 2, # Butterworth order; higher order not yet validated for this pipeline.
"enable_hum_removal": True, # Detect and notch narrow low-frequency hums if present
"hum_psd_window_sec": 4.0, # PSD window length (seconds) for hum detection
"hum_min_freq_hz": 40.0, # Min frequency of narrow-band hum to consider
"hum_max_freq_hz": 100.0, # Max frequency of narrow-band hum to consider
"hum_min_prominence_db": 8.0, # Minimum prominence (dB) above local median to trigger notch
"hum_min_prominence_over_second_db": 3.0, # Gap over next peak before trusting detection
"hum_notch_q": 35.0, # Q factor, Higher = narrower notch (try 35-40 for sharp hums)
"envelope_smooth_window_ms": 40, # Rolling window (ms) for smoothing Hilbert envelope after abs(analytic). Matches common PCG practice (e.g. 50 ms).
# HF noise strip: merge / min-duration only (threshold quantile is hardcoded in noise_segments.py).
"noise_segment_merge_gap_ms": 500.0, # Merge if gap < this before expand_ms and again after (expanded time).
"noise_segment_min_duration_ms": 20.0, # Drop shorter noisy blips after merge.
"noise_segment_expand_ms": 70.0, # Pad each segment start/end by this (quantile gate tends to clip early/late).
# =================================================================================
# 2. Signal Feature Detection
# Governs the initial identification of peaks and troughs in the audio envelope.
# =================================================================================
"min_peak_distance_sec": 0.1, # I Adjusted This✔ Minimum time allowed between any two raw peaks.
"peak_prominence_quantile": 0.50, # Min prominence = this quantile of envelope. Higher reduces false peaks (e.g. Hilbert ripple).
"trough_prominence_quantile": 0.3, # How much a dip must stand out to be considered a 'trough'.
# Peak position refinement: shift each raw peak to super-Gaussian-weighted center-of-mass (~100 ms window).
"peak_refine_window_ms": 150, # Window (ms) around each peak for CoM; ~100 ms covers typical S1 extent.
"peak_refine_max_shift_ms": 40, # Cap shift so noisy envelope cannot pull peak more than this (ms).
"peak_refine_super_gaussian_n": 5, # Super-Gaussian exponent (flat top); higher = more flat top.
# =================================================================================
# 3. Noise Estimation & Rejection
# Rules for calculating the dynamic noise floor and vetoing noisy peaks.
# =================================================================================
# --- 3.1. Dynamic Noise Floor ---
"noise_floor_quantile": 0.25, # Quantile of troughs used to calculate the noise floor. lower = more sensitive to noise.
"noise_window_sec": 2.5, # I Adjusted This✔ Rolling window in seconds. smaller means more sensitive to noise.
"trough_rejection_multiplier": 6.0, # I Adjusted This✔ A trough N-times higher than the draft noise floor is rejected.
# I wanted to keep this value high to be conservative
# --- 3.2. Peak Noise Vetoing ---
"noise_confidence_threshold": 0.6, # A peak is rejected if its calculated "noise confidence" exceeds this.
"trough_veto_multiplier": 2.1, # Vetoes a small peak if the next peak is N-times larger.
"trough_noise_multiplier": 3.0, # Marks a peak as noisy if its preceding trough is N-times the noise floor.
"strong_peak_override_ratio": 6.0, # A peak N-times the noise floor will bypass noise-rejection rules.
# =================================================================================
# 4. S1/S2 Pairing & Confidence Engine
# The core logic for identifying S1-S2 pairs based on timing and physiology.
# =================================================================================
# --- 4.1. Core Pairing Rules ---
"pairing_confidence_threshold": 0.50, # Confidence score required to classify two peaks as an S1-S2 pair.
"pass1_pairing_confidence_threshold": 0.7, # Pass 1 only: min S1–S2 pairing confidence for anchor beats (overrides pairing_confidence_threshold for that run).
"s1_s2_interval_cap_sec": 0.4, # The absolute maximum time (seconds) allowed between S1 and S2.
"min_s1_s2_interval_sec": 0.10, # Absolute minimum (100ms)
"min_s1_s2_interval_rr_fraction": 0.23, # Or 23% of total RR interval
# Diastole (S2→next S1) plausibility bounds — used by calculate_bpm_intervals and Pass 3 correction.
"min_diastole_nominal_frac": 0.35, # Diastole can be this fraction of its nominal before flagged as too short. Lower → more tolerant of compressed diastoles (conservative). Higher → flag earlier (aggressive).
"max_diastole_nominal_frac": 2.0, # Diastole longer than this × nominal → considered a gap (used by dropout detection). Lower → flag gaps earlier.
"min_diastole_sec": 0.08, # Absolute floor (seconds) for diastole regardless of BPM. Prevents near-zero diastoles at very high heart rates.
# S1 and S2 acoustic event duration bounds (BPM-independent physiological constants).
# These define how long the audible heart sound event itself lasts, not the intervals between sounds.
# Used by Pass 3 to gate corrections that would squeeze a state into an impossibly short window.
"s1_min_sec": 0.030, # Shortest plausible S1 sound duration (10ms absolute floor).
"s1_nominal_sec": 0.080, # Typical S1 sound duration (~40ms).
"s1_max_sec": 0.120, # Longest plausible S1 sound (beyond this it blurs into systole).
"s2_min_sec": 0.030, # Shortest plausible S2 sound duration (10ms absolute floor).
"s2_nominal_sec": 0.080, # S2 is generally shorter and softer than S1 (~30ms typical).
"s2_max_sec": 0.120, # Longest plausible S2 sound.
# BPM-dependent expected S1-S2 (Weissler: https://www.desmos.com/calculator/ebqshptip0)
"s1_s2_expected_weissler_ref_et_ms": 320, # Reference ejection time (ms) at ref_bpm.
"s1_s2_expected_weissler_ref_bpm": 60, # BPM at which ref_et_ms is defined.
"s1_s2_expected_weissler_slope_ms_per_bpm": 1.26, # ET decrease (ms) per BPM.
"noise_prominence_threshold": 0.35, # Peaks below this ratio are "suspect noise"
"enable_lookahead_skipping": True, # Enable/disable lookahead skipping
# --- 4.2. Amplitude-Based Confidence Model ---
"deviation_smoothing_factor": 0.05, # Smoothing applied to the peak-to-peak amplitude deviation series.
# --- 4.3. Physiology-Based Confidence Adjustment ---
"stability_history_window": 20, # Number of recent beats used to determine rhythm stability.
"stability_confidence_floor": 0.7, # I Adjusted This✔ At 0% pairing success, confidence is multiplied by this.
"stability_confidence_ceiling": 1.3, # I Adjusted This✔ At 100% pairing success, confidence is multiplied by this (e.g., a 10% boost).
"recovery_phase_stability_floor": 0.90, # Disable stability penalty during recovery (0% pairing → factor = 1.0)
"s1_s2_boost_ratio": 1.2, # S1 strength must be > (S2 strength * this value) to get a confidence boost.
"boost_amount_min": 0.10, # Additive confidence boost for a "good" pair in an unstable section.
"boost_amount_max": 0.35, # Additive confidence boost for a "good" pair in a stable section.
"penalty_amount_min": 0.10, # Subtractive confidence penalty for a "bad" pair in a stable section.
"penalty_amount_max": 0.30, # Subtractive confidence penalty for a "bad" pair in an unstable section.
"forward_look_drop_threshold": 0.4, # If next peak < 60% of S2, it's suspicious
"forward_look_max_penalty": 0.4, # Max penalty for this scenario
"pairing_rr_penalty_max": 0.25, # Multiplicative penalty for RR mismatch vs 60/BPM when evaluating an S1-S2 pair.
# Contractility: S1/S2 prominence ratio. Expected from history (past N pairs) or BPM power-curve fallback.
"contractility_expected_use_history": True, # If True, expected S1/S2 = mean of last N accepted pairs; else BPM power curve.
"contractility_expected_history_count": 8, # Number of past S1/S2 ratios to average.
"contractility_expected_history_min": 1, # Min history length before using average (else BPM fallback).
"contractility_pair_rate_window_sec": 5.0, # Pair rate in this window blends history vs BPM: 100% pairs → use history; lower → blend toward BPM.
# BPM fallback: power curve expected_ratio = low + (high - low) * ((BPM - bpm_min) / (bpm_max - bpm_min)) ** exponent.
"contractility_bpm_min": 60, # BPM at which ratio = low_ratio.
"contractility_bpm_max": 200, # BPM at which ratio = high_ratio.
"contractility_low_ratio": 0.9, # Expected S1/S2 at bpm_min (rest).
"contractility_high_ratio": 3.5, # Expected S1/S2 at bpm_max (high exertion).
"contractility_power_exponent": 0.6, # <1: steep rise at low BPM then flatter (contractility kicks in early).
# Asymmetric deviation-based curve: L2=(1-r_low), L1=(1-a_low), R1=(1+a_high), R2=(1+r_high) × expected.
"contractility_zero_crossing_low": 0.3, # Left zero-crossing: L1 = expected × (1 - this).
"contractility_zero_crossing_high": 0.4, # Right zero-crossing: R1 = expected × (1 + this).
"contractility_penalty_ramp_fraction_low": 1.3, # Left ramp end: L2 = expected × (1 - this); penalty max at L2.
"contractility_penalty_ramp_fraction_high": 2.5, # Right ramp end: R2 = expected × (1 + this); penalty max at R2.
"contractility_boost_max": 0.2, # Max multiplicative boost at expected: confidence *= (1 + boost).
"contractility_penalty_max": 0.5, # Max multiplicative penalty when far outside band.
"recovery_phase_duration_sec": 120, # Duration (seconds) of the high-contractility state after peak BPM.
"recovery_phase_min_peak_bpm": 110, # Only enable recovery-phase adjust if pass 1 peak BPM >= this (avoids activating when BPM stays low).
# --- 4.4. V-Shaped Interval: boost near expected, penalty outside ---
# Linear boost from 0 at expected±zero_crossing to max at expected; linear penalty outside that band.
"interval_v_penalty_max": 0.75, # Max penalty (multiplicative) at ramp ends.
"interval_v_boost_max": 0.6, # Max boost at expected: confidence *= (1 + boost). 0 at zero-crossing boundaries.
"interval_zero_crossing_fraction": 0.2, # Fraction of expected where effect crosses zero: boost zone [expected*(1±this)].
"interval_v_short_ramp_end_fraction": 0.4, # Left: below this fraction of expected → hard reject; ramp from here up to left zero-crossing.
"interval_v_long_ramp_end_fraction": 2.0, # Right: ramp from right zero-crossing to this × expected → full penalty.
"interval_v_long_reject_fraction": 2.5, # Right: above this × expected → hard reject.
# Expected S1-S2 from past pairs (when enabled, overrides BPM-based expected for the V-shape)
"s1_s2_expected_use_history": True, # If True, expected = mean of last N accepted S1-S2 intervals; else BPM-based.
"s1_s2_expected_history_count": 10, # Number of past S1-S2 intervals to average.
"s1_s2_expected_history_min": 1, # Minimum history length before using average (else fallback to BPM).
# =================================================================================
# 5. Rhythm Plausibility & Validation
# Rules for the algorithm's long-term BPM belief and beat-to-beat timing checks.
# =================================================================================
# --- 5.1. Long-Term BPM Belief ---
"min_bpm": 40, # Absolute minimum BPM the algorithm will consider valid.
"max_bpm": 240, # Absolute maximum BPM the algorithm will consider valid.
"bpm_belief_learning_rate": 0.05, # EMA weight for each new beat; lower = smoother but slower to track changes.
"bpm_belief_max_change_per_beat": 3.0, # Speed limiter: max BPM shift allowed per beat (scaled by interval length).
# --- 5.2. Beat-to-Beat Validation ---
"rr_interval_max_decrease_pct": 0.45, # A new R-R interval can't be more than 45% shorter than the previous one.
"rr_interval_max_increase_pct": 0.70, # A new R-R interval can't be more than 70% longer than the previous one.
"lone_s1_min_strength_ratio": 0.29, # I Adjusted This✔ A Lone S1 candidate's strength must be at least this fraction of the previous S1's.
"lone_s1_forward_check_pct": 0.44, # I Adjusted This✔ A Lone S1 is rejected if the next peak is too close, implying a BPM spike.
"lone_s1_forward_penalty_factor": 0.52, # I Adjusted This✔ Multiplier applied when forward check suspects the peak is actually an S2.
# --- 5.3. Lone S1 Gradient Confidence Engine ---
"lone_s1_confidence_threshold": 0.50, # Final combined score needed to be accepted as a Lone S1.
"lone_s1_rhythm_weight": 0.65, # The weight given to the rhythmic timing score (0.0 to 1.0).
"lone_s1_amplitude_weight": 0.35, # The weight given to the amplitude consistency score.
# k consecutive noise raw peaks before current + span ≈ (k+1)×RR → score span/(k+1) vs RR.
"lone_s1_missed_beat_tolerance_frac": 0.22, # |span − m×RR| / (m×RR) must be ≤ this (m = k+1).
# =================================================================================
# 6. Pass 3 — Correction + dense state timeline (bridge to Pass 4)
# =================================================================================
# pass3's job is to generate the state sequence based on data from pass2. then correct that sequence.
#
# --- 6.1 S2 spectral-profile alignment (global; not “outside the loop” only — also Pass A, B/C rebuilds, final paint) ---
"pass3_align_s2_to_s2_spectral_profile": False, # If True, slide FFT vs S2 spectral template to refine S2 time; if False, nominal ejection-time index only. HF-noise beats still skip alignment per cycle.
"pass3_align_s2_window_ms": 120.0, # FFT search width for initial rebuild / Pass B–C rebuild / final paint (ms), ±half around nominal S2.
# --- 6.2 HF-noise — four-phase cardiac layout from interpolated BPM (noise_event_segments) ---
"pass3_enable_noise_repair": True, # If True and noise_event_segments exist, BPM-based partition for affected cycles before spectral alignment; LT BPM masked in noise then interpolated.
# --- 6.3 Correction loop (A→B→C) — iteration cap + Pass A–only search width (pass3_align_s2_to_s2_spectral_profile still applies in Pass A) ---
"pass3_correction_max_iters": 200, # Max rounds of A→B→C; use 0 to skip the loop entirely. Higher fixes more disjoint issues; risks over-correction.
"pass3_resnap_s2_window_ms": 220.0, # Pass A only: FFT search width when re-snapping S2 for implausible systole (wider = search farther).
"pass3_systole_slack_frac": 0.15, # Pass A: tolerate systole this much shorter/longer vs BPM bounds before calling it “bad” (higher = fewer resnaps).
"pass3_diastole_slack_frac": 0.20, # Pass A diagnostics + Pass C: diastole “too short” threshold slack (higher = fewer flip/demote triggers).
# --- 6.4 Pass B — insert missing S1 in long RR ---
"pass3_enable_insert_missing_s1": False, # True = may add S1 in obvious gap spans (recall vs false-insert risk).
"pass3_rr_too_long_frac": 1.7, # RR must exceed this × expected RR to consider an insert (lower = insert more aggressively).
"pass3_gap_fill_max_duration_sec": 10.0, # Skip insert logic when RR exceeds this (likely dropout, not missed beat).
"pass3_insert_s1_search_window_ms": 180.0, # Total width (ms); ±half around expected beat for raw-peak insert search.
"pass3_insert_use_spectrum": False, # If True, allow S1 template search when no raw peak in window.
"pass3_insert_spectrum_stride_ms": 5.0, # Template search step (ms); smaller = finer, slower.
"pass3_insert_spectrum_min_margin": 0.15, # Min best-vs-second score gap to accept spectral insert.
"pass3_insert_spectrum_envelope_margin": 1.05, # Require envelope ≥ this × dynamic noise floor at candidate; ≤0 disables.
"pass3_insert_spectrum_target_sr": None, # None = WAV native SR for insert template; or set e.g. 32000 to match fft_aggregate_sr.
# --- 6.5 Pass C — phase / sequence fixes (false S1, S1↔S2 flip, faint S2) ---
"pass3_enable_phase_correction": False, # Master switch for Pass C.
"pass3_phase_min_score_delta": 0.15, # Peak must win label_scores by this much to demote/remove (higher = only obvious fixes).
# Pass C.1 short-RR false S1: uses diastole_min from §4.1 (min_diastole_nominal_frac / min_diastole_sec), not a separate pass3_rr_too_short_frac.
"pass3_local_peak_window_ms": 100.0, # Total width (ms); ±half for sensitive local-peak hunt (Pass C.3 / helpers).
"pass3_local_peak_sensitivity_factor": 0.6, # vs dynamic noise floor; lower = more sensitive peaks, more false positives.
"pass3_s2_spectral_min_templates": 3, # Min paired S2 templates before any spectral S2 path runs (insert context).
# --- 6.6 Final state timeline — envelope boundary paint (after loop; also caps the “before” snapshot half-windows) ---
"pass3_state_s1_window_ms": 120.0, # Ceiling (ms) on how far transient edge detection may extend around each S1 peak.
"pass3_state_s2_window_ms": 120.0, # Same for S2.
"pass3_state_edge_alpha": 0.03, # Transient edge threshold as a fraction of weighted peak height (lower → wider S1/S2 regions).
"pass3_state_edge_n_exp": 4.0, # Super-Gaussian exponent for edge weighting (higher → harder cap at window edge). Keep ≤ peak_refine_super_gaussian_n.
# --- 6.7 Emissions for Pass 4 Viterbi (requires pass3_generate_emissions=True) ---
"pass3_generate_emissions": False, # If True, fill analysis_data["pass3_emissions"] after the timeline is final.
"pass3_emission_spectral_tau": 5.0, # Softmax temperature mapping spectral scores → probabilities.
"pass3_emission_gate_width_ms": 80.0, # Gaussian width scale (ms) for bumps seeded at S1/S2 event times (wider = smoother spread).
"pass3_emission_noise_floor": 0.05, # Baseline P(noise) added before normalizing emission rows.
# =================================================================================
# 7. Output, HRV & Reporting
# Controls for final calculations, reports, and plots
# =================================================================================
"output_smoothing_window_sec": 5, # Time window (seconds) for smoothing the final BPM curve for display.
"hrv_window_size_beats": 40, # Sliding window size (in beats) for HRV calculation.
"hrv_step_size_beats": 5, # How many beats the HRV window moves in each step.
"enable_hrv_frequency_domain": True, # If True, compute Lomb-Scargle LF/HF and optional global VLF/LF/HF.
"hrv_global_min_duration_sec": 300.0, # Only compute global spectrum when recording duration >= this (5 min).
"plot_amplitude_scale_factor": 250.0, # Adjusts the default y-axis range of the signal amplitude plot.
# In plotting.py: avoid dashed lines (dash=...) for line traces--they cause noticeable lag.
"plot_downsample_factor": 4, # Downsample only large traces: Bandpass / Noise Removed / Noise Envelope, Dynamic Noise Floor (keep 1 of every N points). Does NOT apply to Average S1/S2 contractility, BPM, HRV, or markers.
"pass1_bpm_outlier_window_sec": 10.0, # Half-window (seconds) for pass 1 BPM outlier removal: keep point if within median ± k*MAD in [t-window, t+window].
"pass1_bpm_outlier_mad_k": 2.5, # Number of MADs. Lower = more aggressive outlier removal.
"pass1_bpm_global_outlier_mad_k": 5.0, # After local pass: global median ± k*MAD. Higher = less sensitive. Set <= 0 to disable this pass.
"pass2_instant_bpm_outlier_window_sec": 8.0, # Half-window (seconds) for pass 2/3 instantaneous BPM: local MAD outlier removal.
"pass2_instant_bpm_outlier_mad_k": 8, # Local MADs for pass 2/3 instant BPM. Lower = more aggressive.
"pass1_bpm_loess_frac": 0.02, # LOESS fraction for pass 1 BPM curve (lower = tighter fit).
"s1_s2_outlier_window_sec": 8.0, # Half-window (seconds) for S1-S2 interval outlier removal: MAD in local time window.
"s1_s2_outlier_mad_k": 2.5, # Number of MADs for S1-S2 interval local outlier removal.
"s1_s2_global_outlier_mad_k": 5.0, # After local pass: global median ± k*MAD on interval (s). Higher = less sensitive. <=0 disables.
"s1_s2_loess_frac": 0.05, # LOESS fraction for S1-S2 interval best-fit curve.
"contractility_average_window_sec": 1.0, # Time to average S1/S2 contractility plot: Used in: long-term (contractility vs BPM), short-term (S1 vs inhale/exhale)
# --- 7.1. Long Plot Optimization ---
# When enabled, very long recordings can skip detailed debug traces in the HTML plot
# to keep file sizes manageable. Shorter recordings are always shown in full detail.
"optimize_long_plots": False, # Whether to enable long-plot optimization.
"long_plot_duration_threshold_sec": 600.0, # Duration threshold (seconds) to treat a file as "long" (default: 10 minutes).
# --- 7.1.1. FFT Profiles (S1/S2 frequency spectra from raw and preprocessed audio) ---
"enable_fft_profiles": True, # If True, generate separate HTML with S1/S2 FFT profiles.
"fft_window_ms": 120.0, # Time window (ms) centered on each peak for FFT.
"fft_max_peaks_per_type": 200, # Max S1 and S2 peaks (each) for FFT; selected by highest pairing confidence. Lone S1s excluded.
"fft_aggregate_sr": 32000, # Sample rate for multi-file FFT aggregation (common grid). Per-file uses native sr.
"fft_neutral_band_low_hz": 10000.0, # Neutral band low (Hz) for S2→S1 alignment (force same level in this band).
"fft_neutral_band_high_hz": 14000.0, # Neutral band high (Hz) for S2→S1 alignment.
"fft_separation_low_hz": 10.0, # Low bound (Hz) for S1 vs S2 frequency separation vector (algorithm use).
"fft_separation_high_hz": 15000.0, # High bound (Hz) for S1 vs S2 frequency separation vector.
# =================================================================================
# 8. Pass 4 — Viterbi Holistic Decoder (requires pass3_generate_emissions=True)
# =================================================================================
"enable_pass4": False, # Guard: off until implementation matures. Set True to run Viterbi after Pass 3.
"pass4_transition_self_loop_weight": 0.85, # Higher → decoder prefers longer state durations (more inertia). Lower → allows faster state transitions.
"pass4_emission_weight": 0.7, # Balance between spectral emissions (1.0) and BPM-prior only (0.0). Tune when emission quality is uncertain.
}
# Single source of truth for pipeline output toggles. GUI and analyze_wav_file use this;
# add new options here only (GUI builds checkboxes and get_output_options from these keys).
DEFAULT_OUTPUT_OPTIONS = {
"html": True,
# When False (default), generated HTML opens with S1/S2 beat hover tooltips off; user can enable via toolbar.
"html_s1_s2_hover_on_by_default": False,
# When True, embed a small script in the HTML file instead of copying interactive_plot.js (no audio/spectrogram/label JS).
"html_inline_interactive_script": False,
"png": False,
"csv": False,
"summary": False,
"debug": True,
"filtered_wav": False,
# When True, converted/copied/split working WAVs are written under the output folder; when False, a temp dir is used.
"working_wav_in_output": False,
"spectrogram": False,
"fft_profiles": True,
"regression_log": False,
}
def strip_output_filename_emojis(stem: str) -> str:
"""Remove specific emojis from a file name stem used for generated outputs (input files are unchanged)."""
for ch in ("⭐", "🌟", "💦"):
stem = stem.replace(ch, "")
return stem
def output_stem_from_path(file_path: str) -> str:
return strip_output_filename_emojis(os.path.basename(os.path.splitext(file_path)[0]))