Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions core/src/complement.rs
Original file line number Diff line number Diff line change
Expand Up @@ -443,6 +443,7 @@ const fn span_label(technique: SpanTechnique) -> &'static str {
SpanTechnique::HammerOn => "hammer_on",
SpanTechnique::PullOff => "pull_off",
SpanTechnique::Vibrato => "vibrato",
SpanTechnique::LetRing => "let_ring",
}
}

Expand Down
10 changes: 10 additions & 0 deletions core/src/corpus.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,13 @@ use crate::structure::{ComplexityProfile, StructureMetrics};
/// `rights` key) keep loading and re-serialize losslessly. Rights status is
/// not derivable from content, so it is captured at curation time and cannot
/// be backfilled.
///
/// Tag taxonomy is intentionally *not* versioned here: [`SwancoreTag`] grows
/// additively (e.g. `let_ring`, #75) and `SCHEMA_VERSION` tracks structural
/// `ChunkMeta` changes (the optional-field, forward-compatible pattern above),
/// not the tag set — a new tag only breaks readers that hard-reject unknown
/// variants, a curation-tooling concern, not a corpus-structure one
/// (decisions 2026-06-19).
pub const SCHEMA_VERSION: u32 = 7;

// ── identifiers ───────────────────────────────────────────────────────────────
Expand Down Expand Up @@ -106,6 +113,8 @@ pub enum SwancoreTag {
Bend,
Vibrato,
PalmMute,
/// Let ring — notes left to sustain into following beats.
LetRing,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Bump the corpus schema for the new tag

Adding SwancoreTag::LetRing creates a new serialized corpus tag value ("let_ring"), and all_variants() now exposes it to the CLI/web curation paths that write ChunkMeta.tags, but SCHEMA_VERSION remains 7. A newly curated v7 manifest or chunk containing this tag is no longer readable by older v7 tooling because serde rejects unknown enum variants, so the version no longer identifies compatible corpus data; bump the schema version and document the tag-taxonomy change when adding this persisted enum value.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The mechanism is right (serde rejects unknown variants), but we're deliberately keeping SCHEMA_VERSION = 7 — maintainer's call. Every version v1–v7 is a structural ChunkMeta change (a new optional field under the forward-compatible "pre-vN records keep loading" pattern); none tracks the tag set. A SwancoreTag addition fits neither that mechanism nor that compat direction (yours is backward compat — old reader, new data), and #75 adds ~15 more tags, so versioning the taxonomy would turn SCHEMA_VERSION into a tag counter and muddy what it identifies.

Made the policy explicit so it isn't ambiguous going forward (333a3cc): a note in the SCHEMA_VERSION doc + a decisions-log Y-statement. The residual risk (a pinned pre-tag build hard-rejecting a chunk with a newer tag) is accepted — griff's reader and writer ship together.


Generated by Claude Code

NaturalHarmonic,
ArtificialHarmonic,
// ── rhythm ─────────────────────────────────────────────────────────────
Expand Down Expand Up @@ -144,6 +153,7 @@ impl SwancoreTag {
Self::Bend,
Self::Vibrato,
Self::PalmMute,
Self::LetRing,
Self::NaturalHarmonic,
Self::ArtificialHarmonic,
Self::Syncopated,
Expand Down
1 change: 1 addition & 0 deletions core/src/dump.rs
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,7 @@ const fn span_name(technique: SpanTechnique) -> &'static str {
SpanTechnique::HammerOn => "hammer_on",
SpanTechnique::PullOff => "pull_off",
SpanTechnique::Vibrato => "vibrato",
SpanTechnique::LetRing => "let_ring",
}
}

Expand Down
2 changes: 2 additions & 0 deletions core/src/event.rs
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,8 @@ pub enum SpanTechnique {
PullOff,
/// Vibrato.
Vibrato,
/// Let ring — the note is left to sustain into following beats.
LetRing,
}

