You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
HapticPattern.events is already public, so these are extension functions over the event list. No executor or platform code is involved.
Design decisions
scaleIntensity maps every intensity to HapticIntensity.Custom(value * factor). The Custom constructor already clamps to [0, 1] (HapticIntensity.kt:61), so no extra guard is needed. Predefined levels are downgraded to Custom, since a scaled STRONG has no predefined equivalent. Negative factors are rejected with require.
timeStretch scales event edges, not durations: newStart = round(start * f), newEnd = round((start + duration) * f), newDuration = newEnd - newStart. Rounding start and duration independently would let adjacent event boundaries drift apart.
reversed() mirrors each event on the pattern span: newStart = span - (start + duration). Transforms may produce overlapping events and do not resolve them; playback serializes overlaps through mergeToSerial (highest intensity wins). That is why this work depends on feat(core): serial-timeline waveform pipeline (overlap, fall-ramp, handle lifecycle) #85.
repeated is defined on top of then, so repeated(n) equals folding then n times by construction.
rawSpanMs() (executor/RawSpanMs.kt:27) and PatternElement.totalDurationMs (element/PatternElement.kt:55) compute the same max end time over events. Both delegate to a single internal HapticPattern.spanMs() after this change. The tree-sum totalDurationMs overrides on SequenceElement/RepeatElement are sequential-scheduling math, not span, and stay untouched.
Acceptance criteria
Property tests (kotest-property, same idiom as InsertFallRampsTest):
p.reversed().reversed() == p
p.timeStretch(1f) == p; p.timeStretch(a).timeStretch(b) within events.size ms of p.timeStretch(a * b)
span(p.timeStretch(f)) equals round(span(p) * f) within rounding tolerance
intensity stays in [0, 1] after any scaleIntensity
then is associative; the empty pattern is its identity; span(a then b) == span(a) + span(b)
p.repeated(1) == p; p.repeated(0) is empty; event count multiplies by times
One top-level declaration per file, apiDump regenerated.
Summary
Compiled patterns should be manipulable as plain values. This adds pure transforms on
HapticPattern, each returning a new pattern:HapticPattern.eventsis already public, so these are extension functions over the event list. No executor or platform code is involved.Design decisions
scaleIntensitymaps every intensity toHapticIntensity.Custom(value * factor). TheCustomconstructor already clamps to [0, 1] (HapticIntensity.kt:61), so no extra guard is needed. Predefined levels are downgraded toCustom, since a scaledSTRONGhas no predefined equivalent. Negative factors are rejected withrequire.timeStretchscales event edges, not durations:newStart = round(start * f),newEnd = round((start + duration) * f),newDuration = newEnd - newStart. Rounding start and duration independently would let adjacent event boundaries drift apart.reversed()mirrors each event on the pattern span:newStart = span - (start + duration). Transforms may produce overlapping events and do not resolve them; playback serializes overlaps throughmergeToSerial(highest intensity wins). That is why this work depends on feat(core): serial-timeline waveform pipeline (overlap, fall-ramp, handle lifecycle) #85.repeatedis defined on top ofthen, sorepeated(n)equals foldingthenn times by construction.rawSpanMs()(executor/RawSpanMs.kt:27) andPatternElement.totalDurationMs(element/PatternElement.kt:55) compute the same max end time over events. Both delegate to a single internalHapticPattern.spanMs()after this change. The tree-sumtotalDurationMsoverrides onSequenceElement/RepeatElementare sequential-scheduling math, not span, and stay untouched.Acceptance criteria
Property tests (kotest-property, same idiom as
InsertFallRampsTest):p.reversed().reversed() == pp.timeStretch(1f) == p;p.timeStretch(a).timeStretch(b)withinevents.sizems ofp.timeStretch(a * b)span(p.timeStretch(f))equalsround(span(p) * f)within rounding tolerancescaleIntensitythenis associative; the empty pattern is its identity;span(a then b) == span(a) + span(b)p.repeated(1) == p;p.repeated(0)is empty; event count multiplies bytimesOne top-level declaration per file,
apiDumpregenerated.Depends on #85.