Skip to content

v0.0.3

Choose a tag to compare

@MagicalTux MagicalTux released this 30 May 02:42
· 25 commits to master since this release
4731288

Other

  • SmfFile::track_names() iteration helper (FF 03)
  • SmfFile::cue_points() iteration helper (FF 07)
  • SmfFile::lyrics() iteration helper (FF 05)
  • SmfFile::markers() iteration helper (FF 06)
  • add cargo-fuzz harness over smf / sf2 / dls / sfz parsers (round 172)
  • fix parse_key integer overflow on huge octave magnitudes
  • add SmfFile::key_signatures() iteration helper
  • SmfFile::tempo_map() iteration helper (FF 51)
  • SmfFile::time_signatures() iteration helper (FF 58)

Round 192 — SmfFile::track_names() iteration helper (FF 03)

  • New smf::TrackNameEvent { tick, track, text } plus
    SmfFile::track_names() -> Vec<TrackNameEvent>. Collects every
    track-name meta event (FF 03 len text, the DAW-track-list
    convention from the Standard MIDI File 1.0 specification) from
    every track, pins each one to the absolute tick of its parent
    track via cumulative TrackEvent::delta sums, then merges the
    per-track sequences with a stable sort by tick — track 0 wins
    over track 1 at the same tick, matching the same merge rule used
    by SmfFile::cue_points() / SmfFile::markers() /
    SmfFile::lyrics() / SmfFile::tempo_map() /
    SmfFile::time_signatures() / SmfFile::key_signatures() and by
    scheduler.rs §"merged event list, sorted by absolute tick".
  • Only FF 03 is selected. Other text-kind meta events
    (FF 01 general text, FF 02 copyright, FF 04 instrument
    name, FF 05 lyric, FF 06 marker, FF 07 cue point) are
    filtered out so callers populating a DAW track-list label don't
    have to discriminate themselves.
  • Authoring tools conventionally emit at most one FF 03 per track
    at tick 0, but the spec does not constrain count or placement.
    The helper surfaces every occurrence so callers that only want
    the first name per track can collect into a
    HashMap<usize, TrackNameEvent> keyed on TrackNameEvent::track,
    while callers tracking renames over time read the full Vec. On
    a format-0 file the single track's FF 03 is conventionally read
    as the sequence title.
  • TrackNameEvent::text_bytes() borrows the raw text payload
    unchanged (the SMF spec leaves the encoding unspecified —
    historically Latin-1, modern DAWs emit UTF-8). text_lossy()
    returns Cow<str> using String::from_utf8_lossy, so invalid
    UTF-8 surfaces as U+FFFD replacement characters rather than
    panicking — convenient default for callers that only need the
    human-readable track label.
  • Cost is linear in the total event count and bounded above by the
    parser's existing MAX_EVENTS_PER_FILE cap; the helper does not
    introduce a new allocation ceiling.
  • 6 new unit tests in src/smf.rs::tests cover: empty input,
    single name at tick 0, per-track names on a format-1 two-track
    file (Drums / Bass), two FF 03 events on one track in time
    order (Intro at tick 0, Main at tick 480), stable sort at the
    same tick across two tracks, filtering against the full
    text-meta neighbourhood (FF 01 general text, FF 02 copyright,
    FF 04 instrument name, FF 05 lyric, FF 06 marker, FF 07
    cue — with cross-checks that the marker / lyric / cue helpers
    stay uncontaminated), and text_lossy() resilience against
    non-UTF-8 bytes. Brings the in-crate unit suite from 333 to 339
    unit tests, all passing under cargo test.
  • Docstring cross-links: SmfFile::lyrics() and
    SmfFile::cue_points() now point at SmfFile::track_names in
    their "distinct from" enumerations, so the doc graph between the
    six text-meta helpers stays bidirectionally connected.

