Skip to content

Commit

Permalink
Merge pull request #1263 from rvilarl/fix/1257
Browse files Browse the repository at this point in the history
OSMD VexflowPatch: share noteheads
  • Loading branch information
0xfe authored Feb 19, 2022
2 parents fe6add6 + 69fcdf6 commit 91a6714
Show file tree
Hide file tree
Showing 3 changed files with 120 additions and 18 deletions.
4 changes: 2 additions & 2 deletions src/note.ts
Original file line number Diff line number Diff line change
Expand Up @@ -550,9 +550,9 @@ export abstract class Note extends Tickable {
return this;
}

// Get all modifiers of a specific type in the `ModifierContext`
/** Get all modifiers of a specific type in `this.modifiers`. */
getModifiersByType(type: string): Modifier[] {
return this.checkModifierContext().getMembers(type) as Modifier[];
return this.modifiers.filter((modifier) => modifier.getCategory() === type);
}

/** Get the coordinates for where modifiers begin. */
Expand Down
96 changes: 80 additions & 16 deletions src/stavenote.ts
Original file line number Diff line number Diff line change
Expand Up @@ -182,22 +182,82 @@ export class StaveNote extends StemmableNote {

// Test for two voice note intersection
if (voices === 2) {
const lineSpacing = noteU.stemDirection === noteL.stemDirection ? 0.0 : 0.5;
const lineSpacing =
noteU.note.hasStem() && noteL.note.hasStem() && noteU.stemDirection === noteL.stemDirection ? 0.0 : 0.5;
if (noteU.minLine <= noteL.maxLine + lineSpacing) {
if (noteU.isrest) {
// shift rest up
shiftRestVertical(noteU, noteL, 1);
} else if (noteL.isrest) {
// shift rest down
shiftRestVertical(noteL, noteU, -1);
} else if (noteU.stemDirection === noteL.stemDirection) {
// upper voice is middle voice, so shift it right
xShift = voiceXShift + 2;
noteU.note.setXShift(xShift);
} else {
// shift lower voice right
xShift = voiceXShift + 2;
noteL.note.setXShift(xShift);
//Instead of shifting notes, remove the appropriate flag
//If we are sharing a line, switch one notes stem direction.
//If we are sharing a line and in the same voice, only then offset one note
const lineDiff = Math.abs(noteU.line - noteL.line);
if (noteU.note.hasStem() && noteL.note.hasStem()) {
//If we have different dot values, must offset
//Or If we have a white mixed with a black notehead, must offset
let whiteNoteHeadCount = 0;
let blackNoteHeadCount = 0;
if (Tables.durationToNumber(noteU.note.duration) === 2) {
whiteNoteHeadCount++;
} else if (Tables.durationToNumber(noteU.note.duration) > 2) {
blackNoteHeadCount++;
}
if (Tables.durationToNumber(noteL.note.duration) === 2) {
whiteNoteHeadCount++;
} else if (Tables.durationToNumber(noteL.note.duration) > 2) {
blackNoteHeadCount++;
}
if (
(whiteNoteHeadCount !== 2 && blackNoteHeadCount !== 2) ||
noteU.note.getModifiersByType(Category.Dot).length !== noteL.note.getModifiersByType(Category.Dot).length
) {
xShift = voiceXShift + 2;
if (noteU.stemDirection === noteL.stemDirection) {
// upper voice is middle voice, so shift it right
noteU.note.setXShift(xShift);
} else {
// shift lower voice right
noteL.note.setXShift(xShift);
}
} else if (lineDiff < 1 && lineDiff > 0) {
//if the notes are quite close but not on the same line, shift
xShift = voiceXShift + 2;
if (noteU.stemDirection === noteL.stemDirection) {
// upper voice is middle voice, so shift it right
noteU.note.setXShift(xShift);
} else {
// shift lower voice right
noteL.note.setXShift(xShift);
}
} else if (noteU.note.voice !== noteL.note.voice) {
//If we are not in the same voice
if (noteU.stemDirection === noteL.stemDirection) {
if (noteU.line != noteL.line) {
xShift = voiceXShift + 2;
noteU.note.setXShift(xShift);
} else {
//same line, swap stem direction for one note
if (noteL.stemDirection === 1) {
noteL.stemDirection = -1;
noteL.note.setStemDirection(-1);
}
}
}
} //Very close whole notes
} else {
xShift = voiceXShift + 2;
if (noteU.stemDirection === noteL.stemDirection) {
// upper voice is middle voice, so shift it right
noteU.note.setXShift(xShift);
} else {
// shift lower voice right
noteL.note.setXShift(xShift);
}
}
}
}

Expand Down Expand Up @@ -748,13 +808,13 @@ export class StaveNote extends StemmableNote {
setStyle(style: ElementStyle): this {
super.setStyle(style);
this._noteHeads.forEach((notehead) => notehead.setStyle(style));
this.stem?.setStyle(style);
if (this.stem) this.stem.setStyle(style);
return this;
}

setStemStyle(style: ElementStyle): this {
const stem = this.getStem();
stem?.setStyle(style);
if (stem) stem.setStyle(style);
return this;
}
getStemStyle(): ElementStyle | undefined {
Expand Down Expand Up @@ -1091,9 +1151,11 @@ export class StaveNote extends StemmableNote {
this.stem.adjustHeightForFlag();
}

ctx.openGroup('stem', undefined, { pointerBBox: true });
this.stem?.setContext(ctx).draw();
ctx.closeGroup();
if (this.stem) {
ctx.openGroup('stem', undefined, { pointerBBox: true });
this.stem.setContext(ctx).draw();
ctx.closeGroup();
}
}

/** Primarily used as the scaling factor for grace notes, GraceNote will return the required scale. */
Expand Down Expand Up @@ -1155,9 +1217,11 @@ export class StaveNote extends StemmableNote {
// Format note head x positions
this._noteHeads.forEach((notehead) => notehead.setX(xBegin));

// Format stem x positions
const stemX = this.getStemX();
this.stem?.setNoteHeadXBounds(stemX, stemX);
if (this.stem) {
// Format stem x positions
const stemX = this.getStemX();
this.stem.setNoteHeadXBounds(stemX, stemX);
}

L('Rendering ', this.isChord() ? 'chord :' : 'note :', this.keys);

Expand Down
38 changes: 38 additions & 0 deletions tests/stavenote_tests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ const StaveNoteTests = {
run('Flag and Dots Placement - Stem Down', dotsAndFlagsStemDown);
run('Beam and Dot Placement - Stem Up', dotsAndBeamsUp);
run('Beam and Dot Placement - Stem Down', dotsAndBeamsDown);
run('Note Heads Placement - Simple', noteHeadsSimple);
run('Center Aligned Note', centerAlignedRest);
run('Center Aligned Note with Articulation', centerAlignedRestFermata);
run('Center Aligned Note with Annotation', centerAlignedRestAnnotation);
Expand Down Expand Up @@ -995,6 +996,43 @@ function dotsAndBeamsDown(options: TestOptions, contextBuilder: ContextBuilder):
ok(true, 'Full Dot');
}

function noteHeadsSimple(options: TestOptions): void {
const vf = VexFlowTests.makeFactory(options, 800, 250);
const score = vf.EasyScore();

const system1 = vf.System({ y: 100, x: 50, width: 200 });
system1
.addStave({
voices: [
score.voice([...score.beam(score.notes('a4/8, b4/8', { stem: 'up' })), ...score.notes('a4/q/r, a4/h/r')]),
score.voice(score.notes('g4/w')),
],
})
.addClef('treble')
.addTimeSignature('4/4');

const system2 = vf.System({ y: 100, x: 250, width: 150 });
system2.addStave({
voices: [score.voice(score.notes('b4/h, b4/h/r')), score.voice(score.notes('b4/w'))],
});

const system3 = vf.System({ y: 100, x: 400, width: 150 });
system3.addStave({
voices: [score.voice(score.notes('d5/h, d5/h/r')), score.voice(score.notes('e4/w'))],
});

const system4 = vf.System({ y: 100, x: 550, width: 150 });
system4.addStave({
voices: [
score.voice(score.notes('e4/q, e4/q/r, e4/h/r')),
score.voice(score.notes('e4/8, e4/8/r, e4/q/r, e4/h/r')),
],
});

vf.draw();
expect(0);
}

function centerAlignedRest(options: TestOptions): void {
const f = VexFlowTests.makeFactory(options, 400, 160);
const stave = f.Stave({ x: 10, y: 10, width: 350 }).addClef('treble').addTimeSignature('4/4');
Expand Down

0 comments on commit 91a6714

Please sign in to comment.