diff --git a/playground-template/alphatex-editor.html b/playground-template/alphatex-editor.html index 92b056cbe..83faa6ac1 100644 --- a/playground-template/alphatex-editor.html +++ b/playground-template/alphatex-editor.html @@ -1,66 +1,42 @@ - - - AlphaTab alphaTex Editor Demo - - - - + + + AlphaTab alphaTex Editor Demo - - - + + + + - - - + + + - - - - - -
-
-
+ + + + + + + + + + +
+
+
\title "Canon Rock" \subtitle "JerryC" \tempo 90 @@ -71,28 +47,31 @@ 12.2{v f} 14.2{v f}.4 :8 15.2 17.2 | 14.1.2 :8 17.2 15.1 14.1{h} 17.2 | 15.2{v d}.4 :16 17.2{h} 15.2 :8 14.2 14.1 17.1{b(0 4 4 0)}.4 | - 15.1.8 :16 14.1{tu 3} 15.1{tu 3} 14.1{tu 3} :8 17.2 15.1 14.1 :16 12.1{tu 3} 14.1{tu 3} 12.1{tu 3} :8 15.2 14.2 | - 12.2 14.3 12.3 15.2 :32 14.2{h} 15.2{h} 14.2{h} 15.2{h}14.2{h} 15.2{h}14.2{h} 15.2{h}14.2{h} 15.2{h}14.2{h} 15.2{h}14.2{h} 15.2{h}14.2{h} 15.2{h} -
+ 15.1.8 :16 14.1{tu 3} 15.1{tu 3} 14.1{tu 3} :8 17.2 15.1 14.1 :16 12.1{tu 3} 14.1{tu 3} 12.1{tu 3} :8 15.2 + 14.2 | + 12.2 14.3 12.3 15.2 :32 14.2{h} 15.2{h} 14.2{h} 15.2{h}14.2{h} 15.2{h}14.2{h} 15.2{h}14.2{h} 15.2{h}14.2{h} + 15.2{h}14.2{h} 15.2{h}14.2{h} 15.2{h}
- - - + + + - window.at = setupControl('#alphaTab'); - setupEditor(window.at, '#editor'); - }; - req.open('GET', 'control-template.html'); - req.send(); - - - + \ No newline at end of file diff --git a/playground-template/alphatex-editor.js b/playground-template/alphatex-editor.js index 2db61de4e..ae9f24990 100644 --- a/playground-template/alphatex-editor.js +++ b/playground-template/alphatex-editor.js @@ -13,6 +13,6 @@ function setupEditor(api, selector) { mode: 'ace/mode/tex' }); editor.session.on('change', () => { - api.tex(editor.getSession().getDocument().getAllLines().join('\n')); + api.tex(editor.getSession().getDocument().getAllLines().join('\n'), 'all'); }); } diff --git a/playground-template/control.html b/playground-template/control.html index 4ba418291..a066b2bfa 100644 --- a/playground-template/control.html +++ b/playground-template/control.html @@ -1,56 +1,42 @@ - - - AlphaTab Control Demo - - - - + + + AlphaTab Control Demo - - - + + + + - - - - -
- - - - + + + + + + + + + +
+ + + + + \ No newline at end of file diff --git a/playground-template/full-page.html b/playground-template/full-page.html index 2a7df1531..a31075bdc 100644 --- a/playground-template/full-page.html +++ b/playground-template/full-page.html @@ -1,57 +1,43 @@ - - - AlphaTab Full Page Demo - - - - + + + AlphaTab Full Page Demo - - - + + + + - - - - - -
- - - - + + + + + + + + + + +
+ + + + + \ No newline at end of file diff --git a/playground-template/simple.html b/playground-template/simple.html index 448abd8e6..35cf7fd5d 100644 --- a/playground-template/simple.html +++ b/playground-template/simple.html @@ -1,57 +1,37 @@ - - - AlphaTab Simple Demo - - - - + + + AlphaTab Simple Demo - - - + + + + - - - -
- + - - - + + + + +
+ + + + \ No newline at end of file diff --git a/src.compiler/csharp/CSharpAstTransformer.ts b/src.compiler/csharp/CSharpAstTransformer.ts index 4b561739b..9def7d374 100644 --- a/src.compiler/csharp/CSharpAstTransformer.ts +++ b/src.compiler/csharp/CSharpAstTransformer.ts @@ -1235,6 +1235,12 @@ export default class CSharpAstTransformer { csMethod.isOverride = true; } break; + case 'Equals': + if (csMethod.parameters.length === 1) { + csMethod.isVirtual = false; + csMethod.isOverride = true; + } + break; } parent.members.push(csMethod); diff --git a/src.csharp/AlphaTab.Test/AlphaTab.Test.csproj b/src.csharp/AlphaTab.Test/AlphaTab.Test.csproj index 4d0daefe9..74b41307b 100644 --- a/src.csharp/AlphaTab.Test/AlphaTab.Test.csproj +++ b/src.csharp/AlphaTab.Test/AlphaTab.Test.csproj @@ -7,6 +7,7 @@ enable 8 true + $(NoWarn);0659;0168 diff --git a/src.csharp/AlphaTab.Test/Test/Globals.cs b/src.csharp/AlphaTab.Test/Test/Globals.cs index d24a13ffa..427376b4a 100644 --- a/src.csharp/AlphaTab.Test/Test/Globals.cs +++ b/src.csharp/AlphaTab.Test/Test/Globals.cs @@ -3,14 +3,14 @@ namespace AlphaTab.Test { - public class Globals + public static class Globals { public static Expector Expect(T actual) { return new Expector(actual); } - public static void Fail(object message) + public static void Fail(object? message) { Assert.Fail(Convert.ToString(message)); } @@ -33,7 +33,7 @@ public Expector WithContext(string message) return this; } - public void ToEqual(object expected, string message = null) + public void ToEqual(object expected, string? message = null) { if (expected is int i && _actual is double) { @@ -42,7 +42,7 @@ public void ToEqual(object expected, string message = null) Assert.AreEqual(expected, _actual, _message + message); } - public void ToBeCloseTo(double expected, string message = null) + public void ToBeCloseTo(double expected, string? message = null) { if (_actual is IConvertible c) { @@ -65,7 +65,7 @@ public void ToBe(object expected) public void ToBeTruthy() { - Assert.AreNotEqual(default, _actual, _message); + Assert.AreNotEqual(default!, _actual, _message); } public void ToBeTrue() @@ -82,7 +82,7 @@ public void ToBeTrue() public void ToBeFalsy() { - Assert.AreEqual(default, _actual, _message); + Assert.AreEqual(default!, _actual, _message); } } } diff --git a/src.csharp/AlphaTab.Test/VisualTests/PixelMatch.cs b/src.csharp/AlphaTab.Test/VisualTests/PixelMatch.cs index 0e0e78174..4406a5e14 100644 --- a/src.csharp/AlphaTab.Test/VisualTests/PixelMatch.cs +++ b/src.csharp/AlphaTab.Test/VisualTests/PixelMatch.cs @@ -317,7 +317,7 @@ public PixelMatchOptions() internal class PixelMatchResult { - public SKBitmap Output { get; set; } + public SKBitmap? Output { get; set; } public int DifferentPixels { get; set; } public int TotalPixels { get; set; } public double Mismatch => DifferentPixels / (double) TotalPixels; diff --git a/src.csharp/AlphaTab.Test/VisualTests/VisualTestHelper.cs b/src.csharp/AlphaTab.Test/VisualTests/VisualTestHelper.cs index 571e2f348..bc5d299e4 100644 --- a/src.csharp/AlphaTab.Test/VisualTests/VisualTestHelper.cs +++ b/src.csharp/AlphaTab.Test/VisualTests/VisualTestHelper.cs @@ -77,7 +77,7 @@ public static async Task RunVisualTestScore(Score score, string referenceFileNam settings.Core.Engine = "skia"; settings.Core.EnableLazyLoading = false; settings.Core.UseWorkers = false; - + if(!referenceFileName.StartsWith("test-data/")) { referenceFileName = $"test-data/visual-tests/{referenceFileName}"; } @@ -89,7 +89,7 @@ public static async Task RunVisualTestScore(Score score, string referenceFileNam var totalWidth = 0.0; var totalHeight = 0.0; - var task = new TaskCompletionSource(); + var task = new TaskCompletionSource(); var renderer = new ScoreRenderer(settings); renderer.Width = 1300; @@ -112,7 +112,7 @@ public static async Task RunVisualTestScore(Score score, string referenceFileNam if (await Task.WhenAny(task.Task, Task.Delay(2000)) == task.Task) { - await CompareVisualResult( + CompareVisualResult( totalWidth, totalHeight, result, @@ -127,7 +127,7 @@ await CompareVisualResult( } } - private static async Task CompareVisualResult(double totalWidth, double totalHeight, + private static void CompareVisualResult(double totalWidth, double totalHeight, List result, string referenceFileName, Uint8Array referenceFileData, string? message) { @@ -135,6 +135,8 @@ private static async Task CompareVisualResult(double totalWidth, double totalHei // https://github.com/mono/SkiaSharp/issues/1253 return; + // ReSharper disable once HeuristicUnreachableCode +#pragma warning disable 162 SKBitmap finalBitmap; using (var finalImageSurface = SKSurface.Create(new SKImageInfo((int) totalWidth, @@ -219,6 +221,7 @@ private static async Task CompareVisualResult(double totalWidth, double totalHei } File.Delete(finalImageFileName); +#pragma warning restore 162 } } } diff --git a/src.csharp/AlphaTab.Windows/WinForms/AlphaTabControl.cs b/src.csharp/AlphaTab.Windows/WinForms/AlphaTabControl.cs index 4e69aaf78..8728633d7 100644 --- a/src.csharp/AlphaTab.Windows/WinForms/AlphaTabControl.cs +++ b/src.csharp/AlphaTab.Windows/WinForms/AlphaTabControl.cs @@ -70,7 +70,7 @@ public Settings Settings /// [Browsable(false)] [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] - public AlphaTabApiBase Api { get; private set; } + public AlphaTabApiBase Api { get; private set; } = null!; /// /// Initializes a new instance of the class. diff --git a/src.csharp/AlphaTab.Windows/WinForms/WinFormsUiFacade.cs b/src.csharp/AlphaTab.Windows/WinForms/WinFormsUiFacade.cs index 90f322d33..d1745eb52 100644 --- a/src.csharp/AlphaTab.Windows/WinForms/WinFormsUiFacade.cs +++ b/src.csharp/AlphaTab.Windows/WinForms/WinFormsUiFacade.cs @@ -64,7 +64,7 @@ void OnVisibilityChanged(object? sender, EventArgs e) ); } - protected override Stream OpenDefaultSoundFont() + protected override Stream? OpenDefaultSoundFont() { return typeof(NAudioSynthOutput).Assembly.GetManifestResourceStream( typeof(NAudioSynthOutput), "default.sf2"); diff --git a/src.csharp/AlphaTab.Windows/Wpf/WpfUiFacade.cs b/src.csharp/AlphaTab.Windows/Wpf/WpfUiFacade.cs index 302ec0b5c..b7d020f99 100644 --- a/src.csharp/AlphaTab.Windows/Wpf/WpfUiFacade.cs +++ b/src.csharp/AlphaTab.Windows/Wpf/WpfUiFacade.cs @@ -69,7 +69,7 @@ void OnVisibilityChanged(object sender, ); } - protected override Stream OpenDefaultSoundFont() + protected override Stream? OpenDefaultSoundFont() { return typeof(NAudioSynthOutput).Assembly.GetManifestResourceStream( typeof(NAudioSynthOutput), "default.sf2"); diff --git a/src.csharp/AlphaTab/Platform/CSharp/ManagedUiFacade.cs b/src.csharp/AlphaTab/Platform/CSharp/ManagedUiFacade.cs index a3faa8fb9..fd60462eb 100644 --- a/src.csharp/AlphaTab/Platform/CSharp/ManagedUiFacade.cs +++ b/src.csharp/AlphaTab/Platform/CSharp/ManagedUiFacade.cs @@ -39,7 +39,7 @@ public IScoreRenderer CreateWorkerRenderer() return new ManagedThreadScoreRenderer(Api.Settings, BeginInvoke); } - protected abstract Stream OpenDefaultSoundFont(); + protected abstract Stream? OpenDefaultSoundFont(); public IAlphaSynth CreateWorkerPlayer() { diff --git a/src/generated/model/StaffSerializer.ts b/src/generated/model/StaffSerializer.ts index ed35f0514..93da74bf3 100644 --- a/src/generated/model/StaffSerializer.ts +++ b/src/generated/model/StaffSerializer.ts @@ -7,6 +7,7 @@ import { Staff } from "@src/model/Staff"; import { JsonHelper } from "@src/io/JsonHelper"; import { BarSerializer } from "@src/generated/model/BarSerializer"; import { ChordSerializer } from "@src/generated/model/ChordSerializer"; +import { TuningSerializer } from "@src/generated/model/TuningSerializer"; import { Bar } from "@src/model/Bar"; import { Chord } from "@src/model/Chord"; export class StaffSerializer { @@ -30,8 +31,7 @@ export class StaffSerializer { o.set("capo", obj.capo); o.set("transpositionPitch", obj.transpositionPitch); o.set("displayTranspositionPitch", obj.displayTranspositionPitch); - o.set("tuning", obj.tuning); - o.set("tuningName", obj.tuningName); + o.set("stringTuning", TuningSerializer.toJson(obj.stringTuning)); o.set("showTablature", obj.showTablature); o.set("showStandardNotation", obj.showStandardNotation); o.set("isPercussion", obj.isPercussion); @@ -65,12 +65,6 @@ export class StaffSerializer { case "displaytranspositionpitch": obj.displayTranspositionPitch = (v as number); return true; - case "tuning": - obj.tuning = (v as number[]); - return true; - case "tuningname": - obj.tuningName = (v as string); - return true; case "showtablature": obj.showTablature = (v as boolean); return true; @@ -84,6 +78,10 @@ export class StaffSerializer { obj.standardNotationLineCount = (v as number); return true; } + if (["stringtuning"].indexOf(property) >= 0) { + TuningSerializer.fromJson(obj.stringTuning, (v as Map)); + return true; + } return false; } } diff --git a/src/generated/model/TuningSerializer.ts b/src/generated/model/TuningSerializer.ts new file mode 100644 index 000000000..6204d492c --- /dev/null +++ b/src/generated/model/TuningSerializer.ts @@ -0,0 +1,40 @@ +// +// This code was auto-generated. +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +import { Tuning } from "@src/model/Tuning"; +import { JsonHelper } from "@src/io/JsonHelper"; +export class TuningSerializer { + public static fromJson(obj: Tuning, m: unknown): void { + if (!m) { + return; + } + JsonHelper.forEach(m, (v, k) => this.setProperty(obj, k.toLowerCase(), v)); + } + public static toJson(obj: Tuning | null): Map | null { + if (!obj) { + return null; + } + const o = new Map(); + o.set("isStandard", obj.isStandard); + o.set("name", obj.name); + o.set("tunings", obj.tunings); + return o; + } + public static setProperty(obj: Tuning, property: string, v: unknown): boolean { + switch (property) { + case "isstandard": + obj.isStandard = (v as boolean); + return true; + case "name": + obj.name = (v as string); + return true; + case "tunings": + obj.tunings = (v as number[]); + return true; + } + return false; + } +} + diff --git a/src/importer/AlphaTexImporter.ts b/src/importer/AlphaTexImporter.ts index 95d9412f5..9e1e3149e 100644 --- a/src/importer/AlphaTexImporter.ts +++ b/src/importer/AlphaTexImporter.ts @@ -212,7 +212,7 @@ export class AlphaTexImporter extends ScoreImporter { this._currentTrack.playbackInfo.secondaryChannel = this._trackChannel++; this._currentStaff = this._currentTrack.staves[0]; this._currentStaff.displayTranspositionPitch = -12; - this._currentStaff.tuning = Tuning.getDefaultTuningFor(6)!.tunings; + this._currentStaff.stringTuning.tunings = Tuning.getDefaultTuningFor(6)!.tunings; this._score.addTrack(this._currentTrack); this._lyrics.set(this._currentTrack.index, []); this._currentDynamics = DynamicValue.F; @@ -699,7 +699,7 @@ export class AlphaTexImporter extends ScoreImporter { let text: string = (this._syData as string).toLowerCase(); if (text === 'piano' || text === 'none' || text === 'voice') { // clear tuning - this._currentStaff.tuning = []; + this._currentStaff.stringTuning.tunings = []; this._currentStaff.displayTranspositionPitch = 0; } else { this.error('tuning', AlphaTexSymbols.Tuning, true); @@ -713,7 +713,7 @@ export class AlphaTexImporter extends ScoreImporter { tuning.push(t.realValue); this._sy = this.newSy(); } while (this._sy === AlphaTexSymbols.Tuning); - this._currentStaff.tuning = tuning; + this._currentStaff.stringTuning.tunings = tuning; break; default: this.error('tuning', AlphaTexSymbols.Tuning, true); @@ -776,7 +776,7 @@ export class AlphaTexImporter extends ScoreImporter { } this._sy = this.newSy(); } - this._currentStaff.addChord(chord.name.toLowerCase(), chord); + this._currentStaff.addChord(this.getChordId(this._currentStaff, chord.name), chord); return true; default: return false; @@ -979,52 +979,52 @@ export class AlphaTexImporter extends ScoreImporter { // reset to defaults this._currentStaff.displayTranspositionPitch = 0; - this._currentStaff.tuning = []; + this._currentStaff.stringTuning.tunings = []; if (program == 15 || program >= 24 && program <= 31) { // dulcimer+guitar E4 B3 G3 D3 A2 E2 this._currentStaff.displayTranspositionPitch = -12; - this._currentStaff.tuning = Tuning.getDefaultTuningFor(6)!.tunings; + this._currentStaff.stringTuning.tunings = Tuning.getDefaultTuningFor(6)!.tunings; } else if (program >= 32 && program <= 39) { // bass G2 D2 A1 E1 this._currentStaff.displayTranspositionPitch = -12; - this._currentStaff.tuning = [43, 38, 33, 28]; + this._currentStaff.stringTuning.tunings = [43, 38, 33, 28]; } else if (program == 40 || program == 44 || program == 45 || program == 48 || program == 49 || program == 50 || program == 51) { // violin E3 A3 D3 G2 - this._currentStaff.tuning = [52, 57, 50, 43]; + this._currentStaff.stringTuning.tunings = [52, 57, 50, 43]; } else if (program == 41) { // viola A3 D3 G2 C2 - this._currentStaff.tuning = [57, 50, 43, 36]; + this._currentStaff.stringTuning.tunings = [57, 50, 43, 36]; } else if (program == 42) { // cello A2 D2 G1 C1 - this._currentStaff.tuning = [45, 38, 31, 24]; + this._currentStaff.stringTuning.tunings = [45, 38, 31, 24]; } else if (program == 43) { // contrabass // G2 D2 A1 E1 this._currentStaff.displayTranspositionPitch = -12; - this._currentStaff.tuning = [43, 38, 33, 28]; + this._currentStaff.stringTuning.tunings = [43, 38, 33, 28]; } else if (program == 105) { // banjo // D3 B2 G2 D2 G3 - this._currentStaff.tuning = [50, 47, 43, 38, 55]; + this._currentStaff.stringTuning.tunings = [50, 47, 43, 38, 55]; } else if (program == 106) { // shamisen // A3 E3 A2 - this._currentStaff.tuning = [57, 52, 45]; + this._currentStaff.stringTuning.tunings = [57, 52, 45]; } else if (program == 107) { // koto // E3 A2 D2 G1 - this._currentStaff.tuning = [52, 45, 38, 31]; + this._currentStaff.stringTuning.tunings = [52, 45, 38, 31]; } else if (program == 110) { // Fiddle // E4 A3 D3 G2 - this._currentStaff.tuning = [64, 57, 50, 43]; + this._currentStaff.stringTuning.tunings = [64, 57, 50, 43]; } + this._staffTuningApplied = true; } - let anyBeatData = false; let voice: Voice = bar.voices[0]; while (this._sy !== AlphaTexSymbols.Pipe && this._sy !== AlphaTexSymbols.Eof) { @@ -1296,7 +1296,7 @@ export class AlphaTexImporter extends ScoreImporter { if (syData === 'ch') { this._sy = this.newSy(); let chordName: string = (this._syData as string); - let chordId: string = chordName.toLowerCase(); + let chordId: string = this.getChordId(this._currentStaff, chordName); if (!this._currentStaff.chords.has(chordId)) { let chord: Chord = new Chord(); chord.showDiagram = false; @@ -1388,6 +1388,10 @@ export class AlphaTexImporter extends ScoreImporter { return false; } + private getChordId(currentStaff: Staff, chordName: string): string { + return chordName.toLowerCase() + currentStaff.index + currentStaff.track.index + } + private applyTuplet(beat: Beat, tuplet: number): void { switch (tuplet) { case 3: diff --git a/src/importer/Gp3To5Importer.ts b/src/importer/Gp3To5Importer.ts index 4860b8bb0..de6bfb978 100644 --- a/src/importer/Gp3To5Importer.ts +++ b/src/importer/Gp3To5Importer.ts @@ -359,7 +359,8 @@ export class Gp3To5Importer extends ScoreImporter { tuning.push(stringTuning); } } - mainStaff.tuning = tuning; + mainStaff.stringTuning.tunings = tuning; + let port: number = IOHelper.readInt32LE(this.data); let index: number = IOHelper.readInt32LE(this.data) - 1; let effectChannel: number = IOHelper.readInt32LE(this.data) - 1; diff --git a/src/importer/GpifParser.ts b/src/importer/GpifParser.ts index 2fb41a7a0..1168df898 100644 --- a/src/importer/GpifParser.ts +++ b/src/importer/GpifParser.ts @@ -748,10 +748,10 @@ export class GpifParser { for (let i: number = 0; i < tuning.length; i++) { tuning[tuning.length - 1 - i] = parseInt(tuningParts[i]); } - staff.tuning = tuning; + staff.stringTuning.tunings = tuning; break; case 'Label': - staff.tuningName = c.innerText; + staff.stringTuning.name = c.innerText; break; } } @@ -941,7 +941,7 @@ export class GpifParser { tuning[tuning.length - 1 - i] = parseInt(tuningParts[i]); } for (let staff of track.staves) { - staff.tuning = tuning; + staff.stringTuning.tunings = tuning; staff.showStandardNotation = true; staff.showTablature = true; } diff --git a/src/importer/MusicXmlImporter.ts b/src/importer/MusicXmlImporter.ts index 54c3130f3..73036bf76 100644 --- a/src/importer/MusicXmlImporter.ts +++ b/src/importer/MusicXmlImporter.ts @@ -366,7 +366,7 @@ export class MusicXmlImporter extends ScoreImporter { switch (c.localName) { case 'staff-lines': for (let staff of track.staves) { - staff.tuning = new Array(parseInt(c.innerText)).fill(0); + staff.stringTuning.tunings = new Array(parseInt(c.innerText)).fill(0); } break; case 'staff-tuning': @@ -377,7 +377,7 @@ export class MusicXmlImporter extends ScoreImporter { } for (let staff of track.staves) { if (this.isEmptyTuning(staff.tuning)) { - staff.tuning = []; + staff.stringTuning.tunings = []; } } } @@ -1370,7 +1370,7 @@ export class MusicXmlImporter extends ScoreImporter { } } if (this.isEmptyTuning(track.staves[0].tuning)) { - track.staves[0].tuning = []; + track.staves[0].stringTuning.tunings = []; } } diff --git a/src/model/MusicFontSymbol.ts b/src/model/MusicFontSymbol.ts index 781bb29f4..a9c5513e1 100644 --- a/src/model/MusicFontSymbol.ts +++ b/src/model/MusicFontSymbol.ts @@ -122,6 +122,17 @@ export enum MusicFontSymbol { PictEdgeOfCymbal = 0xe729, + GuitarString0 = 0xe833, + GuitarString1 = 0xe834, + GuitarString2 = 0xe835, + GuitarString3 = 0xe836, + GuitarString4 = 0xe837, + GuitarString5 = 0xe838, + GuitarString6 = 0xe839, + GuitarString7 = 0xe83A, + GuitarString8 = 0xe83B, + GuitarString9 = 0xe83C, + GuitarGolpe = 0xe842, FretboardX = 0xe859, diff --git a/src/model/Staff.ts b/src/model/Staff.ts index ea301b9ea..73093dd6d 100644 --- a/src/model/Staff.ts +++ b/src/model/Staff.ts @@ -2,6 +2,7 @@ import { Bar } from '@src/model/Bar'; import { Chord } from '@src/model/Chord'; import { Track } from '@src/model/Track'; import { Settings } from '@src/Settings'; +import { Tuning } from './Tuning'; /** * This class describes a single staff within a track. There are instruments like pianos @@ -55,15 +56,24 @@ export class Staff { * guitar tablature. Unlike the {@link Note.string} property this array directly represents * the order of the tracks shown in the tablature. The first item is the most top tablature line. */ - public tuning: number[] = []; + public stringTuning: Tuning = new Tuning("", [], false); + + /** + * Get or set the values of the related guitar tuning. + */ + public get tuning(): number[] { + return this.stringTuning.tunings; + } /** * Gets or sets the name of the tuning. */ - public tuningName: string = ""; + public get tuningName(): string { + return this.stringTuning.name; + } public get isStringed(): boolean { - return this.tuning.length > 0; + return this.stringTuning.tunings.length > 0; } /** @@ -88,6 +98,7 @@ export class Staff { public standardNotationLineCount: number = 5; public finish(settings: Settings): void { + this.stringTuning.finish(); for (let i: number = 0, j: number = this.bars.length; i < j; i++) { this.bars[i].finish(settings); } diff --git a/src/model/Tuning.ts b/src/model/Tuning.ts index 76db73ad5..970b2a8d1 100644 --- a/src/model/Tuning.ts +++ b/src/model/Tuning.ts @@ -1,5 +1,6 @@ /** * This public class represents a predefined string tuning. + * @json */ export class Tuning { private static _sevenStrings: Tuning[] = []; @@ -176,10 +177,23 @@ export class Tuning { * @param tuning The tuning. * @param isStandard if set to`true`[is standard]. */ - public constructor(name: string, tuning: number[], isStandard: boolean) { + public constructor(name: string = '', tuning: number[] | null = null, isStandard: boolean = false) { this.isStandard = isStandard; this.name = name; - this.tunings = tuning; + this.tunings = tuning ?? []; + } + + /** + * Tries to detect the name and standard flag of the tuning from a known tuning list based + * on the string values. + */ + public finish() { + const knownTuning = Tuning.findTuning(this.tunings); + if (knownTuning) { + this.name = knownTuning.name; + this.isStandard = knownTuning.isStandard; + } + this.name = this.name.trim(); } } diff --git a/src/platform/javascript/AlphaTabWebWorker.ts b/src/platform/javascript/AlphaTabWebWorker.ts index cc7f4ad99..2942744bf 100644 --- a/src/platform/javascript/AlphaTabWebWorker.ts +++ b/src/platform/javascript/AlphaTabWebWorker.ts @@ -29,7 +29,7 @@ export class AlphaTabWebWorker { let cmd: any = data ? data.cmd : ''; switch (cmd) { case 'alphaTab.initialize': - let settings: Settings = JsonConverter.jsObjectToSettings(data.settings); new Settings(); + let settings: Settings = JsonConverter.jsObjectToSettings(data.settings); Logger.logLevel = settings.core.logLevel; this._renderer = new ScoreRenderer(settings); this._renderer.partialRenderFinished.on(result => { diff --git a/src/platform/javascript/Html5Canvas.ts b/src/platform/javascript/Html5Canvas.ts index 25a18b74a..0d36ce4b0 100644 --- a/src/platform/javascript/Html5Canvas.ts +++ b/src/platform/javascript/Html5Canvas.ts @@ -277,6 +277,8 @@ export class Html5Canvas implements ICanvas { this._context.textBaseline = 'middle'; if (centerAtPosition) { this._context.textAlign = 'center'; + } else { + this._context.textAlign = 'left'; } this._context.fillText(symbols, x, y); this._context.textBaseline = baseLine; diff --git a/src/rendering/effects/DummyEffectGlyph.ts b/src/rendering/effects/DummyEffectGlyph.ts index 01c98b498..579805218 100644 --- a/src/rendering/effects/DummyEffectGlyph.ts +++ b/src/rendering/effects/DummyEffectGlyph.ts @@ -1,23 +1,26 @@ +import { Color } from '@src/model/Color'; import { ICanvas } from '@src/platform/ICanvas'; import { EffectGlyph } from '@src/rendering/glyphs/EffectGlyph'; -import { RenderingResources } from '@src/RenderingResources'; export class DummyEffectGlyph extends EffectGlyph { - private _s: string; + private _w:number; + private _h:number; - public constructor(x: number, y: number, s: string) { + public constructor(x: number, y: number, w: number = 20, h: number = 20) { super(x, y); - this._s = s; + this._w = w; + this._h = h; } public doLayout(): void { - this.width = 20 * this.scale; + this.width = this._w * this.scale; + this.height = this._h * this.scale; } public paint(cx: number, cy: number, canvas: ICanvas): void { - let res: RenderingResources = this.renderer.resources; - canvas.strokeRect(cx + this.x, cy + this.y, this.width, 20 * this.scale); - canvas.font = res.tablatureFont; - canvas.fillText(this._s, cx + this.x, cy + this.y); + let c = canvas.color; + canvas.color = Color.random(); + canvas.fillRect(cx + this.x, cy + this.y, this.width, this.height); + canvas.color = c; } } diff --git a/src/rendering/glyphs/ChordDiagramContainerGlyph.ts b/src/rendering/glyphs/ChordDiagramContainerGlyph.ts index 30f0cf760..27fd1fad8 100644 --- a/src/rendering/glyphs/ChordDiagramContainerGlyph.ts +++ b/src/rendering/glyphs/ChordDiagramContainerGlyph.ts @@ -1,18 +1,10 @@ import { Chord } from '@src/model/Chord'; -import { ICanvas } from '@src/platform/ICanvas'; import { ChordDiagramGlyph } from '@src/rendering/glyphs/ChordDiagramGlyph'; -import { ChordDiagramRowGlyph } from '@src/rendering/glyphs/ChordDiagramRowGlyph'; -import { GlyphGroup } from '@src/rendering/glyphs/GlyphGroup'; - -export class ChordDiagramContainerGlyph extends GlyphGroup { - private static readonly Padding: number = 3; - private _rows: ChordDiagramRowGlyph[] = []; - public height: number = 0; +import { RowContainerGlyph } from './RowContainerGlyph'; +export class ChordDiagramContainerGlyph extends RowContainerGlyph { public constructor(x: number, y: number) { super(x, y); - this.height = 0.0; - this.glyphs = []; } public addChord(chord: Chord): void { @@ -23,42 +15,4 @@ export class ChordDiagramContainerGlyph extends GlyphGroup { this.glyphs!.push(chordDiagram); } } - - public doLayout(): void { - let x: number = 0; - let y: number = 0; - let padding: number = 2 * ChordDiagramContainerGlyph.Padding * this.scale; - this._rows = []; - let row: ChordDiagramRowGlyph = new ChordDiagramRowGlyph(x, y); - row.width = this.width; - for (let g of this.glyphs!) { - if (x + g.width < this.width) { - row.addChord(g as ChordDiagramGlyph); - x += g.width; - } else { - if (!row.isEmpty) { - row.doLayout(); - this._rows.push(row); - y += row.height + padding; - } - x = 0; - row = new ChordDiagramRowGlyph(x, y); - row.width = this.width; - row.addChord(g as ChordDiagramGlyph); - x += g.width; - } - } - if (!row.isEmpty) { - row.doLayout(); - this._rows.push(row); - y += row.height + padding; - } - this.height = y + padding; - } - - public paint(cx: number, cy: number, canvas: ICanvas): void { - for (let row of this._rows) { - row.paint(cx + this.x, cy + this.y + ChordDiagramContainerGlyph.Padding * this.scale, canvas); - } - } } diff --git a/src/rendering/glyphs/ChordDiagramRowGlyph.ts b/src/rendering/glyphs/ChordDiagramRowGlyph.ts deleted file mode 100644 index 6453e413b..000000000 --- a/src/rendering/glyphs/ChordDiagramRowGlyph.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { ChordDiagramGlyph } from '@src/rendering/glyphs/ChordDiagramGlyph'; -import { GlyphGroup } from '@src/rendering/glyphs/GlyphGroup'; - -export class ChordDiagramRowGlyph extends GlyphGroup { - private _glyphWidth: number = 0; - public height: number = 0; - - public constructor(x: number, y: number) { - super(x, y); - this.glyphs = []; - } - - public doLayout(): void { - let x: number = (this.width - this._glyphWidth) / 2; - for (let glyph of this.glyphs!) { - glyph.x = x; - x += glyph.width; - } - } - - public addChord(chord: ChordDiagramGlyph): void { - this.glyphs!.push(chord); - this._glyphWidth += chord.width; - if (chord.height > this.height) { - this.height = chord.height; - } - } -} diff --git a/src/rendering/glyphs/EffectGlyph.ts b/src/rendering/glyphs/EffectGlyph.ts index f37f6a0e7..52bb29ceb 100644 --- a/src/rendering/glyphs/EffectGlyph.ts +++ b/src/rendering/glyphs/EffectGlyph.ts @@ -23,7 +23,6 @@ export class EffectGlyph extends Glyph { */ public previousGlyph: EffectGlyph | null = null; - public height: number = 0; public constructor(x: number = 0, y: number = 0) { super(x, y); diff --git a/src/rendering/glyphs/GhostParenthesisGlyph.ts b/src/rendering/glyphs/GhostParenthesisGlyph.ts index 92136ec65..9813adf98 100644 --- a/src/rendering/glyphs/GhostParenthesisGlyph.ts +++ b/src/rendering/glyphs/GhostParenthesisGlyph.ts @@ -11,8 +11,6 @@ export class GhostParenthesisGlyph extends Glyph { this._isOpen = isOpen; } - public height: number = 0; - public doLayout(): void { super.doLayout(); this.width = GhostParenthesisGlyph.Size * this.scale; diff --git a/src/rendering/glyphs/Glyph.ts b/src/rendering/glyphs/Glyph.ts index 6b4feb569..8f0f2bfad 100644 --- a/src/rendering/glyphs/Glyph.ts +++ b/src/rendering/glyphs/Glyph.ts @@ -9,6 +9,7 @@ export class Glyph { public x: number; public y: number; public width: number = 0; + public height: number = 0; public renderer!: BarRendererBase; public constructor(x: number, y: number) { diff --git a/src/rendering/glyphs/GlyphGroup.ts b/src/rendering/glyphs/GlyphGroup.ts index d6f662b9f..d2d23b68f 100644 --- a/src/rendering/glyphs/GlyphGroup.ts +++ b/src/rendering/glyphs/GlyphGroup.ts @@ -35,6 +35,9 @@ export class GlyphGroup extends Glyph { if (!this.glyphs) { this.glyphs = []; } + if(this.renderer) { + g.renderer = this.renderer; + } this.glyphs.push(g); } diff --git a/src/rendering/glyphs/NoteNumberGlyph.ts b/src/rendering/glyphs/NoteNumberGlyph.ts index bf34928a0..ca2970477 100644 --- a/src/rendering/glyphs/NoteNumberGlyph.ts +++ b/src/rendering/glyphs/NoteNumberGlyph.ts @@ -17,7 +17,6 @@ export class NoteNumberGlyph extends Glyph { private _trillNoteStringWidth: number = 0; public isEmpty: boolean = false; - public height: number = 0; public noteStringWidth: number = 0; public constructor(x: number, y: number, note: Note) { diff --git a/src/rendering/glyphs/NumberGlyph.ts b/src/rendering/glyphs/NumberGlyph.ts index c3bb85c1a..2a859964d 100644 --- a/src/rendering/glyphs/NumberGlyph.ts +++ b/src/rendering/glyphs/NumberGlyph.ts @@ -3,7 +3,7 @@ import { Glyph } from '@src/rendering/glyphs/Glyph'; import { GlyphGroup } from '@src/rendering/glyphs/GlyphGroup'; export class NumberGlyph extends GlyphGroup { - public static readonly height:number = 18; + public static readonly numberHeight:number = 18; private _number: number = 0; private _scale: number = 0; diff --git a/src/rendering/glyphs/RowContainerGlyph.ts b/src/rendering/glyphs/RowContainerGlyph.ts new file mode 100644 index 000000000..49da572bd --- /dev/null +++ b/src/rendering/glyphs/RowContainerGlyph.ts @@ -0,0 +1,54 @@ +import { ICanvas, TextAlign } from '@src/platform/ICanvas'; +import { RowGlyphContainer } from '@src/rendering/glyphs/RowGlyphContainer'; +import { GlyphGroup } from '@src/rendering/glyphs/GlyphGroup'; + +export class RowContainerGlyph extends GlyphGroup { + private static readonly Padding: number = 3; + private _rows: RowGlyphContainer[] = []; + private _align: TextAlign; + + public constructor(x: number, y: number, align: TextAlign = TextAlign.Center) { + super(x, y); + this.height = 0.0; + this.glyphs = []; + this._align = align; + } + + public doLayout(): void { + let x: number = 0; + let y: number = 0; + let padding: number = 2 * RowContainerGlyph.Padding * this.scale; + this._rows = []; + let row: RowGlyphContainer = new RowGlyphContainer(x, y, this._align); + row.width = this.width; + for (let g of this.glyphs!) { + if (x + g.width < this.width) { + row.addGlyphToRow(g); + x += g.width; + } else { + if (!row.isEmpty) { + row.doLayout(); + this._rows.push(row); + y += row.height + padding; + } + x = 0; + row = new RowGlyphContainer(x, y, this._align); + row.width = this.width; + row.addGlyphToRow(g); + x += g.width; + } + } + if (!row.isEmpty) { + row.doLayout(); + this._rows.push(row); + y += row.height + padding; + } + this.height = y + padding; + } + + public paint(cx: number, cy: number, canvas: ICanvas): void { + for (let row of this._rows) { + row.paint(cx + this.x, cy + this.y + RowContainerGlyph.Padding * this.scale, canvas); + } + } +} diff --git a/src/rendering/glyphs/RowGlyphContainer.ts b/src/rendering/glyphs/RowGlyphContainer.ts new file mode 100644 index 000000000..f365d7a1a --- /dev/null +++ b/src/rendering/glyphs/RowGlyphContainer.ts @@ -0,0 +1,41 @@ +import { TextAlign } from '@src/platform/ICanvas'; +import { GlyphGroup } from '@src/rendering/glyphs/GlyphGroup'; +import { Glyph } from './Glyph'; + +export class RowGlyphContainer extends GlyphGroup { + private _glyphWidth: number = 0; + private _align: TextAlign; + + public constructor(x: number, y: number, align: TextAlign = TextAlign.Center) { + super(x, y); + this.glyphs = []; + this._align = align; + } + + public doLayout(): void { + let x: number = 0; + switch (this._align) { + case TextAlign.Left: + x = 0; + break; + case TextAlign.Center: + x = (this.width - this._glyphWidth) / 2; + break; + case TextAlign.Right: + x = this.width - this._glyphWidth; + break; + } + for (let glyph of this.glyphs!) { + glyph.x = x; + x += glyph.width; + } + } + + public addGlyphToRow(glyph: Glyph): void { + this.glyphs!.push(glyph); + this._glyphWidth += glyph.width; + if (glyph.height > this.height) { + this.height = glyph.height; + } + } +} diff --git a/src/rendering/glyphs/TimeSignatureGlyph.ts b/src/rendering/glyphs/TimeSignatureGlyph.ts index 8e4a5363a..6d3260b5f 100644 --- a/src/rendering/glyphs/TimeSignatureGlyph.ts +++ b/src/rendering/glyphs/TimeSignatureGlyph.ts @@ -41,7 +41,7 @@ export abstract class TimeSignatureGlyph extends GlyphGroup { this.addGlyph(common); super.doLayout(); } else { - const numberHeight = NumberGlyph.height * this.scale; + const numberHeight = NumberGlyph.numberHeight * this.scale; let numerator: NumberGlyph = new NumberGlyph(0, -numberHeight / 2, this._numerator, this.numberScale); let denominator: NumberGlyph = new NumberGlyph(0, numberHeight / 2, this._denominator, this.numberScale); this.addGlyph(numerator); diff --git a/src/rendering/glyphs/TuningContainerGlyph.ts b/src/rendering/glyphs/TuningContainerGlyph.ts new file mode 100644 index 000000000..5facb7498 --- /dev/null +++ b/src/rendering/glyphs/TuningContainerGlyph.ts @@ -0,0 +1,19 @@ +import { Tuning } from '@src/model/Tuning'; +import { TextAlign } from '@src/platform/ICanvas'; +import { RowContainerGlyph } from './RowContainerGlyph'; +import { TuningGlyph } from './TuningGlyph'; + +export class TuningContainerGlyph extends RowContainerGlyph { + public constructor(x: number, y: number) { + super(x, y, TextAlign.Left); + } + + public addTuning(tuning: Tuning, trackLabel: string): void { + if (tuning.tunings.length > 0) { + let tuningGlyph: TuningGlyph = new TuningGlyph(0, 0, tuning, trackLabel); + tuningGlyph.renderer = this.renderer; + tuningGlyph.doLayout(); + this.glyphs!.push(tuningGlyph); + } + } +} diff --git a/src/rendering/glyphs/TuningGlyph.ts b/src/rendering/glyphs/TuningGlyph.ts index 36490bc79..56d9fa7d2 100644 --- a/src/rendering/glyphs/TuningGlyph.ts +++ b/src/rendering/glyphs/TuningGlyph.ts @@ -1,40 +1,89 @@ +import { MusicFontSymbol } from '@src/model/MusicFontSymbol'; import { Tuning } from '@src/model/Tuning'; import { TextAlign } from '@src/platform/ICanvas'; import { GlyphGroup } from '@src/rendering/glyphs/GlyphGroup'; import { TextGlyph } from '@src/rendering/glyphs/TextGlyph'; -import { RenderingResources } from '@src/RenderingResources'; +import { MusicFontGlyph } from './MusicFontGlyph'; export class TuningGlyph extends GlyphGroup { - private _scale: number = 0; - private _resources: RenderingResources; - public height: number = 0; + private _tuning: Tuning; + private _trackLabel: string; - public constructor(x: number, y: number, scale: number, resources: RenderingResources, tuning: Tuning) { + public constructor(x: number, y: number, tuning: Tuning, trackLabel: string) { super(x, y); - this._scale = scale; - this._resources = resources; - this.createGlyphs(tuning); + this._tuning = tuning; + this._trackLabel = trackLabel; + this.glyphs = []; } + public doLayout() { + if (this.glyphs!.length > 0) { + return; + } + this.createGlyphs(this._tuning); + for (const g of this.glyphs!) { + g.renderer = this.renderer + g.doLayout(); + } + } + + /** + * The height of the GuitarString# glyphs at scale 1 + */ + private static readonly CircleNumberHeight: number = 20; + + private createGlyphs(tuning: Tuning): void { + const scale = this.renderer.scale; + const res = this.renderer.resources; + this.height = 0; + + const rowHeight = 15 * scale; + + // Track name + if (this._trackLabel.length > 0) { + this.addGlyph(new TextGlyph(0, this.height, this._trackLabel, res.effectFont, TextAlign.Left)); + this.height += rowHeight; + } + // Name - this.addGlyph(new TextGlyph(0, 0, tuning.name, this._resources.effectFont, TextAlign.Left)); - this.height += 15 * this._scale; + this.addGlyph(new TextGlyph(0, this.height, tuning.name, res.effectFont, TextAlign.Left)); + + const stringColumnWidth = 64 * scale; + + this.renderer.scoreRenderer.canvas!.font = res.effectFont; + this.width = Math.max( + this.renderer.scoreRenderer.canvas!.measureText(this._trackLabel) * scale, + Math.max( + this.renderer.scoreRenderer.canvas!.measureText(tuning.name) * scale, + 2 * stringColumnWidth + ) + ) + + this.height += rowHeight; if (!tuning.isStandard) { + const circleScale = 0.7; + const circleHeight = TuningGlyph.CircleNumberHeight * circleScale * scale; + // Strings let stringsPerColumn: number = Math.ceil(tuning.tunings.length / 2.0) | 0; let currentX: number = 0; let currentY: number = this.height; for (let i: number = 0, j: number = tuning.tunings.length; i < j; i++) { - let str: string = '(' + (i + 1) + ') = ' + Tuning.getTextForTuning(tuning.tunings[i], false); - this.addGlyph(new TextGlyph(currentX, currentY, str, this._resources.effectFont, TextAlign.Left)); - currentY += this.height; + const symbol = ((MusicFontSymbol.GuitarString0 as number) + (i + 1)) as MusicFontSymbol; + this.addGlyph(new MusicFontGlyph(currentX, currentY + circleHeight / 1.2, circleScale, symbol)); + + const str: string = '= ' + Tuning.getTextForTuning(tuning.tunings[i], false); + this.addGlyph(new TextGlyph(currentX + circleHeight + 1 * scale, currentY, str, res.effectFont, TextAlign.Left)); + currentY += rowHeight; if (i === stringsPerColumn - 1) { currentY = this.height; - currentX += 43 * this._scale; + currentX += stringColumnWidth; } } - this.height += stringsPerColumn * (15 * this._scale); + this.height += stringsPerColumn * rowHeight; } + + this.width += 15 * scale; } } diff --git a/src/rendering/layout/PageViewLayout.ts b/src/rendering/layout/PageViewLayout.ts index ccd6ccd97..344392a0c 100644 --- a/src/rendering/layout/PageViewLayout.ts +++ b/src/rendering/layout/PageViewLayout.ts @@ -57,10 +57,13 @@ export class PageViewLayout extends ScoreLayout { // 1. Score Info y = this.layoutAndRenderScoreInfo(x, y, -1); // - // 2. Chord Diagrms + // 2. Tunings + y = this.layoutAndRenderTunings(y, -1); + // + // 3. Chord Diagrms y = this.layoutAndRenderChordDiagrams(y, -1); // - // 3. One result per StaveGroup + // 4. One result per StaveGroup y = this.layoutAndRenderScore(x, y); this.height = y + this._pagePadding[3]; } @@ -78,14 +81,48 @@ export class PageViewLayout extends ScoreLayout { // 1. Score Info y = this.layoutAndRenderScoreInfo(x, y, oldHeight); // - // 2. Chord Digrams + // 2. Tunings + y = this.layoutAndRenderTunings(y, oldHeight); + // + // 3. Chord Digrams y = this.layoutAndRenderChordDiagrams(y, oldHeight); // - // 2. One result per StaveGroup + // 4. One result per StaveGroup y = this.resizeAndRenderScore(x, y, oldHeight); this.height = y + this._pagePadding![3]; } + private layoutAndRenderTunings(y: number, totalHeight: number = -1): number { + if (!this.tuningGlyph) { + return y; + } + + let res: RenderingResources = this.renderer.settings.display.resources; + this.tuningGlyph.width = this.width; + this.tuningGlyph.doLayout(); + + let tuningHeight = this.tuningGlyph.height + 11 * this.scale;; + y += tuningHeight; + + let canvas: ICanvas = this.renderer.canvas!; + canvas.beginRender(this.width, tuningHeight); + canvas.color = res.scoreInfoColor; + canvas.textAlign = TextAlign.Center; + this.tuningGlyph.paint(this._pagePadding![0], 0, canvas); + let result: unknown = canvas.endRender(); + + let e = new RenderFinishedEventArgs(); + e.width = this.width; + e.height = tuningHeight; + e.renderResult = result; + e.totalWidth = this.width; + e.totalHeight = totalHeight < 0 ? y : totalHeight; + e.firstMasterBarIndex = -1; + e.lastMasterBarIndex = -1; + (this.renderer.partialRenderFinished as EventEmitterOfT).trigger(e); + return y; + } + private layoutAndRenderChordDiagrams(y: number, totalHeight: number = -1): number { if (!this.chordDiagrams) { return y; @@ -154,13 +191,9 @@ export class PageViewLayout extends ScoreLayout { if (musicOrWords) { y += musicOrWordsHeight; } - if (this.tuningGlyph) { - y += 20 * scale; - this.tuningGlyph.x = x; - this.tuningGlyph.y = y; - y += this.tuningGlyph.height; - } - y += 20 * scale; + + y += 17 * this.scale; + let canvas: ICanvas = this.renderer.canvas!; canvas.beginRender(this.width, y); canvas.color = res.scoreInfoColor; @@ -168,9 +201,6 @@ export class PageViewLayout extends ScoreLayout { this.scoreInfoGlyphs.forEach(g => { g.paint(0, 0, canvas); }); - if (this.tuningGlyph) { - this.tuningGlyph.paint(0, 0, canvas); - } let result: unknown = canvas.endRender(); let e = new RenderFinishedEventArgs(); diff --git a/src/rendering/layout/ScoreLayout.ts b/src/rendering/layout/ScoreLayout.ts index 18ffe56f0..c6492a85d 100644 --- a/src/rendering/layout/ScoreLayout.ts +++ b/src/rendering/layout/ScoreLayout.ts @@ -6,13 +6,11 @@ import { Font, FontStyle } from '@src/model/Font'; import { Score } from '@src/model/Score'; import { Staff } from '@src/model/Staff'; import { Track } from '@src/model/Track'; -import { Tuning } from '@src/model/Tuning'; import { ICanvas, TextAlign } from '@src/platform/ICanvas'; import { BarRendererBase } from '@src/rendering/BarRendererBase'; import { BarRendererFactory } from '@src/rendering/BarRendererFactory'; import { ChordDiagramContainerGlyph } from '@src/rendering/glyphs/ChordDiagramContainerGlyph'; import { TextGlyph } from '@src/rendering/glyphs/TextGlyph'; -import { TuningGlyph } from '@src/rendering/glyphs/TuningGlyph'; import { RenderFinishedEventArgs } from '@src/rendering/RenderFinishedEventArgs'; import { ScoreRenderer } from '@src/rendering/ScoreRenderer'; import { RenderStaff } from '@src/rendering/staves/RenderStaff'; @@ -21,6 +19,7 @@ import { RenderingResources } from '@src/RenderingResources'; import { Logger } from '@src/Logger'; import { EventEmitterOfT } from '@src/EventEmitter'; import { NotationSettings, NotationElement } from '@src/NotationSettings'; +import { TuningContainerGlyph } from '../glyphs/TuningContainerGlyph'; /** * This is the base public class for creating new layouting engines for the score renderer. @@ -36,7 +35,7 @@ export abstract class ScoreLayout { protected scoreInfoGlyphs: Map = new Map(); protected chordDiagrams: ChordDiagramContainerGlyph | null = null; - protected tuningGlyph: TuningGlyph | null = null; + protected tuningGlyph: TuningContainerGlyph | null = null; protected constructor(renderer: ScoreRenderer) { this.renderer = renderer; @@ -120,38 +119,35 @@ export abstract class ScoreLayout { ); } } + + const fakeBarRenderer = new BarRendererBase( + this.renderer, + this.renderer.tracks![0].staves[0].bars[0] + ); + if (notation.isNotationElementVisible(NotationElement.GuitarTuning)) { - let staffWithTuning: Staff | null = null; + let tunings: Staff[] = []; for (let track of this.renderer.tracks!) { for (let staff of track.staves) { if (!staff.isPercussion && staff.isStringed && staff.tuning.length > 0 && staff.showTablature) { - staffWithTuning = staff; + tunings.push(staff); break; } } - if (staffWithTuning) { - break; - } } // tuning info - if (staffWithTuning) { - let tuning: Tuning; - if (staffWithTuning.tuningName) { - tuning = new Tuning(staffWithTuning.tuningName, staffWithTuning.tuning, false); - } else { - tuning = Tuning.findTuning(staffWithTuning.tuning) ?? new Tuning('', staffWithTuning.tuning, false); + if (tunings.length > 0) { + this.tuningGlyph = new TuningContainerGlyph(0, 0); + this.tuningGlyph.renderer = fakeBarRenderer; + for (const t of tunings) { + this.tuningGlyph.addTuning(t.stringTuning, tunings.length > 1 ? t.track.name : ''); } - - this.tuningGlyph = new TuningGlyph(0, 0, this.scale, res, tuning); } } // chord diagram glyphs if (notation.isNotationElementVisible(NotationElement.ChordDiagrams)) { this.chordDiagrams = new ChordDiagramContainerGlyph(0, 0); - this.chordDiagrams.renderer = new BarRendererBase( - this.renderer, - this.renderer.tracks![0].staves[0].bars[0] - ); + this.chordDiagrams.renderer = fakeBarRenderer; let chords: Map = new Map(); for (let track of this.renderer.tracks!) { for (let staff of track.staves) { diff --git a/test-data/visual-tests/effects-and-annotations/chords.png b/test-data/visual-tests/effects-and-annotations/chords.png index 75151624f..aac8f5447 100644 Binary files a/test-data/visual-tests/effects-and-annotations/chords.png and b/test-data/visual-tests/effects-and-annotations/chords.png differ diff --git a/test-data/visual-tests/effects-and-annotations/tuplets-advanced.png b/test-data/visual-tests/effects-and-annotations/tuplets-advanced.png index 832d43665..31212481f 100644 Binary files a/test-data/visual-tests/effects-and-annotations/tuplets-advanced.png and b/test-data/visual-tests/effects-and-annotations/tuplets-advanced.png differ diff --git a/test-data/visual-tests/general/tuning.png b/test-data/visual-tests/general/tuning.png index 4ac8d0ae7..406739134 100644 Binary files a/test-data/visual-tests/general/tuning.png and b/test-data/visual-tests/general/tuning.png differ diff --git a/test-data/visual-tests/guitar-tabs/multiple-tunings.gp b/test-data/visual-tests/guitar-tabs/multiple-tunings.gp new file mode 100644 index 000000000..c93fbbaca Binary files /dev/null and b/test-data/visual-tests/guitar-tabs/multiple-tunings.gp differ diff --git a/test-data/visual-tests/guitar-tabs/rhythm-with-beams.png b/test-data/visual-tests/guitar-tabs/rhythm-with-beams.png index 7eee2b096..a5d2da4eb 100644 Binary files a/test-data/visual-tests/guitar-tabs/rhythm-with-beams.png and b/test-data/visual-tests/guitar-tabs/rhythm-with-beams.png differ diff --git a/test-data/visual-tests/guitar-tabs/rhythm.png b/test-data/visual-tests/guitar-tabs/rhythm.png index 293070aa2..665e4c3d0 100644 Binary files a/test-data/visual-tests/guitar-tabs/rhythm.png and b/test-data/visual-tests/guitar-tabs/rhythm.png differ diff --git a/test-data/visual-tests/layout/multi-track.png b/test-data/visual-tests/layout/multi-track.png index 2eb5e073c..61a3d7e2d 100644 Binary files a/test-data/visual-tests/layout/multi-track.png and b/test-data/visual-tests/layout/multi-track.png differ diff --git a/test-data/visual-tests/notation-elements/guitar-tuning-off.png b/test-data/visual-tests/notation-elements/guitar-tuning-off.png index cee17ea5e..bf322d830 100644 Binary files a/test-data/visual-tests/notation-elements/guitar-tuning-off.png and b/test-data/visual-tests/notation-elements/guitar-tuning-off.png differ diff --git a/test-data/visual-tests/notation-elements/guitar-tuning-on.png b/test-data/visual-tests/notation-elements/guitar-tuning-on.png index 9fd81c78a..08aea68f8 100644 Binary files a/test-data/visual-tests/notation-elements/guitar-tuning-on.png and b/test-data/visual-tests/notation-elements/guitar-tuning-on.png differ diff --git a/test-data/visual-tests/special-notes/grace-notes-advanced.png b/test-data/visual-tests/special-notes/grace-notes-advanced.png index 5f335cf78..2f485d8ed 100644 Binary files a/test-data/visual-tests/special-notes/grace-notes-advanced.png and b/test-data/visual-tests/special-notes/grace-notes-advanced.png differ diff --git a/test-data/visual-tests/special-tracks/drum-tabs.png b/test-data/visual-tests/special-tracks/drum-tabs.png index 3b7da2a37..ff3363862 100644 Binary files a/test-data/visual-tests/special-tracks/drum-tabs.png and b/test-data/visual-tests/special-tracks/drum-tabs.png differ diff --git a/test-data/visual-tests/special-tracks/grand-staff.png b/test-data/visual-tests/special-tracks/grand-staff.png index a348c2d23..7589f2a5f 100644 Binary files a/test-data/visual-tests/special-tracks/grand-staff.png and b/test-data/visual-tests/special-tracks/grand-staff.png differ diff --git a/test-data/visual-tests/special-tracks/percussion.png b/test-data/visual-tests/special-tracks/percussion.png index a437dfd07..4046bd1a8 100644 Binary files a/test-data/visual-tests/special-tracks/percussion.png and b/test-data/visual-tests/special-tracks/percussion.png differ diff --git a/test/exporter/Gp7Exporter.test.ts b/test/exporter/Gp7Exporter.test.ts index 62faf72c2..536d92468 100644 --- a/test/exporter/Gp7Exporter.test.ts +++ b/test/exporter/Gp7Exporter.test.ts @@ -39,15 +39,14 @@ describe('Gp7ExporterTest', () => { const fileName = name.substr(name.lastIndexOf('/') + 1); const exported = exportGp7(expected); - - await TestPlatform.saveFile(fileName, exported); - const actual = prepareGp7ImporterWithBytes(exported).readScore(); const expectedJson = JsonConverter.scoreToJsObject(expected); const actualJson = JsonConverter.scoreToJsObject(actual) - ComparisonHelpers.expectJsonEqual(expectedJson, actualJson, '<' + fileName + '>', ignoreKeys); + if (!ComparisonHelpers.expectJsonEqual(expectedJson, actualJson, '<' + fileName + '>', ignoreKeys)) { + await TestPlatform.saveFile(fileName, exported); + } } catch (e) { fail(e); } @@ -114,7 +113,7 @@ describe('Gp7ExporterTest', () => { ]); }); - it('alphatex-to-gp7', async () => { + it('alphatex-to-gp7', () => { const tex = `\\title "Canon Rock" \\subtitle "JerryC" \\tempo 90 diff --git a/test/model/ComparisonHelpers.ts b/test/model/ComparisonHelpers.ts index 25ab43ac4..00e150099 100644 --- a/test/model/ComparisonHelpers.ts +++ b/test/model/ComparisonHelpers.ts @@ -1,44 +1,54 @@ export class ComparisonHelpers { - public static expectJsonEqual(expected: unknown, actual: unknown, path: string, ignoreKeys: string[] | null) { + public static expectJsonEqual(expected: unknown, actual: unknown, path: string, ignoreKeys: string[] | null): boolean { const expectedType = typeof expected; const actualType = typeof actual; // NOTE: performance wise expect() seems quite expensive // that's why we do a manual check for most asserts + let result = true; if (actualType != expectedType) { fail(`Type Mismatch on hierarchy: ${path}, '${actualType}' != '${expectedType}'`); + result = false; } switch (actualType) { case 'boolean': if ((actual as boolean) != (expected as boolean)) { fail(`Boolean mismatch on hierarchy: ${path}, '${actual}' != '${expected}'`); + result = false; } break; case 'number': if (Math.abs((actual as number) - (expected as number)) >= 0.000001) { fail(`Number mismatch on hierarchy: ${path}, '${actual}' != '${expected}'`); + result = false; } break; case 'object': if ((actual === null) !== (expected === null)) { fail(`Null mismatch on hierarchy: ${path}, '${actual}' != '${expected}'`); + result = false; } else if (actual) { if (Array.isArray(actual) !== Array.isArray(expected)) { fail(`IsArray mismatch on hierarchy: ${path}`); + result = false; } else if (Array.isArray(actual) && Array.isArray(expected)) { if (actual.length !== expected.length) { fail(`Array Length mismatch on hierarchy: ${path}, ${actual.length} != ${expected.length}`); + result = false; } else { for (let i = 0; i < actual.length; i++) { - ComparisonHelpers.expectJsonEqual(expected[i], actual[i], `${path}[${i}]`, ignoreKeys); + if(!ComparisonHelpers.expectJsonEqual(expected[i], actual[i], `${path}[${i}]`, ignoreKeys)) { + result = false; + } } } } else if (expected instanceof Map) { if (!(actual instanceof Map)) { fail(`Map mismatch on hierarchy: ${path}, '${actual}' != '${expected}'`); + result = false; } else { const expectedMap = expected as Map; const actualMap = actual as Map; @@ -52,6 +62,7 @@ export class ComparisonHelpers { const expectedKeyList = expectedKeys.join(','); if (actualKeyList !== expectedKeyList) { fail(`Object Keys mismatch on hierarchy: ${path}, '${actualKeyList}' != '${expectedKeyList}'`); + result = false; } else { for (const key of actualKeys) { switch (key) { @@ -63,8 +74,10 @@ export class ComparisonHelpers { case 'tieDestinationNoteId': break; default: - if(!ignoreKeys || ignoreKeys.indexOf(key) === -1) { - ComparisonHelpers.expectJsonEqual(expectedMap.get(key), actualMap.get(key), `${path}.${key}`, ignoreKeys); + if (!ignoreKeys || ignoreKeys.indexOf(key) === -1) { + if(!ComparisonHelpers.expectJsonEqual(expectedMap.get(key), actualMap.get(key), `${path}.${key}`, ignoreKeys)) { + result = false; + } } break; } @@ -74,20 +87,25 @@ export class ComparisonHelpers { } } else { fail('Need Map serialization for comparing json objects'); + result = false; } } break; case 'string': if ((actual as string) != (expected as string)) { fail(`String mismatch on hierarchy: ${path}, '${actual}' != '${expected}'`); + result = false; } break; case 'undefined': if (actual !== expected) { fail(`null mismatch on hierarchy: ${path}, '${actual}' != '${expected}'`); + result = false; } break; } + + return result; } diff --git a/test/model/Lyrics.test.ts b/test/model/Lyrics.test.ts index 1e7395b9b..6d23d9c75 100644 --- a/test/model/Lyrics.test.ts +++ b/test/model/Lyrics.test.ts @@ -110,24 +110,24 @@ describe('LyricsTests', () => { expect(score.tracks[0].staves[0].bars[2].voices[0].beats[3].lyrics![1]).toBeFalsy(); }); - it('spaces', async () => { + it('spaces', () => { testLyrics('AAA BBB CCC DDD EEE', ['AAA', 'BBB', 'CCC', 'DDD', 'EEE']); testLyrics('AAA BBB CCC', ['AAA', '', 'BBB', '', '', 'CCC']); }); - it('new-lines', async () => { + it('new-lines', () => { testLyrics('AAA\r\nBBB\rCCC\nDDD\r\nEEE', ['AAA', 'BBB', 'CCC', 'DDD', 'EEE']); }); - it('dash', async () => { + it('dash', () => { testLyrics('AAA-BBB CCC- DDD EEE--FFF', ['AAA-', 'BBB', 'CCC-', 'DDD', 'EEE--', 'FFF']); }); - it('plus', async () => { + it('plus', () => { testLyrics('AAA+BBB CCC++DDD EEE+ FFF', ['AAA BBB', 'CCC DDD', 'EEE ', 'FFF']); }); - it('comments', async () => { + it('comments', () => { testLyrics('[ABCD]AAA BBB', ['AAA', 'BBB']); testLyrics('[ABCD] AAA BBB', ['', 'AAA', 'BBB']); testLyrics('[AAA BBB\r\nCCC DDD]AAA BBB', ['AAA', 'BBB']);