/// Where a technique came from — import-side provenance (ADR-0018; the
Expand Down
18 changes: 18 additions & 0 deletions core/src/gp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -597,6 +597,9 @@ fn map_gp_note_marks(
if effect.vibrato {
push_span(SpanTechnique::Vibrato);
}
if effect.let_ring {
push_span(SpanTechnique::LetRing);
}

let mut marks = NoteMarks::empty();
if effect.accentuated_note || effect.heavy_accentuated_note {
Expand Down Expand Up @@ -1090,6 +1093,21 @@ mod tests {
assert_eq!(spans[0].evidence, TechniqueEvidence::explicit());
}

#[test]
fn gp_let_ring_emits_explicit_span() {
// let-ring is a per-note sustain technique GP records (#75); it must
// surface as a LetRing TechniqueSpan so derivation/curation can see it.
let effect = GpNoteEffect {
let_ring: true,
..GpNoteEffect::default()
};
let mut spans = Vec::new();
let _ = map_gp_note_marks(&effect, Ticks(0), Ticks(480), &mut spans);
assert_eq!(spans.len(), 1);
assert_eq!(spans[0].technique, SpanTechnique::LetRing);
assert_eq!(spans[0].evidence, TechniqueEvidence::explicit());
}

#[test]
fn dead_note_imports_as_dead_marked_note() {
// A muted "X" note (NoteType::Dead) still carries a real (string, fret):
Expand Down
5 changes: 4 additions & 1 deletion core/src/technique.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,14 +35,15 @@ pub struct DerivedTechniques {
}

/// Spanning techniques in canonical (declaration) order.
const SPANS: [SpanTechnique; 7] = [
const SPANS: [SpanTechnique; 8] = [
SpanTechnique::Slide,
SpanTechnique::Bend,
SpanTechnique::Legato,
SpanTechnique::PalmMute,
SpanTechnique::HammerOn,
SpanTechnique::PullOff,
SpanTechnique::Vibrato,
SpanTechnique::LetRing,
];

/// `lower_snake_case` name for a spanning technique.
Expand All @@ -55,6 +56,7 @@ const fn span_name(t: SpanTechnique) -> &'static str {
SpanTechnique::HammerOn => "hammer_on",
SpanTechnique::PullOff => "pull_off",
SpanTechnique::Vibrato => "vibrato",
SpanTechnique::LetRing => "let_ring",
}
}

Expand All @@ -68,6 +70,7 @@ const fn span_tag(t: SpanTechnique) -> Option<SwancoreTag> {
SpanTechnique::HammerOn => Some(SwancoreTag::HammerOn),
SpanTechnique::PullOff => Some(SwancoreTag::PullOff),
SpanTechnique::Vibrato => Some(SwancoreTag::Vibrato),
SpanTechnique::LetRing => Some(SwancoreTag::LetRing),
SpanTechnique::Legato => None,
}
}
Expand Down
9 changes: 9 additions & 0 deletions core/tests/technique_tags.rs
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,15 @@ fn maps_spans_and_marks_to_direct_tags_and_a_superset_name_list() {
}
}

#[test]
fn let_ring_span_derives_the_let_ring_tag_and_name() {
// let-ring is newly parsed from GP (#75): a LetRing span must auto-derive
// both the dedicated SwancoreTag::LetRing and the "let_ring" name.
let d = derive_techniques(&score_with(&[SpanTechnique::LetRing], NoteMarks::empty()), 0);
assert_eq!(d.tags, vec![SwancoreTag::LetRing]);
assert_eq!(d.names, vec!["let_ring".to_owned()]);
}

#[test]
fn presence_only_so_repeats_dedupe_and_output_is_deterministic() {
// The same technique across groups/notes is "present", counted once.
Expand Down
11 changes: 11 additions & 0 deletions docs/decisions.log.md
Original file line number Diff line number Diff line change
Expand Up @@ -1251,3 +1251,14 @@ Architectural decisions go to [`adr/`](adr/) instead.
is duplicated and kept in step (both fronts now test the track-consistency
rule). Accepted: `arrange` generation is untouched, and `web/dist` stays
gitignored (CI rebuilds it on deploy).

- 2026-06-19 — In the context of auto-deriving the `let_ring` tag (and the rest
of #75's tag taxonomy to come), facing Codex's point that a new serialized
`SwancoreTag` value is unreadable by older `SCHEMA_VERSION = 7` tooling (serde
rejects unknown enum variants), we decided for keeping the version at 7 and
growing the tag taxonomy additively, and against bumping per tag or once for
the whole #75 expansion, to keep `SCHEMA_VERSION` meaning what v1–v7 set it to
mean — structural `ChunkMeta` field additions under the forward-compatible
optional-field pattern, not a tag counter — accepting that a pinned pre-tag
build hard-rejects a chunk carrying a newer tag (a curation-tooling concern,
since griff's reader and writer ship together).