Round 186 — SmfFile::cue_points() iteration helper (FF 07)

  • New smf::CueEvent { tick, track, text } plus
    SmfFile::cue_points() -> Vec<CueEvent>. Collects every cue-point
    meta event (FF 07 len text, the Standard MIDI File 1.0
    film-score / theatrical sync convention) from every track, pins
    each one to the absolute tick of its parent track via cumulative
    TrackEvent::delta sums, then merges the per-track sequences
    with a stable sort by tick — track 0 wins over track 1 at the
    same tick, matching the same merge rule used by
    SmfFile::markers() / SmfFile::lyrics() /
    SmfFile::tempo_map() / SmfFile::time_signatures() /
    SmfFile::key_signatures() and by scheduler.rs §"merged event
    list, sorted by absolute tick".
  • Only FF 07 is selected. Other text-kind meta events
    (FF 01 general text, FF 02 copyright, FF 03 track name,
    FF 04 instrument name, FF 05 lyric, FF 06 marker, …) are
    filtered out so callers driving external synchronisation (scene
    change, SFX trigger, video cue) don't have to discriminate
    themselves.
  • CueEvent::text_bytes() borrows the raw text payload unchanged
    (the SMF spec leaves the encoding unspecified — historically
    Latin-1, modern editors emit UTF-8). text_lossy() returns
    Cow<str> using String::from_utf8_lossy, so invalid UTF-8
    surfaces as U+FFFD replacement characters rather than panicking
    — convenient default for callers that only need the human-readable
    cue name.
  • Cost is linear in the total event count and bounded above by
    the parser's existing MAX_EVENTS_PER_FILE cap; the helper does
    not introduce a new allocation ceiling.
  • 8 new unit tests in src/smf.rs::tests cover: empty input,
    single cue at tick 0, three-cue in-order sequence (the
    Intro / SceneA / SceneB shape), multi-track merge order,
    stable sort at the same tick, filtering against neighbouring
    text kinds (FF 03 track name, FF 05 lyric, FF 06 marker —
    with a cross-check that the marker and lyric helpers stay
    uncontaminated), absolute-tick accounting through running-status
    channel events, and text_lossy() resilience against non-UTF-8
    bytes. Brings the in-crate suite from 323 to 331 unit tests, all
    passing under cargo test -p oxideav-midi.
  • Docstring cross-links: SmfFile::markers() and SmfFile::lyrics()
    now point at SmfFile::cue_points() for the film-score sync
    stream so callers searching either doc find the cue companion.

Round 182 — SmfFile::lyrics() iteration helper (FF 05)

  • New smf::LyricEvent { tick, track, text } plus
    SmfFile::lyrics() -> Vec<LyricEvent>. Collects every lyric meta
    event (FF 05 len text, the karaoke .kar syllable convention)
    from every track, pins each one to the absolute tick of its
    parent track via cumulative TrackEvent::delta sums, then merges
    the per-track sequences with a stable sort by tick — track 0
    wins over track 1 at the same tick, matching the same merge rule
    used by SmfFile::markers() / SmfFile::tempo_map() /
    SmfFile::time_signatures() / SmfFile::key_signatures() and
    by scheduler.rs §"merged event list, sorted by absolute tick".
  • Only FF 05 is selected. Other text-kind meta events
    (FF 01 general text, FF 02 copyright, FF 03 track name,
    FF 04 instrument name, FF 06 marker, FF 07 cue point, …)
    are filtered out so karaoke callers iterating syllables don't
    have to discriminate themselves.
  • LyricEvent::text_bytes() borrows the raw text payload
    unchanged (the SMF spec leaves the encoding unspecified —
    historically Latin-1, modern files emit UTF-8). text_lossy()
    returns Cow<str> using String::from_utf8_lossy, so invalid
    UTF-8 surfaces as U+FFFD replacement characters rather than
    panicking — convenient default for callers that only need the
    human-readable text.
  • Cost is linear in the total event count and bounded above by
    the parser's existing MAX_EVENTS_PER_FILE cap; the helper
    does not introduce a new allocation ceiling.
  • 8 new unit tests in src/smf.rs::tests cover: empty input,
    single syllable at tick 0, four-syllable in-order sequence
    (the "Twinkle, Twinkle" .kar shape), multi-track merge order,
    stable sort at the same tick, filtering against neighbouring
    text kinds (FF 03 track name, FF 06 marker), absolute-tick
    accounting through running-status channel events, and
    text_lossy() resilience against non-UTF-8 bytes. Brings the
    in-crate suite from 315 to 323 unit tests, all passing under
    cargo test -p oxideav-midi.
  • Docstring cross-link: SmfFile::markers() now points at
    SmfFile::lyrics() for the karaoke syllable stream so callers
    searching the marker docs find the lyric companion.

