Skip to content

Commit

Permalink
Merge pull request #1170 from rvilarl/fix/1169
Browse files Browse the repository at this point in the history
fix #1169 applyAccidentals
  • Loading branch information
0xfe authored Oct 8, 2021
2 parents f48bde4 + 57f0f39 commit 0dea4ab
Show file tree
Hide file tree
Showing 2 changed files with 204 additions and 13 deletions.
13 changes: 8 additions & 5 deletions src/accidental.ts
Original file line number Diff line number Diff line change
Expand Up @@ -397,7 +397,8 @@ export class Accidental extends Modifier {
if (!keySignature) keySignature = 'C';

// Get the scale map, which represents the current state of each pitch.
const scaleMap = music.createScaleMap(keySignature);
const scaleMapKey = music.createScaleMap(keySignature);
const scaleMap: Record<string, string> = {};

tickPositions.forEach((tickPos: number) => {
const tickables = tickNoteMap[tickPos];
Expand All @@ -416,18 +417,20 @@ export class Accidental extends Modifier {
const staveNote = t;
staveNote.keys.forEach((keyString: string, keyIndex: number) => {
const key = music.getNoteParts(keyString.split('/')[0]);
const octave = keyString.split('/')[1];

// Force a natural for every key without an accidental
const accidentalString = key.accidental || 'n';
const pitch = key.root + accidentalString;

// Determine if the current pitch has the same accidental
// as the scale state
const sameAccidental = scaleMap[key.root] === pitch;
if (!scaleMap[key.root + octave]) scaleMap[key.root + octave] = scaleMapKey[key.root];
const sameAccidental = scaleMap[key.root + octave] === pitch;

// Determine if an identical pitch in the chord already
// modified the accidental state
const previouslyModified = modifiedPitches.indexOf(pitch) > -1;
const previouslyModified = modifiedPitches.indexOf(keyString) > -1;

// Remove accidentals
staveNote.getModifiers().forEach((modifier, index) => {
Expand All @@ -444,7 +447,7 @@ export class Accidental extends Modifier {
if (!sameAccidental || (sameAccidental && previouslyModified)) {
// Modify the scale map so that the root pitch has an
// updated state
scaleMap[key.root] = pitch;
scaleMap[key.root + octave] = pitch;

// Create the accidental
const accidental = new Accidental(accidentalString);
Expand All @@ -453,7 +456,7 @@ export class Accidental extends Modifier {
staveNote.addAccidental(keyIndex, accidental);

// Add the pitch to list of pitches that modified accidentals
modifiedPitches.push(pitch);
modifiedPitches.push(keyString);
}
});

Expand Down
204 changes: 196 additions & 8 deletions tests/accidental_tests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,10 @@ const AccidentalTests = {
run('Automatic Accidentals - No Accidentals Necessary (EasyScore)', automaticAccidentals3);
run('Automatic Accidentals - Multi Voice Inline', automaticAccidentalsMultiVoiceInline);
run('Automatic Accidentals - Multi Voice Offset', automaticAccidentalsMultiVoiceOffset);
run('Automatic Accidentals - Key C, Single Octave', automaticAccidentalsCornerCases1);
run('Automatic Accidentals - Key C, Two Octaves', automaticAccidentalsCornerCases2);
run('Automatic Accidentals - Key C#, Single Octave', automaticAccidentalsCornerCases3);
run('Automatic Accidentals - Key C#, Two Octaves', automaticAccidentalsCornerCases4);
run('Factory API', factoryAPI);
},
};
Expand Down Expand Up @@ -107,8 +111,8 @@ function autoAccidentalWorking(): void {
equal(hasAccidental(notes[2]), true, 'Added flat');
equal(hasAccidental(notes[3]), true, 'Added sharp');
equal(hasAccidental(notes[4]), false, 'Sharp remembered');
equal(hasAccidental(notes[5]), false, 'Flat remembered');
equal(hasAccidental(notes[6]), false, 'Flat remembered');
equal(hasAccidental(notes[5]), true, 'Added flat(different octave)');
equal(hasAccidental(notes[6]), true, 'Added flat(different octave)');
equal(hasAccidental(notes[7]), false, 'sharp remembered');

notes = [
Expand Down Expand Up @@ -930,12 +934,12 @@ function automaticAccidentalsMultiVoiceOffset(options: TestOptions): void {

equal(hasAccidental(notes1[0]), true);
equal(hasAccidental(notes1[1]), false);
equal(hasAccidental(notes1[2]), false);
equal(hasAccidental(notes1[3]), false);
equal(hasAccidental(notes1[4]), false);
equal(hasAccidental(notes1[5]), false);
equal(hasAccidental(notes1[6]), false);
equal(hasAccidental(notes1[7]), false);
equal(hasAccidental(notes1[2]), true);
equal(hasAccidental(notes1[3]), true);
equal(hasAccidental(notes1[4]), true);
equal(hasAccidental(notes1[5]), true);
equal(hasAccidental(notes1[6]), true);
equal(hasAccidental(notes1[7]), true);

new Formatter().joinVoices([voice0, voice1]).formatToStave([voice0, voice1], stave);

Expand All @@ -944,6 +948,190 @@ function automaticAccidentalsMultiVoiceOffset(options: TestOptions): void {
ok(true);
}

function automaticAccidentalsCornerCases1(options: TestOptions): void {
const f = VexFlowTests.makeFactory(options, 700, 150);
const stave = f.Stave().addKeySignature('C');

const notes0 = [
{ keys: ['c/4'], duration: '4', stem_direction: -1 },
{ keys: ['c#/4'], duration: '4', stem_direction: -1 },
{ keys: ['c#/4'], duration: '4', stem_direction: -1 },
{ keys: ['c/4'], duration: '4', stem_direction: -1 },
{ keys: ['c/4'], duration: '4', stem_direction: -1 },
{ keys: ['cb/4'], duration: '4', stem_direction: -1 },
{ keys: ['cb/4'], duration: '4', stem_direction: -1 },
{ keys: ['c/4'], duration: '4', stem_direction: -1 },
{ keys: ['c/4'], duration: '4', stem_direction: -1 },
].map(f.StaveNote.bind(f));

const voice0 = f.Voice().setMode(Voice.Mode.SOFT).addTickables(notes0);

Accidental.applyAccidentals([voice0], 'C');

equal(hasAccidental(notes0[0]), false);
equal(hasAccidental(notes0[1]), true);
equal(hasAccidental(notes0[2]), false);
equal(hasAccidental(notes0[3]), true);
equal(hasAccidental(notes0[4]), false);
equal(hasAccidental(notes0[5]), true);
equal(hasAccidental(notes0[6]), false);
equal(hasAccidental(notes0[7]), true);
equal(hasAccidental(notes0[8]), false);

new Formatter().joinVoices([voice0]).formatToStave([voice0], stave);

f.draw();

ok(true);
}

function automaticAccidentalsCornerCases2(options: TestOptions): void {
const f = VexFlowTests.makeFactory(options, 700, 150);
const stave = f.Stave().addKeySignature('C');

const notes0 = [
{ keys: ['c/4'], duration: '4', stem_direction: -1 },
{ keys: ['c/5'], duration: '4', stem_direction: -1 },
{ keys: ['c#/4'], duration: '4', stem_direction: -1 },
{ keys: ['c#/5'], duration: '4', stem_direction: -1 },
{ keys: ['c#/4'], duration: '4', stem_direction: -1 },
{ keys: ['c#/5'], duration: '4', stem_direction: -1 },
{ keys: ['c/4'], duration: '4', stem_direction: -1 },
{ keys: ['c/5'], duration: '4', stem_direction: -1 },
{ keys: ['c/4'], duration: '4', stem_direction: -1 },
{ keys: ['c/5'], duration: '4', stem_direction: -1 },
{ keys: ['cb/4'], duration: '4', stem_direction: -1 },
{ keys: ['cb/5'], duration: '4', stem_direction: -1 },
{ keys: ['cb/4'], duration: '4', stem_direction: -1 },
{ keys: ['cb/5'], duration: '4', stem_direction: -1 },
{ keys: ['c/4'], duration: '4', stem_direction: -1 },
{ keys: ['c/5'], duration: '4', stem_direction: -1 },
{ keys: ['c/4'], duration: '4', stem_direction: -1 },
{ keys: ['c/5'], duration: '4', stem_direction: -1 },
].map(f.StaveNote.bind(f));

const voice0 = f.Voice().setMode(Voice.Mode.SOFT).addTickables(notes0);

Accidental.applyAccidentals([voice0], 'C');

equal(hasAccidental(notes0[0]), false);
equal(hasAccidental(notes0[2]), true);
equal(hasAccidental(notes0[4]), false);
equal(hasAccidental(notes0[6]), true);
equal(hasAccidental(notes0[8]), false);
equal(hasAccidental(notes0[10]), true);
equal(hasAccidental(notes0[12]), false);
equal(hasAccidental(notes0[14]), true);
equal(hasAccidental(notes0[16]), false);
equal(hasAccidental(notes0[1]), false);
equal(hasAccidental(notes0[3]), true);
equal(hasAccidental(notes0[5]), false);
equal(hasAccidental(notes0[7]), true);
equal(hasAccidental(notes0[9]), false);
equal(hasAccidental(notes0[11]), true);
equal(hasAccidental(notes0[13]), false);
equal(hasAccidental(notes0[15]), true);
equal(hasAccidental(notes0[17]), false);

new Formatter().joinVoices([voice0]).formatToStave([voice0], stave);

f.draw();

ok(true);
}

function automaticAccidentalsCornerCases3(options: TestOptions): void {
const f = VexFlowTests.makeFactory(options, 700, 150);
const stave = f.Stave().addKeySignature('C#');

const notes0 = [
{ keys: ['c/4'], duration: '4', stem_direction: -1 },
{ keys: ['c#/4'], duration: '4', stem_direction: -1 },
{ keys: ['c#/4'], duration: '4', stem_direction: -1 },
{ keys: ['c/4'], duration: '4', stem_direction: -1 },
{ keys: ['c/4'], duration: '4', stem_direction: -1 },
{ keys: ['cb/4'], duration: '4', stem_direction: -1 },
{ keys: ['cb/4'], duration: '4', stem_direction: -1 },
{ keys: ['c/4'], duration: '4', stem_direction: -1 },
{ keys: ['c/4'], duration: '4', stem_direction: -1 },
].map(f.StaveNote.bind(f));

const voice0 = f.Voice().setMode(Voice.Mode.SOFT).addTickables(notes0);

Accidental.applyAccidentals([voice0], 'C#');

equal(hasAccidental(notes0[0]), true);
equal(hasAccidental(notes0[1]), true);
equal(hasAccidental(notes0[2]), false);
equal(hasAccidental(notes0[3]), true);
equal(hasAccidental(notes0[4]), false);
equal(hasAccidental(notes0[5]), true);
equal(hasAccidental(notes0[6]), false);
equal(hasAccidental(notes0[7]), true);
equal(hasAccidental(notes0[8]), false);

new Formatter().joinVoices([voice0]).formatToStave([voice0], stave);

f.draw();

ok(true);
}

function automaticAccidentalsCornerCases4(options: TestOptions): void {
const f = VexFlowTests.makeFactory(options, 700, 150);
const stave = f.Stave().addKeySignature('C#');

const notes0 = [
{ keys: ['c/4'], duration: '4', stem_direction: -1 },
{ keys: ['c/5'], duration: '4', stem_direction: -1 },
{ keys: ['c#/4'], duration: '4', stem_direction: -1 },
{ keys: ['c#/5'], duration: '4', stem_direction: -1 },
{ keys: ['c#/4'], duration: '4', stem_direction: -1 },
{ keys: ['c#/5'], duration: '4', stem_direction: -1 },
{ keys: ['c/4'], duration: '4', stem_direction: -1 },
{ keys: ['c/5'], duration: '4', stem_direction: -1 },
{ keys: ['c/4'], duration: '4', stem_direction: -1 },
{ keys: ['c/5'], duration: '4', stem_direction: -1 },
{ keys: ['cb/4'], duration: '4', stem_direction: -1 },
{ keys: ['cb/5'], duration: '4', stem_direction: -1 },
{ keys: ['cb/4'], duration: '4', stem_direction: -1 },
{ keys: ['cb/5'], duration: '4', stem_direction: -1 },
{ keys: ['c/4'], duration: '4', stem_direction: -1 },
{ keys: ['c/5'], duration: '4', stem_direction: -1 },
{ keys: ['c/4'], duration: '4', stem_direction: -1 },
{ keys: ['c/5'], duration: '4', stem_direction: -1 },
].map(f.StaveNote.bind(f));

const voice0 = f.Voice().setMode(Voice.Mode.SOFT).addTickables(notes0);

Accidental.applyAccidentals([voice0], 'C#');

equal(hasAccidental(notes0[0]), true);
equal(hasAccidental(notes0[2]), true);
equal(hasAccidental(notes0[4]), false);
equal(hasAccidental(notes0[6]), true);
equal(hasAccidental(notes0[8]), false);
equal(hasAccidental(notes0[10]), true);
equal(hasAccidental(notes0[12]), false);
equal(hasAccidental(notes0[14]), true);
equal(hasAccidental(notes0[16]), false);
equal(hasAccidental(notes0[1]), true);
equal(hasAccidental(notes0[3]), true);
equal(hasAccidental(notes0[5]), false);
equal(hasAccidental(notes0[7]), true);
equal(hasAccidental(notes0[9]), false);
equal(hasAccidental(notes0[11]), true);
equal(hasAccidental(notes0[13]), false);
equal(hasAccidental(notes0[15]), true);
equal(hasAccidental(notes0[17]), false);

new Formatter().joinVoices([voice0]).formatToStave([voice0], stave);

f.draw();

ok(true);
}

function factoryAPI(options: TestOptions): void {
const f = VexFlowTests.makeFactory(options, 700, 240);
f.Stave({ x: 10, y: 10, width: 550 });
Expand Down

0 comments on commit 0dea4ab

Please sign in to comment.