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: add support for multiple tempo changes in bar #1600

Merged
merged 4 commits into from
Aug 3, 2024
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
4 changes: 2 additions & 2 deletions playground-template/alphatex-editor.html
Original file line number Diff line number Diff line change
@@ -78,13 +78,13 @@
req.onload = data => {
document.getElementById('placeholder').outerHTML = req.responseText;

const element = document.getElementById('alphaTab');
const element = document.getElementById('alphaTabControl');
delete element.dataset.file;
delete element.dataset.tracks;
element.dataset.tex = true;
element.innerHTML = document.getElementById('editor').innerHTML;

window.at = setupControl('#alphaTab', { file: undefined, tex: ''});
window.at = setupControl('#alphaTabControl', { file: undefined, tex: ''});
setupEditor(window.at, '#editor');
};
req.open('GET', 'control-template.html');
13 changes: 13 additions & 0 deletions src.csharp/AlphaTab.Test/Test/Globals.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.Collections;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace AlphaTab.Test
@@ -66,6 +67,7 @@ public Expector(T actual)
public Expector<T> To => this;
public NotExpector<T> Not => new(_actual);
public Expector<T> Be => this;
public Expector<T> Have => this;

public void Equal(object? expected, string? message = null)
{
@@ -108,6 +110,17 @@ public void Ok()
{
Assert.AreNotEqual(default!, _actual);
}
public void Length(int length)
{
if(_actual is ICollection collection)
{
Assert.AreEqual(length, collection.Count);
}
else
{
Assert.Fail("Length can only be used with collection operands");
}
}

public void True()
{
Original file line number Diff line number Diff line change
@@ -44,6 +44,9 @@ class Expector<T>(private val actual: T) {
val be
get() = this

val have
get() = this

fun equal(expected: Any?, message: String? = null) {
var expectedTyped: Any? = expected

@@ -66,6 +69,18 @@ class Expector<T>(private val actual: T) {
}
}

fun length(expected:Double) {
if(actual is alphaTab.collections.List<*>) {
kotlin.test.assertEquals(expected.toInt(), actual.length.toInt())
} else if(actual is alphaTab.collections.DoubleList) {
kotlin.test.assertEquals(expected.toInt(), actual.length.toInt())
} else if(actual is alphaTab.collections.BooleanList) {
kotlin.test.assertEquals(expected.toInt(), actual.length.toInt())
} else {
kotlin.test.fail("length can only be used with collection operands");
}
}

// fun toBe(expected: Any) {
// var expectedTyped = expected
// if (expected is Int && actual is Double) {
Original file line number Diff line number Diff line change
@@ -110,6 +110,10 @@ public class List<T> : Iterable<T> {
return List(_data.subList(start.toInt(), _data.size))
}

public fun shift(): T {
return _data.removeAt(0)
}

public fun splice(start: Double, deleteCount: Double, vararg newElements: T) {
var actualStart = start.toInt()
if (actualStart < 0)
52 changes: 26 additions & 26 deletions src/exporter/GpifWriter.ts
Original file line number Diff line number Diff line change
@@ -944,28 +944,30 @@ export class GpifWriter {
masterTrackNode.addElement('Anacrusis');
}

const initialTempoAutomation = automations.addElement('Automation');
initialTempoAutomation.addElement('Type').innerText = 'Tempo';
initialTempoAutomation.addElement('Linear').innerText = 'false';
initialTempoAutomation.addElement('Bar').innerText = '0';
initialTempoAutomation.addElement('Position').innerText = '0';
initialTempoAutomation.addElement('Visible').innerText = 'true';
initialTempoAutomation.addElement('Value').innerText = `${score.tempo} 2`;
if (score.tempoLabel) {
initialTempoAutomation.addElement('Text').innerText = score.tempoLabel;
if(score.masterBars[0].tempoAutomations.length === 0){
const initialTempoAutomation = automations.addElement('Automation');
initialTempoAutomation.addElement('Type').innerText = 'Tempo';
initialTempoAutomation.addElement('Linear').innerText = 'false';
initialTempoAutomation.addElement('Bar').innerText = '0';
initialTempoAutomation.addElement('Position').innerText = '0';
initialTempoAutomation.addElement('Visible').innerText = 'true';
initialTempoAutomation.addElement('Value').innerText = `${score.tempo} 2`;
if (score.tempoLabel) {
initialTempoAutomation.addElement('Text').innerText = score.tempoLabel;
}
}

for (const mb of score.masterBars) {
if (mb.index > 0 && mb.tempoAutomation) {
for (const automation of mb.tempoAutomations) {
const tempoAutomation = automations.addElement('Automation');
tempoAutomation.addElement('Type').innerText = 'Tempo';
tempoAutomation.addElement('Linear').innerText = mb.tempoAutomation.isLinear ? 'true' : 'false';
tempoAutomation.addElement('Linear').innerText = automation.isLinear ? 'true' : 'false';
tempoAutomation.addElement('Bar').innerText = mb.index.toString();
tempoAutomation.addElement('Position').innerText = mb.tempoAutomation.ratioPosition.toString();
tempoAutomation.addElement('Position').innerText = automation.ratioPosition.toString();
tempoAutomation.addElement('Visible').innerText = 'true';
tempoAutomation.addElement('Value').innerText = `${mb.tempoAutomation.value} 2`;
if (mb.tempoAutomation.text) {
tempoAutomation.addElement('Text').innerText = mb.tempoAutomation.text;
tempoAutomation.addElement('Value').innerText = `${automation.value} 2`;
if (automation.text) {
tempoAutomation.addElement('Text').innerText = automation.text;
}
}
}
@@ -1255,7 +1257,7 @@ export class GpifWriter {
let chordFret = chord.strings[i];
if (chordFret !== -1) {
const fretNode = diagram.addElement('Fret');
const chordString = (chord.strings.length - 1 - i);
const chordString = chord.strings.length - 1 - i;
fretNode.attributes.set('string', chordString.toString());
fretNode.attributes.set('fret', (chordFret - chord.firstFret + 1).toString());
if (!fretToStrings.has(chordFret)) {
@@ -1275,7 +1277,7 @@ export class GpifWriter {
Fingers.LittleFinger,
Fingers.AnnularFinger,
Fingers.MiddleFinger,
Fingers.IndexFinger,
Fingers.IndexFinger
];

for (const fret of frets) {
@@ -1305,22 +1307,20 @@ export class GpifWriter {
}
}


const showName = diagram.addElement('Property');
showName.attributes.set('name', 'ShowName');
showName.attributes.set('type', 'bool');
showName.attributes.set('value', chord.showName ? "true" : "false");
showName.attributes.set('value', chord.showName ? 'true' : 'false');

const showDiagram = diagram.addElement('Property');
showDiagram.attributes.set('name', 'ShowDiagram');
showDiagram.attributes.set('type', 'bool');
showDiagram.attributes.set('value', chord.showDiagram ? "true" : "false");
showDiagram.attributes.set('value', chord.showDiagram ? 'true' : 'false');

const showFingering = diagram.addElement('Property');
showFingering.attributes.set('name', 'ShowFingering');
showFingering.attributes.set('type', 'bool');
showFingering.attributes.set('value', chord.showFingering ? "true" : "false");

showFingering.attributes.set('value', chord.showFingering ? 'true' : 'false');

// TODO Chord details
const chordNode = diagram.addElement('Chord');
@@ -1439,8 +1439,8 @@ export class GpifWriter {

instrumentSet.addElement('Name').innerText = GpifWriter.DrumKitProgramInfo.instrumentSetName;
instrumentSet.addElement('Type').innerText = GpifWriter.DrumKitProgramInfo.instrumentSetType;
let currentElementType: string = "";
let currentElementName: string = "";
let currentElementType: string = '';
let currentElementName: string = '';
let currentArticulations: XmlNode = new XmlNode();
let counterPerType = new Map<string, number>();
const elements = instrumentSet.addElement('Elements');
@@ -1600,7 +1600,7 @@ export class GpifWriter {
}

private writeFermatas(parent: XmlNode, masterBar: MasterBar) {
const fermataCount = (masterBar.fermata?.size ?? 0);
const fermataCount = masterBar.fermata?.size ?? 0;
if (fermataCount === 0) {
return;
}
20 changes: 9 additions & 11 deletions src/generated/model/MasterBarSerializer.ts
Original file line number Diff line number Diff line change
@@ -37,7 +37,7 @@ export class MasterBarSerializer {
o.set("timesignaturecommon", obj.timeSignatureCommon);
o.set("tripletfeel", obj.tripletFeel as number);
o.set("section", SectionSerializer.toJson(obj.section));
o.set("tempoautomation", AutomationSerializer.toJson(obj.tempoAutomation));
o.set("tempoautomations", obj.tempoAutomations.map(i => AutomationSerializer.toJson(i)));
if (obj.fermata !== null) {
const m = new Map<string, unknown>();
o.set("fermata", m);
@@ -83,6 +83,14 @@ export class MasterBarSerializer {
case "tripletfeel":
obj.tripletFeel = JsonHelper.parseEnum<TripletFeel>(v, TripletFeel)!;
return true;
case "tempoautomations":
obj.tempoAutomations = [];
for (const o of (v as (Map<string, unknown> | null)[])) {
const i = new Automation();
AutomationSerializer.fromJson(i, o);
obj.tempoAutomations.push(i);
}
return true;
case "fermata":
obj.fermata = new Map<number, Fermata>();
JsonHelper.forEach(v, (v, k) => {
@@ -114,16 +122,6 @@ export class MasterBarSerializer {
}
return true;
}
if (["tempoautomation"].indexOf(property) >= 0) {
if (v) {
obj.tempoAutomation = new Automation();
AutomationSerializer.fromJson(obj.tempoAutomation, v as Map<string, unknown>);
}
else {
obj.tempoAutomation = null;
}
return true;
}
return false;
}
}
39 changes: 25 additions & 14 deletions src/importer/AlphaTexImporter.ts
Original file line number Diff line number Diff line change
@@ -1500,6 +1500,12 @@ export class AlphaTexImporter extends ScoreImporter {
beat.crescendo = CrescendoType.Crescendo;
} else if (syData === 'dec') {
beat.crescendo = CrescendoType.Decrescendo;
} else if(syData === 'tempo') {
// NOTE: playbackRatio is calculated on score finish when playback positions are known
const tempoAutomation = this.readTempoAutomation();
beat.automations.push(tempoAutomation);
beat.voice.bar.masterBar.tempoAutomations.push(tempoAutomation);
return true;
} else if (syData === 'tp') {
this._sy = this.newSy();
beat.tremoloSpeed = Duration.Eighth;
@@ -1977,18 +1983,8 @@ export class AlphaTexImporter extends ScoreImporter {
}
this._sy = this.newSy();
} else if (syData === 'tempo') {
this._allowFloat = true;
this._sy = this.newSy();
this._allowFloat = false;
if (this._sy !== AlphaTexSymbols.Number) {
this.error('tempo', AlphaTexSymbols.Number, true);
}
let tempoAutomation: Automation = new Automation();
tempoAutomation.isLinear = false;
tempoAutomation.type = AutomationType.Tempo;
tempoAutomation.value = this._syData as number;
master.tempoAutomation = tempoAutomation;
this._sy = this.newSy();
const tempoAutomation = this.readTempoAutomation();
master.tempoAutomations.push(tempoAutomation);
} else if (syData === 'section') {
this._sy = this.newSy();
if (this._sy !== AlphaTexSymbols.String) {
@@ -2036,16 +2032,31 @@ export class AlphaTexImporter extends ScoreImporter {
}
}

if (master.index === 0 && !master.tempoAutomation) {
if (master.index === 0 && master.tempoAutomations.length === 0) {
let tempoAutomation: Automation = new Automation();
tempoAutomation.isLinear = false;
tempoAutomation.type = AutomationType.Tempo;
tempoAutomation.value = this._score.tempo;
master.tempoAutomation = tempoAutomation;
master.tempoAutomations.push(tempoAutomation);
}
return anyMeta;
}

private readTempoAutomation() {
this._allowFloat = true;
this._sy = this.newSy();
this._allowFloat = false;
if (this._sy !== AlphaTexSymbols.Number) {
this.error('tempo', AlphaTexSymbols.Number, true);
}
const tempoAutomation: Automation = new Automation();
tempoAutomation.isLinear = false;
tempoAutomation.type = AutomationType.Tempo;
tempoAutomation.value = this._syData as number;
this._sy = this.newSy();
return tempoAutomation;
}

private applyAlternateEnding(master: MasterBar): void {
let num = this._syData as number;
if (num < 1) {
20 changes: 6 additions & 14 deletions src/importer/CapellaParser.ts
Original file line number Diff line number Diff line change
@@ -140,7 +140,6 @@ export class CapellaParser {
// voice counts and contents might be inconsistent
// we need to ensure we have an equal amount of voices across all bars
// and voices must contain an empty beat at minimum
let tempo = this.score.tempo;
for (const track of this.score.tracks) {
const trackVoiceCount = this._voiceCounts.get(track.index)!;
for (const staff of track.staves) {
@@ -160,15 +159,6 @@ export class CapellaParser {
voice.addBeat(emptyBeat);
}
}

const mb = bar.masterBar;
if (mb.tempoAutomation) {
if (mb.tempoAutomation.value !== tempo) {
tempo = mb.tempoAutomation.value;
} else {
mb.tempoAutomation = null;
}
}
}
}
}
@@ -619,10 +609,12 @@ export class CapellaParser {
const noteObjects = element.findChildElement('noteObjects');

if (systemElement.attributes.has('tempo')) {
this._currentBar.masterBar.tempoAutomation = new Automation();
this._currentBar.masterBar.tempoAutomation.isLinear = true;
this._currentBar.masterBar.tempoAutomation.type = AutomationType.Tempo;
this._currentBar.masterBar.tempoAutomation.value = parseInt(systemElement.attributes.get('tempo')!);
const automation = new Automation()
automation.isLinear = true;
automation.type = AutomationType.Tempo;
automation.value = parseInt(systemElement.attributes.get('tempo')!);
automation.ratioPosition = this._currentVoiceState.currentPosition / this._currentVoiceState.currentBarDuration;
this._currentBar.masterBar.tempoAutomations.push(automation);
}

if (noteObjects) {
7 changes: 4 additions & 3 deletions src/importer/Gp3To5Importer.ts
Original file line number Diff line number Diff line change
@@ -137,8 +137,9 @@ export class Gp3To5Importer extends ScoreImporter {
// To be more in line with the GP7 structure we create an
// initial tempo automation on the first masterbar
if (this._score.masterBars.length > 0) {
this._score.masterBars[0].tempoAutomation = Automation.buildTempoAutomation(false, 0, this._score.tempo, 2);
this._score.masterBars[0].tempoAutomation.text = this._score.tempoLabel;
const automation = Automation.buildTempoAutomation(false, 0, this._score.tempo, 2);;
automation.text = this._score.tempoLabel;
this._score.masterBars[0].tempoAutomations.push(automation);
}

this._score.finish(this.settings);
@@ -880,7 +881,7 @@ export class Gp3To5Importer extends ScoreImporter {
tempoAutomation.type = AutomationType.Tempo;
tempoAutomation.value = tableChange.tempo;
beat.automations.push(tempoAutomation);
beat.voice.bar.masterBar.tempoAutomation = tempoAutomation;
beat.voice.bar.masterBar.tempoAutomations.push(tempoAutomation);
}
}

2 changes: 1 addition & 1 deletion src/importer/GpifParser.ts
Original file line number Diff line number Diff line change
@@ -2332,7 +2332,7 @@ export class GpifParser {
this.score.tempoLabel = automation.text;
}
}
masterBar.tempoAutomation = automation;
masterBar.tempoAutomations.push(automation);
}
}
}
Loading