Skip to content

Keep a segment's time and position math from overflowing in a run of months #94

Closed

Description

@kahrendt

Problem

Within one segment of an anchor, offset_us() computes samples * 10^6 and time_position() computes (unshifted_us - u_j) * rate in int64. Both overflow (undefined behavior) once one segment has run about 2.4e7 s at 384 kHz (about 278 days), or about 6 years at 48 kHz. A live input that plays one format on one anchor that long then maps positions and cells to wrong times. #24 split the boundary time (Segment::elapsed_s and elapsed_ps), which holds at any length, but these two products remain.

Where

  • int64_t offset_us(const Segment& segment, uint64_t position) {
    const int64_t samples = static_cast<int64_t>(position - segment.position);
    return floor_div(samples * US_PER_SECOND, segment.rate);
    }
  • // first segment extrapolates it
    const int64_t position =
    static_cast<int64_t>(segment.position) +
    ceil_div((unshifted_us - segment.unshifted_us) * int64_t{segment.rate}, US_PER_SECOND);

Evidence

Confirmed by arithmetic: int64 holds 9.22e18; at 384 kHz (MAX_INPUT_RATE) a segment reaches 9.22e12 samples, so samples * 10^6 overflows, after 2.4e7 s, and (unshifted_us - u_j) * 384000 overflows at the same elapsed time. Not reproduced; reaching it needs one anchor playing for months.

Direction

Owner's choice, because of the cost. unshifted_time() and cell_position() run per frame and per cell (group_audio.cpp, HostView::time_of(), admission), and 64-bit multiply and divide are slow on the ESP32-P4. Options:

  • Split each product the way add_segment() does (q = samples / rate, r = samples % rate), which adds 64-bit work to the per-frame paths.
  • Start a new segment (same rate and channels) when one grows past a bound, in add_segment()'s own path, so each segment's products stay small and the per-frame math is unchanged. segment_output_index() is exact across segments, so the extra boundaries do not move any output index.
  • Document the bound and leave it.

Done when

  • A pure-function table row in tests/test_sched_timeline.cpp past the bound (for example one segment of 300 days at 384 kHz) maps positions and cells as an ordinary row does, and trips UBSan before the change; plus a Control row at ordinary durations.
  • No 64-bit multiply or divide added per sample, cell or frame, unless the owner chooses that option.

Activity

  1. self-assigned this
    on Oct 16, 2026
  2. kahrendt commented on Oct 16, 2026

    @kahrendt
    ContributorAuthor

    Claimed 0c55a139; branch issue-94-segment-math-split

  3. kahrendt commented on Oct 16, 2026

    @kahrendt
    ContributorAuthor

    PR: #96

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions