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

Analyze, Document and Fix PartConfiguration handling #1241

Merged
merged 1 commit into from
Aug 5, 2023
Merged
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
141 changes: 78 additions & 63 deletions src/importer/PartConfiguration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,112 +2,125 @@ import { GpBinaryHelpers } from '@src/importer/Gp3To5Importer';
import { ByteBuffer } from '@src/io/ByteBuffer';
import { IOHelper } from '@src/io/IOHelper';
import { Score } from '@src/model/Score';
import { Staff } from '@src/model/Staff';
import { Track } from '@src/model/Track';

export class TrackConfiguration {

// PartConfiguration File Format Notes.
// Based off Guitar Pro 8
// The file contains a serialized "Score View Collection" filled like this:
// There is always 1 ScoreView holding a TrackViewGroup for each Track contained in the file. This is the multi-track layout.
// Additionally there is 1 ScoreView individually for each track with only 1 TrackViewGroup of this Group.

// The Guitar Pro UI seem to update both the multi-track and the single-track layouts when changin the displayed staves.
// But technically it would support showing tracks different in multi-track.


// File:
// int32 (big endian) | Number of Score Views
// ScoreView[] | The individual score views
// int32 (big endian) | The index to the currently active view

// ScoreView:
// 1 byte (boolean) | "Multi Rest" - Whether multiple bars with rests should be combined (Bar > Multi Rest option)
// int32 (big endian) | Number of following tems
// TrackViewGroup[] | The individual track view groups

// TrackViewGroup:
// 1 byte | Track View Group Type Bitflag
// | 0th bit: showStandardNotation
// | 1th bit: showTablature
// | 2nd bit: showSlash
// | 3rd bit: numberedNotation (GP8 feature - jiǎnpǔ aka Chinese Number Notation)
// | if no bits set -> activate standard notation
//


export class ScoreView {
public isMultiRest: boolean = false;
public trackViewGroups: TrackViewGroup[] = [];
}

export class TrackViewGroup {
public showSlash: boolean = false;
public showStandardNotation: boolean = false;
public showTablature: boolean = false;
}

export class Part {
public isMultiRest: boolean = false;
public tracks: TrackConfiguration[] = [];
}

export class PartConfiguration {
public parts: Part[] = [];
public zoomLevel: number = 0;
public layout: number = 0;
public scoreViews: ScoreView[] = [];

public apply(score: Score): void {
let staffIndex: number = 0;
let trackIndex: number = 0;
// the PartConfiguration is really twisted compared to how the score structure looks like.
// the first part typically contains the settings for the first staff of all tracks.
// but then there is 1 part with 1 track for each other staff of the tracks.
// So the structure in the PartConfig appears to be:
// Parts[0].Tracks = { Track1-Staff1, Track2-Staff1, Track3-Staff1, Track4-Staff1, .. }
// Parts[1].Tracks = { Track1-Staff2 }
// Parts[2].Tracks = { Track2-Staff2 }
// Parts[3].Tracks = { Track3-Staff2 }
// Parts[4].Tracks = { Track4-Staff2 }
//
// even if a track has only 1 staff, there are 2 staff configurations stored.
// I hope Arobas never changes this in the format as the PartConfiguration is not versionized.
for (let part of this.parts) {
for (let trackConfig of part.tracks) {
// for now we only look at the first score view which seem to hold
// the config for all tracks.
if(this.scoreViews.length > 0) {
let trackIndex = 0;
for (let trackConfig of this.scoreViews[0].trackViewGroups) {
if (trackIndex < score.tracks.length) {
let track: Track = score.tracks[trackIndex];
if (staffIndex < track.staves.length) {
let staff: Staff = track.staves[staffIndex];
const track: Track = score.tracks[trackIndex];
for(const staff of track.staves) {
staff.showTablature = trackConfig.showTablature;
staff.showStandardNotation = trackConfig.showStandardNotation;
}
}
trackIndex++;
if (trackIndex >= score.tracks.length) {
staffIndex++;
trackIndex = 0;
}
}
}
}

public constructor(partConfigurationData: Uint8Array) {
let readable: ByteBuffer = ByteBuffer.fromBuffer(partConfigurationData);
let entryCount: number = IOHelper.readInt32BE(readable);

for (let i: number = 0; i < entryCount; i++) {
let part = new Part();
this.parts.push(part);
part.isMultiRest = GpBinaryHelpers.gpReadBool(readable);
let groupCount: number = IOHelper.readInt32BE(readable);
for (let j: number = 0; j < groupCount; j++) {

const scoreViewCount: number = IOHelper.readInt32BE(readable);

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

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

scoreView.isMultiRest = GpBinaryHelpers.gpReadBool(readable);

const trackViewGroupCount: number = IOHelper.readInt32BE(readable);
for (let j: number = 0; j < trackViewGroupCount; j++) {
let flags: number = readable.readByte();
// enable at least standard notation
if (flags === 0) {
flags = 1;
}
let trackConfiguration = new TrackConfiguration();
let trackConfiguration = new TrackViewGroup();
trackConfiguration.showStandardNotation = (flags & 0x01) !== 0;
trackConfiguration.showTablature = (flags & 0x02) !== 0;
trackConfiguration.showSlash = (flags & 0x04) !== 0;
part.tracks.push(trackConfiguration);
scoreView.trackViewGroups.push(trackConfiguration);
}
}
}

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

const parts: Part[] = [
new Part() // default part always exists
const scoreViews: ScoreView[] = [
new ScoreView() // Multi Track Score View
];

for (const track of score.tracks) {
for (const staff of track.staves) {

const trackConfiguration = new TrackConfiguration();
trackConfiguration.showStandardNotation = staff.showStandardNotation;
trackConfiguration.showTablature = staff.showTablature;

if (staff.index === 0) {
parts[0].tracks.push(trackConfiguration);
} else {
let part = new Part();
part.tracks.push(trackConfiguration);
parts.push(part);
}
}
const trackConfiguration = new TrackViewGroup();
// NOTE: unclear how multi staff settings are meant in this format
// in the Guitar Pro UI there is no individual staff config
trackConfiguration.showStandardNotation = track.staves[0].showStandardNotation;
trackConfiguration.showTablature = track.staves[0].showTablature;

scoreViews[0].trackViewGroups.push(trackConfiguration);

const singleTrackScoreView = new ScoreView();
singleTrackScoreView.trackViewGroups.push(trackConfiguration);
scoreViews.push(singleTrackScoreView);
}

IOHelper.writeInt32BE(writer, parts.length);
for (const part of parts) {
IOHelper.writeInt32BE(writer, scoreViews.length);
for (const part of scoreViews) {
writer.writeByte(part.isMultiRest ? 1 : 0);
IOHelper.writeInt32BE(writer, part.tracks.length);
for(const track of part.tracks) {
IOHelper.writeInt32BE(writer, part.trackViewGroups.length);
for(const track of part.trackViewGroups) {
let flags = 0;
if(track.showStandardNotation) {
flags = flags | 0x01;
Expand All @@ -122,6 +135,8 @@ export class PartConfiguration {
}
}

IOHelper.writeInt32BE(writer, 1 /* First Single Track Layout */);

return writer.toArray();
}
}