feat: griff split — one corpus chunk per phrase (auto-split #2a: core + CLI) - #70
Conversation
griff split (feature #2a) slices a track into one chunk per phrase; each chunk must be a standalone, independently-measurable score over a contiguous run of bars. Pins extract_bars(score, bars): whole bars re-indexed from 0, ticks rebased to 0, notes outside the span dropped by onset, out-of-range end clamped. References griff_core::slice::extract_bars, which does not exist yet — fails to compile until the green step. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TTUbGjzD8ysnVnCJnZJE95
extract_bars(score, bars) returns a standalone, independently-measurable score over a contiguous run of bars: bars re-indexed from 0, all ticks (bars, notes, rests, technique spans) rebased to 0, notes/rests outside the span dropped by onset, spans clamped to it. bars.end clamps to the bar count; an empty or reversed range yields no bars while preserving the track/voice skeleton. The keystone for `griff split` (one chunk per phrase). 2/2 slice_extract tests pass; clippy -D warnings (incl. nursery) clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TTUbGjzD8ysnVnCJnZJE95
bar_segments(master_bars, cut_ticks) partitions a track's bars into contiguous, non-overlapping ranges cut at the phrase-boundary onsets — each snapped to its containing bar, with start-of-track and same-bar cuts collapsing. Paired with slice::extract_bars it yields one standalone score per phrase: the segmentation `griff split` (and the web split in #2b) reuse. 4/4 tests; clippy clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TTUbGjzD8ysnVnCJnZJE95
New `griff split <file>` slices the first note-bearing track at its phrase boundaries (split::bar_segments over detect_boundaries' cuts) and writes one standalone chunk per phrase to <stem>.p<N>.chunk.json. Each chunk is a slice::extract_bars sub-score, measured on its own bars and stamped with its source bar_range (the original bar indices it covers); the curator's tags, rights and cohort are gathered once and inherited by every phrase. phrase_chunks (the pure builder) is unit-tested: the chunks tile the bars contiguously, carry bar_range, and id-suffix per phrase. Full workspace green; clippy -D warnings (incl. nursery) clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TTUbGjzD8ysnVnCJnZJE95
|
@codex review Generated by Claude Code |
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (1)
🚧 Files skipped from review as they are similar to previous changes (1)
📝 WalkthroughWalkthroughAdds a ChangesPhrase-split feature
Sequence Diagram(s)sequenceDiagram
participant User
participant cmd_split
participant phrase_chunks
participant bar_segments
participant extract_bars
participant curate_phrases
User->>cmd_split: griff split <file>
cmd_split->>cmd_split: import score, gather curation metadata
cmd_split->>phrase_chunks: score, meta
phrase_chunks->>bar_segments: master_bars, cut_ticks
bar_segments-->>phrase_chunks: Vec<Range<usize>> (bar index segments)
loop per segment
phrase_chunks->>extract_bars: score, bar range
extract_bars-->>phrase_chunks: rebased Score slice
phrase_chunks->>phrase_chunks: measure + stamp bar_range & ID suffix
end
phrase_chunks-->>cmd_split: Vec<ChunkMeta>
cmd_split->>curate_phrases: chunks, output stem
curate_phrases->>curate_phrases: serialize + write <stem>.pN.chunk.json
curate_phrases-->>User: print per-phrase measurements & total count
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (1)
cli/src/main.rs (1)
853-860: ⚡ Quick winReplace magic boolean with named constant or parameter.
Line 858 passes a bare
falsetogather_curate_inputs, making the call site unclear. Based on line 141's doc comment mentioning "ensemble mode", this boolean likely controls ensemble vs. single-track curation.♻️ Proposed fix: add clarity at the call site
- let inputs = gather_curate_inputs(false)?; + const ENSEMBLE_MODE: bool = false; + let inputs = gather_curate_inputs(ENSEMBLE_MODE)?;Or, if
gather_curate_inputscan be refactored to accept a named parameter, that would be even clearer.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@cli/src/main.rs` around lines 853 - 860, The `cmd_split` function passes a bare `false` to the `gather_curate_inputs` call on line 858, which lacks clarity about its purpose. Based on the doc comment at line 141 mentioning ensemble mode, this boolean controls whether to use ensemble curation mode. Define a named constant that clearly expresses this intent (such as `ENABLE_ENSEMBLE_MODE` or similar) and assign it the value `false`, then replace the bare `false` argument in the `gather_curate_inputs(false)` call with this named constant to make the call site self-documenting.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@cli/src/main.rs`:
- Around line 866-905: The commit a96e411 contains both the test
`phrase_chunks_tile_the_bars_with_bar_range_and_ids` and the implementation of
the `phrase_chunks` function together, which violates the TDD requirement of
committing tests first in a separate commit. Reset this commit, then create two
separate commits in order: first commit the test
`phrase_chunks_tile_the_bars_with_bar_range_and_ids` alone, then commit the
`phrase_chunks` implementation function alone in a second commit. This ensures
the test exists and can be run before the implementation is added.
In `@core/src/slice.rs`:
- Around line 61-111: The current commit violates TDD discipline by including
both the test file core/tests/slice_extract.rs and the implementation in
core/src/slice.rs (specifically the extract_bars function) in the same commit.
You need to reset this commit and re-structure it into two separate commits:
first commit the test file core/tests/slice_extract.rs with failing tests, then
in a subsequent commit add the extract_bars implementation in core/src/slice.rs.
This ensures tests are defined before implementation, following the required TDD
commit discipline.
In `@core/src/split.rs`:
- Around line 21-41: The function bar_segments and its unit tests (located
around lines 79-101) were committed together in the same commit, but the project
guidelines require tests to be committed separately and first, following the TDD
red-green cycle. Use interactive rebase on the commit a96e411 to split it into
two commits: first commit should contain only the unit tests for bar_segments in
a failing state, and the second commit should contain the implementation of
bar_segments that makes those tests pass. This allows the repository history to
follow the required pattern of test-first development.
---
Nitpick comments:
In `@cli/src/main.rs`:
- Around line 853-860: The `cmd_split` function passes a bare `false` to the
`gather_curate_inputs` call on line 858, which lacks clarity about its purpose.
Based on the doc comment at line 141 mentioning ensemble mode, this boolean
controls whether to use ensemble curation mode. Define a named constant that
clearly expresses this intent (such as `ENABLE_ENSEMBLE_MODE` or similar) and
assign it the value `false`, then replace the bare `false` argument in the
`gather_curate_inputs(false)` call with this named constant to make the call
site self-documenting.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: fcae6cf4-e94d-4e6f-bfe2-3d3ad55711cd
📒 Files selected for processing (5)
cli/src/main.rscore/src/lib.rscore/src/slice.rscore/src/split.rscore/tests/slice_extract.rs
| pub fn extract_bars(score: &Score, bars: Range<usize>) -> Score { | ||
| let count = score.master_bars.len(); | ||
| let lo = bars.start.min(count); | ||
| let hi = bars.end.clamp(lo, count); | ||
| let selected = score.master_bars.get(lo..hi).unwrap_or_default(); | ||
|
|
||
| let offset = selected.first().map_or(0, |b| b.tick_range.start.0); | ||
| let seg_end = selected.last().map_or(offset, |b| b.tick_range.end.0); | ||
|
|
||
| let master_bars = selected | ||
| .iter() | ||
| .enumerate() | ||
| .map(|(i, b)| MasterBar { | ||
| index: i, | ||
| tick_range: rebased_range(b.tick_range, offset), | ||
| time_signature: b.time_signature, | ||
| tempo: b.tempo, | ||
| repeat: b.repeat, | ||
| }) | ||
| .collect(); | ||
|
|
||
| let tracks = score | ||
| .tracks | ||
| .iter() | ||
| .map(|t| Track { | ||
| name: t.name.clone(), | ||
| channel: t.channel, | ||
| tuning: t.tuning.clone(), | ||
| voices: t | ||
| .voices | ||
| .iter() | ||
| .map(|v| Voice { | ||
| id: v.id, | ||
| event_groups: v | ||
| .event_groups | ||
| .iter() | ||
| .filter_map(|g| sliced_group(g, offset, seg_end)) | ||
| .collect(), | ||
| }) | ||
| .collect(), | ||
| }) | ||
| .collect(); | ||
|
|
||
| Score { | ||
| ticks_per_quarter: score.ticks_per_quarter, | ||
| master_bars, | ||
| tracks, | ||
| source_meta: score.source_meta.clone(), | ||
| loss: LossReport::new(), | ||
| } | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Verify that tests precede implementation
echo "=== slice.rs implementation commits ==="
git log --oneline --follow -- core/src/slice.rs | head -5
echo ""
echo "=== slice_extract.rs test commits ==="
git log --oneline --follow -- core/tests/slice_extract.rs | head -5
echo ""
echo "=== Commit timeline (most recent first) ==="
git log --oneline --all --follow -- core/src/slice.rs core/tests/slice_extract.rs | head -10
echo ""
echo "=== Detailed: Which file was added first? ==="
git log --diff-filter=A --format="%ai %H %s" --follow -- core/tests/slice_extract.rs
git log --diff-filter=A --format="%ai %H %s" --follow -- core/src/slice.rsRepository: PhysShell/griff
Length of output: 664
🏁 Script executed:
#!/bin/bash
# Examine the commit that contains both files
echo "=== Commit a96e411 contents (what was added/modified) ==="
git show --stat a96e411 | head -50
echo ""
echo "=== Check if both files exist in commit a96e411 ==="
git show a96e411:core/tests/slice_extract.rs &>/dev/null && echo "slice_extract.rs: EXISTS in a96e411" || echo "slice_extract.rs: NOT in a96e411"
git show a96e411:core/src/slice.rs &>/dev/null && echo "slice.rs: EXISTS in a96e411" || echo "slice.rs: NOT in a96e411"
echo ""
echo "=== Full history without --follow ==="
git log --oneline core/src/slice.rs core/tests/slice_extract.rs 2>/dev/null | head -20
echo ""
echo "=== When were these files first created? ==="
git log --diff-filter=A --oneline -- core/tests/slice_extract.rs
git log --diff-filter=A --oneline -- core/src/slice.rsRepository: PhysShell/griff
Length of output: 3471
TDD commit discipline violated: tests and implementation committed together.
The coding guidelines require tests to be committed in a separate, earlier commit before implementation code is touched. However, both core/tests/slice_extract.rs and core/src/slice.rs were added in the same commit (a96e411, 2026-06-18 12:33:49 +0000). The test file must be committed first—in a separate commit—with failing tests, before the extract_bars implementation is added.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@core/src/slice.rs` around lines 61 - 111, The current commit violates TDD
discipline by including both the test file core/tests/slice_extract.rs and the
implementation in core/src/slice.rs (specifically the extract_bars function) in
the same commit. You need to reset this commit and re-structure it into two
separate commits: first commit the test file core/tests/slice_extract.rs with
failing tests, then in a subsequent commit add the extract_bars implementation
in core/src/slice.rs. This ensures tests are defined before implementation,
following the required TDD commit discipline.
Source: Coding guidelines
| pub fn bar_segments(master_bars: &[MasterBar], cut_ticks: &[u32]) -> Vec<Range<usize>> { | ||
| let bar_count = master_bars.len(); | ||
| if bar_count == 0 { | ||
| return Vec::new(); | ||
| } | ||
| let mut cuts: BTreeSet<usize> = BTreeSet::new(); | ||
| cuts.insert(0); | ||
| cuts.insert(bar_count); | ||
| for &tick in cut_ticks { | ||
| match bar_containing(master_bars, tick) { | ||
| Some(idx) if idx > 0 => { | ||
| cuts.insert(idx); | ||
| } | ||
| _ => {} | ||
| } | ||
| } | ||
| cuts.iter() | ||
| .zip(cuts.iter().skip(1)) | ||
| .map(|(&start, &end)| start..end) | ||
| .collect() | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Verify TDD discipline: tests should precede implementation in separate commits
echo "=== Commit history for split.rs ==="
git log --oneline --follow core/src/split.rs
echo ""
echo "=== Detailed commit sequence ==="
git log --follow --format="%H %s" --reverse core/src/split.rs | while read hash msg; do
echo "Commit: $hash"
echo "Message: $msg"
echo "Files changed:"
git diff-tree --no-commit-id --name-status -r "$hash" | grep split.rs
echo ""
doneRepository: PhysShell/griff
Length of output: 321
🏁 Script executed:
#!/bin/bash
# Examine the full diff of the commit that introduced split.rs
echo "=== Full diff for commit a96e411 ==="
git show a96e411 --stat
echo ""
echo "=== Checking for test code in the commit ==="
git show a96e411 | grep -A 5 -B 5 "test\|#\[test\|#\[cfg(test)"
echo ""
echo "=== Checking for bar_segments implementation ==="
git show a96e411 | grep -A 10 "pub fn bar_segments"Repository: PhysShell/griff
Length of output: 50374
Split this into two sequential commits: tests first, then implementation.
The coding guidelines require that unit tests for new public functions be committed separately and first ("Never commit new pub fn or pub struct implementation in the same commit as the tests that cover it"). Currently, bar_segments and its unit tests (lines 79–101) were introduced together in commit a96e411.
Rewrite the history to commit the failing tests alone, then the implementation that makes them pass, following the TDD red-green cycle.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@core/src/split.rs` around lines 21 - 41, The function bar_segments and its
unit tests (located around lines 79-101) were committed together in the same
commit, but the project guidelines require tests to be committed separately and
first, following the TDD red-green cycle. Use interactive rebase on the commit
a96e411 to split it into two commits: first commit should contain only the unit
tests for bar_segments in a failing state, and the second commit should contain
the implementation of bar_segments that makes those tests pass. This allows the
repository history to follow the required pattern of test-first development.
Source: Coding guidelines
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: a96e41139b
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| meta.source.bar_range = | ||
| Some((u32::try_from(start).unwrap_or(0), u32::try_from(end).unwrap_or(0))); |
There was a problem hiding this comment.
Store split bar ranges with an inclusive end
When griff split persists a phrase covering the half-open segment start..end, it writes end directly into source.bar_range. That field is documented and consumed as inclusive [first_bar, last_bar] (core/src/corpus.rs:67, and preview merge checks a_end + 1 == b_start), so a four-bar chunk is recorded as (0, 4) instead of (0, 3). Downstream curation will think every split chunk covers one extra bar, which makes provenance and later split/merge operations off by one; convert the non-empty segment end to end - 1 before storing it.
Useful? React with 👍 / 👎.
| .position(|t| primary_voice_note_count(t) > 0); | ||
| let id = format!("{}_p{phrase}", inputs.id); | ||
| let title = format!("{} (phrase {phrase})", inputs.title); | ||
| let mut meta = build_chunk_meta(&sub, path, measured, id, title, inputs, None); |
There was a problem hiding this comment.
Avoid writing chunks for note-less split segments
When a detected boundary is the long rest in the last bar, bar_segments creates a trailing segment that contains only that rest. This is reachable for GP inputs because rest/empty beats are imported as rest atoms (core/src/gp.rs:392) and the S4 detector treats long rests as hard boundaries; for that slice measured is None, but the code still writes a ChunkMeta with no structure/gesture/complexity. That pollutes the corpus with silent phrase chunks, so note-less segments should be skipped or merged before calling build_chunk_meta.
Useful? React with 👍 / 👎.
Codex flagged two corpus-correctness bugs in `griff split`: - P1: source.bar_range is stored as the half-open (start, end), but SourceRef documents it as inclusive [first, last] (core/src/corpus.rs:67) and the preview split/merge math (preview/src/curation.rs) assumes inclusive — so a bar [0,4) chunk is recorded as (0,4) instead of (0,3), off by one. - P2: a phrase segment with only rests is still written as a silent, measurement-less ChunkMeta, polluting the corpus. Pins both: rewrites the tiling test for inclusive [first,last] and adds a chunks_for_segments test driving an explicit sounding + silent segment pair, asserting the silent one is dropped and the stored range ends at end-1. References super::chunks_for_segments, which does not exist yet — fails to compile until green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TTUbGjzD8ysnVnCJnZJE95
chunks_for_segments now backs phrase_chunks: it stores source.bar_range as the inclusive [first, last] = (start, end-1) that SourceRef documents and the preview split/merge math expects (fixes the off-by-one, Codex P1), and drops segments whose slice has no note-bearing track instead of writing a silent, measurement-less chunk — kept chunks renumber from 0 (Codex P2). Also names the single-track intent at the gather_curate_inputs call site (CodeRabbit nitpick). Both new tests pass; full workspace green; clippy -D warnings clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TTUbGjzD8ysnVnCJnZJE95
|
Reconciling the TDD-commit-discipline findings against this branch's actual
Per maintainer preference we're not rewriting the already-reviewed history for those two. The keystone demonstrates the red→green discipline, and the follow-up corpus fix below also followed it strictly; subsequent commits stay test-first. Codex P1 / P2 fixed (
Generated by Claude Code |
|
@codex review Generated by Claude Code |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 8a79674de6
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| let measured = sub | ||
| .tracks | ||
| .iter() | ||
| .position(|t| primary_voice_note_count(t) > 0)?; |
There was a problem hiding this comment.
Keep split chunks on the detected track
In multi-track tabs where the first note-bearing track used for boundary detection is silent in one sliced bar range but a later track has notes there, this re-selects that later track after slicing instead of treating the target phrase as silent. The resulting chunk is cut using boundaries from one part but measures/stores techniques and boundaries from another part, even though griff split is documented as single-track chunks from the first note-bearing track. Pass the originally selected track through and drop/measure segments based on that same track.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed in 65e1751 (red 2d6ba25 → green 65e1751). You're right — chunks_for_segments re-selected the first note-bearing track of each slice, so a segment silent on the boundary-detection track could be cut on that track yet measured and bar_range-stamped from a later one, against griff split's single-track contract.
Now the detected track index is threaded through and every chunk is cut and measured on that same track. A segment where the detected track is silent is treated as a phrase rest and dropped, even when a later track has notes there. (extract_bars preserves track order, so the index addresses the same part in each slice.)
Red test chunks_for_segments_stays_on_the_detected_track: track 0 sounds in bars 0–1, a second track only in bars 2–3 → asserts [2,4) is dropped, not re-measured on the later track. Workspace tests green; clippy --workspace --all-targets -- -D warnings (nursery=deny) clean.
@codex review
Generated by Claude Code
In a multi-track tab where the boundary-detection track is silent in a sliced bar range but a later track has notes there, chunks_for_segments re-selects that later track, so the chunk is cut on one part yet measured and provenance-stamped from another. griff split is documented as single-track chunks from the first note-bearing track. Thread the detected track index through chunks_for_segments and add a failing test asserting a segment silent on that track is dropped (treated as a phrase rest) rather than re-measured on a later track. Codex P2, PR #70.
chunks_for_segments now cuts and measures every chunk on the same track that boundary detection chose, instead of re-selecting the first note-bearing track of each slice. A segment where the detected track is silent is a phrase rest and is dropped, even when a later track has notes there — so a chunk's boundaries and measurements always describe one part, matching griff split's single-track contract. extract_bars preserves track indices, so the detected index addresses the same part in each slice. Codex P2, PR #70.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 65e1751c94
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| })?; | ||
| let cuts: Vec<u32> = detect_boundaries(score, track) | ||
| .iter() | ||
| .map(|b| b.start_tick) |
There was a problem hiding this comment.
Use the resume tick when cutting pause boundaries
For pause boundaries, start_tick is the beginning of the rest while end_tick is where the next phrase resumes; feeding start_tick into bar_segments makes a mid-bar pause snap to the start of that containing bar. In a tab where a phrase has notes early in a bar and then a long rest until the next bar, this moves that whole bar (including the pre-rest notes) into the following chunk and stamps the wrong bar_range, silently corrupting the phrase chunks used for training. Use the resume tick for pause boundaries (for example when end_tick > start_tick) before snapping to bars.
Useful? React with 👍 / 👎.
Characterization test for the existing behavior CodeRabbit flagged on #71: the split's note_count/push_notes read voice 0 because the whole analysis stack — boundary::detect_phrase_boundaries, structure/gesture/complexity, novelty/closure/complement, MIDI export — reads voices.first(). Phrases are cut from voice 0, so a track sounding only in a secondary voice is unmeasurable and is dropped, matching the CLI's primary_voice_note_count contract. Scanning all voices would cut on voice 0 yet keep/measure on voice 1 and reintroduce the measurement-less chunks PR #70's Codex P2 removed.
First half of auto-split (feature #2). Turns a tab into one corpus chunk per
phrase instead of a single chunk that merely carries phrase boundaries as
metadata — better training granularity. This PR is the core + CLI; the web
side (paginated chunk review + playback) follows in #2b.
What
Core
slice::extract_bars(score, bars) -> Score— the keystone. Extracts acontiguous run of bars as a standalone, independently-measurable score: bars
re-indexed from 0, every tick (bars, notes, rests, technique spans) rebased to
0, notes/rests outside the span dropped by onset, spans clamped.
bars.endclamps to the bar count; an empty/reversed range yields no bars while
preserving the track/voice skeleton.
split::bar_segments(master_bars, cut_ticks) -> Vec<Range<usize>>— mapsphrase-boundary onset ticks to contiguous, non-overlapping bar ranges (each
cut snapped to its containing bar; start-of-track and same-bar cuts collapse).
Reused by the web split in #2b.
CLI
griff split <file>slices the first note-bearing track at its phraseboundaries (
split::bar_segmentsover the existingdetect_boundariescuts)and writes one chunk per phrase to
<stem>.p<N>.chunk.json. Each chunk is anextract_barssub-score, measured on its own bars and stamped with its sourcebar_range(the original bar indices it covers). The curator's tags, rightsand cohort are gathered once and inherited by every phrase.
Design notes
meaningful
source.bar_range. The purephrase_chunksbuilder is separatedfrom the stdin/file-IO wrapper so it is unit-testable.
boundary::detect_phrase_boundariesand thesame config the curate path already uses.
Tests (strict TDD on the keystone)
4c238f3red →3486980green forextract_bars(
core/tests/slice_extract.rs): bar run re-indexed/rebased, out-of-rangenotes dropped, empty/clamped ranges.
split::bar_segmentsunit tests: no-cut whole-track, cut snapping, start/samebar collapse, no-bars.
phrase_chunkstest: chunks tile the bars contiguously, carrybar_range, and id-suffix per phrase.Validation
cargo clippy --workspace --all-targets -- -D warnings(incl.nursery) clean.Next (#2b — web)
Auto-split in the browser capture tool: pagination across the resulting
phrase chunks for review, per-chunk playback (new Web Audio), and the
existing basic generation preserved.
🤖 Generated with Claude Code
Generated by Claude Code
Summary by CodeRabbit
New Features
Tests
bar_rangetiling, renumbering, and dropping silent segments.