Skip to content

fix: Synth event recorder logs settings changes twice (near-duplicate events) - #6

Merged
thorwhalen merged 1 commit into
masterfrom
fix/5-duplicate-events
Aug 10, 2026
Merged

fix: Synth event recorder logs settings changes twice (near-duplicate events)#6
thorwhalen merged 1 commit into
masterfrom
fix/5-duplicate-events

Conversation

@thorwhalen

Copy link
Copy Markdown
Owner

Closes #5

Root cause

One logical parameter change flowed through two code paths that each log:

  1. Synth.update() records the full update dict (the intended chokepoint).
  2. When the update touches any settings parameter, update() calls _rebuild_graph(rebuild_updates), which recorded the settings subset again after rebuilding — a few milliseconds later (the rebuild time).

That exactly matches the issue's log: freq-only (dial) changes recorded once, while every change touching waveform/attack (settings) appeared twice, ~0.5–3 ms apart, the second event holding only the settings subset (identical when the whole update was settings).

_rebuild_graph has exactly one caller (update()), so its record call was pure double-logging — not two legitimate writes, hence no dedup/coalesce window.

Fix

Remove the _record_update call from _rebuild_graph; update() is the single logging chokepoint and its event already contains the rebuild updates. Recorded-event schema (relative_time, updates_dict) is unchanged — replay/render consumers are unaffected.

Evidence (issue repro, run headless with audio='dummy')

Before: 9 events, 3 near-duplicate pairs (matching the issue's listing).
After: 6 events, 0 near-duplicate pairs — one event per logical change:

(0, {'freq': {'value': 440, 'time': 0.025, 'mul': 1, 'add': 0}, 'attack': 0.01, 'waveform': 'sine'})
(1.010, {'freq': 660.0})
(2.013, {'freq': 440, 'waveform': 'triangle'})
(2.516, {'waveform': 'square', 'attack': 0.5})
(4.526, {'waveform': 'sine'})
(5.537, {})

Tests

New hum/tests/test_event_recording.py — 4 headless regression tests (fake synth output + fake dials, no pyo server; a minimal fake pyo module is injected when pyo isn't installed, cleaned up so the import-safety tests still see the real environment):

  • test_dial_only_change_records_exactly_one_event (guards against overcorrection)
  • test_settings_only_change_records_exactly_one_event
  • test_dial_plus_setting_change_records_exactly_one_event (the issue's signature case)
  • test_sequence_of_changes_records_one_event_each (the issue's full sequence + schema/ordering checks)

Mutation-tested: re-introducing the removed record call turns 3 of the 4 tests red in both a with-pyo and a no-pyo environment (the dial-only test correctly stays green — dial changes were never duplicated); restoring the fix turns them green.

Also added hum/tests to pytest testpaths — CI invokes pytest with no path arguments, so hum/tests/ (including the existing import-safety tests) was not being collected in CI at all. Verified by simulating the exact CI pytest invocation (--doctest-modules + ignore list) in a no-pyo environment: 9 passed, 2 skipped.

Full local suite (pytest tests/ hum/tests/ -v, pyo + portaudio available): 10 passed, including the end-to-end test_synth_frequency_sequence audio test.

https://claude.ai/code/session_01GUjT9e7sAyckG1SR9czApr

One logical parameter change flowed through two logging code paths when it
touched a settings parameter: Synth.update() recorded the full update, then
_rebuild_graph() recorded the settings subset again a few milliseconds later,
producing near-duplicate events in the recording (issue #5). Dial-only
changes were unaffected, which is why only waveform/attack changes showed
duplicates.

Collapse to a single logging chokepoint: update() records; _rebuild_graph()
(whose only caller is update()) no longer records. The recorded-event schema
(relative_time, updates_dict) is unchanged.

Add headless regression tests (hum/tests/test_event_recording.py) that
exercise the update -> rebuild path with a fake synth output and fake dials —
no pyo server, and a minimal fake pyo module when pyo is not installed — and
add hum/tests to pytest testpaths so these (and the existing import-safety
tests) actually run in CI, which invokes pytest with no path arguments.

Closes #5

Claude-Session: https://claude.ai/code/session_01GUjT9e7sAyckG1SR9czApr
@thorwhalen
thorwhalen merged commit 01280ad into master Aug 10, 2026
12 checks passed
@thorwhalen
thorwhalen deleted the fix/5-duplicate-events branch August 10, 2026 23:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Event recording makes some (near) duplicates.

1 participant