Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Make track visibility information from files available in model #1491

Merged
merged 2 commits into from
May 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions src.csharp/AlphaTab.Test/Test/Globals.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

namespace AlphaTab.Test
{
public static class assert

Check warning on line 6 in src.csharp/AlphaTab.Test/Test/Globals.cs

View workflow job for this annotation

GitHub Actions / Build and Test C#

The type name 'assert' only contains lower-cased ascii characters. Such names may become reserved for the language.

Check warning on line 6 in src.csharp/AlphaTab.Test/Test/Globals.cs

View workflow job for this annotation

GitHub Actions / Build and Test C#

The type name 'assert' only contains lower-cased ascii characters. Such names may become reserved for the language.
{
public static void Fail(string message)
{
Expand Down Expand Up @@ -119,6 +119,18 @@
}
}

public void False()
{
if (_actual is bool b)
{
Assert.IsFalse(b);
}
else
{
Assert.Fail("ToBeFalse can only be used on bools:");
}
}



public void Throw(Type expected)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,14 @@ class Expector<T>(private val actual: T) {
}
}

fun `false`() {
if (actual is Boolean) {
kotlin.test.assertFalse(actual);
} else {
kotlin.test.fail("toBeFalse can only be used on booleans:");
}
}


fun `throw`(expected: KClass<out Throwable>) {
val actual = actual
Expand Down
3 changes: 3 additions & 0 deletions src/exporter/Gp7Exporter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { IOHelper } from '@src/io/IOHelper';
import { BinaryStylesheet } from '@src/importer/BinaryStylesheet';
import { PartConfiguration } from '@src/importer/PartConfiguration';
import { ZipWriter } from '@src/zip/ZipWriter';
import { LayoutConfiguration } from '@src/importer/LayoutConfiguration';
/**
* This ScoreExporter can write Guitar Pro 7 (gp) files.
*/
Expand All @@ -25,13 +26,15 @@ export class Gp7Exporter extends ScoreExporter {
const gpifXml = gpifWriter.writeXml(score);
const binaryStylesheet = BinaryStylesheet.writeForScore(score);
const partConfiguration = PartConfiguration.writeForScore(score);
const layoutConfiguration = LayoutConfiguration.writeForScore(score);

Logger.debug(this.name, 'Writing ZIP entries');
let fileSystem: ZipWriter = new ZipWriter(this.data);
fileSystem.writeEntry(new ZipEntry('VERSION', IOHelper.stringToBytes('7.0')));
fileSystem.writeEntry(new ZipEntry('Content/', new Uint8Array(0)));
fileSystem.writeEntry(new ZipEntry('Content/BinaryStylesheet', binaryStylesheet));
fileSystem.writeEntry(new ZipEntry('Content/PartConfiguration', partConfiguration));
fileSystem.writeEntry(new ZipEntry('Content/LayoutConfiguration', layoutConfiguration));
fileSystem.writeEntry(new ZipEntry('Content/score.gpif', IOHelper.stringToBytes(gpifXml)));
fileSystem.end();
}
Expand Down
4 changes: 4 additions & 0 deletions src/generated/model/TrackSerializer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ export class TrackSerializer {
o.set("playbackinfo", PlaybackInformationSerializer.toJson(obj.playbackInfo));
o.set("color", Color.toJson(obj.color));
o.set("name", obj.name);
o.set("isvisibleonmultitrack", obj.isVisibleOnMultiTrack);
o.set("shortname", obj.shortName);
o.set("defaultsystemslayout", obj.defaultSystemsLayout);
o.set("systemslayout", obj.systemsLayout);
Expand All @@ -49,6 +50,9 @@ export class TrackSerializer {
case "name":
obj.name = v! as string;
return true;
case "isvisibleonmultitrack":
obj.isVisibleOnMultiTrack = v! as boolean;
return true;
case "shortname":
obj.shortName = v! as string;
return true;
Expand Down
16 changes: 16 additions & 0 deletions src/importer/Gp3To5Importer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -344,11 +344,27 @@ export class Gp3To5Importer extends ScoreImporter {
newTrack.ensureStaveCount(1);
this._score.addTrack(newTrack);
let mainStaff: Staff = newTrack.staves[0];

// Track Flags:
// 1 - Percussion Track
// 2 - 12 Stringed Track
// 4 - Unknown
// 8 - Is Visible on Multi Track
// 16 - Unknown
// 32 - Unknown
// 64 - Unknown
// 128 - Unknown

let flags: number = this.data.readByte();
newTrack.name = GpBinaryHelpers.gpReadStringByteLength(this.data, 40, this.settings.importer.encoding);
if ((flags & 0x01) !== 0) {
mainStaff.isPercussion = true;
}
if (this._versionNumber >= 500) {
newTrack.isVisibleOnMultiTrack = (flags & 0x08) !== 0;
}

//
let stringCount: number = IOHelper.readInt32LE(this.data);
let tuning: number[] = [];
for (let i: number = 0; i < 7; i++) {
Expand Down
19 changes: 17 additions & 2 deletions src/importer/Gp7Importer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,9 @@ import { Score } from '@src/model/Score';
import { Logger } from '@src/Logger';

import { ZipReader } from '@src/zip/ZipReader';
import { ZipEntry } from "@src/zip/ZipEntry";
import { ZipEntry } from '@src/zip/ZipEntry';
import { IOHelper } from '@src/io/IOHelper';
import { LayoutConfiguration } from './LayoutConfiguration';

/**
* This ScoreImporter can read Guitar Pro 7 (gp) files.
Expand Down Expand Up @@ -40,6 +41,7 @@ export class Gp7Importer extends ScoreImporter {
let xml: string | null = null;
let binaryStylesheetData: Uint8Array | null = null;
let partConfigurationData: Uint8Array | null = null;
let layoutConfigurationData: Uint8Array | null = null;
for (let entry of entries) {
switch (entry.fileName) {
case 'score.gpif':
Expand All @@ -51,6 +53,9 @@ export class Gp7Importer extends ScoreImporter {
case 'PartConfiguration':
partConfigurationData = entry.data;
break;
case 'LayoutConfiguration':
layoutConfigurationData = entry.data;
break;
}
}

Expand All @@ -73,12 +78,22 @@ export class Gp7Importer extends ScoreImporter {
Logger.debug(this.name, 'BinaryStylesheet parsed');
}

let partConfigurationParser: PartConfiguration | null = null;
if (partConfigurationData) {
Logger.debug(this.name, 'Start Parsing Part Configuration');
let partConfigurationParser: PartConfiguration = new PartConfiguration(partConfigurationData);
partConfigurationParser = new PartConfiguration(partConfigurationData);
partConfigurationParser.apply(score);
Logger.debug(this.name, 'Part Configuration parsed');
}
if (layoutConfigurationData && partConfigurationParser != null) {
Logger.debug(this.name, 'Start Parsing Layout Configuration');
let layoutConfigurationParser: LayoutConfiguration = new LayoutConfiguration(
partConfigurationParser,
layoutConfigurationData
);
layoutConfigurationParser.apply(score);
Logger.debug(this.name, 'Layout Configuration parsed');
}
return score;
}
}
3 changes: 0 additions & 3 deletions src/importer/GpxFileSystem.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,6 @@ export class GpxFile {
export class GpxFileSystem {
public static readonly HeaderBcFs: string = 'BCFS';
public static readonly HeaderBcFz: string = 'BCFZ';
public static readonly ScoreGpif: string = 'score.gpif';
public static readonly BinaryStylesheet: string = 'BinaryStylesheet';
public static readonly PartConfiguration: string = 'PartConfiguration';

/**
* You can set a file filter method using this setter. On parsing
Expand Down
22 changes: 19 additions & 3 deletions src/importer/GpxImporter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { Score } from '@src/model/Score';
import { Logger } from '@src/Logger';
import { UnsupportedFormatError } from '@src/importer/UnsupportedFormatError';
import { IOHelper } from '@src/io/IOHelper';
import { LayoutConfiguration } from './LayoutConfiguration';

/**
* This ScoreImporter can read Guitar Pro 6 (gpx) files.
Expand All @@ -26,14 +27,15 @@ export class GpxImporter extends ScoreImporter {
Logger.debug(this.name, 'Loading GPX filesystem');
let fileSystem: GpxFileSystem = new GpxFileSystem();
fileSystem.fileFilter = s => {
return s.endsWith('score.gpif') || s.endsWith('BinaryStylesheet') || s.endsWith('PartConfiguration');
return s.endsWith('score.gpif') || s.endsWith('BinaryStylesheet') || s.endsWith('PartConfiguration') || s.endsWith('LayoutConfiguration');
};
fileSystem.load(this.data);
Logger.debug(this.name, 'GPX filesystem loaded');

let xml: string | null = null;
let binaryStylesheetData: Uint8Array | null = null;
let partConfigurationData: Uint8Array | null = null;
let layoutConfigurationData: Uint8Array | null = null;
for (let entry of fileSystem.files) {
switch (entry.fileName) {
case 'score.gpif':
Expand All @@ -45,6 +47,9 @@ export class GpxImporter extends ScoreImporter {
case 'PartConfiguration':
partConfigurationData = entry.data;
break;
case 'LayoutConfiguration':
layoutConfigurationData = entry.data;
break;
}
}

Expand All @@ -67,13 +72,24 @@ export class GpxImporter extends ScoreImporter {
Logger.debug(this.name, 'BinaryStylesheet parsed');
}

let partConfigurationParser: PartConfiguration | null = null;
if (partConfigurationData) {
Logger.debug(this.name, 'Start Parsing Part Configuration');
let partConfiguration: PartConfiguration = new PartConfiguration(partConfigurationData);
partConfiguration.apply(score);
partConfigurationParser = new PartConfiguration(partConfigurationData);
partConfigurationParser.apply(score);
Logger.debug(this.name, 'Part Configuration parsed');
}

if (layoutConfigurationData && partConfigurationParser != null) {
Logger.debug(this.name, 'Start Parsing Layout Configuration');
let layoutConfigurationParser: LayoutConfiguration = new LayoutConfiguration(
partConfigurationParser,
layoutConfigurationData
);
layoutConfigurationParser.apply(score);
Logger.debug(this.name, 'Layout Configuration parsed');
}

return score;
}
}
132 changes: 132 additions & 0 deletions src/importer/LayoutConfiguration.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
import { ByteBuffer } from "@src/io/ByteBuffer";
import { IOHelper } from "@src/io/IOHelper";
import { Score } from "@src/model/Score";
import { PartConfiguration } from "./PartConfiguration";

// PartConfiguration File Format Notes.
// Based off Guitar Pro 8
// The LayoutConfiguration is aligned with the data in the PartConfiguration.
// We haven't fully deciphered how they handle everything but its enough for our needs.

// File:
// int32 (big endian) | Zoom Level encoded
// | 25% - 00 00 00 00
// | 50% - 00 00 00 01
// | 75% - 00 00 00 02
// | 90% - 00 00 00 03
// | 100% - 00 00 00 04
// | 110% - 00 00 00 05
// | 125% - 00 00 00 06
// | 150% - 00 00 00 07
// | 200% - 00 00 00 08
// | 300% - 00 00 00 08 (same as 200% -> strange)
// | 400% - 00 00 00 09
// | 800% - 00 00 00 0A
// | Custom 69% - 00 00 00 02 (same as 75% -> closest match on 25% steps?)
// | Custom 113% - 00 00 00 05 (same as 110% -> closest match on 25% steps?)
// | Fit To Page- 00 00 00 08 (same as 200% -> strange)
// | Fit To width- 00 00 00 08 (same as 200% -> strange)
// 1 byte (enum) | The main score view to use.
// | 0x00 -> Page - Vertical,
// | 0x01 -> Page - Grid,
// | 0x02 -> Page - Parchment,
// | 0x03 -> Screen - Vertical,
// | 0x04 -> Screen - Horizontal,
// | 0x05 -> Page - Horizontal
// 1 byte (bool) | MultiVoice Cursor (CTRL+M)
// ScoreView[] | Data for all score views (number is aligned with PartConfiguration)

// ScoreView:
// TrackViewGroup[] | The individual track view groups (number is aligned with PartConfiguration)

// TrackViewGroup:
// 1 byte (bool) | isVisible (true -> 0xFF, false -> 0x00)


class LayoutConfigurationScoreView {
public trackViewGroups: LayoutConfigurationTrackViewGroup[] = [];
}

class LayoutConfigurationTrackViewGroup {
public isVisible: boolean = false;
}

enum GuitarProView {
PageVertical = 0x00,
PageGrid = 0x01,
PageParchment = 0x02,
ScreenVertical = 0x03,
ScreenHorizontal = 0x04,
PageHorizontal = 0x05
}

export class LayoutConfiguration {
public zoomLevel:number = 4;
public view:GuitarProView = GuitarProView.PageVertical;
public muiltiVoiceCursor:boolean = false;
public scoreViews: LayoutConfigurationScoreView[] = [];

public constructor(
partConfiguration:PartConfiguration,
layoutConfigurationData: Uint8Array) {
const readable: ByteBuffer = ByteBuffer.fromBuffer(layoutConfigurationData);

this.zoomLevel = IOHelper.readInt32BE(readable);
this.view = readable.readByte() as GuitarProView;
this.muiltiVoiceCursor = readable.readByte() != 0

const scoreViewCount: number = partConfiguration.scoreViews.length;

for (let i: number = 0; i < scoreViewCount; i++) {

const scoreView = new LayoutConfigurationScoreView();
this.scoreViews.push(scoreView);

const partScoreView = partConfiguration.scoreViews[i];

for (let j: number = 0; j < partScoreView.trackViewGroups.length; j++) {

const trackViewGroup = new LayoutConfigurationTrackViewGroup();
trackViewGroup.isVisible = readable.readByte() != 0;

scoreView.trackViewGroups.push(trackViewGroup);
}
}

}

public apply(score: Score): void {
if(this.scoreViews.length > 0) {
let trackIndex = 0;
for (let trackConfig of this.scoreViews[0].trackViewGroups) {
if (trackIndex < score.tracks.length) {
const track = score.tracks[trackIndex];
track.isVisibleOnMultiTrack = trackConfig.isVisible;
}
trackIndex++;
}
}
}

public static writeForScore(score: Score,): Uint8Array {
const writer = ByteBuffer.withCapacity(128);

IOHelper.writeInt32BE(writer, 4); // 100% Zoom
writer.writeByte(0x00) // Page - Vertical

const isMultiVoice = score.tracks.length > 0 && score.tracks[0].staves[0].bars[0].isMultiVoice;
writer.writeByte(isMultiVoice ? 0xFF : 0x00);

// ScoreView[0] => Multi Track Score View
for (const track of score.tracks) {
writer.writeByte(track.isVisibleOnMultiTrack ? 0xFF : 0x00);
}

// Single Track Views for each track
for (const _track of score.tracks) {
writer.writeByte(0xFF);
}

return writer.toArray();
}
}
Loading
Loading