Skip to content

Commit

Permalink
feat: Add note ornaments (#1747)
Browse files Browse the repository at this point in the history
  • Loading branch information
Danielku15 authored Nov 10, 2024
1 parent 1a882a6 commit a6d5246
Show file tree
Hide file tree
Showing 16 changed files with 175 additions and 2 deletions.
2 changes: 2 additions & 0 deletions src/Environment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ import { GolpeEffectInfo } from './rendering/effects/GolpeEffectInfo';
import { GolpeType } from './model/GolpeType';
import { WahPedalEffectInfo } from './rendering/effects/WahPedalEffectInfo';
import { BeatBarreEffectInfo } from './rendering/effects/BeatBarreEffectInfo';
import { NoteOrnamentEffectInfo } from './rendering/effects/NoteOrnamentEffectInfo';

export class LayoutEngineFactory {
public readonly vertical: boolean;
Expand Down Expand Up @@ -511,6 +512,7 @@ export class Environment {
new EffectBarRendererFactory(Environment.StaffIdBeforeScoreAlways, [
new FermataEffectInfo(),
new BeatBarreEffectInfo(),
new NoteOrnamentEffectInfo(),
new WahPedalEffectInfo(),
]),
new EffectBarRendererFactory(
Expand Down
7 changes: 6 additions & 1 deletion src/NotationSettings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -316,7 +316,12 @@ export enum NotationElement {
/**
* The Beat barre effect signs above and below the staff "1/2B IV ─────┐"
*/
BeatBarre
EffectBeatBarre,

/**
* The note ornaments like turns and mordents.
*/
EffectNoteOrnament,
}

/**
Expand Down
5 changes: 5 additions & 0 deletions src/exporter/GpifWriter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import { MasterBar } from '@src/model/MasterBar';
import { MusicFontSymbol } from '@src/model/MusicFontSymbol';
import { Note } from '@src/model/Note';
import { NoteAccidentalMode } from '@src/model/NoteAccidentalMode';
import { NoteOrnament } from '@src/model/NoteOrnament';
import { Ottavia } from '@src/model/Ottavia';
import { PercussionMapper } from '@src/model/PercussionMapper';
import { PickStroke } from '@src/model/PickStroke';
Expand Down Expand Up @@ -382,6 +383,10 @@ export class GpifWriter {
} else {
noteNode.addElement('InstrumentArticulation').innerText = '0';
}

if(note.ornament !== NoteOrnament.None){
noteNode.addElement('Ornament').innerText = NoteOrnament[note.ornament];
}
}

private writeNoteProperties(parent: XmlNode, note: Note) {
Expand Down
1 change: 1 addition & 0 deletions src/generated/model/NoteCloner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ export class NoteCloner {
clone.durationPercent = original.durationPercent;
clone.accidentalMode = original.accidentalMode;
clone.dynamics = original.dynamics;
clone.ornament = original.ornament;
return clone;
}
}
5 changes: 5 additions & 0 deletions src/generated/model/NoteSerializer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { Fingers } from "@src/model/Fingers";
import { Duration } from "@src/model/Duration";
import { NoteAccidentalMode } from "@src/model/NoteAccidentalMode";
import { DynamicValue } from "@src/model/DynamicValue";
import { NoteOrnament } from "@src/model/NoteOrnament";
export class NoteSerializer {
public static fromJson(obj: Note, m: unknown): void {
if (!m) {
Expand Down Expand Up @@ -67,6 +68,7 @@ export class NoteSerializer {
o.set("durationpercent", obj.durationPercent);
o.set("accidentalmode", obj.accidentalMode as number);
o.set("dynamics", obj.dynamics as number);
o.set("ornament", obj.ornament as number);
obj.toJson(o);
return o;
}
Expand Down Expand Up @@ -184,6 +186,9 @@ export class NoteSerializer {
case "dynamics":
obj.dynamics = JsonHelper.parseEnum<DynamicValue>(v, DynamicValue)!;
return true;
case "ornament":
obj.ornament = JsonHelper.parseEnum<NoteOrnament>(v, NoteOrnament)!;
return true;
}
return obj.setProperty(property, v);
}
Expand Down
16 changes: 15 additions & 1 deletion src/importer/AlphaTexImporter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ import { GolpeType } from '@src/model/GolpeType';
import { FadeType } from '@src/model/FadeType';
import { WahPedal } from '@src/model/WahPedal';
import { BarreShape } from '@src/model/BarreShape';
import { NoteOrnament } from '@src/model/NoteOrnament';

/**
* A list of terminals recognized by the alphaTex-parser
Expand Down Expand Up @@ -1648,6 +1649,7 @@ export class AlphaTexImporter extends ScoreImporter {
this.error('beat-barre', AlphaTexSymbols.Number, true);
}
beat.barreFret = this._syData as number;
beat.barreShape = BarreShape.Full;
this._sy = this.newSy();

if (this._sy === AlphaTexSymbols.String) {
Expand All @@ -1662,7 +1664,7 @@ export class AlphaTexImporter extends ScoreImporter {
break;
}
}

return true;
} else {
// string didn't match any beat effect syntax
Expand Down Expand Up @@ -2007,6 +2009,18 @@ export class AlphaTexImporter extends ScoreImporter {

note.accidentalMode = ModelUtils.parseAccidentalMode(this._syData as string);
this._sy = this.newSy();
} else if (syData === 'turn') {
this._sy = this.newSy();
note.ornament = NoteOrnament.Turn;
} else if (syData === 'iturn') {
this._sy = this.newSy();
note.ornament = NoteOrnament.InvertedTurn;
} else if (syData === 'umordent') {
this._sy = this.newSy();
note.ornament = NoteOrnament.UpperMordent;
} else if (syData === 'lmordent') {
this._sy = this.newSy();
note.ornament = NoteOrnament.LowerMordent;
} else if (this.applyBeatEffect(note.beat)) {
// Success
} else {
Expand Down
17 changes: 17 additions & 0 deletions src/importer/GpifParser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ import { GolpeType } from '@src/model/GolpeType';
import { FadeType } from '@src/model/FadeType';
import { WahPedal } from '@src/model/WahPedal';
import { BarreShape } from '@src/model/BarreShape';
import { NoteOrnament } from '@src/model/NoteOrnament';

/**
* This structure represents a duration within a gpif
Expand Down Expand Up @@ -1975,6 +1976,22 @@ export class GpifParser {
case 'InstrumentArticulation':
note.percussionArticulation = parseInt(c.innerText);
break;
case 'Ornament':
switch (c.innerText) {
case 'Turn':
note.ornament = NoteOrnament.Turn;
break;
case 'InvertedTurn':
note.ornament = NoteOrnament.InvertedTurn;
break;
case 'UpperMordent':
note.ornament = NoteOrnament.UpperMordent;
break;
case 'LowerMordent':
note.ornament = NoteOrnament.LowerMordent;
break;
}
break;
}
}
}
Expand Down
4 changes: 4 additions & 0 deletions src/model/MusicFontSymbol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,10 @@ export enum MusicFontSymbol {
DynamicFFF = 0xe530,

OrnamentTrill = 0xe566,
OrnamentTurn = 0xe567,
OrnamentTurnInverted = 0xe568,
OrnamentShortTrill = 0xe56c,
OrnamentMordent = 0xe56d,

StringsDownBow = 0xe610,
StringsUpBow = 0xe612,
Expand Down
6 changes: 6 additions & 0 deletions src/model/Note.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { Logger } from '@src/Logger';
import { ModelUtils } from '@src/model/ModelUtils';
import { PickStroke } from '@src/model/PickStroke';
import { PercussionMapper } from '@src/model/PercussionMapper';
import { NoteOrnament } from './NoteOrnament';

class NoteIdBag {
public tieDestinationNoteId: number = -1;
Expand Down Expand Up @@ -498,6 +499,11 @@ export class Note {
*/
public effectSlurDestination: Note | null = null;

/**
* The ornament applied on the note.
*/
public ornament: NoteOrnament = NoteOrnament.None;

public get stringTuning(): number {
return this.beat.voice.bar.staff.capo + Note.getStringTuning(this.beat.voice.bar.staff, this.string);
}
Expand Down
10 changes: 10 additions & 0 deletions src/model/NoteOrnament.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/**
* Lists all note ornaments.
*/
export enum NoteOrnament {
None,
InvertedTurn,
Turn,
UpperMordent,
LowerMordent
}
39 changes: 39 additions & 0 deletions src/rendering/effects/NoteOrnamentEffectInfo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { Beat } from '@src/model';
import { NotationElement } from '@src/NotationSettings';
import { BarRendererBase } from '../BarRendererBase';
import { EffectBarGlyphSizing } from '../EffectBarGlyphSizing';
import { EffectBarRendererInfo } from '../EffectBarRendererInfo';
import { EffectGlyph } from '../glyphs/EffectGlyph';
import { Settings } from '@src/Settings';
import { NoteOrnament } from '@src/model/NoteOrnament';
import { NoteOrnamentGlyph } from '../glyphs/NoteOrnamentGlyph';

export class NoteOrnamentEffectInfo extends EffectBarRendererInfo {
public get notationElement(): NotationElement {
return NotationElement.EffectNoteOrnament;
}

public get hideOnMultiTrack(): boolean {
return false;
}

public get canShareBand(): boolean {
return true;
}

public get sizingMode(): EffectBarGlyphSizing {
return EffectBarGlyphSizing.SingleOnBeat;
}

public shouldCreateGlyph(settings: Settings, beat: Beat): boolean {
return beat.notes.some(n => n.ornament !== NoteOrnament.None);
}

public createNewGlyph(renderer: BarRendererBase, beat: Beat): EffectGlyph {
return new NoteOrnamentGlyph(beat.notes.find(n=>n.ornament != NoteOrnament.None)!.ornament);
}

public canExpand(from: Beat, to: Beat): boolean {
return false;
}
}
34 changes: 34 additions & 0 deletions src/rendering/glyphs/NoteOrnamentGlyph.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { NoteOrnament } from '@src/model/NoteOrnament';
import { MusicFontGlyph } from './MusicFontGlyph';
import { MusicFontSymbol } from '@src/model';
import { ICanvas } from '@src/platform';

export class NoteOrnamentGlyph extends MusicFontGlyph {
constructor(ornament: NoteOrnament) {
super(0, 0, 1, NoteOrnamentGlyph.getSymbol(ornament));
this.center = true;
}

public override doLayout(): void {
this.width = 26 * this.scale;
this.height = 18 * this.scale;
}

private static getSymbol(ornament: NoteOrnament): MusicFontSymbol {
switch (ornament) {
case NoteOrnament.InvertedTurn:
return MusicFontSymbol.OrnamentTurnInverted;
case NoteOrnament.Turn:
return MusicFontSymbol.OrnamentTurn;
case NoteOrnament.UpperMordent:
return MusicFontSymbol.OrnamentShortTrill;
case NoteOrnament.LowerMordent:
return MusicFontSymbol.OrnamentMordent;
}
return MusicFontSymbol.None;
}

public override paint(cx: number, cy: number, canvas: ICanvas): void {
super.paint(cx, cy + this.height - 4 * this.scale, canvas);
}
}
Binary file not shown.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
27 changes: 27 additions & 0 deletions test/importer/AlphaTexImporter.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ import { Settings } from '@src/Settings';
import { assert, expect } from 'chai';
import { ModelUtils } from '@src/model/ModelUtils';
import { GolpeType } from '@src/model/GolpeType';
import { FadeType } from '@src/model/FadeType';
import { BarreShape } from '@src/model/BarreShape';
import { NoteOrnament } from '@src/model/NoteOrnament';

describe('AlphaTexImporterTest', () => {
function parseTex(tex: string): Score {
Expand Down Expand Up @@ -1312,4 +1315,28 @@ describe('AlphaTexImporterTest', () => {
expect(score.tracks[0].staves[0].bars[0].voices[0].beats[0].golpe).to.equal(GolpeType.Finger);
expect(score.tracks[0].staves[0].bars[0].voices[0].beats[1].golpe).to.equal(GolpeType.Thumb);
});

it('fade', () => {
let score = parseTex('3.3 { f } 3.3 { fo } 3.3 { vs } ');
expect(score.tracks[0].staves[0].bars[0].voices[0].beats[0].fade).to.equal(FadeType.FadeIn);
expect(score.tracks[0].staves[0].bars[0].voices[0].beats[1].fade).to.equal(FadeType.FadeOut);
expect(score.tracks[0].staves[0].bars[0].voices[0].beats[2].fade).to.equal(FadeType.VolumeSwell);
});

it('barre', () => {
let score = parseTex('3.3 { barre 5 } 3.3 { barre 14 half }');
expect(score.tracks[0].staves[0].bars[0].voices[0].beats[0].barreFret).to.equal(5);
expect(score.tracks[0].staves[0].bars[0].voices[0].beats[0].barreShape).to.equal(BarreShape.Full);

expect(score.tracks[0].staves[0].bars[0].voices[0].beats[1].barreFret).to.equal(14);
expect(score.tracks[0].staves[0].bars[0].voices[0].beats[1].barreShape).to.equal(BarreShape.Half);
});

it('ornaments', () => {
let score = parseTex('3.3 { turn } 3.3 { iturn } 3.3 { umordent } 3.3 { lmordent }');
expect(score.tracks[0].staves[0].bars[0].voices[0].beats[0].notes[0].ornament).to.equal(NoteOrnament.Turn);
expect(score.tracks[0].staves[0].bars[0].voices[0].beats[1].notes[0].ornament).to.equal(NoteOrnament.InvertedTurn);
expect(score.tracks[0].staves[0].bars[0].voices[0].beats[2].notes[0].ornament).to.equal(NoteOrnament.UpperMordent);
expect(score.tracks[0].staves[0].bars[0].voices[0].beats[3].notes[0].ornament).to.equal(NoteOrnament.LowerMordent);
});
});
4 changes: 4 additions & 0 deletions test/visualTests/features/EffectsAndAnnotations.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -230,4 +230,8 @@ describe('EffectsAndAnnotationsTests', () => {
it('barre', async () => {
await VisualTestHelper.runVisualTest('effects-and-annotations/barre.gp');
});

it('ornaments', async () => {
await VisualTestHelper.runVisualTest('effects-and-annotations/ornaments.gp');
});
});

0 comments on commit a6d5246

Please sign in to comment.