Skip to content

Commit e9857be

Browse files
authored
Handle accidentals on tied notes across bars correctly (#485)
1 parent 9ae6706 commit e9857be

File tree

6 files changed

+53
-31
lines changed

6 files changed

+53
-31
lines changed

src/rendering/ScoreBarRenderer.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ export class ScoreBarRenderer extends BarRendererBase {
5151
public constructor(renderer: ScoreRenderer, bar: Bar) {
5252
super(renderer, bar);
5353
this._startSpacing = false;
54-
this.accidentalHelper = new AccidentalHelper(bar);
54+
this.accidentalHelper = new AccidentalHelper(this);
5555
}
5656

5757
public getBeatDirection(beat: Beat): BeamDirection {

src/rendering/utils/AccidentalHelper.ts

Lines changed: 47 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { Note } from '@src/model/Note';
66
import { NoteAccidentalMode } from '@src/model/NoteAccidentalMode';
77
import { ModelUtils } from '@src/model/ModelUtils';
88
import { PercussionMapper } from '../../model/PercussionMapper';
9+
import { ScoreBarRenderer } from '../ScoreBarRenderer';
910

1011

1112
class BeatLines {
@@ -20,6 +21,7 @@ class BeatLines {
2021
*/
2122
export class AccidentalHelper {
2223
private _bar: Bar;
24+
private _barRenderer: ScoreBarRenderer;
2325

2426
/**
2527
* a lookup list containing an info whether the notes within an octave
@@ -103,8 +105,9 @@ export class AccidentalHelper {
103105
*/
104106
public minLine: number = -1000;
105107

106-
public constructor(bar: Bar) {
107-
this._bar = bar;
108+
public constructor(barRenderer: ScoreBarRenderer) {
109+
this._barRenderer = barRenderer;
110+
this._bar = barRenderer.bar;
108111
}
109112

110113
public static getPercussionLine(bar: Bar, noteValue: number): number {
@@ -231,19 +234,15 @@ export class AccidentalHelper {
231234
switch (accidentalMode) {
232235
case NoteAccidentalMode.ForceSharp:
233236
accidentalToSet = AccidentalType.Sharp;
234-
hasNoteAccidentalWithinOctave = true;
235237
break;
236238
case NoteAccidentalMode.ForceDoubleSharp:
237239
accidentalToSet = AccidentalType.DoubleSharp;
238-
hasNoteAccidentalWithinOctave = true;
239240
break;
240241
case NoteAccidentalMode.ForceFlat:
241242
accidentalToSet = AccidentalType.Flat;
242-
hasNoteAccidentalWithinOctave = true;
243243
break;
244244
case NoteAccidentalMode.ForceDoubleFlat:
245245
accidentalToSet = AccidentalType.DoubleFlat;
246-
hasNoteAccidentalWithinOctave = true;
247246
break;
248247
default:
249248
// if note has an accidental in the octave, we place a symbol
@@ -257,36 +256,56 @@ export class AccidentalHelper {
257256
break;
258257
}
259258

260-
// do we need an accidental on the note?
261-
if (accidentalToSet !== AccidentalType.None) {
262-
// if we already have an accidental on this line we will reset it if it's the same
263-
if (this._registeredAccidentals.has(line)) {
264-
if (this._registeredAccidentals.get(line) === accidentalToSet) {
265-
accidentalToSet = AccidentalType.None;
259+
// Issue #472: Tied notes across bars do not show the accidentals but also
260+
// do not register them.
261+
// https://ultimatemusictheory.com/tied-notes-with-accidentals/
262+
let skipAccidental = false;
263+
if (note && note.isTieDestination && note.beat.index === 0) {
264+
// candidate for skip, check further if start note is on the same line
265+
const previousRenderer = this._barRenderer.previousRenderer as ScoreBarRenderer;
266+
if (previousRenderer) {
267+
const tieOriginLine = previousRenderer.accidentalHelper.getNoteLine(note.tieOrigin!);
268+
if(tieOriginLine === line) {
269+
skipAccidental = true;
266270
}
267271
}
268-
// if there is no accidental on the line, and the key signature has it set already, we clear it on the note
269-
else if (hasKeySignatureAccidentalSetForNote && accidentalToSet === accidentalForKeySignature) {
270-
accidentalToSet = AccidentalType.None;
271-
}
272+
}
272273

273-
// register the new accidental on the line if any.
274-
if (accidentalToSet != AccidentalType.None) {
275-
this._registeredAccidentals.set(line, accidentalToSet);
276-
}
274+
275+
if (skipAccidental) {
276+
accidentalToSet = AccidentalType.None;
277277
} else {
278-
// if we don't want an accidental, but there is already one applied, we place a naturalize accidental
279-
// and clear the registration
280-
if (this._registeredAccidentals.has(line)) {
281-
// if there is already a naturalize symbol on the line, we don't care.
282-
if (this._registeredAccidentals.get(line) === AccidentalType.Natural) {
278+
// do we need an accidental on the note?
279+
if (accidentalToSet !== AccidentalType.None) {
280+
// if we already have an accidental on this line we will reset it if it's the same
281+
if (this._registeredAccidentals.has(line)) {
282+
if (this._registeredAccidentals.get(line) === accidentalToSet) {
283+
accidentalToSet = AccidentalType.None;
284+
}
285+
}
286+
// if there is no accidental on the line, and the key signature has it set already, we clear it on the note
287+
else if (hasKeySignatureAccidentalSetForNote && accidentalToSet === accidentalForKeySignature) {
283288
accidentalToSet = AccidentalType.None;
284-
} else {
285-
accidentalToSet = AccidentalType.Natural;
289+
}
290+
291+
// register the new accidental on the line if any.
292+
if (accidentalToSet != AccidentalType.None) {
286293
this._registeredAccidentals.set(line, accidentalToSet);
287294
}
288295
} else {
289-
this._registeredAccidentals.delete(line);
296+
// if we don't want an accidental, but there is already one applied, we place a naturalize accidental
297+
// and clear the registration
298+
if (this._registeredAccidentals.has(line)) {
299+
// if there is already a naturalize symbol on the line, we don't care.
300+
if (this._registeredAccidentals.get(line) === AccidentalType.Natural) {
301+
accidentalToSet = AccidentalType.None;
302+
} else {
303+
accidentalToSet = AccidentalType.Natural;
304+
this._registeredAccidentals.set(line, accidentalToSet);
305+
}
306+
} else {
307+
this._registeredAccidentals.delete(line);
308+
}
290309
}
291310
}
292311
}
34.7 KB
Loading
34.5 KB
Loading
9.52 KB
Binary file not shown.

test/visualTests/features/NotationLegend.test.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -96,15 +96,18 @@ describe('NotationLegend', () => {
9696
it('mixed-default', async () => { await runNotationLegendTest(`mixed-default.png`, 121, 7, false); });
9797
it('mixed-songbook', async () => { await runNotationLegendTest(`mixed-songbook.png`, 121, 7, true); });
9898

99-
async function runNotationLegendTest(referenceFileName: string, startBar: number, barCount: number, songBook: boolean): Promise<void> {
99+
it('tied-note-accidentals-default', async () => { await runNotationLegendTest(`tied-note-accidentals-default.png`, 1, -1, false, 'tied-note-accidentals.gp'); });
100+
it('tied-note-accidentals-songbook', async () => { await runNotationLegendTest(`tied-note-accidentals-songbook.png`, 1, -1, true, 'tied-note-accidentals.gp'); });
101+
102+
async function runNotationLegendTest(referenceFileName: string, startBar: number, barCount: number, songBook: boolean, fileName: string = 'notation-legend.gp'): Promise<void> {
100103
let settings: Settings = new Settings();
101104
settings.display.layoutMode = LayoutMode.Horizontal;
102105
settings.display.startBar = startBar;
103106
settings.display.barCount = barCount;
104107
if (songBook) {
105108
settings.setSongBookModeSettings();
106109
}
107-
const inputFileData = await TestPlatform.loadFile(`test-data/visual-tests/notation-legend/notation-legend.gp`);
110+
const inputFileData = await TestPlatform.loadFile(`test-data/visual-tests/notation-legend/${fileName}`);
108111
let score: Score = ScoreLoader.loadScoreFromBytes(inputFileData, settings);
109112
await VisualTestHelper.runVisualTestScore(score, `notation-legend/${referenceFileName}`, settings, [0]);
110113
}

0 commit comments

Comments
 (0)