92 lines
3.2 KiB
TypeScript
92 lines
3.2 KiB
TypeScript
import * as alphaTab from '@coderline/alphatab';
|
|
|
|
/**
|
|
* Result structure representing a successfully mapped Songsterr duration in alphaTab terms.
|
|
*/
|
|
export interface DurationMappingResult {
|
|
duration: alphaTab.model.Duration; // Closest matching alphaTab standard duration enum value
|
|
dots: number; // Number of dot annotations (0, 1, or 2)
|
|
isApproximate: boolean; // Flag indicating if the duration had to be approximated due to custom fractions
|
|
}
|
|
|
|
// Available base durations supported natively by alphaTab and standard GP files
|
|
const baseDurations: alphaTab.model.Duration[] = [
|
|
alphaTab.model.Duration.Whole,
|
|
alphaTab.model.Duration.Half,
|
|
alphaTab.model.Duration.Quarter,
|
|
alphaTab.model.Duration.Eighth,
|
|
alphaTab.model.Duration.Sixteenth,
|
|
alphaTab.model.Duration.ThirtySecond,
|
|
alphaTab.model.Duration.SixtyFourth
|
|
];
|
|
|
|
/**
|
|
* Maps a fractional duration tuple [numerator, denominator] from Songsterr's API payload
|
|
* to a standardized alphaTab.model.Duration plus any dot values (dotted or double-dotted notes).
|
|
*
|
|
* Since Songsterr can utilize arbitrary fractional times, this searches through a
|
|
* matrix of valid base durations combined with 0 to 2 dots, selecting the combination
|
|
* that yields the minimum numeric delta.
|
|
*
|
|
* @param duration An array containing [numerator, denominator] representing a musical time slice.
|
|
* @returns A DurationMappingResult containing mapped duration, dots, and a precision flag.
|
|
*/
|
|
export function mapSongsterrDuration(
|
|
duration: [number, number] | undefined
|
|
): DurationMappingResult {
|
|
// If no duration is provided, default to a standard quarter note and mark it as approximate.
|
|
if (!duration) {
|
|
return {
|
|
duration: alphaTab.model.Duration.Quarter,
|
|
dots: 0,
|
|
isApproximate: true
|
|
};
|
|
}
|
|
|
|
const [numerator, denominator] = duration;
|
|
if (!numerator || !denominator) {
|
|
return {
|
|
duration: alphaTab.model.Duration.Quarter,
|
|
dots: 0,
|
|
isApproximate: true
|
|
};
|
|
}
|
|
|
|
// Calculate the numeric target duration value (e.g., 1/4 = 0.25, 3/8 = 0.375)
|
|
const targetValue = numerator / denominator;
|
|
let bestDuration = alphaTab.model.Duration.Quarter;
|
|
let bestDots = 0;
|
|
let bestDelta = Number.POSITIVE_INFINITY;
|
|
|
|
// Search space: check each base duration against 0, 1, or 2 dots.
|
|
for (const candidateDuration of baseDurations) {
|
|
// Convert duration enum back to its numeric value (e.g., Duration.Quarter has underlying enum value of 4 -> 1/4)
|
|
const baseValue = 1 / Number(candidateDuration);
|
|
for (const dots of [0, 1, 2]) {
|
|
// Dotted notes add half of the preceding value:
|
|
// 1 dot = base + base/2
|
|
// 2 dots = base + base/2 + base/4
|
|
const dottedValue =
|
|
baseValue +
|
|
(dots >= 1 ? baseValue / 2 : 0) +
|
|
(dots >= 2 ? baseValue / 4 : 0);
|
|
|
|
const delta = Math.abs(dottedValue - targetValue);
|
|
// Track the closest match
|
|
if (delta < bestDelta) {
|
|
bestDelta = delta;
|
|
bestDuration = candidateDuration;
|
|
bestDots = dots;
|
|
}
|
|
}
|
|
}
|
|
|
|
return {
|
|
duration: bestDuration,
|
|
dots: bestDots,
|
|
// Consider it exact if the delta is within a minuscule threshold
|
|
isApproximate: bestDelta > 0.000001
|
|
};
|
|
}
|
|
|