add initial version of tab-downloader
This commit is contained in:
91
songsterr/duration-mapper.ts
Normal file
91
songsterr/duration-mapper.ts
Normal file
@@ -0,0 +1,91 @@
|
||||
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
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user