diff --git a/src/Environment.ts b/src/Environment.ts index 27ae63131..5c4345dda 100644 --- a/src/Environment.ts +++ b/src/Environment.ts @@ -65,6 +65,8 @@ import { FreeTimeEffectInfo } from './rendering/effects/FreeTimeEffectInfo'; import { ScoreBarRenderer } from './rendering/ScoreBarRenderer'; import { TabBarRenderer } from './rendering/TabBarRenderer'; import { SustainPedalEffectInfo } from './rendering/effects/SustainPedalEffectInfo'; +import { GolpeEffectInfo } from './rendering/effects/GolpeEffectInfo'; +import { GolpeType } from './model/GolpeType'; export class LayoutEngineFactory { public readonly vertical: boolean; @@ -102,7 +104,7 @@ export class Environment { private static readonly StaffIdBeforeNumberedAlways = 'before-numbered-always'; private static readonly StaffIdBeforeTabAlways = 'before-tab-always'; private static readonly StaffIdBeforeTabHideable = 'before-tab-hideable'; - + private static readonly StaffIdBeforeEndAlways = 'before-end-always'; /** * The font size of the music font in pixel. @@ -515,7 +517,8 @@ export class Environment { new SlightBeatVibratoEffectInfo(), new WideNoteVibratoEffectInfo(), new SlightNoteVibratoEffectInfo(), - new LeftHandTapEffectInfo() + new LeftHandTapEffectInfo(), + new GolpeEffectInfo(GolpeType.Finger) ], (_, staff) => staff.showStandardNotation ), @@ -527,6 +530,7 @@ export class Environment { new CrescendoEffectInfo(), new OttaviaEffectInfo(false), new DynamicsEffectInfo(), + new GolpeEffectInfo(GolpeType.Thumb, (s, b) => b.voice.bar.staff.showStandardNotation), new SustainPedalEffectInfo() ]), // no before-numbered-hideable @@ -558,11 +562,15 @@ export class Environment { new PalmMuteEffectInfo(), new PickStrokeEffectInfo(), new PickSlideEffectInfo(), - new LeftHandTapEffectInfo() + new LeftHandTapEffectInfo(), + new GolpeEffectInfo(GolpeType.Finger, (s, b) => !b.voice.bar.staff.showStandardNotation) ], (_, staff) => staff.showTablature ), - new TabBarRendererFactory() + new TabBarRendererFactory(), + new EffectBarRendererFactory(Environment.StaffIdBeforeEndAlways, [ + new GolpeEffectInfo(GolpeType.Thumb, (s, b) => !b.voice.bar.staff.showStandardNotation) + ]) ]; } @@ -583,7 +591,8 @@ export class Environment { Environment.StaffIdBeforeScoreAlways, Environment.StaffIdBeforeNumberedAlways, Environment.StaffIdBeforeTabAlways, - ScoreBarRenderer.StaffId + ScoreBarRenderer.StaffId, + Environment.StaffIdBeforeEndAlways ]); staveProfiles.set( StaveProfile.Score, @@ -595,7 +604,8 @@ export class Environment { Environment.StaffIdBeforeScoreAlways, Environment.StaffIdBeforeNumberedAlways, Environment.StaffIdBeforeTabAlways, - TabBarRenderer.StaffId + TabBarRenderer.StaffId, + Environment.StaffIdBeforeEndAlways ]); staveProfiles.set( StaveProfile.Tab, diff --git a/src/NotationSettings.ts b/src/NotationSettings.ts index 461816c59..31a19c208 100644 --- a/src/NotationSettings.ts +++ b/src/NotationSettings.ts @@ -13,7 +13,11 @@ export enum TabRhythmMode { /** * Rhythm notation is shown and behaves like normal score notation with connected bars. */ - ShowWithBars + ShowWithBars, + /** + * Automatic detection whether the tabs should show rhythm based on hidden standard notation. + */ + Automatic, } /** @@ -297,7 +301,12 @@ export enum NotationElement { /** * The Sustain pedal effect shown above the staff "Ped.____*" */ - EffectSustainPedal + EffectSustainPedal, + + /** + * The Golpe effect signs above and below the saff. + */ + EffectGolpe } /** @@ -334,7 +343,7 @@ export class NotationSettings { /** * Whether to show rhythm notation in the guitar tablature. */ - public rhythmMode: TabRhythmMode = TabRhythmMode.Hidden; + public rhythmMode: TabRhythmMode = TabRhythmMode.Automatic; /** * The height of the rythm bars. diff --git a/src/exporter/GpifWriter.ts b/src/exporter/GpifWriter.ts index f83154e51..f48fa0f07 100644 --- a/src/exporter/GpifWriter.ts +++ b/src/exporter/GpifWriter.ts @@ -12,6 +12,7 @@ import { Duration } from '@src/model/Duration'; import { DynamicValue } from '@src/model/DynamicValue'; import { Fermata, FermataType } from '@src/model/Fermata'; import { Fingers } from '@src/model/Fingers'; +import { GolpeType } from '@src/model/GolpeType'; import { GraceType } from '@src/model/GraceType'; import { HarmonicType } from '@src/model/HarmonicType'; import { KeySignatureType } from '@src/model/KeySignatureType'; @@ -747,6 +748,10 @@ export class GpifWriter { beatNode.addElement('Notes').innerText = beat.notes.map(n => n.id).join(' '); } + if(beat.golpe !== GolpeType.None) { + beatNode.addElement('Golpe').innerText = GolpeType[beat.golpe]; + } + this.writeBeatProperties(beatNode, beat); this.writeBeatXProperties(beatNode, beat); diff --git a/src/generated/model/BeatCloner.ts b/src/generated/model/BeatCloner.ts index a0777ab98..a12529c58 100644 --- a/src/generated/model/BeatCloner.ts +++ b/src/generated/model/BeatCloner.ts @@ -58,6 +58,7 @@ export class BeatCloner { clone.playbackStart = original.playbackStart; clone.displayDuration = original.displayDuration; clone.playbackDuration = original.playbackDuration; + clone.golpe = original.golpe; clone.dynamics = original.dynamics; clone.invertBeamDirection = original.invertBeamDirection; clone.preferredBeamDirection = original.preferredBeamDirection; diff --git a/src/generated/model/BeatSerializer.ts b/src/generated/model/BeatSerializer.ts index cec2ef79e..f556f5976 100644 --- a/src/generated/model/BeatSerializer.ts +++ b/src/generated/model/BeatSerializer.ts @@ -20,6 +20,7 @@ import { VibratoType } from "@src/model/VibratoType"; import { GraceType } from "@src/model/GraceType"; import { PickStroke } from "@src/model/PickStroke"; import { CrescendoType } from "@src/model/CrescendoType"; +import { GolpeType } from "@src/model/GolpeType"; import { DynamicValue } from "@src/model/DynamicValue"; import { BeamDirection } from "@src/rendering/utils/BeamDirection"; import { BeatBeamingMode } from "@src/model/Beat"; @@ -72,6 +73,7 @@ export class BeatSerializer { o.set("playbackstart", obj.playbackStart); o.set("displayduration", obj.displayDuration); o.set("playbackduration", obj.playbackDuration); + o.set("golpe", obj.golpe as number); o.set("dynamics", obj.dynamics as number); o.set("invertbeamdirection", obj.invertBeamDirection); o.set("preferredbeamdirection", obj.preferredBeamDirection as number | null); @@ -202,6 +204,9 @@ export class BeatSerializer { case "playbackduration": obj.playbackDuration = v! as number; return true; + case "golpe": + obj.golpe = JsonHelper.parseEnum(v, GolpeType)!; + return true; case "dynamics": obj.dynamics = JsonHelper.parseEnum(v, DynamicValue)!; return true; diff --git a/src/importer/AlphaTexImporter.ts b/src/importer/AlphaTexImporter.ts index c55a4d9d1..2f6f11168 100644 --- a/src/importer/AlphaTexImporter.ts +++ b/src/importer/AlphaTexImporter.ts @@ -39,6 +39,7 @@ import { Settings } from '@src/Settings'; import { ByteBuffer } from '@src/io/ByteBuffer'; import { PercussionMapper } from '@src/model/PercussionMapper'; import { NoteAccidentalMode } from '@src/model'; +import { GolpeType } from '@src/model/GolpeType'; /** * A list of terminals recognized by the alphaTex-parser @@ -1617,6 +1618,14 @@ export class AlphaTexImporter extends ScoreImporter { beat.deadSlapped = true; this._sy = this.newSy(); return true; + } else if (syData === 'glpf') { + this._sy = this.newSy(); + beat.golpe = GolpeType.Finger; + return true; + } else if (syData === 'glpt') { + this._sy = this.newSy(); + beat.golpe = GolpeType.Thumb; + return true; } else { // string didn't match any beat effect syntax return false; diff --git a/src/importer/GpifParser.ts b/src/importer/GpifParser.ts index 43c75ebd2..6857085aa 100644 --- a/src/importer/GpifParser.ts +++ b/src/importer/GpifParser.ts @@ -46,6 +46,7 @@ import { TextBaseline } from '@src/platform/ICanvas'; import { BeatCloner } from '@src/generated/model/BeatCloner'; import { NoteCloner } from '@src/generated/model/NoteCloner'; import { Logger } from '@src/Logger'; +import { GolpeType } from '@src/model/GolpeType'; /** * This structure represents a duration within a gpif @@ -1615,6 +1616,16 @@ export class GpifParser { case 'DeadSlapped': beat.deadSlapped = true; break; + case 'Golpe': + switch (c.innerText) { + case 'Finger': + beat.golpe = GolpeType.Finger; + break; + case 'Thumb': + beat.golpe = GolpeType.Thumb; + break; + } + break; } } } diff --git a/src/model/Beat.ts b/src/model/Beat.ts index b32572877..5c00557d0 100644 --- a/src/model/Beat.ts +++ b/src/model/Beat.ts @@ -22,6 +22,7 @@ import { Settings } from '@src/Settings'; import { BeamDirection } from '@src/rendering/utils/BeamDirection'; import { BeatCloner } from '@src/generated/model/BeatCloner'; import { GraceGroup } from '@src/model/GraceGroup'; +import { GolpeType } from './GolpeType'; /** * Lists the different modes on how beaming for a beat should be done. @@ -400,6 +401,11 @@ export class Beat { */ public playbackDuration: number = 0; + /** + * The type of golpe to play. + */ + public golpe: GolpeType = GolpeType.None; + public get absoluteDisplayStart(): number { return this.voice.bar.masterBar.start + this.displayStart; } diff --git a/src/model/GolpeType.ts b/src/model/GolpeType.ts new file mode 100644 index 000000000..9bdd73fd3 --- /dev/null +++ b/src/model/GolpeType.ts @@ -0,0 +1,19 @@ +/** + * Lists all golpe types. + */ +export enum GolpeType { + /** + * No Golpe played. + */ + None, + + /** + * Play a golpe with the thumb. + */ + Thumb, + + /** + * Play a golpe with a finger. + */ + Finger +} diff --git a/src/rendering/TabBarRenderer.ts b/src/rendering/TabBarRenderer.ts index 7e19ad4f8..b180bea18 100644 --- a/src/rendering/TabBarRenderer.ts +++ b/src/rendering/TabBarRenderer.ts @@ -36,7 +36,7 @@ export class TabBarRenderer extends LineBarRenderer { public constructor(renderer: ScoreRenderer, bar: Bar) { super(renderer, bar); - if(!bar.staff.showStandardNotation) { + if (!bar.staff.showStandardNotation) { this.showTimeSignature = true; this.showRests = true; this.showTiedNotes = true; @@ -51,10 +51,18 @@ export class TabBarRenderer extends LineBarRenderer { return this.bar.staff.tuning.length; } - public override get drawnLineCount(): number { + public override get drawnLineCount(): number { return this.bar.staff.tuning.length; } + public get rhythmMode() { + let mode = this.settings.notation.rhythmMode; + if (mode === TabRhythmMode.Automatic) { + mode = this.bar.staff.showStandardNotation ? TabRhythmMode.Hidden : TabRhythmMode.ShowWithBars; + } + return mode; + } + /** * Gets the relative y position of the given steps relative to first line. * @param line the line of the particular string where 0 is the most top line @@ -95,7 +103,7 @@ export class TabBarRenderer extends LineBarRenderer { } protected override adjustSizes(): void { - if (this.settings.notation.rhythmMode !== TabRhythmMode.Hidden) { + if (this.rhythmMode !== TabRhythmMode.Hidden) { this.height += this.settings.notation.rhythmHeight * this.settings.display.scale; this.bottomPadding += this.settings.notation.rhythmHeight * this.settings.display.scale; } @@ -103,7 +111,7 @@ export class TabBarRenderer extends LineBarRenderer { public override doLayout(): void { super.doLayout(); - if (this.settings.notation.rhythmMode !== TabRhythmMode.Hidden) { + if (this.rhythmMode !== TabRhythmMode.Hidden) { this._hasTuplets = false; for (let voice of this.bar.voices) { if (this.hasVoiceContainer(voice)) { @@ -156,8 +164,7 @@ export class TabBarRenderer extends LineBarRenderer { this.bar.masterBar.timeSignatureNumerator, this.bar.masterBar.timeSignatureDenominator, this.bar.masterBar.timeSignatureCommon, - this.bar.masterBar.isFreeTime, - + this.bar.masterBar.isFreeTime ) ); } @@ -173,17 +180,17 @@ export class TabBarRenderer extends LineBarRenderer { public override paint(cx: number, cy: number, canvas: ICanvas): void { super.paint(cx, cy, canvas); - if (this.settings.notation.rhythmMode !== TabRhythmMode.Hidden) { + if (this.rhythmMode !== TabRhythmMode.Hidden) { this.paintBeams(cx, cy, canvas); this.paintTuplets(cx, cy, canvas); } } public override drawBeamHelperAsFlags(h: BeamingHelper): boolean { - return super.drawBeamHelperAsFlags(h) || this.settings.notation.rhythmMode === TabRhythmMode.ShowWithBeams; + return super.drawBeamHelperAsFlags(h) || this.rhythmMode === TabRhythmMode.ShowWithBeams; } - protected override getFlagTopY(beat: Beat, _direction:BeamDirection): number { + protected override getFlagTopY(beat: Beat, _direction: BeamDirection): number { const startGlyph: TabBeatGlyph = this.getOnNotesGlyphForBeat(beat) as TabBeatGlyph; if (!startGlyph.noteNumbers || beat.duration === Duration.Half) { return this.height - this.settings.notation.rhythmHeight * this.settings.display.scale - this.tupletSize; @@ -192,7 +199,7 @@ export class TabBarRenderer extends LineBarRenderer { } } - protected override getFlagBottomY(_beat: Beat, _direction:BeamDirection): number { + protected override getFlagBottomY(_beat: Beat, _direction: BeamDirection): number { return this.getFlagAndBarPos(); } diff --git a/src/rendering/effects/GolpeEffectInfo.ts b/src/rendering/effects/GolpeEffectInfo.ts new file mode 100644 index 000000000..64bced15d --- /dev/null +++ b/src/rendering/effects/GolpeEffectInfo.ts @@ -0,0 +1,49 @@ +import { NotationElement } from '@src/NotationSettings'; +import { EffectBarGlyphSizing } from '../EffectBarGlyphSizing'; +import { Settings } from '@src/Settings'; +import { Beat } from '@src/model'; +import { GolpeType } from '@src/model/GolpeType'; +import { EffectBarRendererInfo } from '../EffectBarRendererInfo'; +import { BarRendererBase } from '../BarRendererBase'; +import { EffectGlyph } from '../glyphs/EffectGlyph'; +import { GuitarGolpeGlyph } from '../glyphs/GuitarGolpeGlyph'; + +export class GolpeEffectInfo extends EffectBarRendererInfo { + private _type: GolpeType; + private _shouldCreate?: (settings: Settings, beat: Beat) => boolean; + + public constructor(type: GolpeType, shouldCreate?: (settings: Settings, beat: Beat) => boolean) { + super(); + this._type = type; + this._shouldCreate = shouldCreate; + } + + public get notationElement(): NotationElement { + return NotationElement.EffectGolpe; + } + + 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 { + const shouldCreate = this._shouldCreate; + return beat.golpe == this._type && (!shouldCreate || shouldCreate(settings, beat)); + } + + public createNewGlyph(renderer: BarRendererBase, beat: Beat): EffectGlyph { + return new GuitarGolpeGlyph(0, 0, true); + } + + public canExpand(from: Beat, to: Beat): boolean { + return false; + } +} diff --git a/src/rendering/glyphs/GuitarGolpeGlyph.ts b/src/rendering/glyphs/GuitarGolpeGlyph.ts index 0e4e2deae..a4bc98681 100644 --- a/src/rendering/glyphs/GuitarGolpeGlyph.ts +++ b/src/rendering/glyphs/GuitarGolpeGlyph.ts @@ -1,11 +1,13 @@ import { ICanvas } from '@src/platform/ICanvas'; -import { MusicFontGlyph } from '@src/rendering/glyphs/MusicFontGlyph'; import { MusicFontSymbol } from '@src/model/MusicFontSymbol'; import { NoteHeadGlyph } from '@src/rendering/glyphs/NoteHeadGlyph'; +import { EffectGlyph } from './EffectGlyph'; -export class GuitarGolpeGlyph extends MusicFontGlyph { - public constructor(x: number, y: number) { - super(x, y, NoteHeadGlyph.GraceScale, MusicFontSymbol.GuitarGolpe); +export class GuitarGolpeGlyph extends EffectGlyph { + private _center: boolean; + public constructor(x: number, y: number, center: boolean = false) { + super(x, y); + this._center = center; } public override doLayout(): void { @@ -14,6 +16,12 @@ export class GuitarGolpeGlyph extends MusicFontGlyph { } public override paint(cx: number, cy: number, canvas: ICanvas): void { - super.paint(cx, cy + this.height, canvas); + canvas.fillMusicFontSymbol( + cx + this.x, + cy + this.y + this.height, + NoteHeadGlyph.GraceScale * this.scale, + MusicFontSymbol.GuitarGolpe, + this._center + ); } } diff --git a/src/rendering/glyphs/TabBeatGlyph.ts b/src/rendering/glyphs/TabBeatGlyph.ts index 887896202..1d71fac05 100644 --- a/src/rendering/glyphs/TabBeatGlyph.ts +++ b/src/rendering/glyphs/TabBeatGlyph.ts @@ -104,7 +104,7 @@ export class TabBeatGlyph extends BeatOnNoteGlyphBase { // // Note dots // - if (this.container.beat.dots > 0 && tabRenderer.settings.notation.rhythmMode !== TabRhythmMode.Hidden) { + if (this.container.beat.dots > 0 && tabRenderer.rhythmMode !== TabRhythmMode.Hidden) { this.addGlyph(new SpacingGlyph(0, 0, 5 * this.scale)); for (let i: number = 0; i < this.container.beat.dots; i++) { this.addGlyph( diff --git a/test-data/visual-tests/effects-and-annotations/golpe-tab.gp b/test-data/visual-tests/effects-and-annotations/golpe-tab.gp new file mode 100644 index 000000000..03a0ecfe9 Binary files /dev/null and b/test-data/visual-tests/effects-and-annotations/golpe-tab.gp differ diff --git a/test-data/visual-tests/effects-and-annotations/golpe-tab.png b/test-data/visual-tests/effects-and-annotations/golpe-tab.png new file mode 100644 index 000000000..13d09efc2 Binary files /dev/null and b/test-data/visual-tests/effects-and-annotations/golpe-tab.png differ diff --git a/test-data/visual-tests/effects-and-annotations/golpe.gp b/test-data/visual-tests/effects-and-annotations/golpe.gp new file mode 100644 index 000000000..51a63f003 Binary files /dev/null and b/test-data/visual-tests/effects-and-annotations/golpe.gp differ diff --git a/test-data/visual-tests/effects-and-annotations/golpe.png b/test-data/visual-tests/effects-and-annotations/golpe.png new file mode 100644 index 000000000..067b8e681 Binary files /dev/null and b/test-data/visual-tests/effects-and-annotations/golpe.png differ diff --git a/test-data/visual-tests/music-notation/brushes.png b/test-data/visual-tests/music-notation/brushes.png index 4d713072f..14e06f272 100644 Binary files a/test-data/visual-tests/music-notation/brushes.png and b/test-data/visual-tests/music-notation/brushes.png differ diff --git a/test-data/visual-tests/special-notes/grace-notes-alignment.png b/test-data/visual-tests/special-notes/grace-notes-alignment.png index 611f0c097..83b30945c 100644 Binary files a/test-data/visual-tests/special-notes/grace-notes-alignment.png and b/test-data/visual-tests/special-notes/grace-notes-alignment.png differ diff --git a/test/importer/AlphaTexImporter.test.ts b/test/importer/AlphaTexImporter.test.ts index 59dcecaf7..667fd82d5 100644 --- a/test/importer/AlphaTexImporter.test.ts +++ b/test/importer/AlphaTexImporter.test.ts @@ -23,6 +23,7 @@ import { ScoreRenderer } from '@src/rendering/ScoreRenderer'; import { Settings } from '@src/Settings'; import { assert, expect } from 'chai'; import { ModelUtils } from '@src/model/ModelUtils'; +import { GolpeType } from '@src/model/GolpeType'; describe('AlphaTexImporterTest', () => { function parseTex(tex: string): Score { @@ -1267,29 +1268,48 @@ describe('AlphaTexImporterTest', () => { it('accidental-mode', () => { // song level - let score = parseTex('\\accidentals auto . F##4') - expect(score.tracks[0].staves[0].bars[0].voices[0].beats[0].notes[0].accidentalMode).to.equal(NoteAccidentalMode.Default); + let score = parseTex('\\accidentals auto . F##4'); + expect(score.tracks[0].staves[0].bars[0].voices[0].beats[0].notes[0].accidentalMode).to.equal( + NoteAccidentalMode.Default + ); // track level - score = parseTex('\\track "T1" F##4 | \\track "T2" \\accidentals auto F##4') - expect(score.tracks[0].staves[0].bars[0].voices[0].beats[0].notes[0].accidentalMode).to.equal(NoteAccidentalMode.ForceDoubleSharp); - expect(score.tracks[1].staves[0].bars[0].voices[0].beats[0].notes[0].accidentalMode).to.equal(NoteAccidentalMode.Default); + score = parseTex('\\track "T1" F##4 | \\track "T2" \\accidentals auto F##4'); + expect(score.tracks[0].staves[0].bars[0].voices[0].beats[0].notes[0].accidentalMode).to.equal( + NoteAccidentalMode.ForceDoubleSharp + ); + expect(score.tracks[1].staves[0].bars[0].voices[0].beats[0].notes[0].accidentalMode).to.equal( + NoteAccidentalMode.Default + ); // staff level - score = parseTex('\\track "T1" \\staff F##4 \\staff \\accidentals auto F##4') - expect(score.tracks[0].staves[0].bars[0].voices[0].beats[0].notes[0].accidentalMode).to.equal(NoteAccidentalMode.ForceDoubleSharp); - expect(score.tracks[0].staves[1].bars[0].voices[0].beats[0].notes[0].accidentalMode).to.equal(NoteAccidentalMode.Default); + score = parseTex('\\track "T1" \\staff F##4 \\staff \\accidentals auto F##4'); + expect(score.tracks[0].staves[0].bars[0].voices[0].beats[0].notes[0].accidentalMode).to.equal( + NoteAccidentalMode.ForceDoubleSharp + ); + expect(score.tracks[0].staves[1].bars[0].voices[0].beats[0].notes[0].accidentalMode).to.equal( + NoteAccidentalMode.Default + ); // bar level - score = parseTex('F##4 | \\accidentals auto F##4') - expect(score.tracks[0].staves[0].bars[0].voices[0].beats[0].notes[0].accidentalMode).to.equal(NoteAccidentalMode.ForceDoubleSharp); - expect(score.tracks[0].staves[0].bars[1].voices[0].beats[0].notes[0].accidentalMode).to.equal(NoteAccidentalMode.Default); + score = parseTex('F##4 | \\accidentals auto F##4'); + expect(score.tracks[0].staves[0].bars[0].voices[0].beats[0].notes[0].accidentalMode).to.equal( + NoteAccidentalMode.ForceDoubleSharp + ); + expect(score.tracks[0].staves[0].bars[1].voices[0].beats[0].notes[0].accidentalMode).to.equal( + NoteAccidentalMode.Default + ); }); it('dead-slap', () => { - // song level - let score = parseTex('r { ds }') + let score = parseTex('r { ds }'); expect(score.tracks[0].staves[0].bars[0].voices[0].beats[0].isRest).to.be.false; expect(score.tracks[0].staves[0].bars[0].voices[0].beats[0].deadSlapped).to.be.true; }); + + it('golpe', () => { + let score = parseTex('3.3 { glpf } 3.3 { glpt }'); + 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); + }); }); diff --git a/test/visualTests/features/EffectsAndAnnotations.test.ts b/test/visualTests/features/EffectsAndAnnotations.test.ts index db4b64782..c2cfb6532 100644 --- a/test/visualTests/features/EffectsAndAnnotations.test.ts +++ b/test/visualTests/features/EffectsAndAnnotations.test.ts @@ -181,4 +181,12 @@ describe('EffectsAndAnnotationsTests', () => { it('dead-slap', async () => { await VisualTestHelper.runVisualTest('effects-and-annotations/dead-slap.gp'); }); + + it('golpe', async () => { + await VisualTestHelper.runVisualTest('effects-and-annotations/golpe.gp'); + }); + + it('golpe-tab', async () => { + await VisualTestHelper.runVisualTest('effects-and-annotations/golpe-tab.gp'); + }); });