Round 176 — SmfFile::markers() iteration helper (FF 06)

  • New smf::MarkerEvent { tick, track, text } plus
    SmfFile::markers() -> Vec<MarkerEvent>. Collects every marker
    meta event (FF 06 len text, the DAW song-section convention)
    from every track, pins each one to the absolute tick of its
    parent track via cumulative TrackEvent::delta sums, then merges
    the per-track sequences with a stable sort by tick — track 0
    wins over track 1 at the same tick, matching the same merge
    rule used by SmfFile::tempo_map() /
    SmfFile::time_signatures() / SmfFile::key_signatures() and
    by scheduler.rs §"merged event list, sorted by absolute tick".
  • Only FF 06 is selected. Other text-kind meta events
    (FF 03 track name, FF 05 lyric, FF 07 cue point, …)
    are filtered out so callers iterating section labels don't have
    to discriminate themselves.
  • MarkerEvent::text_bytes() borrows the raw text payload
    unchanged (the SMF spec leaves the encoding unspecified —
    historically Latin-1, modern DAWs emit UTF-8). text_lossy()
    returns Cow<str> using String::from_utf8_lossy, so invalid
    UTF-8 surfaces as U+FFFD replacement characters rather than
    panicking — convenient default for callers that only need the
    human-readable label.
  • Cost is linear in the total event count and bounded above by
    the parser's existing MAX_EVENTS_PER_FILE cap; the helper
    does not introduce a new allocation ceiling.
  • 8 new unit tests in src/smf.rs::tests cover: empty input,
    single marker at tick 0, multiple per-track markers in order,
    multi-track merge order, stable sort at the same tick,
    filtering against neighbouring text kinds (FF 03 / FF 05),
    absolute-tick accounting through running-status channel
    events, and text_lossy() resilience against non-UTF-8 bytes.
    Brings the in-crate suite from 307 to 315 unit tests, all
    passing under cargo test -p oxideav-midi.

Round 172 — cargo-fuzz harness over every attacker-facing parser

  • New fuzz/ crate (its own [workspace] so it doesn't drag the
    umbrella) with four libfuzzer-sys targets covering every parser
    that takes attacker-controlled bytes end-to-end:
    • smf exercises oxideav_midi::smf::parse plus the three public
      iteration helpers (tempo_map, time_signatures,
      key_signatures) on every successful parse, so the
      cumulative-tick accounting + meta-event extraction paths cover
      fuzz-discovered shapes too.
    • sf2 exercises instruments::sf2::Sf2Bank::parse (the full
      RIFF/sfbk walker + LIST INFO / LIST sdta / LIST pdta
      cross-link resolution).
    • dls exercises instruments::dls::DlsBank::parse (the
      RIFF/DLS<space> walker + colh / ptbl / lins-list /
      wvpl-list / per-instrument rgn / rgn2 / wsmp / wlnk /
      art1 / art2 chain).
    • sfz exercises instruments::sfz::parse_str (the comment
      stripper + tokenizer + <global> / <master> / <group> /
      <region> header walker + opcode flattening + every typed
      field parser).
  • Each target asserts the contract every parser advertises: arbitrary
    bytes return a Result, with no panic / OOM / integer overflow
    (debug) / out-of-bounds index on any path. The return value is
    intentionally discarded.
  • Curated seed corpora under fuzz/corpus/<target>/ give the fuzzer
    a head start across the well-formed, partial-but-legal, and
    known-edge shapes. The sfz corpus also keeps the round-172
    regression input (regression_r172_octave_overflow.sfz.bin) so the
    fixed crash stays under perpetual fuzzer pressure.
  • Fuzz-discovered bug fix: sfz::parse_key (note-name → MIDI key
    conversion) used to panic with attempt to multiply with overflow
    in a debug build when handed an octave whose magnitude approached
    i32::MAX (e.g. lokey=C-2011420400, the libfuzzer crash sample).
    The (octave + 1) * 12 + note_idx + accidental chain now uses
    checked_add / checked_mul and falls out to None on overflow,
    matching the existing xyz / c100 / c-100 rejection paths.
    New parse_key_octave_extremes_do_not_overflow lib test pins the
    fix against C-2011420400, i32::MAX, i32::MIN, the g
    note-name variant, and the previously-tested moderate
    out-of-MIDI-range pair (c100 / c-100).
  • Initial 4×~45 s runs cleared 30+ million inputs across smf /
    sf2 / dls and 2 M inputs across sfz with zero remaining
    crashes. The harness can run indefinitely; CI does not gate on
    fuzz time.
  • 306 → 307 lib tests, 14 → 14 integration tests, 0 ignored.

