Repository navigation
feat(render): show transposed guitar tabs on playable strings - #3007
Merged
Merged
Conversation
…C# and Kotlin Adds an Int8Array runtime type to the C# and Kotlin ports and gives Int16Array/Uint16Array the same fill/subarray/set surface as Int32Array. The transpiler no longer rejects Int8Array and strips its type arguments like the other typed arrays.
…eric code - Compound bitwise assignments (a |= b) now cast the right operand to int like plain bitwise operators do; before, a double right operand did not compile in C# or Kotlin. - A non-null assertion on pop()/shift() of a number/boolean array no longer emits .Value in C#; the runtime helpers return the plain element type. - Kotlin DoubleList.splice(start, deleteCount) copied the wrong range and corrupted or threw for removals before the end; it now matches JS and clamps like JS. Adds the inserting splice(start, deleteCount, ...items). - Kotlin BooleanList.fill and List.fill return the list like JS fill.
… staves TabFingeringSolver decides on which string and fret tab notes are shown when the staff is transposed, purely from the model. Notes keep their string whenever the transposed fret is playable; otherwise the bars connected by ties, slides and hammer-ons/pull-offs are re-fingered with a beam search preferring fewer unplayable notes, fewer moved notes and less hand movement and stretch. Natural harmonics are mapped to the node of the transposed pitch, or shown as artificial harmonic when none exists. Not yet wired into rendering.
When a staff with tablature is transposed (notation.transpositionPitches, \transpose), the tab no longer just subtracts the transposition from the fret. TabBarRenderer resolves the fingering of the segment of linked bars once per layout via TabFingeringSolver and all tab glyphs read the displayed string and fret from it: fret numbers, note lines, staff line gaps, ties, slurs, bends, brushes, flags and the harmonic labels. The model stays untouched. Notes which fit on no string keep their string and the out-of-range fret and are painted in the new RenderingResources.tabUnplayableFretColor. String-number circles show the displayed string and left-hand finger numbers of re-fingered notes are hidden. A partial render restarts at the first bar of the affected segment.
…he search buffers The fast path of TabFingeringSolver reads the model directly instead of building the note tables first, so segments keeping their fingering allocate nothing. The beam search keeps only the states of the previous slice plus back pointers and new-group deltas in typed arrays, merges the voices instead of sorting beats and avoids splice and iterator allocations in its loops. Natural harmonics needing the artificial-harmonic fallback on their own string no longer pass the fast path, which displayed them at the wrong pitch.
Adds a 'Transposed tablature' section to the guitar tablature page, the new tabUnplayableFretColor to the palette page, the 1.10 release notes and the behavior to the transpositionPitches JSDoc.
…etup for unchanged segments Costs are compared by candidate slot and passed to and from the slice scoring through an array, so no doubles cross function boundaries. The fast path runs before the solver state is created, so segments keeping their fingering allocate nothing. Back pointers are stored as bytes.
… out the tab Replaces the segment-wide beam search (TabFingeringSolver, TabFingeringSegment) by TabFingering, which decides the string of each note while TabBeatGlyph creates its note glyphs and keeps the result on the NoteNumberGlyph. It only reads the beat, the glyphs of earlier beats and the tie/slide/hammer-on links of the notes: continued notes follow their origin, notes playable on their string keep it, the others take the free string closest to the frets of the beat or the previous beat. Natural harmonics without a node at the transposed pitch are marked unplayable instead of being shown as artificial harmonics.
…n by other notes A note which no string can play keeps its own string only if no other note of the beat took it; otherwise it moves to the free string where its fret is closest to the neck instead of overlapping the other note.
…and transposed track Each case renders the original track and the track transposed through notation.transpositionPitches one above the other. Replaces the render-based unit test harness, which the visual cases now cover.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Note
AI-authored disclosure (
alphatab-ai-authored-v1)Portions of this content were authored by an AI agent. The agent has read
AGENTS.md and the human submitter accepts responsibility for
compliance with the rules in that document.
Issues
Fixes #1587
Proposed changes
Problem
With
notation.transpositionPitches(or alphaTex\transpose), the tab subtracts the transposition from the fret and never changes the string. Transposing down turns open strings into negative frets (-4); transposing up pushes notes beyond the neck.Behavior
When a staff with tablature is transposed, every note is placed on a playable position of the staff's tuning:
display.resources.tabUnplayableFretColor(default red). If another note of the beat took that string, the note moves to the free string where its fret is closest to the neck instead of overlapping it.The model is not changed. The positions exist only on the rendered tab, so changing
transpositionPitchesat runtime takes effect on the next render.Design
TabFingeringdecides the positions of a beat whileTabBeatGlyphcreates its note glyphs. It reads only the beat, the glyphs of earlier beats and the tie/slide/hammer-on links of the notes. The result lives onNoteNumberGlyph(displayString,displayFret,isMoved,isUnplayable); ties, slurs, bends, brushes, flags, the skyline and the staff-line gaps read it from there. Nothing is stored on the model or the layout, and there is no extra model traversal during rendering. A partial render restarts at the bar of the earliest origin of a note in the changed bar, so a placement whose origin is in an earlier system is decided again.Untransposed staves and percussion staves take the existing path unchanged.
Platform
Fixes for gaps found while implementing this:
Int8Array;fill/subarray/setforInt16ArrayandUint16Array; KotlinDoubleList.splicenow matches JS (it copied the wrong range for removals before the end of the list) and has the inserting overload; KotlinBooleanList.fillandList.fillreturn the list.a |= b) cast the right operand to int like plain bitwise operators; a non-null assertion onpop()/shift()of a number array no longer emits.Valuein C#.Measurements
test-data(342 files, transposed by −4, +3 and +7): moved notes keep the hand movement per beat of a segment-wide beam-search prototype (1.55 vs 1.56 frets), with 3.7% more unplayable notes and slightly wider chords (2.62 vs 2.44 frets).test-data/conversion/full-song.gp, compared withdevelop: no measurable difference untransposed; about +0.3 MB at −4.Known limitations and follow-ups
Checklist
Tests:
GuitarTabs.test.ts(transpose-*), each rendering the original and the transposed track. They cover open strings, chords, ties across barlines, hammer-ons/slides, pull-offs to open strings, bends, natural harmonics, dead notes, trills, ukulele, bass, capo, grace notes, repeated bars and multiple voices.TabFingeringPartialUpdate.test.ts: a partial render matches a fresh render when a hammer-on crosses a system boundary.AI authorship disclosure
(
alphatab-ai-authored-v1) is present at the top of this body, and I havepersonally reviewed every change and can explain each one
Further details
website/