108 lines
3.6 KiB
TypeScript
108 lines
3.6 KiB
TypeScript
/**
|
|
* Time utility functions for KGSP
|
|
*/
|
|
|
|
import { TIME_CONSTANTS } from '../constants/coreConstants';
|
|
import type { TimeSignature } from '../types/projectTypes';
|
|
|
|
/**
|
|
* Parse and validate time signature string in format "numerator/denominator"
|
|
* @param timeSignatureStr - String in format "numerator/denominator" (e.g., "4/4", "3/4", "6/8")
|
|
* @returns TimeSignature object if valid, null if invalid
|
|
*/
|
|
export function parseTimeSignature(timeSignatureStr: string): TimeSignature | null {
|
|
// Remove any whitespace and check format
|
|
const trimmed = timeSignatureStr.trim();
|
|
|
|
// Check if it contains exactly one slash
|
|
const parts = trimmed.split('/');
|
|
if (parts.length !== 2) {
|
|
return null;
|
|
}
|
|
|
|
// Parse numerator and denominator
|
|
const numerator = parseInt(parts[0]);
|
|
const denominator = parseInt(parts[1]);
|
|
|
|
// Check if both are valid numbers
|
|
if (isNaN(numerator) || isNaN(denominator)) {
|
|
return null;
|
|
}
|
|
|
|
// Validate against available values
|
|
if (!TIME_CONSTANTS.AVAILABLE_TIME_SIGNATURE_NUMERATORS.includes(numerator)) {
|
|
return null;
|
|
}
|
|
|
|
if (!TIME_CONSTANTS.AVAILABLE_TIME_SIGNATURE_DENOMINATORS.includes(denominator)) {
|
|
return null;
|
|
}
|
|
|
|
return { numerator, denominator };
|
|
}
|
|
|
|
/**
|
|
* Get formatted error message for invalid time signature
|
|
* @returns User-friendly error message with available options
|
|
*/
|
|
export function getTimeSignatureErrorMessage(): string {
|
|
const numerators = TIME_CONSTANTS.AVAILABLE_TIME_SIGNATURE_NUMERATORS.join(', ');
|
|
const denominators = TIME_CONSTANTS.AVAILABLE_TIME_SIGNATURE_DENOMINATORS.join(', ');
|
|
|
|
return `Invalid time signature format. Please use "numerator/denominator" format.\n\nAvailable numerators: ${numerators}\nAvailable denominators: ${denominators}\n\nExamples: 4/4, 3/4, 6/8, 12/8`;
|
|
}
|
|
|
|
/**
|
|
* Convert beats to combined bar/beat and time format (BBB:B | mm:ss:mmm)
|
|
* @param beats - Current position in beats
|
|
* @param bpm - Beats per minute
|
|
* @param timeSignature - Time signature object with numerator and denominator
|
|
* @returns Formatted time string in BBB:B | mm:ss:mmm format
|
|
*/
|
|
export function beatsToTimeString(
|
|
beats: number,
|
|
bpm: number,
|
|
timeSignature: { numerator: number; denominator: number }
|
|
): string {
|
|
// Calculate bar and beat information
|
|
const beatsPerBar = timeSignature.numerator;
|
|
const currentBar = Math.floor(beats / beatsPerBar) + 1; // 1-indexed bars
|
|
const beatInBar = Math.floor(beats % beatsPerBar) + 1; // 1-indexed beats within bar
|
|
|
|
// Calculate total seconds from beats for time display
|
|
const totalSeconds = (beats / bpm) * 60;
|
|
|
|
// Extract minutes, seconds, and milliseconds
|
|
const minutes = Math.floor(totalSeconds / 60);
|
|
const seconds = Math.floor(totalSeconds % 60);
|
|
const milliseconds = Math.floor((totalSeconds % 1) * 1000);
|
|
|
|
// Format bar/beat part
|
|
const BBB = currentBar.toString().padStart(3, '0');
|
|
const B = beatInBar.toString();
|
|
|
|
// Format time part with leading zeros
|
|
const mm = minutes.toString().padStart(2, '0');
|
|
const ss = seconds.toString().padStart(2, '0');
|
|
const mmm = milliseconds.toString().padStart(3, '0');
|
|
|
|
return `${BBB}:${B} | ${mm}:${ss}:${mmm}`;
|
|
}
|
|
|
|
/**
|
|
* Format a Date object into a human-readable local datetime string with timezone.
|
|
* Example: 2025/08/21, 19:57:11 GMT-7
|
|
*/
|
|
export function formatLocalDateTime(date: Date): string {
|
|
const options: Intl.DateTimeFormatOptions = {
|
|
year: 'numeric',
|
|
month: '2-digit',
|
|
day: '2-digit',
|
|
hour: '2-digit',
|
|
minute: '2-digit',
|
|
second: '2-digit',
|
|
hour12: false,
|
|
timeZoneName: 'short'
|
|
};
|
|
return date.toLocaleString(undefined, options);
|
|
} |