Round 128 — SmfFile::key_signatures() iteration helper

  • New smf::KeySignatureChange { tick, track, sharps_flats, mode }
    pins one decoded FF 59 02 sf mi Key Signature meta event to the
    absolute tick (cumulative delta-sum) at which it fires on its parent
    track. sharps_flats is the signed -7..=+7 accidental count
    (negative = flats); mode is 0 major or 1 minor.
    KeySignatureChange::tonic_name() returns the tonic spelling
    ("C", "F#", "Bb", …) and name() returns the full key name
    ("C major", "A minor", …). Both helpers consult a 15-entry
    lookup keyed by sf + 7 and return None for out-of-range sf
    or any mode other than 0 / 1, so junk payloads stay observable
    but never panic. is_major() / is_minor() mirror the mode bit.
  • New SmfFile::key_signatures() walks every track, sums per-track
    deltas into absolute ticks, collects every
    MetaEvent::KeySignature, and returns the merged stream sorted by
    tick. The sort is stable so two changes at the same tick keep the
    per-track insertion order — track 0 wins over track 1 at the same
    tick, matching the scheduler's merge convention and the existing
    tempo_map / time_signatures helpers.
  • 8 new lib tests (smf::tests): empty-when-no-meta-event;
    single-change-at-tick-zero (C major, all four fields + the two
    display helpers); three changes within one track (C major →
    A major → C minor); merge across two tracks sorted by tick; stable
    sort keeps track 0 before track 1 at the same tick; absolute-tick
    accounting after running-status channel events; the full 30-entry
    circle-of-fifths name table (both modes, every sf in -7..=+7);
    out-of-range sf / unknown mode produce None.
  • 298 → 306 lib tests, 14 → 14 integration tests, 0 ignored.

Round 125 — SmfFile::tempo_map() iteration helper

  • New smf::TempoChange { tick, track, microseconds_per_quarter_note, bpm } pins one decoded FF 51 03 tt tt tt Set Tempo meta event to
    the absolute tick (cumulative delta-sum) at which it fires on its
    parent track. bpm is pre-computed as
    60_000_000.0 / microseconds_per_quarter_note;
    microseconds_per_quarter_note == 0 maps to f64::INFINITY so a
    degenerate payload can't divide-by-zero. TempoChange::new is the
    public constructor that does the pre-computation.
  • New SmfFile::tempo_map() walks every track, sums per-track deltas
    into absolute ticks, collects every MetaEvent::Tempo, and returns
    the merged stream sorted by tick. The sort is stable so two changes
    at the same tick keep the per-track insertion order — track 0 wins
    over track 1 at the same tick, matching the scheduler's merge
    convention and the existing SmfFile::time_signatures() helper.
  • 7 new lib tests (smf::tests): empty-when-no-meta-event;
    single-change-at-tick-zero (with the BPM cross-check); three
    changes within one track; merge across two tracks sorted by tick;
    stable sort keeps track 0 before track 1 at the same tick;
    absolute-tick accounting after running-status channel events;
    zero µs/qn maps to +INF BPM without panic.
  • 291 → 298 lib tests, 14 → 14 integration tests, 0 ignored.

Round 122 — SmfFile::time_signatures() iteration helper

  • New smf::TimeSignatureChange { tick, track, numerator, denominator_pow2, clocks_per_click, notated_32nd_per_quarter }
    pins one decoded FF 58 04 nn dd cc bb meta event to the absolute
    tick (cumulative delta-sum) at which it fires on its parent track.
    TimeSignatureChange::denominator() returns 1 << dd, saturated
    at u32::MAX so a spec-illegal dd >= 32 can't overflow the
    shift.
  • New SmfFile::time_signatures() walks every track, sums per-track
    deltas into absolute ticks, collects every
    MetaEvent::TimeSignature, and returns the merged stream sorted
    by tick. The sort is stable so two changes at the same tick keep
    the per-track insertion order — track 0 wins over track 1 at the
    same tick, matching the scheduler's merge convention.
  • 7 new lib tests (smf::tests): empty-when-no-meta-event;
    single-change-at-tick-zero (all six fields); three changes within
    one track; merge across two tracks sorted by tick; stable sort
    keeps track 0 before track 1 at the same tick; absolute-tick
    accounting after running-status channel events; denominator
    saturates on a pathological dd >= 32.
  • 284 → 291 lib tests, 9 → 9 integration tests, 0 ignored.