diff --git a/src/importer/AlphaTexImporter.ts b/src/importer/AlphaTexImporter.ts index 93db6d11c..a24a5ff38 100644 --- a/src/importer/AlphaTexImporter.ts +++ b/src/importer/AlphaTexImporter.ts @@ -1453,7 +1453,7 @@ export class AlphaTexImporter extends ScoreImporter { } } - private isNoteText(txt: string) { + private isNoteText(txt: string): boolean { return txt === 'x' || txt === '-' || txt === 'r'; } @@ -1564,7 +1564,7 @@ export class AlphaTexImporter extends ScoreImporter { this._sy = this.newSy(); } const points = note.bendPoints; - if(points != null){ + if (points != null) { while (points.length > 60) { points.splice(points.length - 1, 1); } @@ -1583,7 +1583,6 @@ export class AlphaTexImporter extends ScoreImporter { } } } - if (this._sy !== AlphaTexSymbols.RParensis) { this.error('bend-effect', AlphaTexSymbols.RParensis, true); } @@ -1789,6 +1788,27 @@ export class AlphaTexImporter extends ScoreImporter { } master.repeatCount = this._syData as number; this._sy = this.newSy(); + } else if (syData === 'ae') { + this._sy = this.newSy(); + if (this._sy === AlphaTexSymbols.LParensis) { + this._sy = this.newSy(); + if (this._sy !== AlphaTexSymbols.Number) { + this.error('alternateending', AlphaTexSymbols.Number, true) + } + this.applyAlternateEnding(master); + while (this._sy === AlphaTexSymbols.Number) { + this.applyAlternateEnding(master); + } + if (this._sy !== AlphaTexSymbols.RParensis) { + this.error('alternateending-list', AlphaTexSymbols.RParensis, true); + } + this._sy = this.newSy(); + } else { + if (this._sy !== AlphaTexSymbols.Number) { + this.error('alternateending', AlphaTexSymbols.Number, true) + } + this.applyAlternateEnding(master); + } } else if (syData === 'ks') { this._sy = this.newSy(); if (this._sy !== AlphaTexSymbols.String) { @@ -1881,4 +1901,15 @@ export class AlphaTexImporter extends ScoreImporter { } return anyMeta; } + + private applyAlternateEnding(master: MasterBar): void { + let num = this._syData as number; + if (num < 1) { + // Repeat numberings start from 1 + this.error('alternateending', AlphaTexSymbols.Number, true) + } + // Alternate endings bitflag starts from 0 + master.alternateEndings |= 1 << (num - 1); + this._sy = this.newSy(); + } } diff --git a/test/audio/MidiPlaybackController.test.ts b/test/audio/MidiPlaybackController.test.ts index 5574728a6..19a73f950 100644 --- a/test/audio/MidiPlaybackController.test.ts +++ b/test/audio/MidiPlaybackController.test.ts @@ -6,13 +6,16 @@ import { Logger } from '@src/Logger'; import { GpImporterTestHelper } from '@test/importer/GpImporterTestHelper'; describe('MidiPlaybackControllerTest', () => { - const testRepeat: ((score: Score, expectedIndexes: number[]) => void) = (score: Score, expectedIndexes: number[]): void => { + const testRepeat: ((score: Score, expectedIndexes: number[], maxBars: number) => void) = (score: Score, expectedIndexes: number[], maxBars: number): void => { let controller: MidiPlaybackController = new MidiPlaybackController(score); let i: number = 0; while (!controller.finished) { let index: number = controller.index; controller.processCurrent(); if (controller.shouldPlay) { + if (i > maxBars) { + fail('Too many bars generated'); + } Logger.debug('Test', `Checking index ${i}, expected[${expectedIndexes[i]}]`, i, expectedIndexes[i]); expect(index).toEqual(expectedIndexes[i]); i++; @@ -23,57 +26,65 @@ describe('MidiPlaybackControllerTest', () => { expect(controller.finished).toBe(true); }; - it('repeat-close', async () => { - const reader = await GpImporterTestHelper.prepareImporterWithFile('audio/repeat-close.gp5'); + const testGuitarProRepeat: ((file: string, expectedBars: number[], maxBars: number) => Promise) = async (file: string, expectedBars: number[], maxBars: number): Promise => { + let reader = await GpImporterTestHelper.prepareImporterWithFile(file); let score: Score = reader.readScore(); + testRepeat(score, expectedBars, maxBars); + } + + const testAlphaTexRepeat: ((tex: string, expectedBars: number[], maxBars: number) => void) = (tex: string, expectedBars: number[], maxBars: number): void => { + let importer: AlphaTexImporter = new AlphaTexImporter(); + importer.initFromString(tex, new Settings()); + let score: Score = importer.readScore(); + testRepeat(score, expectedBars, maxBars); + } + + it('repeat-close', async () => { + let file = 'audio/repeat-close.gp5'; let expectedIndexes = [0, 1, 0, 1, 2]; - testRepeat(score, expectedIndexes); + testGuitarProRepeat(file, expectedIndexes, 20); }); it('repeat-close-multi', async () => { - const reader = await GpImporterTestHelper.prepareImporterWithFile('audio/repeat-close-multi.gp5'); - let score: Score = reader.readScore(); + let file = 'audio/repeat-close-multi.gp5'; let expectedIndexes = [0, 1, 0, 1, 0, 1, 0, 1, 2]; - testRepeat(score, expectedIndexes); + testGuitarProRepeat(file, expectedIndexes, 20); }); it('repeat-close-without-start-at-beginning', async () => { - const reader = await GpImporterTestHelper.prepareImporterWithFile( - 'audio/repeat-close-without-start-at-beginning.gp5' - ); - let score: Score = reader.readScore(); + let file = 'audio/repeat-close-without-start-at-beginning.gp5'; let expectedIndexes = [0, 1, 0, 1]; - testRepeat(score, expectedIndexes); + testGuitarProRepeat(file, expectedIndexes, 20); }); it('repeat-close-alternate-endings', async () => { - const reader = await GpImporterTestHelper.prepareImporterWithFile( - 'audio/repeat-close-alternate-endings.gp5' - ); - let score: Score = reader.readScore(); + let file = 'audio/repeat-close-alternate-endings.gp5'; let expectedIndexes = [0, 1, 0, 2, 3, 0, 1, 0, 4]; - testRepeat(score, expectedIndexes); + testGuitarProRepeat(file, expectedIndexes, 20); }); it('repeat-with-alphaTex', () => { let tex: string = '\\ro 1.3 2.3 3.3 4.3 | 5.3 6.3 7.3 8.3 | \\rc 2 1.3 2.3 3.3 4.3 | \\ro \\rc 3 1.3 2.3 3.3 4.3'; - let importer: AlphaTexImporter = new AlphaTexImporter(); - importer.initFromString(tex, new Settings()); - let score: Score = importer.readScore(); - let playedBars: number[] = []; - let controller: MidiPlaybackController = new MidiPlaybackController(score); - while (!controller.finished) { - let index: number = controller.index; - playedBars.push(index); - controller.processCurrent(); - controller.moveNext(); - if (playedBars.length > 50) { - fail('Too many bars generated'); - } - } let expectedBars: number[] = [0, 1, 2, 0, 1, 2, 3, 3, 3]; + testAlphaTexRepeat(tex, expectedBars, 50); + }); - expect(playedBars.join(',')).toEqual(expectedBars.join(',')); + it('alternate-endings-with-alphaTex', () => { + let tex: string = ` + \\ro \\ae 1 1.1.1 | \\ae 2 2.1 | \\ae 3 3.1 | + 4.3.4*4 | + \\ae 1 1.1.1 | \\ae 2 2.1 | \\ae 3 3.1 | + 4.3.4*4 | + \\ae (1 3) 1.1.1 | \\ae 2 \\rc 3 2.1 + `; + let expectedBars: number[] = [ + 0, 4, 8, // First round: 1st, 5th and 9th bar which have the ending for 1. + 1, 5, 9, // Second round: 2nd, 6th and 10th bar which have the ending for 2. + 2, 3, 6, 7, 8 // Third round: 3rd, 4th, 7th, 8th and 9th which have the ending for 3. + // 4th and 8th bar don't have the ending explicitly + // but extended from the previous bar. + ]; + testAlphaTexRepeat(tex, expectedBars, 50); }); }); diff --git a/test/importer/AlphaTexImporter.test.ts b/test/importer/AlphaTexImporter.test.ts index 550802bf9..dfa33823d 100644 --- a/test/importer/AlphaTexImporter.test.ts +++ b/test/importer/AlphaTexImporter.test.ts @@ -769,12 +769,59 @@ describe('AlphaTexImporterTest', () => { let tex: string = '\\ro 1.3 2.3 3.3 4.3 | 5.3 6.3 7.3 8.3 | \\rc 2 1.3 2.3 3.3 4.3 | \\ro \\rc 3 1.3 2.3 3.3 4.3 |'; let score: Score = parseTex(tex); + expect(score.masterBars[0].isRepeatStart).toBe(true); + expect(score.masterBars[1].isRepeatStart).toBe(false); + expect(score.masterBars[2].isRepeatStart).toBe(false); + expect(score.masterBars[3].isRepeatStart).toBe(true); expect(score.masterBars[0].repeatCount).toEqual(0); expect(score.masterBars[1].repeatCount).toEqual(0); expect(score.masterBars[2].repeatCount).toEqual(2); expect(score.masterBars[3].repeatCount).toEqual(3); }); + it('alternate-endings', () => { + let tex: string = '\\ro 4.3*4 | \\ae (1 2 3) 6.3*4 | \\ae 4 \\rc 4 6.3 6.3 6.3 5.3 |'; + let score: Score = parseTex(tex); + expect(score.masterBars[0].isRepeatStart).toBe(true); + expect(score.masterBars[1].isRepeatStart).toBe(false); + expect(score.masterBars[2].isRepeatStart).toBe(false); + expect(score.masterBars[0].repeatCount).toEqual(0); + expect(score.masterBars[1].repeatCount).toEqual(0); + expect(score.masterBars[2].repeatCount).toEqual(4); + expect(score.masterBars[0].alternateEndings).toEqual(0b0000); + expect(score.masterBars[1].alternateEndings).toEqual(0b0111); + expect(score.masterBars[2].alternateEndings).toEqual(0b1000); + }) + + it('random-alternate-endings', () => { + let tex: string = ` + \\ro \\ae 1 1.1.1 | \\ae 2 2.1 | \\ae 3 3.1 | + 4.3.4*4 | + \\ae 1 1.1.1 | \\ae 2 2.1 | \\ae 3 3.1 | + 4.3.4*4 | + \\ae (1 3) 1.1.1 | \\ae 2 \\rc 3 2.1 | + `; + let score: Score = parseTex(tex); + expect(score.masterBars[0].isRepeatStart).toBe(true); + for (let i = 1; i <= 9; i++) { + expect(score.masterBars[i].isRepeatStart).toBe(false); + } + for (let i = 0; i <= 8; i++) { + expect(score.masterBars[i].repeatCount).toEqual(0); + } + expect(score.masterBars[9].repeatCount).toEqual(3); + expect(score.masterBars[0].alternateEndings).toEqual(0b001); + expect(score.masterBars[1].alternateEndings).toEqual(0b010); + expect(score.masterBars[2].alternateEndings).toEqual(0b100); + expect(score.masterBars[3].alternateEndings).toEqual(0b000); + expect(score.masterBars[4].alternateEndings).toEqual(0b001); + expect(score.masterBars[5].alternateEndings).toEqual(0b010); + expect(score.masterBars[6].alternateEndings).toEqual(0b100); + expect(score.masterBars[7].alternateEndings).toEqual(0b000); + expect(score.masterBars[8].alternateEndings).toEqual(0b101); + expect(score.masterBars[9].alternateEndings).toEqual(0b010); + }) + it('default-transposition-on-instruments', () => { let tex: string = ` \\track "Piano with Grand Staff" "pno." @@ -904,7 +951,7 @@ describe('AlphaTexImporterTest', () => { expect(score.tracks[0].name).toEqual("🎸"); expect(score.tracks[0].staves[0].bars[0].voices[0].beats[2].lyrics![0]).toEqual("🤘"); }); - + it('does-not-hang-on-backslash', () => { try { parseTex('\\title Test . 3.3 \\') @@ -914,7 +961,7 @@ describe('AlphaTexImporterTest', () => { } }) - function runSectionNoteSymbolTest(noteSymbol:string) { + function runSectionNoteSymbolTest(noteSymbol: string) { const score = parseTex(`1.3.4 * 4 | \\section Verse ${noteSymbol}.1 | 2.3.4*4`); expect(score.masterBars.length).toEqual(3);